Under the term “maintainability” we usually mean the ease of understanding and making changes to the code, as well as the learning curve for a new developer. There are many ways of writing any given functionality, but some will produce more maintainable code than others. Poor code maintainability leads to longer development times for new features and worse estimation accuracy. It also increases the potential for introducing new bugs.
How to Make Your Codebase Maintainable and Robust for Years to Come
Under the term “maintainability” we usually mean the ease of understanding and making changes to the code, as well as the learning curve for a new developer. There are many ways of writing any given functionality, but some will produce more maintainable code than others. Poor code maintainability leads to longer development times for new features and worse estimation accuracy. It also increases the potential for introducing new bugs.
Code design and code quality are key factors of overall maintainability. Compared to a book, you can think of code design as the story, and code quality as the writing (use of language). It’s easier to fix quality. Fixing design is often much harder.
From now on I will refer to code design, quality and maintainability as just codebase quality.
What affects codebase quality?
Poor codebase quality can be a result of developer inexperience, time pressure or inefficient management (frequent changes to project goals/scope after development has begun leads to all kinds of problems).
Codebase quality often deteriorates to some degree with age – as the libraries a project uses get updated, best practices evolve and change, so even well-written code may need touch-ups over time. It’s also affected by factors such as development team rotation.
Usually, it’s a combination of all these issues.
The best way to think about it is to treat this as technical debt. Technical debt is IT jargon for “stuff we put off for later”. Usually, technical debt is accumulated by cutting corners due to time constraints.
How do we make sure we provide the best codebase quality?
We utilize a wide range of tools and techniques to minimize the problem. We’re also prepared to work on existing codebases.
Code review
One of the most important practices in managing codebase quality is actually having somebody else look at the changes. Another programmer can look from the outside perspective and quickly assess if the code “passes muster”. They can also suggest better solutions. You can think of this as asynchronous pair programming in a sense.
There are multiple ways to solve any problem in programming, so having this diversity of ideas is always beneficial, even for experienced programmers. Having another person review a programmer's work helps spot mistakes, point out things that should have more test coverage, or suggest a better, more concise syntax.
Pair programming
Two heads are better than one and for particularly complex problems, it sometimes makes sense to have two developers working on the same thing simultaneously. You can think of it as a continuous code review. It obviously requires more developer time, but usually results in much better solutions.
Static analysis/linting
There are several tools that help us automatically deal with the most common and easy-to-fix problems. These tools scan the code without actually running it and provide hints on formatting, performance and even security. The ones we use include:
- RuboCop with plugins – the de-facto standard in ruby linting and static analysis. There are RuboCop plugins for many popular libraries, as well as rubocop-performance. It’s highly configurable and can help tailor the codebase to the preferences of developers. It can also autocorrect many common mistakes (like formatting). Used as a part of a CI build, it can work alongside a test suite to ensure quality and security.
- Reek – a tool similar to RuboCop, but focusing on different issues (called code smells). It can point out design issues. Can be used alongside or instead of RuboCop.
- Fasterer – a static analysis tool focused on differences in performance between different syntaxes. It essentially points out free performance gains. Especially useful when processing large amounts of data where server resources really matter.
Application monitoring the build process
We have experience with many tools for application monitoring, code metrics and CI/CD. Our go-to for new projects are Sentry (error monitoring), NewRelic (metrics, error monitoring, etc.), CircleCI (CI/CD), but we are prepared to work with other tools that are present in existing projects. If they are missing, we will recommend the best solutions for a given codebase.
So why is this important? Error monitoring helps us catch problems much faster and have insights into why they happen. It’s always better to know what’s going on and not have to learn about issues from customers.
Metrics are important for optimizing applications – identifying poor performance hotspots and having charts are very helpful in determining which actions we should focus on.
Increase test coverage
Automated tests are probably the most underestimated part of an application. They usually get cut first when there’s time pressure, and this part of the technical debt is rarely paid off on time. The benefits of good test coverage are ignored because they’re invisible. They include time not spent on manual testing and fewer cycles of code review and QA, because they catch many issues before the developer submits their code. They are also very important when updating dependencies of the application, like the Rails framework itself. They catch deprecations and incompatibilities.
All these things go underrated because they are a long-term investment, but in more extreme cases their lack can lead to severe problems.
All these tools have been helping developers create and maintain better code, as well as learn new things. That last one is particularly underestimated, but in IT we have to learn new things constantly as technology evolves. Using static analysis tools can help a lot with adapting new best practices and discovering new methods of solving old problems.
How do the principles of Ruby and Rails themselves help us?
Convention over configuration
One of the core principles of Rails is convention over configuration. To new developers, it means ease of learning, but it really shines in bigger codebases, because it helps reduce the volume of code. The sheer amount of files and lines of code in larger applications can be daunting, so anything that helps reduce these numbers is a good thing.
Class and method naming
Rails is famous for its generators that help make sure new parts of the application are named correctly and put in the right place. This by itself sets a convention and helps developers with structuring their code and naming methods and variables. Naming is immensely important for reducing conceptual overhead and letting the developer understand code faster and easier.
A word on documentation
An important part of maintenance, especially for long-running projects, is documentation. There are various tools to help organize it, like Jira’s Confluence, but in many cases, just a well-kept readme file and code comments are enough.
Despite this, documentation tends to be one of these things that get thrown out first when time or money constraints show up. And like any other element of code quality, its benefits are seen mostly in the long run.
The topic of how to write documentation and what on is a separate question that is out of the scope of this article. Just know that responsible developers (and management) shouldn’t forget about it, unless you want the new team to spend a month just figuring out a critical feature that needs to be updated asap a couple of years on.
How do we resolve codebase quality issues in existing projects?
We have vast experience joining existing projects to help steer them to the path of longevity. We’re often asked to help with legacy projects. Bring them up to date, increase security, modernize the UX, etc. As such, we know the best angles of approach and the easiest ways to achieve big results in a short time.
Incremental improvements
When technical debt isn’t dealt with in due time, it grows and resolving it after a long period of neglect (or just lack of development) requires a meticulous approach, because certain issues depend on each other so we need to divide the work into tickets and resolve them in an efficient order.
This alone is a very interesting and complex issue worth a separate case study. Watch this space.
On-demand webinar: Moving Forward From Legacy Systems
We’ll walk you through how to think about an upgrade, refactor, or migration project to your codebase. By the end of this webinar, you’ll have a step-by-step plan to move away from the legacy system.
Latest blog posts
Ready to talk about your project?
Tell us more
Fill out a quick form describing your needs. You can always add details later on and we’ll reply within a day!
Strategic Planning
We go through recommended tools, technologies and frameworks that best fit the challenges you face.
Workshop Kickoff
Once we arrange the formalities, you can meet your Polcode team members and we’ll begin developing your next project.