As programmers we often talk about “bad” code. But interestingly, I don’t believe I’ve ever heard someone actually define exactly what bad code is.
I find this unfortunate because saying code is “bad” is actually meaningless, especially to a non-technical manager. If a non-technical person hears me say that our code is “bad”, they might believe me, but they won’t understand what it means for the code to be bad or what it would take to make the code good.
If we can put our finger on exactly what we mean when we say “bad code”, maybe it will help us go from sounding like we’re complaining about the code to getting on the path to making the bad code good.
So I’m going to try to define what bad code means.
My definition of bad code
Bad code is code that is risky and/or expensive to change.
Let me break that down.
What does it mean for code to be risky to change?
Have you ever been scared to deploy a code change to production? I have.
What exactly are we afraid of when we’re afraid to deploy a change? Basically, we’re afraid of introducing a regression. If the code we’re working on is hard to understand, then that means we don’t understand the implications of our changes. That means that all the carefulness in the world can’t protect against accidentally breaking some part of the application when we make a change.
Consequently, we cause problems for customers. We cause customers to lose money. We make customers frustrated with our employer’s product. We make everyone trust the development team less. These are all genuinely bad things. If a codebase is sufficiently hard to understand that it poses these risks to the development team, then I would say that that code is bad.
What does it mean for code to be expensive to change?
The cliche that time is money is true. The longer it takes a developer to be able to understand a system, the more expensive that system is to maintain.
In addition to “bad code” another term that gets thrown around to the point of meaninglessness is “maintainability”. We often talk about code being “maintainable” or “unmaintainable” but rarely talk about what exactly that means. Another way to say the same thing would be to say that code has a low cost of maintenance or high cost of maintenance.
This is true of not just code but all parts of a system. If we use unclear variable names, convoluted database schemas, or methods and classes that are too big to quickly and easily grasp, then future maintainers of our system will have to spend more time (which means more money) to be able to understand the system.
The reason I’m talking about understandability is because in order to confidently change code, you first have to understand it to some extent. Have you ever made a one-line change that took literally three seconds to make but then spent 30 minutes studying the code and testing your work because you weren’t sure you understood the implications of your change? The longer that “studying and testing” period, the more expensive the code is to maintain.
What this has to do with testing
My website and podcast are all about testing. Why do I find it important to talk about the definition of “bad code”? What does that have to do with testing?
If bad code is code that’s risky or expensive to change, testing can help make it less risky and less expensive to make changes. Here’s why.
First, testing can aid a developer in making code more understandable. Testing forces us to write code that’s more modular and loosely coupled than what would be allowed if we wrote our code without tests.
Testing can also help mitigate the risk of changing code. If we have a decent suite of tests covering our application, and our tests all pass after we introduce a change, we can have reasonably high confidence that our change didn’t introduce a regression.
I said that testing can help us write more modular and therefore more easily understandable code. The tests themselves can also aid our understanding of the code. If it’s not clear to me what a certain class or method is supposed to do, I can look at the tests for that class and see what the tests say the code is supposed to do.
Lastly, tests enable refactoring. If I know that a piece of code is sufficiently covered by tests, I now have the freedom to change the structure of the code to make it more easily understandable. I can change the variable names, class names and method names to something more meaningful. I can break down big classes and methods into smaller, more understandable ones.
Is it possible to have great test coverage and still have bad code? Absolutely. But at the same time, tests are a powerful tool that can help obliterate bad code.