The two most terrible kinds of names are also the most common:
- Names that mislead you, and
- Important parts of something else, so they don’t even have a name.
In the spirit of naming as a process, we want a quick way to fix these two problems. It needs to be trivially fast and light, so that we can do it the hundreds of thousands of times that most codebases need.
We will turn Missing and Misleading into Obvious Nonsense.
Obvious Nonsense isn’t great, but at least it isn’t misleading! Now it becomes obvious to everybody on the team that the name is nonsense and they won’t be tricked into writing a bug.
Fixing Missing Names
Why start there? Making code readable is our ultimate goal, however often code has many different concepts in the same method/class/field/variable. We want to pull out each concept and give it a name. When a concept is embedded within another thing, we can see its name as missing.
Part 1: Look at Long Things
Focus on long things that are related to the task at hand; the long methods, long classes, long files, long argument lists, long expressions. All of these are more readable and consumable when broken into pieces with one concept each.
Part 2: Look for 1 lump that hangs together
I look around for things that seem to go together. Examples include:
- A paragraph of statements
- A non-obvious expression (usually involving arithmetic or logic)
- A set of parameters that are frequently passed together.
- A set of parameters that are used together in the method (such as a bool which indicates whether another variable is valid)
I just pick one chunk that hangs together. It doesn’t really matter how good a chunk I find. There are techniques that will find better chunks and speed up my understanding, but any chunk will get me one step closer. We can leave refinement for later. Good is too expensive; all I want is better (quickly).
Part 3: Write down by extracting it as Applesauce
We want to understand this lump of stuff. The first step is to create a thing to be named. Typical languages allow us to name any of 5 different things, so we must create one of these things. To do this, we use one of 5 refactorings:
- Extract Method (to name a bunch of statements)
- Introduce Variable, Parameter, or Field (to name an expression)
- Introduce Parameter Object (to name a set of parameters that are passed around together)
We want an Obvious Nonsense name, so use
Applesauce. Everyone on the team will instantly recognize it as nonsense. Also, because there is only one nonsense name, you will need to make this name honest (the next step) before you make more nonsense in the same scope.
Obvious nonsense feels unprofessional. It’s not how you want to represent your work. Creating bugs, however, is more unprofessional – we are just more used to it and have come to accept it. Things that contain many unnamed concepts are difficult to reason about, which will cause developers to write bugs.
The self-directed and coached habit changes for this process address common resistances, including the urge to take the time to create a good name.
Finding Better Chunks in Long Methods
There are a few ways for you to hone your chunk finding skills. Once you’re in a long method and you see that there are possible missing names, use these strategies to find the best pieces to break out.
Look at the Bottom First
Long methods tend to be organized as:
- guard clauses
- use parameters to read the data the method actually wants into local variables
- calculate a result
- either write it down somewhere or return it.
This means that the more important stuff is near the end of the method. So start looking for chunks from the bottom, not the top.
This also makes extraction easier in most languages, because functions often allow multiple parameters but only one return value. So it is easier to flow messy information (like a bunch of local variables) into a function than out. This is not a problem in languages with functions that have the same cardinality for both parameter lists and returns (support destructuring return like Python or support only one parameter like Haskell).
Use Binary Search to Read Less
In general you’re not reading code for the joy of reading it. You’re trying to accomplish something. Use that something to guide what code you bother reading.
Use binary search to quickly find chunks that are highly relevant. Look for large chunks that don’t contain whatever it is you are seeking, or small chunks that do. Either quickly reduces the search space.
Foreshadowing a bit into the next step, when you extract a large chunk of code you know to not be related, just get the name up to barely Honest and then move on. For example, you could call it
Follow Control Structures
Long methods are often dominated by a single control structure (after the guard clauses and fetching data into locals). The body of that control structure is often a good target. If there are multiple control structures in sequence, instead take the whole last control structure.
For example, if a method
BecomeFroglike() contains one giant
foreach loop, extract the body of the loop and call it
MakeOne[whatever the control variable is]BecomeFroglike().
And if a method contains a
foreach loop, then an
if block, then another
foreach loop, extract those into three methods, each taking the whole control structure. The outer method is then just a series of 3 method calls in sequence.
Exception handling is a little special. The body of a
try block is often a good target for extraction. I usually name the resulting function
catch blocks are usually not worth extracting (though there are exceptions), so instead look for ways to extract the useful bits away from them.
Fixing Misleading Names
Another common challenge to reading code is when the name is indicates something different than what it does. When a concept is not being represented accurately by the name, we say its name is misleading.
Part 1: Look at Names that you are using
Focus on whatever names you encounter in the code you are reading.
Part 2: Look for under-information or misinformation
I look around for partial lies. Examples include:
- A method named by when it is called in a lifecycle (PageLoad, PreInit).
- A variable named the same as its type (GridSquare gridsquare).
- Method name that leaves out critical information (method named composeName that also writes that name to the database).
- Any name that ends in -er or -Utils (DocumentManager, CalculationUtils).
Part 3: Write down by renaming it to
Don’t bother trying to figure out what it does or a good name for it. Just call it
Applesauce and move on with your coding. We have the social norm that should always be good; however, if the name is misleading, even if it looks good, is not good. Misleading names will cause someone to write bugs.
Check it in!
I used to consider whether to check in at this point. It feels so small, and
Applesauce feels like such a terrible name!
After 4 more years of practicing this, however, I no longer consider. I always check in.
Both of these changes make the code better. Not a lot better, but better. They are fast, easy, safe, and don’t make anything worse. By checking them in I immediately get back to a clean slate and am ready for whatever comes next.
Replacing a missing name tends to result in more insights about the code, so it is likely that both this commit and the next will be large. I want to split this up from that. Replacing a misleading name will almost always prevent one future bug. It is worth checking that in immediately so that there is no chance a failure at my next task will roll this back. Either way, check in now.