When we work on software projects one of our technical objectives is to ensure its quality and maintainability in the long term. Maintainable code for us is the tool that enables developers to modify or extend the product in the future if need be. For us to support these future developers we need to make sure that the code we produce is easy to read, well documented and tested.
When code is written in a maintainable way it should be easy to read and understand; it should have well-defined points and methods for extensions and modifications; it should allow for catching and fixing bugs and in cases should be easy to port to other operating systems.
Why is writing maintainable code important?
Software maintenance should be a normal part of any software life cycle but it comes at a cost. Several surveys estimate maintenance costs to be between 60% and 80% of the total life cost altogether, however, if the code is not done in a maintainable way, the cost can raise up very high.
Fixing bugs in badly designed and implemented modules will inevitably introduce issues in unexpected and seemingly unrelated places. In the worst-case fixing is not possible and a costly major refactoring or even re-writing will be needed.
Why does software break?
There are several factors that need considering. Creating any software product is a highly creative process that is forced to an outcome by strict deadlines. Creativity and deadlines can be polar opposites to each other which induces time pressure and can lead to sub-optimal implementation of systems and modules.
Sometimes programmers who are assigned to a specific job are working against a short deadline, learning new technologies as they go – often with the “just get it done” mindset due to time pressure. They may not have a clear understanding of the architecture they need to work with or the company does not have the right development process and rules to support them.
All of the above can result in systems breaking, end users experiencing bugs or crashes.
How do we work with code that’s hard to maintain?
Getting started with systems that are written without standards, comments or documentation is exceedingly hard. In our experience, the time it takes to gain a basic understanding of such systems is magnitudes longer, which prolongs the induction period when starting on a project. Working with a brittle codebase leads to uncertainty, re-work and confusion which leads to frustration on the maintainer’s side as they’d simply like to get on with their work.
When we work with such codebases we often propose establishing development processes, ask permissions for cleaning up systems and put the right architecture in place. Generally, we aim to leave the codebase and development process in better shape than it was when we started working with it.
How to do it right?
There isn’t a magic bullet. We believe the key is the development process. It consists of several parts that generally need establishing before coding can start.
Coding and styling standards
Establish rules for how your source code should be written. For many languages there are tools (e.g. for C# and for TypeScript) that can enforce styling and coding rules. These usually embed themselves into the editors programmers use. It is also a good idea to unify the editors themselves. Our preferred cross-platform editor is Visual Studio Code
Many developers see writing tests as a waste of time. In reality, tests, when implemented correctly, can save a lot of time in the long run. They can catch bugs and regressions early. Depending on the projects we recommend the use of unit tests, infrastructure tests, integration tests and automated UI testing.
Use source control
This should go without saying these days but there are still places where source control is not part of the programming routine. Using Git for local and distributed source control is simple and though the learning curve is somewhat steep both technical and non-tech people can learn to use it. Another system we tend to recommend is Perforce. Both have their strengths and weaknesses but using them is the right way to go on about managing source code.
Establish a branching strategy
Some companies opt for not using source control branches and do trunk-based development. Our preference is using branches. Features and bug fixes live in a branch where they can be developed, shared with other developers working on the feature without affecting the main branch. There is no right or wrong choice but the strategy needs to be decided on before starting development. Changing halfway through can be challenging.
Ensure code quality
There are certain steps that can be helped by using the right tools for development. For example, C# has exceptionally good tooling that can help with detecting potential issues with code early. There are similar tools available for other languages too.
Programmers are also great at spotting issues with code. Ask anyone starting to work with a new codebase. This can be used to an advantage. For our projects, we ask developers to get someone else on a screen sharing session and discuss changes with the person by explaining the work they have done. It serves a dual purpose. In many cases, it forces developers to look at their work from an outsider’s perspective. This enables them to spot issues themselves and also the other party can find areas that may have been overlooked. We always ask the developer to show the code working. This interaction helps with keeping in touch with colleagues working in different towns, countries or continents.
Once the code is approved this way it ends up in a code review system where a number of developers review it via a web interface. Then the code can be shared with the rest of the team, in our case merged into the parent branch.
Automate testing and deployment
We are strong believers in automating development processes. It makes them reproducible, leaves no ambiguity around expectations and removes the human element thus reduces the possibility of making simple mistakes.
The merge process runs all the style checks and functionality tests on a server. Code is not allowed to be shared with the rest of the team if any of the tests report failure.
Another area we tend to automate is build creation and deployment. There are several steps to perform when creating builds and the scripted process removes the need for manual checklist. This means the builds are repeatable. The release process – depending on the project of course – may include building and packaging documentation, generating docker images, creating a game executable, burning discs, sending archived data to a remote location and so on.
Writing maintainable code is hard and expensive. It adds overhead that many see as waste of time in the short term but not investing in it will cost money and time for your company further down the road.
If you have any comments, ideas or personal experiences, feel free to share with us. We are curious to hear new ideas, solutions, and perspectives regarding the above. Don’t forget to follow us on our social media!
You can also find the rest of our blog here!