Skip to content

Move Field

A field lives on one record but is consulted and updated mostly through another. Move it to the record where it is actually used, so the data sits beside the behaviour that depends on it and the two records stop reaching across an artificial boundary.

Data structures are the skeleton of a program, and a misplaced field warps everything built on it. The tell-tale signs: a field on record A that is always passed together with record B, a field that B’s methods read and write while A merely stores it, or two records that must be updated in lockstep to keep one value consistent. Each is a hint that the field’s true owner is B.

A discountRate stored on Customer but governed entirely by the customer’s PricingPlan. Before, the rate sits on the customer and the plan reaches over to compute prices. After, the rate lives on the plan, where its rules already are.

// Before
class PricingPlan {
constructor(public name: string) {}
finalPrice(base: number, discountRate: number): number {
return base * (1 - discountRate);
}
}
class Customer {
constructor(
public name: string,
public plan: PricingPlan,
public discountRate: number,
) {}
quote(base: number): number {
return this.plan.finalPrice(base, this.discountRate);
}
}
// After
class PricingPlan {
constructor(public name: string, public discountRate: number) {}
finalPrice(base: number): number {
return base * (1 - this.discountRate);
}
}
class Customer {
constructor(public name: string, public plan: PricingPlan) {}
quote(base: number): number {
return this.plan.finalPrice(base);
}
}
flowchart LR
  subgraph Before["Before"]
    A["Customer<br/>name<br/>discountRate"]
    B["PricingPlan"]
    A -.->|"plan logic reads<br/>discountRate"| B
  end
  subgraph After["After"]
    C["Customer<br/>name<br/>plan"]
    D["PricingPlan<br/>discountRate"]
    C --> D
  end
  Before -.->|"Move Field"| After
A field stored on Customer moves to the PricingPlan that uses it
  1. If the field is public, encapsulate it behind accessors first so every read and write goes through a single point. This makes the later move a one-place change.
  2. Add the field to the target record, along with an accessor there.
  3. Decide how the target reaches the value: it may compute it, store it on construction, or accept it as a parameter.
  4. Redirect each accessor on the source to delegate to the target’s copy of the field.
  5. Run your tests. Behaviour must be unchanged at this checkpoint.
  6. Remove the field from the source once nothing reads it locally any more, and drop the parameters that only existed to carry it.
  7. Run your tests again. A green suite confirms the value now lives in one place.

Move a field when another record’s methods use it more than its current owner does, when the field is always passed alongside another object, or when keeping two copies in sync is a recurring source of bugs.

The cost is touching every caller that read the field directly — which is exactly why encapsulating first pays off. There is no separate named inverse: if later changes make the original record the heavier user, you simply Move Field back.

When should you move a field to another record?
Why encapsulate the field behind accessors before moving it?
Which is a strong sign a field is in the wrong place?
What should you do at each checkpoint in the mechanics?