Legacy Code Cookbook

Make Tests Independent
Recipe: Squeezing Juice out of Over-ripe Tests
While any bug or change would cause one test to fail, many changes cause unrelated tests to fail. This happens because each test is testing too much. Learn how to squeeze each test done so that it only has one reason to fail.

Start Fixing a God Class
Recipe: Converting the God Classes’ Pawns to Work Against It
It seems like fixing a God Class requires taking responsibilities off of it. Often that is too hard, because the God Class is surrounded by procedures that lock it in place. Learn how to convert these pawns to your side so that they can help you remove responsibilities later.

Refactor Databases
Recipe: Fixing Databases with Lego
Bad database schemata cause story delays and generate bugs. The common ways to fix schemata take a long time and interrupt story work. Learn how to change schemata with simple and uniform transformations.

Port Between Technologies
Recipe: Creating and Shipping a Chimera
Migrating between technologies is hard. The common approach is to replace the old technology with the new one. Learn how to incrementally change the technology without disrupting your feature progress.

Test Interactions Between Third Party Services
Recipe: Isolating Dependencies with with Ports & Adapters
Verifying integration with a third party service is hard. The common solution is to write end-to-end integration tests. Learn how to create a different kind of integration test that won’t break with unrelated code changes.

Split a God Class
Recipe: Use Data to Split a God Class
It is hard to split responsibilities out of a God Class because the class is so large and complex. It is difficult to identify the many responsibilities and which methods perform which responsibilities. Learn how to use data to mechanically identify responsibilities without needing to understand the God Class.

DevOps #1: Automate a Manual Process
Recipe: Discover and Automate Manual Processes
Automating manual builds, deploys, or other processes is hard. They rely on specialized knowledge and individual common sense. Others don’t know how to perform the process and the experts do details but forget to automate them. Learn to use checklists to incrementally discover and automate any manual process.

DevOps #2: Enable Unit Testing
Recipe: Make Code Testable Before Testing It
Some code is hard to unit test. But integration tests will cause our pipeline to be constantly broken and mocks will cause it to miss bugs. The solution is to refactor the code to be easy to test. Learn 5 common design flaws and how to fix them to make your code easy to test.

DevOps #3: Escape the Monolith
Recipe: Roadmap from Monolith to Services
Extracting clean code from a Monolith is straightforward but laborious. Legacy code adds thousands of obstacles without obvious solutions. The project will struggle and need personal support from key stakeholders. The solution is to create a clear roadmap and transparent progress that shows real business value along the way. Learn to see what is coming and how to help everyone in your company see the value they are receiving from your efforts.

DevOps #4: Edit Independently
Recipe: Extract Your Code So You Can Edit Independently
Team Independence drives DevOps success, and that starts with editing code independently. However, legacy code blocks single-team code ownership. Classes and methods were designed to support teams that have since grown into entire divisions. The code needs to subdivide to support the new team structure. The solution is to create many small components that each have one reason to change. Learn two recipes for the two kinds of components, plus 6 more recipes to handle the complications caused by legacy code.

DevOps #5: Gather a Scattered Component
Recipe: Gather a Scattered Component
Creating a good design in your component requires that all the code related to your concept needs to be together in your component. However, legacy code blocks such cohesion. Your component’s concept evolved over time and from the interactions of existing code. It is now scattered and isolated. Learn a process and supporting recipes to find, gather, merge, and de-isolate your scattered and isolated code.

DevOps #6: Compile Independently
Recipe: Create a Forward-compatible API
The next step to team independence is for each team to make changes without forcing each other to recompile code. That requires that each component’s API isolate changes. However, legacy code APIs do not isolate changes well. Some changes will be internal to a component, but any change that comes near the interface will cross it. Learn techniques to identify fixed points in your design and make them variable so that fewer changes cross the boundary.

DevOps #7: Integrate Simply
Recipe: Isolate Component Integration Complexity
Each team can compile independently. However, integrating so many components makes the monolith hard to debug. Learn techniques to isolate integration complexity of each component so the system remains readable.

DevOps #8: Verify Integrations
Recipe: Verify Integrations Without Integration Tests
The system works well now, but changes will start causing integration bugs. Our productivity gains came from isolation. Learn how to verify integrated and system properties in isolation.

DevOps #9: Verify that Your Product Uses Dependencies Correctly
Recipe: Create Simulator Tests
Each team can ensure that its dependencies work as intended. Now they have to verify that their code uses the dependencies as they intend. Mocks are the obvious choice, but they duplicate the dependency’s behavior with test setup, and that leads to integration bugs over time. Learn how to use simulators to verify your product’s usage in isolation yet without duplicating behavior.