"The core problem that software engineering should solve is that it's so complex that it doesn't fit the human brain."
This book is one man's audacious attempt to help the world solve that problem. He does a fine job.
Code that Fits in Your Head, by Mark Seemann, is a must-read for every senior software developer.
My all-time favorite programming book is Code Complete by Steve McConnell. It's full of well-researched, practical advice on every detail of writing code in any language: tips on naming, organizing code, defensive programming, designing loops, debugging, etc. For a developer starting out, every page is full of solid gold. I try to keep a few used copies on hand just so that I can give them away to budding programmers. You could say I'm a fan.
Code that Fits in Your Head is to senior software developers what Code Complete is to junior software developers. I can think of no higher praise.
The Forest, Not the Trees
The main difference between the two books is the level of focus.
Whereas Code Complete covers every detail of the code-writing process, Code that Fits in Your Head covers the software-engineering process. Checklists offer a clear example of this distinction. Code that Fits in Your Head discusses the benefits of using checklists; Code Complete includes a checklist at the end of every chapter.
Code that Fits in Your Head focuses on the forest, not the trees.
My Key Takeaways
There are many topics in this book worthy of their own article. To whet your appetite, I'll briefly describe the topics that immediately stuck with me upon my first reading (yes, I plan to reread it):
- Vertical slice
- Hex flowers
- Functional core, imperative shell
Have you ever tried to write down the names of all 50 states in the US?
The first 48 are pretty easy, but remembering those last two is difficult. This is why airplane pilots use checklists. It's too easy to forget to perform some critical step when you have a long list of items you need to remember. Even if you know every item on the list, recalling each item from memory is unreliable.
Thus, Seemann recommends borrowing this technique as a way to improve reliability around recurring software development processes.
Deploying an application into production is hard.
The end user environment is different than the development environment. The backend data files are likely different. The code needs to be committed to version control. You may have third-party dependencies that need to be installed. You also need a way to distribute updates.
Given all this complexity, it would be insane to wait until the end of a software project to tackle this challenge. Yet that's how most software projects work!
Seemann's approach is to start with a minimum working application. It doesn't even really need to do anything. You just need something that you can commit to version control, distribute to end users, and then set up a process to roll out updates.
Why wait until the very end of a project to tackle one of the hardest parts?
Seemann bases much of his book around the science of short-term memory.
In particular, he relies on the well-researched idea that the typical human brain can only hold about seven items in short-term memory. Seemann uses the visual example of a "hex flower" to show how we can refactor code so that it more easily fits in our brain.
He uses cyclomatic complexity and the concept of "chunking" to show how we can break down a class or routine into smaller constituent parts, each of which can fit independently in short-term memory.
If a routine has a cyclomatic complexity greater than seven, Seemann recommends breaking it up into smaller, more easily comprehensible parts.
Functional Core, Imperative Shell
Pure functions are deterministic methods with no side effects.
Most importantly, such functions can be treated as black boxes. We put inputs in one side of the box and get outputs out of the other side. Always the same outputs given the same inputs. And nothing in the black box impacts anything else in our application. Pure functions are easy and reliable to test.
A pure function makes code easier to understand because it only occupies a single chunk of our short-term memory (a single petal in Seemann's hex flower).
If pure functions are so great, why don't we build our entire application from them? Well, ackshully, there are entire languages that take that approach (looking at you Haskell). The problem is it's hard to build anything useful without creating some side effects.
For example, every database INSERT, UPDATE, and DELETE operation is a side effect. File reads and writes rely on or produce side effects. Methods that produce side effects are harder to debug than methods that don't.
The functional core, imperative shell design pattern squares this circle by implementing complex business logic within pure functions, while using traditional imperative code to interact with the various systems at the edge of our application (file system, database, printer, mouse, keyboard, etc.).
Functional core, imperative shell brings together the best of both worlds.