Understandable Code
Sometimes it's code I'm trying to debug, other times it's code I'm considering using in my own code, or even code that calls my own code. What's interesting is that the code often doesn't do what it's supposed to, and that's what makes it challenging.
Reading code is like reading a story. You start at the beginning, follow the flow, and by the end, you understand what the code does and why.
In fact, when code is easy to read, you can zip through it like reading a single word:
When looking at code, we can sight-read small sections easily. However, as the code gets longer,it becomes harder to understand without thinking. Our brain starts to reason through the code, line by line.
For example, when reading the Sieve of Eratosthenes
code, we might think:
- The flags array is filled with 100 false values.
- We're collecting results in this array.
- The loop starts at 2, but why?
- Skipping flagged values, but why?
- Pushing values into the results array, maybe this is the output.
- The second loop is over multiples of the value, marking them. This must be the
Sieve of Eratosthenes
!
We mentally juggle multiple ideas to understand the code. Our brain can only hold a few "balls" in mind at a time. If we try to remember too much, we'll lose track of some of it.
This is because our short-term memory has a limit. We can only hold about seven (plus or minus two) thoughts in our mind at a time. If we try to add more, the new thoughts will push out the old ones.
As programmers, we need to be aware of this limit to avoid getting lost in complex code. If there are too many things we don't understand, it's like trying to remember a long shopping list – it becomes impossible!
What is Good Code
Good code should consider the reader's needs, not overwhelm them. If the code requires too much information at once, things will get forgotten.
Our brains can only hold a certain number of "balls" in our short-term memory. When the number of new ideas, facts, and connections exceeds this limit, things start to drop. This can make the code difficult to understand and even lead to mistakes.
The solution lies in writing code that is easy to read and understand, one step at a time. By doing so, we can avoid the frustration and errors that come with information overload.
Good code is easy to understand because it's organized. Ideas are presented in small, connected pieces that fit in your brain. As you read, these pieces can be combined and then forgotten, leaving you with a single, clear thought.
For example, when you realize that a code generates a list of prime numbers, you stop worrying about the details. You go from understanding many small pieces to having one big idea. Good code makes this easy.
The idea is that code should be built in a way that your brain can quickly understand, combine, and then forget the details. This makes it easy to get to the core idea.
Good names help with understanding. Names like "primes" and "isMultiple" make it easy to see that the array contains prime numbers.
The name "primes" is a convenient reminder that the array holds prime numbers. If the variable had a different name, like "xx", it would be harder to remember what it represents.
Comments also simplify the code by explaining what each section is doing. They help chunk the code, making it easier to understand.
This is the power of abstraction. It allows us to understand complex ideas by grouping smaller details together. Instead of remembering everything, we can remember the main idea and build upon it.
Splitting the code
Splitting the code into separate functions can improve readability. However, it's not a straightforward improvement.
Each function defines a clear concept, making it easier to understand what it does. But, jumping between functions can be overwhelming. You'll need to remember where you started, what variables are used, and what to expect when you exit the function.
This added complexity can be challenging to follow, especially since your brain can only hold onto so much information at a time. There's a common myth that any code can be improved by breaking it down into smaller functions. While abstraction is useful, it's not always the best solution.
The Cost of Abstraction
The simplicity of code is often lost in the pursuit of abstraction.
Some code is concise, but it's hard to understand. You need to investigate to figure out what it does.
The abstracted code is harder to read and understand. You had to navigate multiple lines of code to comprehend it, straining your working memory.
Make It Easier to Understand
When deciding on abstraction, consider this rule: "Will the change make the code simpler and easier to understand?" If yes, implement it. If not, don't.
Mental Budget
With a mental budget of only seven (plus or minus two) thoughts, it's difficult to imagine building complex things.
Short-term memory, on the other hand, is used for working out problems. It's like a temporary workspace where I process information, piece together ideas, and reach conclusions. Once I've figured something out, the result moves into my long-term memory.
When I look at familiar code like sort, it's like reading a familiar book. I don't need to work out details in my short-term memory because I already know the abstraction.
Common Knowledge Vs New Concepts
When writing code, our goal is to make it easy to understand.
Using standard abstractions and patterns common in our team is much easier than inventing new ones. Unless we're confident that the new abstraction will become standardized across the team, it's best to stick with what's already familiar.
The only time it makes sense to introduce a new abstraction is when we know it will be used widely across the codebase and will have important advantages over existing solutions. We should also consider how it will be used by others and make sure it's easy to understand.
The best code leverages how short-term and long-term memory work together. It uses familiar functions and patterns, and introduces new ideas in small, manageable chunks. By doing so, we can make our code easy to read, easy to learn, and easy to extend.
In summary
when writing code, use standard abstractions and patterns, and only introduce new ones when there's a good reason to do so. This will make your code easy to understand and maintain.