Early in my career, my team lead pulled me over to show me something in some code of mine I had written and put up for peer review. I don’t remember the exact context, just that there was a fairly simple operation that could have been done quite simply in 4-5 lines of code, that I had cleverly condensed down to 1. He had the code in question up on the screen and told me “I see what you’re doing, and this works. But it took my pea brain about a minute of really thinking about it to understand what you’re doing and confirm that it works. On the other hand, if you had just written this out in 4-5 lines of code, I’d have understood it instantly. So I’m going to need you to fix this.” It’s a vivid, specific example of something experienced developers try to impart on the newbies – don’t get clever, and don’t hesitate to write more lines in order to be more explicit about what you’re doing. I just finished reading Thinking, Fast and Slow by Daniel Kahneman, and this incident jumped back to my memory, because it was the perfect case study for writing code they way we naturally think.
The basic premise of the book is that our brains generally have 2 “systems,” referred to as System 1 and System 2. System 1 is basically a low-powered, heuristic engine constantly running on auto-pilot. If we do perceive that it’s directing thoughts, we tend to register it as instinct, intuition, or just a feeling about something. System 1 will often try to simplify things into something it can “answer,” even if that simplification isn’t the best approach. System 2 is what we generally think of as more conscious, deeper thought. This is the process where we consider things enough to buck our initial instincts, take counter-intuitive approaches, and go deep into the underlying logic. It’s more “high-powered,” meaning our focus gets narrowed (think the infamous ape moving through basketball players trick).
The admonition to not get clever and use more lines to be more explicit is, without us necessarily knowing it, a call to write our code in a way that favors running our brains on System 1 as much as possible. We don’t talk about the neuroscience behind it (because we’re probably unaware of it), but we’re effectively trying to limit our use of System 2 to the places that absolutely need it. Or, to put it another way, keep the stuff that’s hard to understand to a minimum, because code that’s hard to understand is code that’s hard to keep working successfully.
While I was working through the thought process behind this, Derek Comartin came out with a video that dovetailed nicely into this concept with a take on if-statements that I’m probably going to carry forward in my own coding. Specifically it was his discussion of boolean blindness. His video shows it using C#, I’ll put it here in Go since that’s what I’m using these days:
if !isValid { /* Handle the problem */ }
Specifically, he was objecting to the use of the “!” character. Comartin discusses “boolean blindness,” specifically how easy it is to overlook that (thin) single character in conditionals (as someone who’s overlooked that operator and wondered why his code wasn’t working when the conditional should have followed the “true” path, it’s not an unreasonable take). Instead, he recommends formatting the conditional like this:
if isValid == false { /* Handle the problem */ }
It’s the same thing, but wordier. However, the second if-statement “reads (more) like a book.” Or in other words, it’s easier to parse correctly using System 1.
It’s easy to fall into the siren song of thinking fewer lines of code means “better” code. But the real power behind conciseness is that none of the original meaning or intent is lost when going from something wordier to something shorter. It’s the difference between people who are consistently popular on Twitter (never X), and literally everyone else who thinks they’re cleverer than they actually are.
Any software developer can just spit out code. A lot of software developers can spit out some clever code. The good software developers can break a problem down and approach it in a way that makes it feel simpler than it first appears. The great devs can condense that simpler, easier-to-follow logic. Their System 2 thinking is largely devoted to making their code as System 1-friendly as possible.
There’s a lot of people out there who like to throw out a lot of programming advice and guidelines. A lot of it is decent, if often presented without any sense of context for when that approach works and doesn’t. But when you work with people who have been building software for a while and get a sense of their approach to coding, they tend to favor things that are as System 1-friendly as possible. Simpler designs, more explicit code, and as straightforward an approach as possible. In fact, a lot advice from people who ship code (and have to maintain the code they ship) tends to be implementation tips for how to Keep It Simple, Stupid.
Sure there’s a lot of other acronyms for things you’re supposed to be doing, like SOLID, DRY, and YAGNI. But if you listen to the more tenured amongst the profession, they’ll every so often caution you that “best practices” doesn’t mean “do this without thought or question.” Don’t get me wrong, they’re all for these other concepts…right up until the point that it makes things harder to understand. At that point, they tend to ask themselves if what they’re doing is actually easier than the alternative, and abandon any approach that answers “no.”
I know by this point I’ve made it sound like all code should be mindless and simple, and anything other than that is a personal failure. That’s not really the case (although the vast majority of your code should be as simple as possible, and convoluted logic should be an invitation to re-think what you’re doing). There are going to be places where you need to go into that deep, more conscious thought about your problem in order to solve it. What the best developers have figured out how to do is to limit that to very specific parts of the problem, instead of embracing complexity and requiring deep thought to understand everything. It’s the difference between a small code block (or even a whole function) with a 20-line comment explaining what’s happening that you don’t ever need to touch again, versus a code base where everything takes about 3 times longer than you’d think it should, simply because it’s that big a mess to figure out what’s happening and where your change needs to go in the first place.
Our brains are wired to spend as much time as possible on a default, autopilot mode. This is something that a lot of experienced developers understand intuitively, but there’s something about seeing it spelled out that really makes maximizing code understandability “click.” Knowing that people try to lean on pattern-matching (and substituting simpler problems as “shortcuts” to solving harder problems) puts the common refrain of “don’t over complicate things” into sharper focus, and makes me more sensitive to things that force me out System 1 into the stop-and-think-about-it-deeply System 2. It’s also made me much more appreciative of developers who have the skill to pull their code out of the realm of things that have to be thought about heavily into the land of things that can quickly be understood by pea brains.