Overview

9 Refactoring Legacy Code

In this chapter, a legacy e-commerce cart system is used to show how messy object-oriented code can be steadily transformed into a data-oriented design. The original implementation mixes business logic, persistence, presentation, and external service calls inside mutable entities and controller code, creating tangled responsibilities, hidden state, and lots of opportunities for bugs. Instead of trying to “architect away” the mess too early, the refactoring begins by studying the code as it exists and identifying the real problems in its data model and behavior.

The chapter argues that the deepest issues come from uncontrolled mutation, sequential coupling, partial construction, and hidden knowledge. A cart item may be validated too early, missing important fields like seller information that are only filled in later, which allows invalid states to slip through and cause subtle duplication and inventory errors. To fix this, the chapter works backward from the system’s invariants and introduces immutable data types that make illegal states hard or impossible to represent. Validation is reframed as evidence-gathering for the whole feature, not just error checking on one input object, and new boundary types are used to force the code to gather all necessary information before proceeding.

Once the data model is strengthened, the chapter shows how utilities and algebraic thinking make the code more expressive. Map and set-style helpers such as intersection, union, difference, and value-mapping let the business rules be expressed directly, without loops and branching obscuring the intent. The refactor is intentionally incremental: mutation is still allowed where it helps, but only inside controlled scopes that produce immutable results. By planting these data-driven boundaries in the middle of the legacy codebase, the design gradually shifts toward clarity, correctness, and easier future refactoring, while leaving room to reintroduce higher-level object boundaries later if they now make sense.

The application
Customers (and our system) can move items from their “active” cart into “saved for later”
Boxes and arrows driven refactors for improving the current design
How a single null can sneak through a system
This took me hours careful reading to uncover
Shotgun parsing
Expressing the Cart’s invariants formally
Protecting access to code with data
Drawing boundaries with data
Strong boundaries can be drawn anywhere with data
All we’re allowed to learn during validation
The implicit lifecycle in validation
The visual layout of the code often betrays its true behavior
Validation should be a boundary guarded by data
Another flag planted.
visualizing with the requirements as set operations
Set difference
Algebraic manipulation
Visualizing the feature
Visualizing moving the last of the business logic into the world of data
Mapping between the modern and legacy worlds

Summary

  • Refactoring begins with analysis!
  • Problems must be identified to be fixed. Enumerate them.
  • Good refactors fix problems; great ones prevent them from reoccurring. Make illegal states impossible to represent!
  • Mutation on its own isn’t the enemy. You have to articulate how mutation is causing a problem
  • Sequential coupling is when the methods exposed on your object need called in a special order to avoid errors
  • Shotgun parsing is when code makes decisions on a partially constructed or validated object.
  • Partially constructed objects cause invalid data to silently propagate through the code
  • Hidden knowledge is one of the biggest problems in codebases. Surface it
  • Avoid asking questions about objects and responsibilities too early. Indirection risks hiding, rather than fixing, problems
  • Work backwards from invariants. Translate loose requirements in English prose into formal “for all” statements
  • Enshrine invariants into your data types. Use constructors to defend against bad states.
  • Consider explicitly forbidding nulls during construction rather than relying on convention.
  • The Curry-Howard Correspondence lets us view data types as assertions or claims, and instances of those data types as proof that we can construct them
  • Data can be used to draw powerful boundaries anywhere in an application
  • A data type is a demand for proof. It can control entry to and exit from critical sections of code
  • Validation is about building evidence, not just finding errors.
  • Validation should be treated as a boundary. Its exit should be guarded by data.
  • Invest in writing utilities that make writing data-oriented programs easier in the future
  • Utilities find what features are “missing” in our language or libraries
  • Writing a good utility starts with looking at code and saying “Ah, what you mean to say is…”
  • Don’t worry about performance! The JVM is fast. When in doubt: benchmark!
  • More expressive utility functions can change how we solve problems. They give us new tools for thinking.
  • Avoid cargo culting. Use what works. Mutation is fine as long as it’s controlled!
  • Ask the usual object-oriented questions once you’ve solved the problems in the original code with data. Those are good questions!
  • Refactoring legacy code can feel daunting, if not impossible. But it can be done. Roll up your sleeves and make it happen!

FAQ

What is the main goal of refactoring legacy code toward data orientation?To find the data hidden inside messy object-oriented code, bring it to the surface, and replace fragile object behavior with clearer, immutable data models and incremental refactors.
Why does the chapter recommend starting with the data model instead of redesigning objects first?Because many legacy problems come from unclear or mixed-up data representation. Starting with data helps reveal invariants, reduce hidden knowledge, and make illegal states harder to represent.
What are the main design problems in the legacy shopping cart example?The cart has messy data modeling, uncontrolled mutation, sequential coupling, hidden knowledge, partial construction, and validation that only checks for local errors instead of true feature validity.
What is sequential coupling in the cart code?It is when methods must be called in a specific order for the cart to remain valid. In the example, changing the order of operations can leave the cart in an inconsistent state.
Why is partial construction a problem in the legacy code?Because objects like CartItem are created before all required information is known, leaving them in incomplete or misleading states that can pass through the system and cause bugs later.
How does the chapter use invariants to guide the refactor?It works backward from what must always be true, such as item uniqueness and quantity rules, and encodes those rules in data types like ValidCart and ProductSeller.
Why does the chapter use immutable data types like records?Immutable data prevents corruption after construction, makes correctness easier to reason about, and reduces the need for defensive checks scattered throughout the code.
How does the chapter draw boundaries with data?By using data types as gatekeepers. Code can only proceed if it can construct the required valid data, which forces preconditions to be checked up front.
What role do utility functions play in data-oriented refactoring?They make operations on data structures more expressive and concise, such as transforming maps or combining overlapping collections, so the code focuses on what it means rather than how to do it.
Does the chapter eliminate mutation completely?No. It keeps mutation where it is useful inside controlled scopes, such as temporary local collections during computation, but returns immutable data at the boundaries.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Data-Oriented Programming in Java ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Data-Oriented Programming in Java ebook for free