Write explicit code
You get assigned a Pull request, open it and see a method definition
class LearningMotionController {
void extractPolicyInfo();
};
and immediately wonder: Extract from what? Extract to where?
Congratulations! You just found a piece of code that is not explicit about its intent. It probably does a lot, but you just don't know what. It's all hidden in the implementation.
While hiding implementation details is good, it is hard to strike the right balance between exposing details and hiding them. When reading code I like it to be explicit about its intent. Code that tells me what it does. Code that doesn't leave me guessing. Code that is easy to read, understand and most importantly easy to reason about.
Implicit code increases cognitive load
Consider the following class:
/**
* A class that controls a robotic arm given a learned policy.
*/
class LearningMotionController {
public:
explicit LearningMotionController(Parameters params);
/**
* Given the parameters in params_ it extracts the policy info and puts
* them into policyInfo_.
*/
void extractPolicyInfo();
private:
Parameters params_;
PolicyInfo policyInfo_;
};
It's great that the docstring on extractPolicyInfo() explains what it does. However, comments tend to become outdated and out of sync with the code very quickly. Additionally, if I only look at the code and ignore the comment, it makes me wonder where this data comes from and where it goes. When we find ourselves in this situation we have a couple options:
- Guess what the method or function does
- Dive one abstraction layer lower and understand the internals
- Ask an LLM to dive one abstraction layer lower and summarize the findings.
None of these options are great. Option 1 leaves us only with a guess. Option 2 forces extra cognitive load on every reader, every time. And Option 3 leaves us experiencing unproductive success, getting an answer without building real understanding.
What if we could be more explicit?
Explicit code frees our mind up
By adapting the interface to the following we communicate the intent:
[[nodiscard]] PolicyInfo extractPolicyInfo(const Parameters& params);
/**
* A class that controls a robotic arm given a learned policy.
*/
class LearningMotionController {
public:
explicit LearningMotionController(Parameters params);
private:
PolicyInfo policyInfo_;
};
// Implementation
LearningMotionController::LearningMotionController(Parameters params)
: policyInfo_(extractPolicyInfo(params)) {}
Now we can use extractPolicyInfo(const Parameters& params) in the constructor, using the params parameter. The change is minimal but makes the code explicit and communicates the intent. With a single look at the function declaration we can deduce that it extracts the policy information from the given parameters.
Beyond communicating the intent better, there are other positive changes:
- Reduce State: By giving
paramsexplicitly to theextractPolicyInfomethod in the constructor there is no need to store it in theparams_instance variable. Less state is good because it reduces cognitive load. We are also explicit about what we actually use insideLearningMotionController. - Improved Testability: The free standing function becomes trivial to test as input and output are easily accessible.
- Possible Dependency Injection: We could refactor
LearningMotionControllerfurther such that it only takes what it actually needs: aPolicyInfo.
The next time you write a method that has no return values, no arguments or both ask yourself: Is the intent of this method obvious to fellow engineers and your future self? If not, then refactor your code to be explicit about its intent!