Good Enough Programming (2018)
What is the appropriate driver that causes you to open up code for the sole purpose of making it better? My answer: it's always a failure, but failures happen. You should prioritize not opening the code up for maintenance as much as you prioritize delivering value
I bought a new car recently. I use it to go to the gym and on hiking trips.
If one day I decided to go to the donut shop and liquor store instead, I wouldn’t expect an automotive engineer to show up from Tesla to redesign and program my car. I wouldn’t expect to change anything at all. The concept of how the car is constructed and where I want to go are completely separate things. They should stay that way.
One phrase that represents that concept is “separation of concerns”. There’s a better phrase, however, that we use in programming. It’s when you look at code and note that it has the appropriate “coupling and cohesion”. Coupling is how dependent things are on one another. Cohesion are how stuck together things are.
Inside my car, though, I’d expect the engine and the wheels to be highly coupled. No point in having one without the other and they’re made to work together. I wouldn’t expect them to be highly cohesive. I should be able to put new wheels on the car or change out the engine without it having anything to do with the other pieces. Things that become too tightly-coupled can also end up highly-cohesive.
While coupling and cohesion are programming terms, they also exist in the real world, in things like cars or how we approach our work. A great part of the problem we face as technology folks is that software has eaten the world and nobody is thinking about what that means in terms of coupling and cohesion at a business level, i.e., how we look at our goals. Instead we think in specialty areas, in silos.
Truly getting rid of silos means balancing each silo’s definition of perfection.
Let’s say that last week you wrote an app that people love and use everyday. What are the reasons you would touch it again? Why would you go back in and change anything?
There are only three:
- You want to add new things so that people like it more in the future
- You want to fix broken things, things people currently do not like
- You want to make your work easier. It looks difficult to maintain if one day you have to go back to fix or add things
That’s it.
Option 3 is odd. Let’s explore it. It’s basically saying that the people using your app have no complaints. They like it fine. You are the person that doesn’t like it. Of course, there are many good reasons not to like a piece of code. It’s unwieldy, hard-to-understand, or brittle. It uses the old version of WheedleKnob that nobody else is using. It doesn’t have good unit tests. It breaks some of the rules of Clean Code. You’re sure you’re going to have to add something else in there and now is the time to make it easy to do so.
Your code has a smell. A code smell.
All three reasons to change your app are good ones, but note: the first two items on the list are external factors. There’s something happening outside of your control that causes you to go into the codebase again. The third item? It’s all you. There’s something happening inside your head to cause you to go into the code. What you want you code to look like and what you see in your IDE doesn’t match up. In the first two, there’s coupling between the people who use our app and what we’re doing.
Where’s the coupling in the third reason? How do you know when to start or when you’re done changing your app to make it perfect? For some folks, there’s a little bit of cleanup and refactoring and they’re happy. For other folks, they spend years or decades and still have a huge list of things that still needs fixing.
Without further context, which of these two people are the better programmer? If you can tell, you’re better at this than I am. It is true that as an industry we’ve generally decided on a lot of things that are part of the discipline of being a professional programmer, things like test-driven design for OO projects.
It’s also true that there’s variability for each discipline, like programming, there’s personal taste, there’s team and corporate standards, there’s new versions, there’s idiomatic standards, and there’s an infinitely-expanding list of disciplines that any serious effort touches.
Add to that crappy code, and for most teams they don’t ever touch anything. They’re too scared – at least until they absolutely have to, then they end up breaking stuff. That’s a very bad situation that things like TDD aim to solve. But there’s a difference between being afraid to touch anything and not having criteria for when to start or how much to do. You should never be afraid to touch or refactor your code. That doesn’t mean you should be horsing around with it everyday simply because a new library version came out, either.
If everybody’s happy but you, what does it take to make you happy?
No matter how well you code a system, you can always do better. We’re not just talking about programming. If it were one discipline, the answer would be some kind of team coding standards, which are a great idea. But that’s not the real world. We do not live and work in cohesive teams all using one or two technologies where we have established common coding standards. Any modern true cross-functional team involves dozens of technologies.
There’s databases, servers, UX, release plans, compliance. The technology industry has hundreds of disciplines, with dozens more popping up every month. Each of these disciplines contain teachers, conferences, certifications and a community where the thrust is always the same: more and more precision around what makes a great X. Do X perfectly, and if everybody else does A,B,C,D and so forth? It’ll all come out okay. If not? You should have done X better! Have you read the latest book?
If you don’t like your exception handling strategy, should you start refactoring? What if the DBA doesn’t like the field names? Or the security guy tells you that you need to update to TLS 2.0?
If you don’t see the problem here then you’ve never worked at a large corporation where it requires 30 people to change a log-in form (Been there, got the t-shirt). You’ve never started a personal project and then given up on it after a few months because you seemed to have gotten mired down in all the tiny details that need to be done. You’ve never mastered seven or eight items in a tech stack only to see them all change to new versions while you were learning.
This is a common problem in programming and in technology in general: places where there’s no feedback loop so there’s no real way of knowing when enough is enough. Or how much is too much. When you pick up a dozen pieces to solve a problem, if you’re not careful you’ll have two dozen pieces before you put them back down again. There’s no value being created, only more and more pieces to master.
We have met the enemy and he is us.
Reason 3 in our list, making our work easier, is tightly-coupled to our personal tastes and to a continuously changing array of technologies, disciplines, and versions. Not just tightly-coupled, they’re tightly-cohesive. They’re all stuck together. On any given day, there’s really no reason not to open up the codebase. So here we are, one bunch too afraid to touch anything and the other bunch never having a good reason not to.
Instead of trying to figure out how much is too much, let’s reduce scope. Let’s start on the other end of the spectrum. Let’s assume numbers 1 and 2 on our list are okay, that is, you’ve written something that people find value in, there’s no more to add by way of new features and there’s nothing about it that they don’t like. Let’s talk about how we know we should start.
If there’s no new features to add and nothing the users want fixing, why would you ever open the codebase up to poke around in it? You wouldn’t. It’s not perfect. Of course not. It’s good enough.
This leads us to our definition of Good Enough Programming which any professional serious about delivering solutions should know: Implementing technology has two primary goals: it delivers value and you can walk away from it. To fail at either of these two goals isn’t good enough. Nobody comes to my house and rebuilds my 15-year-old iPod. I pick it up. I push a button. It plays songs. It’s good enough. That’s the minimum acceptable goal.
Having products that are good enough allow me to take them for granted and use them to advance my life and become a better person. Over time, ones that aren’t complicate my life and hurt me, even if they mean well and I like the features.
The same goes for you. Even if you deliver bug-free code for users who love it, if you have to keep opening the codebase/configuration for whatever reasons, your costs (and risks) are effectively infinite. It may be perfect, but it’s not good enough.
We are left with a conclusion that many might find uncomfortable. Although it’s perfectly fine to open up code to make it “better”, and this is a normal thing that happens all of the time, it’s also a failure of some other part of our value creation and delivery strategy. It should be always be considered as such.
It’s important to understand that I’m not talking about the discovery of business value, only the creation and delivery of the technology. If you want to be a good programmer, the first thing you have to know is when to start and stop programming. Somehow because business conditions constantly change, we’ve gotten it into our collective heads that technology must constantly change also. This is a false assumption and a vast over-statement of reality.
Sometimes people might want to use their cars to do new things, like go to the donut shop. So from time-to-time we should look at our cars to make sure they still do things our customers want. That doesn’t mean we go out and start taking people’s existing cars apart in their garages to install donut-holders.
While it is a generic, overall truth that the business environment is constantly-changing and we must change quickly to adjust to it, that doesn’t have to be true for your particular application. If it is, your coupling is too tight. You wouldn’t accept that in a method, you wouldn’t accept it in a car, and you shouldn’t accept it in the way you use technology to make stuff people want. It’s not good enough.
Good Enough Programming means equally prioritizing user value with the ability never to touch the code or configuration again.
Now we can talk about moving as fast as we need to when conditions change. How we can be super-freaking ninja rockstar kung-fu technology lord masters of space and time. We can talk about being perfect. We should always strive for perfection. But that’s never going to fly unless we start out by just being good enough.