Most programmers will at some point have to answer this question. We all like to think that it won't happen to us, but unless you are constantly thinking about how to keep your code good, it will go bad.
Good code doesn't just happen – You have to make it happen.
I'm forced to think about this, because I'm way behind deadline on some development because of the bad code in the rest of the system. This is after I tossed out about 50 000 lines of the worst code. I'm still figuring some parts out, so this will be a series of posts on the relevant subjects as they come up.
I will also write a series on how to write good code at the same time as the ultimate goal is to replace the bad code with good code and make sure that this doesn't happen again.
The system I'm working on is a Java application with over 600 000 lines of code and practically no unit tests, so the focus will be on how to make changes that will keep increasing the quality of the code base without making radical changes. While a lot of the code is not great, the system is stable and does work with very few known bugs. The main problem is just adding new features.
To start with, We'll look at what makes bad code and how it happens in this article. Then in future posts, I'll write about:
- When to throw away code.
- Refactoring existing code.
- Writing unit tests and doing test driven development.
- Using design patterns.
I'll link these once I write them and also add any other topics I think of in here.
What is bad code?
There are a lot of different measures for what makes good and bad code. For me, bad code is code that is unreadable, unmaintainable and untestable.
To make code that is maintainable and testable, your code should be well encapsulated and loosely coupled.
Encapsulation means that all the related code for a feature is together (For example, in the same package). Encapsulation is mostly important because it makes your code easier to understand.
Coupling is when code depends on other code. Loose coupling means that related code only depends on the obvious code in the same class or same package. Esentially, if you can compile a single class without compiling anything else, it is loosely coupled. If you need to compile another class, which compiles another class, which has to compile another class, that code is set to be tightly coupled. Tight coupling is bad because it means that a change to a class might affect a completely different class somewhere else in a seemingly unrelated part of the system. This makes it difficult to make any changes in the system.
Tight coupling also makes it difficult to write unit tests, since each method might depend on methods in a completely different part of the system.
Even if the code works fine, as programmers, we spend a lot more time reading code than writing code. As such, it makes sense that difficult to read code is bad. The biggest mistake is to over complicate things. Don't use more dificult code because it might perform better. Rather use simple code and if there are performance problems, just tune the worst bottlenecks and make sure that you put comments explaining why the more complicated code was used.
On the topic of comments, make sure that comments tell you why something was done, not what was done. The code should tell you what was done.
So how does bad code happen?
The worst time, where code usually goes bad, is after the initial version of your software has been released and you start getting requests for new features. Ad to that tight deadlines because of bug fixing that you didn't anticipate, and most programmers will cut where they can.
There really are only 4 factors that you can realistically change when you are under pressure:
- Move the deadline.
- Cut features.
- Add Resources.
- Compromise on quality.
Usually, management will not cut features or move the deadline, since they have already started marketing. Adding resources are unlikely to happen in any case, but even if it does, by the time people realise that they are in trouble, it is too late to add more people to a project.
This leaves the only option: Compromise on quality.
Most programmers hate doing this, and doesn't necessarily realise immediately that they are doing it, but that's what happens.
“Yes, I know this design isn't great, but we have to ship, so I'll just make it work and we can clean it up later.” Sounds familiar? How many times do you go back and clean it up?
That's called technical debt. And like any other debt, there's interest on it. The longer you leave it, the more it will cost to fix, until eventually you get to a point where everything is too broken to fix and the only solution is to rewrite from scratch. Of course, doing that just starts the process again and you eventually end up in the same position.
Esentially, the only way to keep code from going bad is to put in aeffort to leave the code in a better condition than you found it every time you work on it. Even if you only put some meaningful comments, rename a few variables to make more sense or just write a couple of unit tests.
Always look at the code after you worked on it and ask if you made it better or worse.