Inheriting dysfunctional code is a rite of passage when it comes to working in organizations and software development teams. Sooner or later, you’ll be given code that’s written by someone else.
If it’s not legacy code that was created before your tenure, then it might be from someone whose method of thinking and writing code isn’t quite as wholesome as it should be.
The code is often an overly complicated solution to a seemingly simple problem. You instinctively know that it shouldn’t be this way but a complete rebuild is off the books.
Everyone wants a green-fields project because it’s easy, but the business can’t exactly press pause for five months on a system that, on the surface, seems to be working just fine.
So, what do you do when you’ve inherited dysfunctional code? You know the kind that makes no logical sense and is coded in a way that resembles a drunk and disoriented spider trying to build a web.
The Process of Micro-Rebuilds
Rebuilding a system doesn’t have to occur in a big bang, complete replacement kind of delivery — especially when upper management is uncomfortable with the idea, or the business deems it affordable.
No one wants to be told that the system they’ve invested thousands of dollars into is not what they paid for.
Micro-rebuilds work on the rationale that you are replacing components at the same time as you’re building them. It’s the act of quarantining certain parts of your software, while chipping off the old to pave way for the new.
It’s a common practice in the industry because of time and budget constraints.
Despite the perks of it letting you keep the old system, it can result in a much slower build as developers are split into maintenance, refactoring, and new-build modes. Middleware may need to be developed for the old to work with the new.
Suggest a Parallel Build
PayPal instigated a parallel-build approach back in the early 2000s from Java to Node.js.
Within 18 months, they managed to surpass the original Java app in features and they were able to switch the business over to the new application.
New tech is often very quick to develop with, in contrast to old methods and stacks. It is also easy and cheap to boot up — regardless of size or load demand.
Development teams can be split into two groups — one to maintain and add as necessary while the other creates from scratch until delivery velocity overtakes the old system.
This method of parallel build allowed PayPal to verify and validate the new stack’s delivery speed against the old, in conjunction with delivering a system that’s much more accessible and maintainable.
Salvaging Code Through Refactoring
When a new build is off the books, salvaging code through refactoring may be the only option. However, what does refactoring mean?
Here are some effective methods to help you out.
Isolate and ring-fence
When code is given to you in one big gloop, one of the first things you can do is isolate parts of the logic that aren’t as tightly coupled and turn them into an input-output component.
This will give the code clearer logical boundaries — even if the code inside it is a bit, or beyond, jumbled.
With this method, you are making minimal changes to the original code, which should result in minimal side effects.
While this superficial act of compartmentalizing will not solve the issue of dysfunctional code, it gives the software more structure than what it had, allowing you to identify potential bug-creating modules better.
It also lets you isolate your rewrites — giving you a scope of work and some sort of logical boundary.
Refactor to the rules
Sometimes, jumbled code occurs because the developer behind the code has a limited understanding of the domain they are coding in, with a limited toolset in their knowledge bank.
Sometimes, they are told to just start coding — resulting in vague naming and inconsistent conventions. When these things happen, the best thing you can do as a developer and as a team is to lay down the ground rules.
Start at the bottom and work your way up — what kind of casing do you want to use? How do you describe your components, classes, and functions? Are we using OOP or functional patterns? If so, when? In what context?
While this may feel like just establishing the foundations of programming in general, some teams need it to create cohesive and standardized code.
It will also let you easily identify what and where the new code is, which parts have already been refactored and which bits are legacy code.
Elect a Code Janitor
Every project needs a code janitor. Sometimes they come in the form of a tech lead, sometimes it’s someone else on the team that can identify potential code smell before it hits production.
Whoever this code janitor may be, the act of having another pair of eyes and human brain to sift through your code can prevent you from becoming part of the dysfunctional code.
It’s easy to give up on quality and code robustness when you’ve inherited a bad project or it is restricted due to time.
A code janitor may make you feel like your delivery speed is slowing down, but it’s for a good cause — they slow you down to speed up the team’s delivery as a whole.
Sometimes it is hard to figure out where to begin when it comes to dysfunctional code. You’re not allowed to ‘rebuild’, but when you refactor, you are essentially rewriting the code — sometimes from scratch.
Refactoring doesn’t mean that you have to physically copy and paste code into your structures. It’s the act of creating efficiency and implementing code robustness in a way that the current code base allows.
It can also mean transitional migration as new functions and components are built in parallel. But ultimately, it’s about creating boundaries and clear relationships — which is a character trait that dysfunctional code lacks.