{"componentChunkName":"component---src-components-blog-template-js","path":"/blog/2025-08-10-refactoring-legacy-code/","result":{"data":{"markdownRemark":{"frontmatter":{"title":"Refactoring Legacy Code","date":"2025-08-10"},"html":"<p>Every developer eventually works on code that was written by someone else, or by themselves a long time ago. Legacy code is not necessarily bad code. It is code that works but has become difficult to understand, modify, or extend. Refactoring it safely requires a careful approach.</p>\n<h3>Start with Tests</h3>\n<p>The biggest risk when refactoring is breaking existing functionality. Before changing anything, we need confidence that the current behaviour is preserved. If the code already has tests, we run them to make sure they pass. If it does not, we write characterisation tests first.</p>\n<p>Characterisation tests capture what the code currently does, not what it should do. We call the function with various inputs and assert the actual outputs, even if some of those outputs seem wrong. The goal is to have a safety net that alerts us if our refactoring changes the behaviour.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">test(\"calculates discount for premium users\", () => {\n  const result = calculateDiscount({ tier: \"premium\", total: 100 });\n  expect(result).toBe(15);\n});\n\ntest(\"returns zero discount for unknown tier\", () => {\n  const result = calculateDiscount({ tier: \"unknown\", total: 100 });\n  expect(result).toBe(0);\n});</code></pre></div>\n<h3>Small Steps</h3>\n<p>Refactoring should be done in small, incremental steps. Each step should be a behaviour-preserving transformation that we can verify with tests. Large rewrites are tempting but risky. If something breaks after a thousand-line change, finding the problem is much harder than if the change was twenty lines.</p>\n<p>Common small-step refactorings include renaming variables for clarity, extracting a block of code into a named function, removing dead code, and simplifying conditional logic.</p>\n<h3>Extract and Isolate</h3>\n<p>One of the most useful patterns when dealing with legacy code is extracting pieces into separate functions or modules. A 300-line function is hard to reason about. Breaking it into smaller functions with clear names makes the logic visible.</p>\n<p>When we find a section of code that interacts with an external system like a database or an API, we can extract that interaction behind an interface. This makes the business logic testable without needing the external system, and it creates a clear boundary between the application logic and the infrastructure.</p>\n<h3>Strangler Fig Pattern</h3>\n<p>For larger legacy systems, rewriting everything at once is not practical. The strangler fig pattern is a gradual approach where we build new functionality alongside the old system. New features or requests are routed to the new code, while existing functionality continues to run on the old system. Over time, more and more is handled by the new code until the old system can be retired.</p>\n<p>This works at different scales. It could be replacing one API endpoint at a time, one page at a time in a frontend, or one service at a time in a distributed system.</p>\n<h3>Resist the Urge to Rewrite</h3>\n<p>The temptation to throw everything away and start fresh is strong, especially when the code is messy. But a rewrite carries enormous risk. The existing code, despite its messiness, handles edge cases and business rules that may not be documented anywhere. A rewrite means rediscovering all of those through trial and error, often under pressure to ship.</p>\n<p>Incremental refactoring is slower but safer. It keeps the system running, preserves hard-won business logic, and allows the team to improve the code steadily without betting everything on a ground-up rewrite.</p>"}},"pageContext":{"slug":"/2025-08-10-refactoring-legacy-code/"}},"staticQueryHashes":[]}