How to Know When to Refactor the Architecture of an App
Changing the architecture of an existing application is one of the more challenging projects to undertake and complete successfully. It can be difficult for the technical team and the non-technical stakeholders to get on the same page. It can be anxious work to fundamentally alter the operation of an existing, validated, and heretofore-successful application. It […]
Changing the architecture of an existing application is one of the more challenging projects to undertake and complete successfully. It can be difficult for the technical team and the non-technical stakeholders to get on the same page. It can be anxious work to fundamentally alter the operation of an existing, validated, and heretofore-successful application. It can be a challenge to understand effort in the context of largely unobservable or very subtle changes in behavior. However, there are strategies to mitigate the risk involved.
How to know when it’s NOT time
First, because they are sometimes easier to identify, are reasons not to undertake a change in architecture:
- Moving to a framework/language/platform or library that is new, new, NEW. This proposal can sometimes be born of stagnation and carries a lot of risk. Unless there is a well-understood and business-driven reason, find out why the technical team is itching for something new. Sometimes this can be a symptom of not having enough autonomy or enough engagement with the product vision.
- Justifications based on measurable metrics that haven’t actually been measured. Some valid reasons for updating an architecture are straightforward but require careful, directed attention to get right. Some of these include “performance,” “efficiency,” and “scalability.” It’s essential to have concrete, measurable metrics in place that can demonstrate both a failing on these topics, and a clear understanding of how the architecture itself (and not just some of the code) is holding the application back. A prime example of this is to re-developing some service as a microservice without a reason that is backed up by data!
How to know when it IS time
Developers, especially as they gain experience, develop a sixth sense for code that is somehow not right. This comes in many forms, and the “smell” is from information about parts of the code that are less well understood and used tangentially. Usually, spending the time to investigate the implicated (smelly) code reveals various straightforward problems. However, it’s also important to ensure that the implicated problem is actually an architecture problem and not something more localized that can be corrected without a wholesale change in architecture. Once a code smell has been investigated, it should be easy to answer questions like: “What specifically is wrong here?”; “What is the short and long term impact?”; “Does this require a change in architecture to fix?”
The answers will help inform whether or not an architecture change is warranted. When our project grew from the prototype phase towards the production phase, we noticed a strong code smell. Every new feature or control we added to the application required modifications to three files, which grew to four, and then six. We recognized that our architecture was not well-suited for our use case: the framework we had elected was working against us. So, we reevaluated and spent three weeks moving the application to a new frontend architecture with a new framework. The following three weeks were some of the most productive on the project since it’s prototype phase.
Metrics tell us so
The easiest process by far is when the application has metrics, those metrics are underperforming, and that underperformance can be directly attributed to the architecture. It is still important in this case to ensure that it is truly the architecture and not a particular piece of code or pattern that is implicated. Effects that span the large parts of the application (non-localized) and metrics or profiling that implicate platform code (third party library or framework code that the application relies on but was not written specifically for this application) are stronger indications of an architecture problem.
Sometimes these metrics are not based on application performance, but are important nonetheless. If a project’s architecture is misaligned with its needs, Scrum sprint velocity is a metric that can indicate a problem. As velocity slows, or as feature or bugfix effort increases with each cycle, it can be an indication that the system is gaining complexity in a problematic way. Development effort is a factor in software success, and tracking it can provide valuable insights.
Having “the talk”
Have a reason
Changing the fundamental architecture of an application of any significant size is a stressful undertaking for a good reason (and if it’s not making you anxious, either your application is not yet of a significant size, or you’re not yet informed of the scope and complexity of the upcoming undertaking). This anxiety means that the classic change barriers are going to present themselves in full force (“it’s not broken,” “that’s too expensive,” “changing will take too long,” “it’s not worth it”). This is a good thing. Architecture changes are expensive and should only be undertaken for a good reason. If the arguments for change cannot satisfy the resistance for change, it is possible (even likely) that it’s not time.
Sometimes it feels like metrics speak for themselves, but often they need a bit of help. Subtle effects especially can benefit from providing context. Refactoring is generally a poorly understood process for non-technical stakeholders because it frequently doesn’t produce an artifact or behavior that can be presented as a result. The value is usually reaped in improvements to developer workflow. This can present in a number of ways: reduced stress from eliminating scary code that makes developers anxious every time it has to be altered, increased flexibility to respond to change requests in the future (which, since it makes developers feel like heroes when they can say “yes!” to requests, improves morale and engagement with the project, which has lots of good knock-on effects), and reduction in downtime or time spent in remediation when bugs crop up (since a goal of refactoring is to organize complexity to maximize understanding, and increased understanding makes bugs easier to identify and correct).
To deal with a culture that struggles to value changes like this that lack direct artifacts, there arises a temptation to oversell the change as a silver bullet that will solve myriad problems without offering concrete details because they would not be understood anyway. This is a trap that will simply lead to increased pain later and can only serve to erode trust between developers and stakeholders. Being upfront and honest about the realities (benefits and costs) of an architecture change will reduce tension and promote good decision making.
The more significant the risk of any particular action to a project, the more important it is to be realistic, pragmatic, and honest about the context for the action. Admit (and accept) that a change of this scale comes with uncertainty and challenge. The more visibility the technical team can provide on these risks and challenges to the product owner, the better the communication can be, and the smoother the process can go. This will be a real test of the agility of the team, and it’s vitally important to continue to respect the processes that make good software work, even when anxiety can be running high.
How to succeed
Like any change, risk is inherent and so we use any tools available to mitigate that risk and provide ourselves with assurance that things are progressing safely. The most straightforward of these is testing. A change is architecture is simply a refactor at the very highest level of scope (and thus, a risk). Since the scope is so large, integration testing becomes more important than it would be at other points in the life cycle. If the re-architecture is also paying off technical debt, this can be a good time to expand testing coverage as well.
As you might expect, fundamentals are also key. Again, if this effort is paying off technical debt, taking care (and time) to ensure best practices are used in the new architecture implementation is important for the work to pay dividends effectively. One of the goals of a re-architecture can be to improve the developer workflow to enable faster iteration in the future, and this can only happen when we take the time to do the job right. This means things like adhering to SOLID principles, testing effectively, and keeping iterations small and communication high.
Deliver early, deliver often. Deliver even when it feels like too little and seek feedback. The cycle of delivery and feedback is the most important aspect of continued success. Soliciting feedback on each small iteration of work completed is the kernel of communication that will grow into an effective, transparent, trusting and well-coordinated team. Large, complex undertakings like these prey on our anxiety and make us reluctant to share because the new work is not yet as featureful as the old. This is expected, and we must embrace it and deliver anyway, because to avoid delivery is to avoid feedback and communication, and without these we cannot ever succeed.