A question keeps showing up during my pair programming that’s surprisingly difficult for me to answer. The question is this: what is good code? That’s like asking, “what is good writing?” except it’s more difficult to answer because code is many-dimensional - more so than writing.
I’m using that word “many-dimensional” partly because it sounds cool, but also because it’s the very first piece of information needed to understand the full answer to the question of good code. It says what I mean succinctly and I’m hoping it’s the word you remember to describe this aspect of software. Good code has many dimensions. By “dimension” I mean a property of code where the code in question can exemplify that property or violate it.
For each dimension, good code exemplifies that dimension at all levels of detail. There are a lot of levels of detail. We have individual statements which compose lines, which compose methods or functions, which compose modules or classes or files, which compose folders or packages which finally compose entire projects. Good code is high in each dimension at each level of detail. This is a difficult feat to achieve.
A full in-depth answer to this question isn’t just one book - it’s multiple books. So for now I’m going to summarize what it means to have good code across three dimensions and all levels of detail. The dimensions I’ve chosen for today are executability, functionality, and understandability.
Executability
Good code executes. It executes at every level of detail from the smallest statement up to the whole project. This is what every beginner programmer struggles with and it’s the first thing we care about. You need to understand the syntax of the language.
If you are struggling to write code that compiles and executes because you often have syntax errors or other runtime crashes, I recommend you bookmark this article for later, stop reading it, and go write more code in the language you are struggling with. Maybe you often mess up the syntax for writing functions in JavaScript. Stop reading this, make a new project in your editor, add a blank HTML file, add a script tag to it, and start writing some JS functions that do literally anything. Repeat the practice of writing functions until the keystrokes are muscle memory. Then look up the other ways to make a function and practice with those. At this point, you just need to practice and understand the syntax of functions and its variants.
It sounds silly, but you also might need to practice typing. If your fingers can’t quite remember where the }
is to close your function bodies, it’s more difficult to write proper syntax because you’re thinking too much about where to find the characters. You need to practice more.
Unfortunately, I sometimes see paid professionals struggling at this level. Syntax shouldn’t be a tough question - even for junior engineers. It should be a few seconds to spot and resolve. Maybe a google search for a reminder about any rarely encountered syntax. If you have a hard time fixing the errors, you need to practice more.
Functionality
Separate from executability is the functionality of the code. Not only does good code run (that’s the easy part), it should do the right thing.
Working out what the right behavior is isn’t easy. It sounds easy, but it’s quite challenging in environments where the domain is sufficiently complex - and that’s most domains. The problem is compounded when you realize that it’s possible for one part of the code to do the right thing but for another part of the code to interpret it incorrectly. So the functionality must be correct at every level of detail from statement to the entire project.
The other day I was working on a function that categorizes an order based on information we receive from a database. Our users wanted to know if their order was shipped, scheduled for building, being built, or already delivered. Sounds easy enough, right? Well, it turns out that there are about ten different ways of representing an order that’s scheduled to be built. It’s my job to understand enough about how our business works so that I can differentiate between all the kinds of scheduled orders and write code that could teach that to someone else without me around. That’s the north-star goal. This is hard work, and it takes razor sharp understanding of the business.
I like employing test-driven development where possible to ensure that my code accurately represents the reality of the business environment. TDD simply means that I write an automated test before I write the code to make that test pass. After I make the test pass, I clean up the code where it needs it. I strive to only write the minimum amount of code necessary at each step.
TDD necessitates competency with not only the syntax of the language, but also the tooling environment in which you are coding. For example, TDD in React means you need to know a ton of other tools in order to make it an enjoyable experience. You have to know JavaScript, probably TypeScript too, React, then React Testing Library, Jest, JSDOM, and any other tools you might be using in your project. And that didn’t even include any custom tools or utilities. The amount of information required to do this can quickly get out of hand and I always see new engineers struggle to keep up with the information required to TDD in React. I imagine it’s similar in other environments. My suggestion is to begin with an overview of what tools fill what purpose so that you at least know where to go to look something up. If you can’t differentiate between Jest and React Testing Library, you’ll have a hard time knowing what questions to ask. Ask your team lead to list the major tools you’re using so that you can read the documentation. And then read it until you understand what it does and you can identify it’s use in the project.
Things like these keep me up at night
by u/MeanLittleMachine in programminghumor
Understandability
Starting at this dimension, coding becomes less of a science and more of an art.
Maybe you don’t consider yourself artistic, but if you write code you are at least partially an artist. Please embrace this. You are an artist in the same way that writing is an art for those who communicate in writing for their jobs.
Understandability is the first dimension judged purely based on what people perceive. This might bug you as a programmer because engineers stereotypically like “correct” or “incorrect” answers. But here there is nuance. I love it!
Understandability is different from intention revealing. Intention revealing is a higher and more difficult goal to achieve we’ll talk about later. Understanding is straightforward. It simply means that people understand what your code does. They might not know what you meant for it to do, but they can read the code without having to double check the previous line all the time.
Most people agree that understandable code is short-ish. Long code has the effect of losing the reader’s ability to follow the logic. Once more than two or three variables are introduced at a time it gets absurdly difficult to mentally track each one as they mutate.
This is why we have abstractions - the simplest is the humble function. A good function allows you to hide information from the reader so that they only need to focus on what’s at hand and they can assume something about what that function does based on its name. The word “assume” in that last sentence should have sounded like church bells ringing for a funeral. They are going to assume wrong.
If you’re code is too short you also sacrifice understandability. Functions that are too short are frustrating to read because the reader must jump from function to function to function without the ability to retain any context from the previous lines of code they read. Alternatively, you can try to cram a ton of instructions into one inscrutable line of code (functional programmers are guilty of this) and this is equally frustrating. Nobody likes to scratch their head and squint at a single line for thirty minutes.
I recommend splitting functions into anywhere from 5 - 100 lines of code that all talk about the same level of abstraction.
This last point leads into the next dimension of understandable code - intention revealing. But I can’t finish it tonight so I’m shipping this off and I’ll see you in a future iteration of this series.
Thanks for reading!