Transforming Your Aging Software: A Path to Sustainable Software Development
I fondly remember the excitement of building software from scratch. It was like an adventurous journey with endless possibilities, fast-changing requirements, and quick code updates. But as time passed, things changed. The requirements became clearer, and the code evolved. Even the best code can age, making the software slow and updates challenging. That’s when we started wondering if there was a way to rejuvenate our creation.
Initially, when thinking about coding for a startup, my belief was simple: if the code does what it’s supposed to, it’s good enough.
Here’s an example of our initial snippet code at PASS3 (or as PLAY3 before), as straightforward and naive as it could possibly be as junior engineer code:
public function getProfile()
{
$member = auth()->user();
$member_game_wallet = Cache::remember('member_game_wallet.member_id.' . $member->id, 3600, function () use ($member) {
return MemberGameWallet::where('member_id', $member->id)->get();
});
$member_main_wallet = Cache::remember('member_main_wallet.member_id.' . $member->id, 3600, function () use ($member) {
return MemberMainWallet::where('member_id', $member->id)->get();
});
$point_history = $this->getMemberTodayPointHistory($member->id);
$is_scholar_list = Cache::remember('scholar.member_id.' . $member->id, 3600, function () use ($member) {
return Scholar::where('member_id', $member->id)->get();
});
$member->member_favorite_genres = Cache::remember('member_favorite_genre.member_id.' . $member->id, 3600, function () use ($member) {
return MemberFavoriteGenre::with('genres')->where('member_id', $member->id)->get();
});
return response()->json([
'success' => true,
'message' => 'Success Get Profile !',
'profile' => $member,
'member_game_wallet' => $member_game_wallet,
'member_main_wallet' => $member_main_wallet,
'point_history' => $point_history,
'is_scholar_list' => $is_scholar_list,
]);
}
The Challenge of Legacy Code
First, let’s address the elephant in the room: what is “legacy code” anyway? It’s like that old family recipe that’s been passed down through generations. Your software, once the star of the show, has now been in service for quite a while . While it may have served its purpose brilliantly, it might now be showing signs of wear and tear.
Now we’ll walk you through the journey of modernizing and upgrading legacy code, making it accessible and friendly every step of the way. While systems may have been state-of-the-art when first developed, they can hinder progress and innovation due to several common issues:
Outdated Technologies
Legacy codebases are typically built using technologies and languages that may no longer be supported or have become obsolete. This makes it challenging to integrate new features, maintain security, and keep the system compatible with modern infrastructure. Like in our case in PASS3 , we use Laravel as our Backend code. Laravel monoliths are great for rapid development. But as our company grows, it faces numbers of compatibility and scaling issues.
Lack of Documentation
One of the most significant challenges of legacy code is the absence of comprehensive documentation. Developers working on legacy systems often must decipher the codebase through trial and error, which is time-consuming and error-prone. As you can see on the snippet above, there is no inline documentation or separate documents for documenting the function and the business flow of the code.
Technical Debt
Over time, changes, patches, and quick fixes accumulate, resulting in a substantial amount of technical debt. This debt makes it increasingly difficult to make changes, add features, or fix bugs without introducing new problems. In our case, in PASS3, maintaining legacy code requires so much effort. Also, our fast-paced culture makes us more focused on present feature development than on maintaining legacy code.
“ In a way, tech debt is a dual scenario with respect to the one on engineering investments we just discussed:
Engineering Investments — you want the payoff to be as high as possible, and as early as possible.
Technical Debt — you want the tax to be as low as possible, and pay it as late as possible. “
https://refactoring.fm/p/tech-leadership-for-startups via Listiarso Wastuargo
Strategies for Legacy Code Modernization
Modernizing a legacy codebase is a complex undertaking that requires careful planning and execution. Here are some strategies to consider:
Assess the Codebase
Before diving into modernization efforts, conduct a comprehensive codebase assessment. Identify areas of the code that need improvement, potential security vulnerabilities, and obsolete components. In PASS3, we assess the codebase when metrics are not run as we expected. In example, our database query performance runs high when DAU is spiking.
Prioritize and Plan
Create a well-defined modernization plan that clearly lays out your priorities. Begin by addressing crucial matters, such as security vulnerabilities or components that have a direct impact on business operations. Within our team, there’s been a proactive effort to divide the workload, striking a balance between introducing fresh features and addressing bugs and legacy code refactoring. This division is based on an 80:20 ratio, with 80% dedicated to introducing new features and 20% allocated for bug fixing and legacy code refinement.
Incremental Refactoring
Instead of embarking on a comprehensive overhaul, contemplate the advantages of incremental refactoring. Divide the codebase into smaller, more manageable sections, and enhance them individually. This method reduces disruptions and facilitates ongoing integration and testing. Currently, we are in the midst of refactoring specific components within our Frontend setup. This involves removing unused components, optimizing lifecycles, and enhancing rendering performance to declutter our codebase. It’s a step-by-step process where we carefully evaluate the impact and priorities, tackling one improvement per week.
Also, rather than porting over all components at once, a safer strategy would be to have 2 separate components: one for the legacy component and another for new components. We use Growthbook feature flag for testing and deploying into releases. One switch away for deploying new components. And if it’s messed up, we just switch back into old reliable components. This can boost speed of our incremental refactoring as we can deploy and revert back so fast, feature flag is the key!
Test Thoroughly
Thorough testing is crucial during modernization to ensure that changes don’t introduce new issues. Implement automated testing wherever possible and create comprehensive test suites.
At present, we’ve successfully implemented rigorous testing procedures on our backend, ensuring the reliability of our system. However, when it comes to testing and validating the frontend, we encounter challenges. This includes testing various components, assessing business logic, managing state, and conducting UI/UX tests. In our current operational state, there’s an abundance of testing tasks that demand attention. That is why we adopt feature flag to fasten the testing.
Given our rapid growth, our primary focus remains on delivering value to our users. We aim to strike a balance between the increasing need for comprehensive testing on the frontend and our commitment to meeting growing demands for feature development and delivery.
Documentation and Knowledge Transfer
Document the updated codebase thoroughly. This documentation will be invaluable to current and future developers who work on the system. Additionally, consider knowledge transfer sessions to share insights and best practices. In PASS3 we put documentation into Lark Wiki.
Meeting notes, intricate implementation details, feature discussions, obstacles, research findings, future development plans, and even daily team updates — we diligently documented all of these in our wiki. Although embracing a writing culture was initially challenging, we weren’t fully aware of its benefits. It wasn’t until a pivotal moment when one of our engineers decided to resign, leaving a gap in code and knowledge transfer, that we truly grasped the importance of thorough documentation.
Embrace Modern Technologies
Introduce modern technologies and practices where appropriate. This might involve migrating to a newer programming language, adopting containerization, or transitioning to microservices architecture. Our first infrastructure was awful, but it managed to do its job.
These are of our infrastructure before:
These are now our infrastructure evolves after senior infrastructure comes:
Involve Stakeholders
Maintaining open and transparent communication with stakeholders is crucial as we navigate the modernization journey. By regularly sharing updates and progress reports, we provide our stakeholders with an invaluable window into the transformation process.
Stakeholder engagement goes beyond just keeping them in the loop; it’s about actively seeking their input and feedback. These insights are instrumental in ensuring that the modernized system remains closely aligned with our overarching business objectives. After all, they hold the key to the real-world applications and implications of our efforts, making their perspectives an essential part of our modernization strategy.
Benefits of Modernization
While modernizing a legacy codebase can be challenging, the benefits are substantial:
Improved Maintainability
With modernization, you’re not just tidying up the code; you’re making it easier to maintain and extend. Picture it as renovating a well-loved old house — you’re preserving its charm while ensuring it’s ready for contemporary living. By making your codebase more organized and streamlined, future updates and feature additions become smoother and less time-consuming.
In our situation, one day, one of our engineers attempted to contribute to our backend codebase. To make a long story short, they successfully introduced some code into our system, and it appeared to function correctly. However, upon closer inspection by our senior engineer, it became evident that the folder structure within our codebase was not as intuitive as it should be.
As the engineer had previously asked numerous questions about code locations and functions, the senior engineer recognized the need for a more organized and descriptive code and folder structure. Consequently, a decision was made to embark on a code and folder structure refactor, with a primary goal of enhancing domain clarity within the codebase.
Before:
After:
Notice the differences are new folders named Admin, DSP, and SSP.
Enhanced Security
Security is paramount in today’s digital landscape. When you modernize your legacy code, you patch up potential vulnerabilities and replace outdated security measures with robust, up-to-date ones. It’s like upgrading your home’s security system to keep up with the latest breakthroughs in safety technology — you can sleep soundly knowing you’re well-protected. Engineer happiness is the most important metric in PASS3 🥰.
Increased Efficiency
Just like a car runs more efficiently with a modern engine, your software performs better after modernization. You’re streamlining processes, optimizing algorithms, and reducing resource consumption. This means your applications will run smoother, respond faster, and provide a superior user experience.
Cost Savings
Imagine modernization as a way to declutter and simplify your life. By reducing technical debt and eliminating the need for ongoing maintenance of a convoluted codebase, you’re saving both time and money. Those resources can then be reinvested into further enhancing your software or expanding your business.
Conclusion
Legacy codebase modernization is a critical step for organizations that want to stay competitive and maintain software sustainability. While the process can be challenging, careful planning and a systematic approach can lead to a successful transformation. By embracing modern technologies and best practices, organizations can breathe new life into their aging software systems, ensuring they remain valuable assets for years to come.
Remember that the modernization process is not a one-time effort but an ongoing commitment to keeping your software current and adaptable in a constantly evolving technological landscape.
“ We move, we fail, we fix. Move fast break things. “