Changing Unfamiliar Code? First, Seek to Understand

As developers, we rarely, if ever, look back on a completed project with a sense that every aspect of the codebase was as good as we could possibly make it. We’re always battling time and budget constraints, competing priorities, and shifting goals as we work. So why do we ignore this reality when interacting with code written by others?

Our “Good Enough” Code

The right decision is often not to do the technically best thing, but to do the thing that optimizes trade-offs for the best overall project outcome. The markers of success are a product successfully released, a happy customer, and a budget overrun avoided. Achieving those often means making the tough choice to be satisfied with code that could be better (given a second chance to write it), but where rework provides no practical upside. Good developers understand this.

Our Double Standard

However, when reading code written by others, we often fall into the habit of treating it as a finished product, as the writer’s intended destination (instead of a waypoint). And we judge the code’s flaws accordingly. Maybe we think it’s messy. Maybe it’s not the way we would have written it. Maybe there’s just more of it than we feel we have time to digest.

Often in these situations, one of a few things happens:

  • We learn just enough to figure out what process changes will achieve our desired effect and complete our task. Then we make them — even if they distort and bring (additional) ugliness to the code we’re changing.
  • We build something else on the side. Writing new code is easier than reading and understanding code written by others, so we write new code that aligns with our aesthetic and biases (but this way’s functional!) instead of building on existing parts of the system.
  • We write off the code as flawed and unsalvageable without thinking about how it could evolve from where it is today — seeking to deprecate it and sealing its fate without really having given it a fair shot.

I see all these behaviors again and again. All three are destructive.

Don’t Become the Problem

Dave Rupert recently drew attention to an often-missed subtlety in Ward Cunningham’s formulation of the “technical debt” metaphor. Cunningham didn’t use the metaphor in reference to code being “messy,” poorly tested, or other linguistic and technical aspects. It was in reference to the team’s understanding of the problem at hand and the need to continually refactor the code to accurately reflect that understanding.

The code should, Cunningham said, “look as if we had known what we were doing all along.” (See the video of Cunningham in the linked post above.) Failing to keep the representation in the code in alignment to the understanding of the problem and the domain introduces an impedance mismatch that will slow down the team, regardless of how “clean” that code is.

The flip side of this metaphor is a responsibility as developers to shepherd and build on that understanding in the codebase, not to obscure, duplicate, misrepresent, or distort it. Certainly, there can come a point where the understanding of a problem deviates enough from the original intent — either due to lack of refactoring along the way or to a sudden shift in understanding — that a module may need to be fundamentally reworked to resolve the gap. (It’s also possible that the code really is fundamentally flawed and doesn’t work correctly or is too error prone to work with.) But in many or most cases, that’s not necessary.

To tell the difference, you have to read the code.

  • Learn what understanding of the problem is represented in the code, then identify any misalignment between that understanding and your own.
  • Learn from the code. Often, the misalignment is because you’re missing something important.
  • Understand what’s there, and think carefully about how best it can accommodate the new feature or new insight.
  • Think about where the code appears to have been headed and how it can evolve to be clearer, easier to test, and richer — better able to accommodate new features or a new understanding of the problem.

Until you’ve exhausted those possibilities, you — not the flaws you saw in the code — are the source of the technical debt.

The next time you feel frustrated with someone else’s code that you now need to change, first make sure you’re not actually the source of the problem. Learn its way of thinking. Look for signs of where it’s headed and how it can evolve. As Stephen Covey said, “Seek first to understand, then to be understood.”