What’s Missing

When the only tool you have is a hammer…

So what is it that is missing from software development that makes changing software still so difficult after all these years and developments?

Let’s analyze some of the most effective things that have been tried.

Of course, I’ll recognize the impact of each, but I also want to help you see a pattern. It’s the negative of that pattern that tells us what is missing.

Programming Languages

Early and modern programming languages have, as I said in Chapter, trended toward ever-increasing powers of change.

The code we wrote in C was harder to change than what was expressed in (well-written) C++. The code in C# or Java is easier to change than the C++ that was available at the turn of the century1.

Good languages let you write code that is easier to change later.

The better the language, the more support it gives in this way.

Parametric Polymorphism

Parametric polymorphism was another advancement in software development.

An icon of a info-circle1

The fancy-schmancy academic term for things like C++ templates or what C# generics is “parametric polymorphism”.

This advancement also allows one to write code that isn’t coupled to concrete dependencies. It only really cares what it can ask that other thing to do.

Consequently, a separation is forced between the client and the provider entities. The client can’t make assumptions about what happens inside and the provider can’t make assumptions about how it will be used.

Good use of templates/generics lets you set up your code so that it’s easy to make a single change in the right place when you need to later.

The technology has only advanced from there, with things like type constraints and specializations allowing for an extremely rich solution to some very hard problems.

Object-Oriented Programming (OOP)

The advent of object-oriented programming gave us mental models and code structures that made it easier to understand and organize our code.

When the time came to change it, we2 often knew where to look.

It also provided some limited degree of reuse. This meant that we didn’t have as many cases of needing to repeatedly make the same change in multiple places.

As a result, if you structured your code according to good OOP principles at the beginning, you could change it more easily later.

The better you were with object-oriented programming, the easier it was to change a system down the line.

Design Patterns

While the Gang of Four claims to have merely measured (rather than invented) design patterns, they certainly did their part to make them a part of how we write software: Design Patterns aren’t fully adopted to this day, but they keep gaining traction.

Design Patterns allow developers to invert dependencies and encapsulate variation regardless of the language they are using.

Design Patterns give you a completely different relationship with change. A properly recognized and implemented pattern makes it so that you can change behavior by addition instead of modification.

If you find all the Design Patterns in your problem-space and implement them, it is much easier to swap new functionality in later.

Principles and Slogans

As we learned more about software design, people began to distill the principles into slogans.

I don’t mean to disparage the slogans. Even a cursory glance at history reveals slogans (and propaganda in general) to be a necessary component of any kind of transition within a population.

Three big ones float around our industry, doing good wherever they touch a developer’s mind:

  • DRY - Don’t Repeat Yourself
  • SOLID - 5 principles: Single-Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, Dependency Inversion
  • KISS - Keep It Simple, Stupid

If you understand these principles and adhere to them, you will write code that is much easier to change later.

Test-Driven Development (TDD)

Test-First Programming and, later, Test-Driven Development have revolutionized how software is made.

You start by defining a specification in the form of a test. Often this test doesn’t even compile and those compiler errors tell you what about your design is required by the spec.

At some point, you get the test to the point of failing—that tells you the software doesn’t already do what was specified—and you make the changes required to get it to pass.

As a result, you end up with every important behavior in the system specified by tests. Hopefully, there are very few behaviors that aren’t specified by tests, if any at all. In addition, you end up with a much better design than you often would without it.

When you write code with TDD, that code is so much easier to change later that it’s a wonder anyone builds anything without this discipline.

Did You See The Pattern?

These are the shifts with the largest impact I can see and they all have something in common.

Did you see it? In case you didn’t, I’ll capture the most important line from each section and change the emphasis to highlight the commonality.

Good languages let you write code that is easier to change later.

Good use of templates/generics lets you set up your code so that it’s easy to make a single change in the right place when you need to later.

…if you structured your code according to good OOP principles at the beginning, you could change it more easily later.

If you find all the Design Patterns in your problem-space and implement them, it is much easier to swap new functionality in later.

If you understand these principles and adhere to them, you will write code that is much easier to change later.

When you write code with TDD, that code is so much easier to change later that it’s a wonder anyone builds anything without this discipline.

All the major, widely-adopted approaches to change involves preparing yourself to withstand change in the future. They treat change as something to survive, rather than master.

Perhaps this echoes our instinct to do things like build shelters to protect us from weather.

Change is recognized by all the aforementioned things, but it’s something you have to (ironically) plan for. That means that, when change happens, it was your approach in the past that mattered.

That’s not to say that nobody made an effort to impact how people make changes, though.

Other Innovations

There are a few things that I left out of the previous sections. I think these discoveries are major for our industry and I don’t believe they’ve received the attention they deserve.

One such innovation is refactoring.

While Martin Fowler did not invent it in his book, Refactoring, his position as an authority on the subject and one of its primary advocates cannot be denied.

Yet how many people have apocryphalized the word “refactoring”? How often does someone “refactor” in a manner consistent with what he wrote?

I’m not saying it never happens, but it’s a marginal fraction of the time in my experience. Most people who “refactor” are really just changing design willy-nilly and hoping for the best.

Another work of great import is Working Effectively with Legacy Code by Michael Feathers. This book gives deep insights into how to restore legacy code to modern status. Or, at least, how to maintain and extend it without having to succumb to the temptation of a rewrite.

Test-Obsession

Both Refactoring and Working Effectively with Legacy Code are focused on change, but they seem preoccupied with test coverage, to me.

In Refactoring, Fowler says that the first thing he needs to refactor is tests.

Feathers takes the less-strenuous position of defining legacy code as code not covered in automated tests.

Now, I am a huge proponent of TDD and test automation in general. So I don’t want this section to be taken as resistance to testing.

But I do think that the fact that the two greatest works we have on changing code seem test-focused tells us a few things:

  1. Automated tests are really, really important. Seriously: If you’re not practicing TDD, start.
  2. Even the few attempts to focus on change in the now are somewhat focused on the past… like an attempt to time-travel back to when the tests weren’t written and retrofit them to support current changes.

This focus on testing not only tells us something about what’s missing in the software industry, it presents a material obstacle to refurbishment of old code.

It’s Really about Focus

That focus on “make it like it should have been” really speaks to the mindset of the industry as it has been for my entire career.

The ambient message of the elite appears to have been:

Do it right, today, and you can change it tomorrow.

That focus on supporting future change is not wrong. It’s an important aspect of software development.

But it’s only half of the story.

The Other Half of Coding

The other half is a focus on how you change software. That is, the how of the change itself. I believe that Feathers and Fowler both made a serious effort to move people toward more discipline in this regard. The other luminaries no doubt had their impact as well.

Yet, it wasn’t explicitly called out. This is what we do most of the time, but it is still treated as a natural consequence of decisions made in the past, rather than given the kind of explicit treatment it deserves.

There are a few books, but it doesn’t seem like colleges or bootcamps really spend any energy on this. You have to figure it out for yourself and you may not even think about it directly. For most, it appears to be an accumulation of intuitions that are never formalized or made explicit.

the other half of coding is to shift your mindset from what you want to code to how you want to get there.

We’ve spent half a century building systems to make change possible later, now it’s time to learn how to make change safe now.

To really understand it, though, you’ll need a clear picture of what is in your way.


  1. Modern C++ is extremely powerful and expressive. I can’t rightly say modern C# and Java are more or less powerful than it. But that really only strengthens the point of this paragraph.↩︎

  2. I say “we”, but I’m of a generation that was born after OOP was commonplace. I mean the royal “we”, I suppose.↩︎