Skip to content

Move Function

Take a function that, when you read it, talks mostly about another object — its fields, its methods, its rules — and move the function over to that object. The new home owns the data the function needs, so the function gets simpler and the coupling between the two classes drops.

This is the cure for Feature Envy: a method that seems more interested in a class other than the one it lives in. The tell is the parameter list and the body. If a function takes a plan argument and then reads plan.tier, plan.dailyRate, and plan.discount while barely touching its own object, it envies plan. Move it there, and the reaches-across calls turn into plain field access.

An overdraft charge that lives on Account but is computed almost entirely from the account’s plan. Before, the function reaches into the plan for everything. After, it lives on the plan, where the data already is.

// Before
class AccountPlan {
constructor(public tier: string, public dailyRate: number) {}
}
class Account {
constructor(private plan: AccountPlan, private daysOverdrawn: number) {}
overdraftCharge(): number {
if (this.plan.tier === "premium") {
let base = 10;
if (this.daysOverdrawn > 7) {
base += (this.daysOverdrawn - 7) * this.plan.dailyRate * 0.85;
}
return base;
}
return this.daysOverdrawn * this.plan.dailyRate;
}
}
// After
class AccountPlan {
constructor(public tier: string, public dailyRate: number) {}
overdraftCharge(daysOverdrawn: number): number {
if (this.tier === "premium") {
let base = 10;
if (daysOverdrawn > 7) {
base += (daysOverdrawn - 7) * this.dailyRate * 0.85;
}
return base;
}
return daysOverdrawn * this.dailyRate;
}
}
class Account {
constructor(private plan: AccountPlan, private daysOverdrawn: number) {}
overdraftCharge(): number {
return this.plan.overdraftCharge(this.daysOverdrawn);
}
}
flowchart LR
  subgraph Before["Before"]
    A["Account"]
    A --> B["overdraftCharge()<br/>reads plan.tier<br/>reads plan.dailyRate"]
    C["AccountPlan"]
  end
  subgraph After["After"]
    D["Account"]
    E["AccountPlan"]
    E --> F["overdraftCharge()<br/>reads own tier<br/>reads own dailyRate"]
    D -.->|"delegates"| F
  end
  Before -.->|"Move Function"| After
A function that envies the plan moves onto the plan
  1. Examine everything the function uses in its current home and confirm most of it belongs to the target class. List the few things that still come from the source.
  2. Check whether those source-side elements should travel with the function or be passed in as parameters.
  3. Copy the function into the target class and adjust the body to use the target’s own fields directly.
  4. Compile and resolve any references — the target may need a new parameter for the leftover source data.
  5. Turn the original function into a thin delegator that calls the new home, or replace its callers directly.
  6. Run your tests. Behaviour must be unchanged.
  7. Once every caller goes through the new location, remove the delegator if it no longer earns its place.

Move a function when it consistently reaches across to another object, when several functions on one class would be simpler grouped on another, or when relocating it lets you delete a parameter that was only there to ferry data in.

The cost is that callers may now need a reference to the target object, and a function moved too eagerly can scatter related logic. The inverse is simply moving it back: if the function turns out to depend more on its original context after later changes, Move Function it home again.

Which code smell does Move Function most directly cure?
What is the clearest signal that a function belongs on a different class?
After moving the function, what often happens to the original method?
What is the inverse of Move Function?