Some people work really hard to destroy a codebase, without realising what they’re doing. Try to look out for any of the signs below in your fellow developers, as it’s most likely effecting your code quality. Also watch out, because it could be you.
1. Ego-driven complexity
Software systems are complex, but the skill is to design them in such a way that each individual component or layer is easy to understand.
One fatal mistake that I’ve seen made many times before, is complexity for complexity’s sake. When you’re in the thick of it and in the just-get-it-working mode, it’s tempting to write the code as quickly as possible. This has some major drawbacks:
- you probably won’t be able to understand it next time you come to read it
- nobody else will understand it
- you’ve just locked whatever company you’re working for into never changing that part of the code, unless it’s rewritten or deconstructed in a time consuming process
An even more worrying behaviour, is what I call ego driven complexity, where the coder thinks that complexity is actually good as it in some way boosts their ego and makes them think they’re clever.
When working in a software team of more than one, ego is very dangerous but the problem can be somewhat remedied by defining best practices and doing code reviews.
To strive for simplicity is the best way to make sure you won’t be rewriting the software in a years time. This will keep the code responsive to changing business requirements.
2. No documentation
A trend of recent years is to favour minimal code comments. I also follow this approach, and prefer clearly named methods and variables. Where somebody can’t understand a specific piece of code, that implies that some refactoring needs to be done to make the code easier to read. A comment should be added only as a last resort, where reworking the code still cannot provide the required information.
But, when it comes to understanding what a piece of software does and how it works quickly, we don’t necessarily want to read through all the code.
In fact, if there’s no way for someone to get the answers to these questions easily, that’s a big problem because:
- If nobody that wrote the software works at the company any more, it’s going to be almost impossible to figure out what it does and how it works
- It’s going to be tough to figure out how this software component interacts with other components
- It might even be difficult to find out if this component is actually used any more
To prevent your software from dying this slow death, consider taking these actions now:
- Make sure your repository has a
README.md
describing how to compile and run it - Create a team wiki describing the various software components in your architecture
- Create diagrams showing how the various components work together. I suggest using PlantUML for this, which can be integrated into several wiki services.
- Make sure your continuous integration and delivery process is easy to find. The easiest way to do this is to have all your components follow the same delivery pipeline.
- Provision your infrastructure as code, from within your software repository. This way, there’s no guessing as to how any infrastructure is provisioned, and it’s all version controlled in the same place.
3. Poor variable naming
A classic, for sure, but there is little excuse for poor variable naming. I recently did a pair programming exercise with an interview candidate who came out with some Java code like this:
public List<String> findLines(String str1, String str2) {
String str3 = str1.split(',');
I didn’t challenge him at first, as I was interested to see how he got on with it. After just a few minutes he himself became confused and got some of the variables mixed up. I asked him whether he normally names variables like this and he said this:
Normally I do name variables properly, but not this time because of the time constraints.
I think that’s a common misconception, that you save time by not naming variables properly. You may save a few seconds initially, but if you add up all the time spent trying to understand what’s going on in the code you’ve just written, that far outweighs any saving.
Better to use clear, concise naming like this:
public List<String> findLines(String commaSeparatedLine, String countryCodeToFind) {
String countryCode= commaSeparatedLine.split(',');
4. Null checks everywhere
There’s no better sign of a software product that’s been written under massive time pressure than null checks everywhere. This in a way spreads like a disease, as any developer that joins the project sees the null checks and in most situations follows along.
The 2 main scenarios that cause a developer to write null checks are these:
Methods returning null
public Entity getEntityFromDatabase(String searchString) {
List<Entity> results = dbClient.search(searchString);
if (!results.isEmpty()) {
return results.get(0);
}
return null;
}
In such a scenario, there’s no option than to put a null check on whatever calls this method. The solution though, is never to write such a method in the first place. When embarking on a software project, just make this one single rule:
public methods can never return return null
This will save you a whole load of pain later on. In Java, the way to get round the above problem is to return Optional
, a type that has or doesn’t have a value. This way, it’s explicit what is being returned from the method and nobody needs to consider null as a possible return value.
public Optional<Entity> getEntityFromDatabase(String searchString) {
List<Entity> results = dbClient.search(searchString);
if (!results.isEmpty()) {
return Optional.of(results.get(0));
}
return Optional.empty();
}
Null checking a variable that can never be null
In a codebase where null reigns supreme, developers tend to check for null whether or not the method might actually return null. I’ve seen this happen in many scenarios, even for a constructor variable injected by Spring. If you’ve used Spring Boot before, you’ll know it never injects null!
So try to avoid this kind of code:
@Service
class CustomerService {
public CustomerService(CustomerRepository cutomerRepository) {
if (cutomerRepository == null) {
throw new CustomerServiceException("This could never happen!");
}
}
...
5. No clear testing strategy
This probably warrants a whole post itself, but let’s touch on this topic which may happen when there’s been no direction given in how to test the application or worse still no tests at all.
In the case where the testing strategy has not been considered properly and clearly defined, this can lead to:
- Testing too many layers of the application at once – for example, developers might not know how to use mocking frameworks, so they test the controller, service, and repository layer in every test. This leads to huge, slow, difficult to understand tests because so much setup is required.
- Concentrating on only one area of testing – if too much focus is given to one area, then other very important areas get missed. For example, I’ve seen beautiful unit tests written for a web application, but nothing to test that the application actually starts up and serves requests.
- Testing everything at every layer – even if attention is paid to the different layers of tests, such as unit tests, integration tests, and end to end tests, if you try to test everything at every level this still leads to mayhem. Generally, as you write higher level tests they should be less broad and more focused because the cost of maintaining them is higher (see Martin Fowler’s test pyramid).
The last case of course is where there are no tests at all. This is the perfect way to destroy a codebase because nobody is going to know what the code is meant to do or what are its acceptance criteria. This makes changing it very difficult, and some poor soul will likely have to try to understand what is going on.
The best answer to all this comes by thinking about tests up front and practicing Test Driven Development. If you can answer the questions of what type of tests should we have and what purpose does each type serve, you’ll be going in the right direction.
Conclusion
So go forth, and implement these strategies to destroy whatever codebase you like! In all seriousness, try to consider these points on your current project, and see whether any of them are applicable. Perhaps you or your colleagues exhibit some of the behaviours described? If so, decide what would be a small incremental step to improve the situation by following one of the suggestions above.
If you prefer to learn in video format, check out this accompanying video to this post on the Tom Gregory Tech YouTube channel.