How much time do you spend reading code?
Probably more than you think.
When writing the next line of code, you’re reading what you wrote two minutes ago. You’re reading the other file your teammate wrote two weeks ago. You’re diving into understanding a library written two years ago.
And every single one is a rabbit hole! You read a few lines, jump around to something written a few lines away, jump to a third place, forget the first bit you were reading, go back to refresh yourself, jump back to the third place… it goes on.
Let’s talk about naming: why taking naming your variables, your methods, your classes — everything — more seriously has the potential to 10x your and your team’s development efficiency.
And I’m not here to add to that stack of literature. I’m writing to try to convince you that the consequences of naming in your code well are worth striving for, providing benefits in ways you may have yet to realize.
That said, some of you might already be convinced of that, and would find this toolbox of naming theories and tactics more practically useful than the rest of this post:
And here in-line my personal, abbreviated cheatsheet of naming code smells that I come across most often:
With that out of the way, why should we care?
In the timeless Getting Things Done book on productivity, David Allen describes your human brain to be the equivalent of RAM. He emphasizes that the more you try to store there at a given time, the more likely you are to overload your brain’s RAM, resulting in forgotten details, lost focus, and overall increased stress.
While the RAM — human memory analogy may not be perfect, the point is well taken. Allen’s solution to this limitation is to write everything down. Whenever you hear a good idea, whenever you momentarily remember you need to complete a task — when you’re not actively processing some piece of information: write it down.
And most importantly, make sure you’re writing it down in a system that you’ve conditioned yourself to trust.
If you don’t have a system you can trust, you’re back to square one: there’s no point in freeing up your RAM if you don’t trust your HDD to serve you the information when you need it later.
We can apply this same tactic to the art of coding. When you’ve decided what the next line in your code needs to do, write it down. To do that, you’re first going to need to come up with a good name using all those tactics in the last section. Whether it’s a variable or helper function you’re going to bring out, having the diligence to craft a good name for it is the equivalent of building a productivity system you can trust.
When you (or your teammate) looks at that name later, immediately they’ll comprehend what its purpose is. They won’t have to context-switch and dig into the function’s implementation or hop around to usages of the variable to read your one line of code.
Now when you’re reading through your beautifully modular 10 line main() function to add the latest feature, you can immediately understand that it fits best in between line 8 and 9 with a tweak to the value and name of the argument pass into line 9.
Everyone writes bugs in their code. Some write more than others, but ultimately there’s no avoiding the occasional edge case.
When you name well, it becomes possible to mentally unit-test your code. As a reviewer, I’m no longer inundated with a 1,000 line mess that I have to understand end-to-end to give you any kind of useful feedback.
Observe poorly named code:
def calculate(p, r, n, t): """Returns compound interest for the formula A = P(1 + r/n)^(nt)""" return pow(1 + (r/n), n * t)
And better named code:
def calculate_compound_interest(principal, rate, times_per_year, years): """A = P(1 + r/n)^(nt)""" base = 1 + (rate / times_per_year) exponent = times_per_year * years return pow(base, exponent)
In which one was it easier to find the bug?
You probably found it pretty quickly in both, but when the situation is non-trivial, it’s easy to imagine a reviewer or developer thinking “Ok, that base is definitely correct. Ok, that exponent is definitely correct. Hmm, weren’t we supposed to combine three elements in step 3?” as opposed to “Does this one liner take all the different components of the problem into consideration?”
When you’re working on a large codebase with a large team, maybe with a team that no longer has all of its original members, you’re often faced with the thought, “I’m sure someone has previously solved this sub-problem that’s staring me in the face right now…”
And then you’ll go hunting down the teammate who might have heard of such code, who will tell you to look in X module for Y class in hopes of coming up with Z method. You’ll dive into the codebase, digging through the rabbit holes, only to find some obscurely-named method that looks right, but you’re not sure. You’ll spend another 30 minutes going through line-by-line before determining that this bit makes some incompatible assumptions with what you need.
In an alternative reality, you have an idea of what you want, you search across the codebase for some obvious keywords, see 5 candidate methods immediately, and find out the 4th one is exactly what you need. Maybe you’ll still spot-check the implementation, but at this point you’re only 30 seconds into your search so that’s a small sacrifice to make for the added assurance. You leverage it and move on with your life.
Oftentimes we’re averse to slowing down and taking the time to construct a good name for our next variable because it’ll put a hurdle in our flow — we just want to keep on moving and cranking out those lines of code.
But if you’re incapable of putting a label on the concept you’re about to leverage or implement, is that a flow path you really want to be on? What if you spend the next hour plowing through the implementation, only to realize that 90% of it was all a waste because you forgot to account for a quirk in the application’s domain model?
Maybe you wrote this:
def charge_customer(value): # Let's say you implemented all these helpers as well db_session = getDbSession() insert_value_into_db(db_session, value) inform_customer_of_charge(value)
When you ultimately realize you need something more like this:
def charge_cost_of_coffee(CoffeeDrink): total_coffee_cost = 0 total_coffee_cost += CoffeeDrink.base_cost if CoffeeDrink.has_milk: total_coffee_cost += 0.5 total_coffee_cost += calculate_added_cost_for_size(CoffeeDrink.size) # and so on...
You can see how this problem could balloon in more complicated situations.
Taking the time to name well up-front will save you significant development, review, and maintenance time down the road. Thinking through use cases, understanding if you need extra data from elsewhere, identifying bad assumptions — all of these issues can be exposed if you take a moment to unearth a good name.
It might not be as fun to delay writing the next line of code to put you one step closer to the happy-path solution. It might be more intellectually stimulating to design a solution (implementation) rather than design a problem statement or label (name) for what you’re tasked to do. And you’ll end up better off if you figure out a way to resist that temptation.
Naming is not an easy problem. Code reviewees might kick and scream as you keep refusing to merge their pull request until they have that function named just right. You’re going to get stuck on a variable name, call it “data” in anticipation of getting to the interesting algorithmic challenge to follow — and you’ll forget what you were looking for just as you’re getting in really deep.
Through it all: remember why it matters. Treat the time and potential mental drainage of your future self and that of your teammates with respect. When you and others read your old code, you don’t want them to feel as if they’re re-writing and re-understanding it with your past self, over and over again.
Lean in to the discomfort now, and build a codebase for a better tomorrow.