Every developer can give you examples of applications of which the code base is like spaghetti code. Plenty of reasons why an application might end up in such a situation. Often, evolving insights, pressure to complete work quickly, bad architectural decisions and inconsistency cause code to end up a bit messy. Often, there’s little you can do to prevent this technical debt; entropy just causes a gradual decline into disorder.
The downsides are obvious: the application is harder to maintain, bugs creep in quickly, deployments and production become fragile, changing or adding functionality becomes a time-consuming, complex task. All of these factors hamper DevOps teams to release new versions of the application. This is especially true when migrating to the cloud. In this post we will focus on why refactoring helps the DevOps principles and initiatives in your organization.
What is refactoring?
Refactoring is all about improving the internal structure of the source code of an existing application and at the same time keep the external behavior (e.g. the business features) the same. Some examples to refactor an application are the splitting up of a big monolithic application into several microservices. Another example is to remove unneeded code and to clean up the existing code base. Rewriting the entire architecture is also an example of refactoring.
Strictly speaking refactoring does not add (direct) business value for your organization. It’s about tasks which are like non-functional requirements. End users won’t see any changes to the application since, after the refactoring tasks are completed, the feature should remain the same. However, end users might notice it indirectly. More on that later.
Typical organizational challenges which blocks a team from refactoring an application are:
- The team has no or little knowledge about the code, they might not know where to start or oversee the dependencies and relationships between different blocks of code.
- A product owner can feel the pressure from management to deliver the IT product so they might not give priority to non functional requirements and refactoring. Surprisingly, in the Agile manifesto, the task for non-functional requirements is not even mentioned.
- A lot of times, the people who decide about the features don’t see the benefits of refactoring. It is seen as “overhead”.
When putting refactoring in the perspective of (the principles of) DevOps, it make sense to put refactoring higher on the agenda. Think of quick wins for longer term benefits.
Know the business
One of the biggest advantages of refactoring is to get insight in the business features. When developers know the business features very well, they understand their needs and (existing and probably future) requirements. This helps to bridge the gap between the business and development department. Alignment of business and IT here. Developers know better what to expect from the business and act accordingly.
It also helps to streamline the refinement sessions in which the product owner or business manager explains the details of new requirements. Developers can better think and act from the business perspective. A properly refactored application has a well structured code base. It helps them to better estimate the complexity of the new features. Last but not least, refactoring helps to get a complete overview of the system.
Better code base
A better code base helps to check whether the system meets the requirements in terms of maintainability, overall quality, performance and security. One of the key aspects of refactoring is simplifying and/or reducing the code base where possible.
Learning faster, better maintainability
If there is less code to maintain, it takes less time to learn by (new) developers in the team. Reducing duplication improves the readability and it is easier to maintain since changes do not need to be made at multiple places. This also reflects the DRY principle.
Working with a small code base improves the transition to shift to microservices, since a lot of the “overhead” is stripped away. It also helps to build and test the software components faster since less code needs to be tested and processed during the software development life-cycle phases. Working with microservices reduces the risks of changes, since only a small part of the code base is changed when a new version is rolled out to production.
Debugging applications is easier when those are structured properly. Imaging you want to refactor a simple method (piece of code) of a large monolith application. For this you need to “step through” the code to trace the exact behavior. This is far easier when you have a clean and optimized application (component), dedicated to the feature it is responsible for. When running microservices in the cloud using serverless architecture, this is even more important, since a lot of debugging tools won’t support this deployment method (yet).
A better code base helps to improve the portability of an application (component) because it might has less dependencies on other systems. For example you can choose to deploy one microservice in the public cloud to leverage the unique benefits of it, while another microservice can be deployed in the on-premises datacenter. If you want to migrate to another cloud provider or platform, you simple have less dependencies which contribute to a “vendor lock-in”.
Availability and reliability
Refactoring can help to improve the availability of your application. In cases where downtime cannot be avoided, it’s better to switch off just one microservice instead of the entire application. For example: when you need to upgrade a component which is responsible of generating reports, you only have downtime of that feature. The rest of the application continues to work.
It also improves the reliability of your application since if one microservice is in trouble, it should not effect the others. If you have one big monolith, it’s more time consuming to find the solution for a critical error. Even when the developer team fixes the error and releases a new version of the application there is a risk of breaking another part of the system. The more difficult to understand the application the harder it is to fix errors and/or bugs in a timely manner. So this also reduces the risks of any new deployment: frequent deployments are easier to achieve with microservices.
Faster feedback loops
A refactored application is optimized for its (sole) purpose. Imagine you push your application through the CI and CD pipelines several times a day. Less code means less time to fetch the source code from your source code management system. It lowers the pressure on this system.
Besides this, optimized code means faster compile times. The code that didn’t change, isn’t compiled. When there are a lot of small increments, it saves a lot of compile time for every change. Less storage space is needed to keep all of the versions of the artifacts in your artifact repository. Downloading artifacts to deploy it to a server is also faster.
The previous examples are all from a technical point of view. From the business perspective, the following is true. In the end the new version of the application is delivered faster to your end users. They can start using new features quicker. Perhaps they find critical bugs, or features which have not being implemented exactly according to the requirements. Testers can quickly report it to the development team. In the end the team can then create new user stories and release improvements in the next sprint. Time to market for the application delivery is improved. That’s a big business value!
From a security standpoint, refactoring is beneficial to reduce the risks (shifting security left). Applications which have a good architecture and a good code base expose only what is really needed. This means the attack surface is reduced. Especially when deploying functions directly in the cloud using serverless technologies, this is important since traditional (server and network) security options are limited. Besides this, cleaned up code leaves less code vulnerabilities to exploit. Code which is not there can’t be exploited. Yet another reason to improve and clean up your code bases.
As open source software is getting more popular every day, so is the usage of dependencies. Optimizing and patching code is important tot keep the number of (unneeded) dependencies to a minimum. Outdated dependencies should be upgraded and unused dependencies should be removed. Why should you patch a vulnerability when the dependency is not used at all? All of this helps to keep the amount of time to maintain the application limited. It also helps to reduce the attack surface and the ways hackers can exploit weaknesses.
When scanning for vulnerabilities in dependencies, false positives are likely to pop up. False positives are vulnerabilities (in dependencies and actual source code) which are marked as being a problem but in fact, they are not. When an application is refactored and cleaned up, it’s likely that less false positives are reported. Security teams do not have to review long lists of false positives over and over again. Judgement of false positives still requires a lot of manual work. The attention of the security team can be decreased when there is a big list for every application. Also, it takes more time to prioritize the false positives if the list is very large. So refactoring and improving the application also helps the security teams.
As pointed out in this article, refactoring helps the organization to fulfil a lot of principles of DevOps. The list presented here is never complete, it acts as a starting point. Refactoring helps to save costs since a number of (daily) tasks from DevOps teams can be optimized. In the end, faster feedback loops and improved quality of applications are big advantages for the organization. Faster time to market and end user satisfaction is increased. All of this helps to contribute to the core activities of your organization.