Dead Code, Getting Untangled, and Coupling versus Decoupling
Three full chapters from the book Tidy First? by Kent Beck. The book offers book 33 practical - and increasingly sophisticated - approaches to make your code and systems more tidy.
👋 Hi, this is Gergely with a bonus, free issue of the Pragmatic Engineer Newsletter. In every issue, I cover challenges at Big Tech and startups through the lens of engineering managers and senior engineers.
My favorite book on software architecture is surprisingly concise. It’s called The Philosophy of Software Design, and it does a phenomenal job of underselling itself, as I learned after I read it a few years ago. Read my review of the book, or see my video review.
But now I have found what might just be my new favorite book on refactoring code. It’s Tidy First? by Kent Beck.
Kent Beck’s name will hopefully ring familiar to many of you: Kent has been a software engineer for over 40 years. He is one of the signatories of the Agile Manifesto and has been the front and center of the industry rediscovering test-driven development (TDD). He later worked at companies like Facebook and Gusto. He also writes the newsletter:
I have gotten to know Kent a little bit closer over the past couple of months, thanks to collaborating on the article Measuring developer productivity? A response to McKinsey two-part article we wrote is the most-read article across The Pragmatic Engineer.
In this issue, we share background on the book and three full chapters out of the 33 in the book:
Interesting details about the book
Chapter 2: Dead Code
Chapter 20: Getting Untangled
Chapter 31: Coupling Versus Decoupling
My usual disclaimer: As with all my recommendations, I was not paid to recommend this book, and none of the links are affiliates. See my ethics statement for more details.
1. Interesting details about the book
I met Kent Beck in-person a few weeks before the book was published, and I got my hands on an early copy. Over dinner this November, Kent shared stories about how he decided to write another book, more than 20 years after his last one (Test Driven Development By Example).
Kent had been continually coming back to writing this book for 18 years (!!). It was back in 2005 when he decided to write it. The idea came about after he attended a panel discussion about the 1975 book Structured Design by Ed Yourdon (RIP) and Larry Constantine. Back then, this book was 30 years old – and yet no “modern equivalent” on software design existed. Kent got the idea to write an up-to-date book on how modern software systems are – or should be! – built up.
He kept going back to this topic again and again, only to realize that one book would not cut it. In talking about modern design, you cannot ignore how you first need to tidy the existing code to be ready to change it (modernize it). He kept asking the question of whether he should, indeed, start with a book on “just” tidying up first. The question mark in the title of “Tidy First?” refers to this question, which is surely not unique to just Kent: all of us ask this question when we see legacy code or messy code. Tidy first, or go straight to changing what it computes?
Tidy First? is the first in a series of books about software design. Kent has been coming back to the topic of “taming” software design in a digestible format. An early attempt was around 2009 with the Kent Beck on Responsive Design presentations. Since then, he’s been stashing ideas mostly as blog posts. Here is what he told me about the plans for the series:
“Don’t take this as a definitive prediction. But the plan for the book series is to increase in scale with each edition.
Book 1 – this book! – is about you and how the design decisions you make affect you.
Book 2 is about you and your fellow geeks and how design decisions affect each other. And what to do about this outcome. Software design as an exercise in human relationships.
Book 3 I’m imagining will be about the relationship between business and technology. Finding an effective balance between investment in features & investment in design. Finding a rhythm – one side or the other – requires greater priority for a period, but both should always be present.”
Writing a short book rich with ideas can, indeed, take a lot of time! Tidy First? feels like proof of this. There’s a famous saying from French mathematician and philosopher Blaise Pascal:
“If I had more time, I would have written a shorter letter.”
Tidy First? could have been hardly shorter, as a book. It’s 33 standalone chapters and exactly 100 pages long. Most chapters are two pages long. I have yet to come across such a short book on software engineering.
And yet, the book doesn’t feel short: It feels concise and easy to read. Its ideas are polished so it doesn’t waste the reader’s time.
In a world where it’s challenging enough to find the time to finish a nonfiction book with hundreds of pages: I found it welcome just how easy it was to read the whole book. And despite reading it in less than two hours, I found myself coming back to certain chapters, rereading several of them.
The chapters follow a natural flow and sometimes build on top of each other. The book splits the 33 chapters into three parts:
Tidyings. The “what” in the book. Simple but powerful ideas like the importance of reading order, explaining constants or explicit parameters. Each chapter is short and offers one little design “move” that you can use to make messy code a bit more manageable.
Managing. The “how” in the book. When do you start to tidy? When do you stop? How do you combine tidying with changing the behavior of the system? These are longer chapters than before, offering ideas for chaining tidying activities, batching, and managing the rhythm of tidying.
Theory. The “why” of the book. Why do we tidy, really? What are the tradeoffs between investing in improving the structure of the software versus not investing anything? What principles can we use to decide how to change the structure of software?
The chapters nearly build on one another, and the topics get increasingly sophisticated. The first 15 chapters are a breeze to go through, as each one is a useful tidying idea. Part 2 builds on these tidyings, organizing them into a system in which you can do larger-scale tidyings – or even refactorings. Part 3 offers the most in-depth chapters, thinking about the big picture of tidying and refactoring. Part 3 is the most thoughtful part of the book, and it’s where Kent starts to go deeper and ask questions like what is software design?
With this, let’s jump into one full chapter from all three parts.
The below excerpts are from Tidy First?, by Kent Beck. Copyright © 2023 Kent Beck. Published by O'Reilly Media, Inc. Used with permission.
2. Chapter 2: Dead Code
This chapter is from Part 1: Tidyings. In this part, each chapter offers one little practical design “move.”
Delete it. That’s all. If the code doesn’t get executed, just delete it.
Deleting dead code can feel mighty strange. After all, someone took the time and effort to write it. The organization paid for it. There it is. All somebody has to do to make it valuable is call it again. If we need it again, we’ll be sad we deleted it.
I’ll leave it as an exercise for you, tidy reader, to identify all the cognitive biases I just demonstrated.
Sometimes it’s easy to identify dead code. Sometimes, because of extensive use of reflection, it’s not so easy. If you suspect code isn’t used, pre-tidy it by logging its use. Put the pre-tidying into production and wait until you’re confident.
You might ask, “But what if we need it later?” That’s what version control is for. We aren’t really deleting anything. We just don’t have to look at it right now. If (and this is a long string of conditionals) we 1) have a lot of code that 2) isn’t used right now that 3) we want to use in the future 4) in exactly the same way it was originally written and 5) it still works, then yes, we can get it back. Or we can just write it again, and better. But if worse comes to worst, we can always get it back.
As always, delete only a little code in each tidying diff. That way, if it turns out you were wrong it will be relatively easy to revert the change (see Chapter 28). “A little” is a cognitive measure, not a lines-of-code measure. It could be one clause in a conditional (e.g., you see the condition reduces to true), one routine, one file, one directory.
3. Chapter 20: Getting Untangled
This chapter is from Part 2: Managing. In this part, each chapter covers strategies to fit tidying into your personal workflow. Chapter 20 is about what to do when tidying and changing behavior of the code start to get tangled.
You’re changing the behavior of some code. You see a tidying that would make it easier to change. You tidy. Then you write another test case. Now you need to change the behavior some more. That leads to more tidying. An hour later you:
Actually understand all the behavior changes that need to be made
Actually understand all the tidying that eases those behavior changes
Have a mess of tidyings and changes all tangled together
You have at least three options, none of them attractive:
Ship it as is. This is impolite to reviewers and prone to errors, but it’s quick.
Untangle the tidyings and changes into separate PRs, or a sequence of PRs, or a sequence of commits in a single PR. This is more polite, but it can be a lot of work.
Discard your work in progress and start over, tidying first. This is more work, but it leaves a coherent chain of commits.
The sunk cost fallacy complicates the choice between these options. You have some new tests. They pass. Why would you want to throw that away?
The answer, as always, is because you are not just instructing a computer, you are explaining your intentions for the computer to other people. The shortest path to instructing the computer is not an interesting end goal.
By this point in the book it may not surprise you that I encourage you to experiment with the last option. Re-implementation raises the possibility that you will see something new as you re-implement, letting you squeeze more value out of the same set of behavior changes.
Untangling a ball of yarn starts with noticing that you have a tangle. The sooner you realize the need to untangle, the smaller the job is (and the less important the decision between the strategies becomes). When you first begin consciously tidying, whether first or after, you’ll likely miss the transition between “cruising along making changes” and “oh no, what all have I done?” Don’t worry. You’ll get better at sequencing tidyings and changes over time.
Speaking of “first or after,” it’s time to talk about timing.
4. Chapter 31: Coupling Versus Decoupling
This chapter is from Part 3: Theory. In this part, each chapter goes deeper into why we really tidy our software. This chapter builds on Chapter 29: Coupling, and Chapter 30: Constantine’s Equivalence (that the cost of software is approximately equal to the cost of changing it).
Why don’t you just decouple all the things? Why have any coupling at all?
Coupling, like the Lego piece in the night, often isn’t obvious until you step on it. You go to make a behavior change, then notice, “Oh, if I change this, I will have to change that, and that too.” Or worse, you change this, put it in production, break things, and realize, “Oh, I guess I also have to change that and that.” You’re not aware what unconscious assumptions you’re making.
Discounted cash flows account for some coupling. There’s a quick, coupled way to implement some behavior, and a longer, more expensive, decoupled way. At the time, you made the economically correct decision to implement it with coupling—revenue sooner, expenses later. Now it’s later.
Another legitimate reason to have coupling in a system is because it wasn’t a problem until just now. The boulder that was perched on the hill decided now was a good time to roll down. “Who knew that we would have to translate this into any other language?” And you didn’t. Until you did.
A final reason to have coupling is that some coupling is just inevitable. I’m afraid I don’t have a better argument for this than “confident assertion.” I’ll work on it.
It doesn’t really matter why the coupling is there. You’re faced with a choice today: pay the cost of coupling or pay the cost of decoupling. “Tidy first?” is this decision in miniature (although only some messes are made of coupling).
Let’s take a look at a concrete example—a communication protocol. A simple way to implement it is to have a sending function and a receiving function:
These functions are coupled. Change one, and you’d better change the other. Then you have to worry about deploying the changes in perfect synchronization.
By the hundredth time you modify these functions, you’re probably getting tired of the extra care required. You define an interface definition language:
Poof! Coupling gone. Now you can change the format in one place. No need to change send() and receive() at the same time.
But it turns out the coupling isn’t “gone” gone. Yes, we can change the format in one place, say by adding a third field. However, somewhere deep inside the Sender, we still need to compute that third field. Until we’ve done that, we can’t read and use the field in the Receiver. So Sender and Receiver are still coupled; if Receiver needs to change to use the new field, Sender needs to change too. We have given ourself more options for the order of implementation.
Here’s something I believe but can’t prove or adequately explain: the more you reduce coupling for one class of changes, the greater the coupling becomes for other classes of changes. The practical implication of this (if it matches your intuition) is that you shouldn’t bother to squeeze out every last bit of coupling. The coupling created in doing so isn’t worth it.
Overall, we are left with a trade-off space (Figure 31-1).
This picture is naive in that the exact costs of coupling and decoupling aren’t knowable in advance. These costs both play out over time, which introduces discounted cash flows. Decoupling also creates options, the value of which is uncertain and evolves over time.
The fundamental decision space remains. You can pay the cost of coupling or pay the cost (and reap the benefits) of decoupling. And you can fall anywhere along this continuum. No wonder software design is hard. And we won’t even get to the interpersonal relationship part until the next book in this series.
Writing The Software Engineer’s Guidebook for four years felt like a long time to me – but it’s still a fraction of the 18 years that Kent has been working on the core of the Tidy First? book. The book is a reminder of how sometimes less in writing is more in takeaways. When I looked to write a summary of the book, this is what I came up with:
“Easy-to-read code is simple and concise. And what is an easy-to-read book about writing easy-to-read code? It should also be simple and concise. This book is just that. Highly recommended for anyone writing code, day-in, day-out.”
If you’re someone who codes regularly, I cannot recommend this book enough. I hope you enjoyed this peek inside, and the three ideas of dead code, getting untangled, and coupling versus decoupling.