Rule 2: Clarify Your Intent
Brian Hicks, June 29, 2021
Last week, we talked about simplifying when the program works as part of the four rules of simple design series.
This week, rule two: clarify your intent.
The original four rules prioritize clarity of communication. For example, Beck's formulation says "states every intention important to the programmer." Fowler tightens that up to "reveals intent", and Rainsberger refines it further to "improve names." But what intent are we trying to communicate clearly? Here are a few things you might want to consider:
- What's this thing for?
- How are these two things related?
- Why is this code doing things this way instead of that way?
- Please don't do this thing if you also do that thing.
- Which part of this is core to the program, and which is just supporting structure?
- Where does the stuff you send here end up? Or, where is this thing getting stuff from?
You can communicate these things in a lot of ways, but I want to talk about three: names, comments, and constructors.
Say you have a method called
process_data. Both of those words are really vague! What if we made them better? What's
Maybe you look at the method body and find out it downloads a CSV of accounts and selects all the ones the sales team has identified as likely to buy a premium license. So in this case,
process would be incorrect—it's
download. And it's not just any old
clients, specially ones on the threshold of upgrading.
So, while there are more ways you could improve this (e.g. separating the downloading from the data processing), renaming it to
get_threshold_clients already makes it way easier to understand your code: imagine seeing
get_threshold_clients at a call site. Which would you prefer when reading later?
There's a lot to think about when choosing a good name, so instead of saying more, I'm just going to link some things I've found helpful: Naming Things in Elm by Ally McKnight, Picking better names for variables, functions, and projects by Tom MacWright, What's in a Name? Anti-Patterns to Hard Problem by Katrina Owen, and "Naming Things" is a Poor Name for Naming Things by Hillel Wayne.
Another thing you could do to clarify your intent is to explicitly write it down in a comment. There's the constant debate on "what" comments vs "why" comments, of course—here's Hillel Wayne again on why you need both—but in either form, the best comments give the reader insight into what was going on and what you needed when you wrote your code.
I like to write long documentation comments framed like "Hello, future us! I hope you're having great day. Here's what's up." I've found those to be helpful, both when my coworkers review the code and when we revisit it months later while trying to do something else. Having little hints of intention scattered throughout the code helps remember things we need to keep in mind. Plus, like trying to find a good name, documenting why something exists can often help us realize some other refactor that could help us do what we're doing better!
In addition to names and comments, you can often clarify your intent in by making certain circumstances impossible by construction. As a trivial example, you might know that a value can sometimes be null, so you represent it as an optional type. Or, if you know you'll always have at least one item, you can use a non-empty list to hold the data. I've found this to be a lot easier in ML-family languages like Haskell or Elm, but you can do it in object-oriented languages as well.
There are tons of talks and examples of this online if you search for things like "make illegal states unrepresentable". I like Richard Feldman's 2016 talk Make Impossible States Impossible.
What Happens If You Ignore This?
If you don't clarify your intent when coding, you end up in situations where you have to re-establish context. In names, that might mean having to jump to the definition to figure out what's actually happening. In the case of comments, that might mean having to spend a long time looking through the code in order to find the shape of the program's core algorithm. In constructors, it means the same but trying to figure out if something should be possible or not.
If you don't clarify intent, you'll also miss out on great refactoring opportunities: in the
get_threshold_clients example above, we might see that we want to separate getting the raw data and applying our precise filtering rules. That makes the code more modular (and more testable!) but we might not have seen it if we didn't stop to clarify intent.
In summary, remember that code is read many more times than it's written. Keeping that in mind can go a long way to making things better for your coworkers (or just you, 6 months from now.) There are lot of ways to do this—way more than we can go over here—but you can get a long way by focusing on names, comments, and constructors.