Refactoring Legacy Code
This is a free video tutorial on refactoring legacy code by Continuous Delivery Training.
It covers techniques and best practices for improving and maintaining legacy codebases, making them more manageable and easier to work with.
Continuous Delivery Training
Continuous Delivery Training offers a variety of courses focused on software development practices, including Continuous Integration, Continuous Delivery, DevOps, and Agile methodologies.
Their courses are designed to help developers and teams improve their skills and adopt best practices in software development.
Sign up and access the tutorial
Navigate to the list of courses and select the Refactoring Legacy Code tutorial.
The course includes 5 lessons, providing 1 hour of practical video content.
Enroll for free to create an account and access the Refactoring Legacy Code tutorial.

The Continuous Delivery Training leaves your enrollment active for 1 month, which is usually enough time to complete the tutorial.
GitHub Repository
The code examples for the tutorial are available on GitHub as a template repository:
GitHub - davef77/RefactoringBadCode: Starting point for an exercise in refactoring bad code
You can generate a new repository with the same directory structure and files as the template.
See GitHub’s documentation for Creating a repository from a template.
Four Steps to Refactoring
Dave outlines four key steps to refactoring legacy code safely:
- Removing Clutter
- Reducing Complexity
- Composing Methods
- Refactoring to Testability
Part 1: Approval Testing and Removing Clutter
Approval Testing
Approval Testing is a technique used to verify that the behavior of a system remains unchanged after refactoring.
It involves capturing the current output of the system and comparing it to the output after changes have been made.
How it works:
- Capture the output of the code under test.
- Save this “approved” output to disk.
- On subsequent runs, compare the new output to the approved one.
- If they match, the test passes. If not, the test fails.
This is especially useful when dealing with legacy code, where the original intent and functionality may not be well documented and traditional unit testing is difficult.
Dave demonstrates:
- Creating an approval test for a class
XMLToJSON. - Using sample input data based on comments in the original code.
- Running the test to confirm expected behavior.
Afterward, he checks code coverage – achieving ~66% method coverage and ~85% line coverage, which he considers sufficient to proceed with cautious refactoring.
Removing Clutter
The first step in refactoring is cleaning up unnecessary code and comments:
- Delete redundant comments: Those that merely restate what the code does.
- Remove dead code: Unused or commented-out sections.
- Simplify variable names: Rename variables to be self-explanatory (e.g.,
url→urlToTOC). - Extract small methods: For clarity, such as converting inline logic into helper methods (e.g.,
hasChildren()).
After cleaning up, he reruns the approval tests – confirming everything still works. Code coverage even improves slightly (from ~85% to ~89%).
Summary & Key Lessons
- Approval Testing provides a strong safety net for refactoring legacy code without changing behavior.
- Removing Clutter improves readability, reduces noise, and clarifies structure.
- Version Control is essential for maintaining stability through small, incremental changes.
- Work in Tiny Steps: Commit after each successful test to maintain a safe rollback point.
Part 2: Reducing Complexity and Composing Methods
Reducing Complexity
Once you’ve cleared away obvious clutter, focus on reducing cyclomatic complexity — the number of paths through your code. Deeply nested loops and conditionals often signal that your code is too complex.
The key technique here is Extract Method. Identify related blocks of code, give them descriptive names, and move them into their own methods. This helps clarify intent and makes the code easier to reason about.
For example:
- A block setting up a document node becomes
getNode(). - A loop processing elements becomes
processElements().
Each extraction simplifies the main method, turning tangled logic into a sequence of clear steps.
Always run your tests after each change to ensure stability.
Refactoring is about small, reversible improvements. After every working change, commit your code. Even if you stop early, the code should already be in a better state. Follow the Boy Scout Rule: leave the code cleaner than you found it.
Two refactorings will carry you far: Rename and Extract Method. They’re simple, safe, and powerful.
Composing Methods
Once complexity is reduced, focus on composing methods — organizing them so that each one clearly describes what it
does. For instance, a getJsonForDoc() method might have three clear steps:
- Get the node to parse.
- Process each element.
- Close the JSON string.
This makes the method self-documenting. You no longer need verbose comments — the structure and names tell the story.
Summary & Key Lessons
- Reduce cyclomatic complexity by extracting methods and naming them clearly.
- Use small, safe steps — refactor incrementally and test often.
- Leverage simple refactorings like Rename and Extract Method for big impact.
- Compose methods to tell a story — make your code self-documenting.
- Leave the code better than you found it every time you make a change.
- Aim for progress, not perfection — good refactoring is incremental, not revolutionary.