Skip to main content
CleanCodeMastery

Move Method: Shift Work to the Class Where It Truly Belongs

Learn the Move Method refactoring through a simple school story. Shift a method into the class whose data it uses most so behaviour and data stay together.

25 min read Updated June 11, 2026beginner
refactoringsmoving-featuresmove-methodfeature-envyencapsulationtypescriptcsharp

🏫 The boy who sat in 6A but lived in 6B

Meet Arjun from Class 6A of Sunrise Public School.

On the school register, Arjun belongs to 6A. His name is on the 6A attendance sheet, his bag hangs on a 6A hook, and his desk is in the 6A room, third row from the window. But watch him for one full day, the way the headmistress Mrs. Rao once did, and you will see something funny.

The morning bell rings at 7:40, and Arjun walks straight past his own classroom to Class 6B. His best friends — Sameer, Tanvi, and Rohit — are all in 6B. His football team practises with 6B every Tuesday and Thursday. His science project partner, Tanvi, is in 6B, and their volcano model sits on the 6B window sill. The drama group he acts in? Run by the 6B class teacher, Mrs. Bose. Even his tiffin group sits in the 6B corridor, near the water cooler. The only time Arjun visits 6A is at 8:00 sharp, to answer "Present, ma'am!" when Ms. Iyer takes attendance.

This one boy quietly causes trouble for two whole classrooms. Ms. Iyer plans group work for 6A, but Arjun's group members are in another room, so his group is always one short. Mrs. Bose keeps finding an extra boy in her class and must adjust her seating chart, her group lists, her everything. Messages for Arjun go to 6A first, because that is his official address, and then travel late to 6B — once, he missed a football trial because the notice reached him two periods after everyone else. Sameer jokes that Arjun needs a passport for his daily border crossing.

One Monday, Mrs. Rao stands in the corridor with her cup of tea and watches Arjun make his fourth trip between the two rooms before lunch. She asks one simple question: "Where does Arjun's life actually happen?" The answer is obvious — 6B. So she does the obvious thing. She signs a transfer. Arjun's name moves to the 6B register, his bag moves to a 6B hook, and his desk slides into the room where his friends, his project, and his football team already are.

Within a week, everything is calm. No more running between rooms. No more late messages. No more two teachers adjusting for one boy. One boy, one classroom, all his work and friends in one place.

Figure 1: Arjun's transfer journey — life is bumpy while he sits in the wrong section, and smooth once he moves

In code, we very often find a method exactly like Arjun. It is written inside class A, but every line of its body talks to class B. It reads B's fields, calls B's getters, and makes decisions using B's data. Its register entry says one class; its life happens in another. The fix is Mrs. Rao's fix: transfer it. That transfer has a name — Move Method.

🤔 What is Move Method?

Move Method is a refactoring where we take a method that lives in one class and relocate it to another class — the class whose data it works with the most. In Martin Fowler's book Refactoring (2nd edition), this same idea appears under the name Move Function, because the new edition uses JavaScript, where functions can live outside classes too. The idea is identical.

The recipe, in one breath:

  1. Create the method in the new class (the target).
  2. Copy the body across, fixing it to use the target's own fields directly.
  3. Turn the old method into a tiny forwarder that just calls the new one — or delete it completely once nobody needs it.

Remember the most important rule of every refactoring: the program must behave exactly the same before and after. Move Method does not add a feature, does not fix a bug, does not make anything faster. It only changes where a piece of logic lives, so that the code becomes easier to read and easier to change tomorrow. A refactoring is like Arjun's transfer: the school still has the same number of students, the same syllabus, the same exams. Only the seating became sensible.

Why does the home of a method matter so much? Because of the golden rule of object-oriented design: data and the behaviour that uses that data should live together. When a rule about addresses lives inside the Address class, anyone who opens Address sees everything that can happen to an address. When that rule lives in some faraway DeliveryPlanner file, it becomes an invisible landmine — change Address and something far away silently breaks.

Figure 2: The whole idea of Move Method in one mind map — the smell, the steps, the family
💡

A quick test to find the true home of a method: read its body and underline every field access. If most underlines point at another object — student.marks, student.attendance, student.grade — then that other class is the method's real home. Fowler's rule of thumb is simple: a method should sit in the class whose data it uses most.

One more naming note for your exams and interviews: Move Method belongs to the family of refactorings called Moving Features Between Objects. Its closest siblings are Move Field (the data version of the same idea), Extract Class (when many members want to move to a brand-new home), and Inline Class (when a whole class should move into another).

College corner: in software metrics language, a method like Arjun raises coupling and lowers cohesion. Coupling Between Objects (CBO) counts how many other classes a class depends on; every foreign getter the envious method calls adds an edge. Lack of Cohesion of Methods (LCOM) measures how little a class's methods share its own fields; a method that touches none of its own fields drags LCOM up. Move Method is the rare edit that improves both numbers at once: the source class loses a dependency edge, and the target class gains a method that genuinely uses its fields. When your software engineering paper asks "give one refactoring that reduces coupling and increases cohesion simultaneously", this is the textbook answer.

📋 When do we need it?

You reach for Move Method when you smell one of these:

1. Feature Envy — the main customer. A method in class A keeps calling getters of class B, again and again, while barely touching its own class. The method is "envious" of B's data. This is the Feature Envy smell, and Move Method is its number one cure. If only a part of the method is envious, first use Extract Method to cut that part out, then move the cut piece.

2. Two classes that change together. Every time the address rules change, you must edit both Address and DeliveryPlanner. Every time the marks formula changes, you edit both Student and ReportPrinter. When one change always touches many files, that is Shotgun Surgery. Gathering the scattered methods into one class with Move Method turns five edits into one.

3. A class that is too fat, or too thin. Sometimes a Large Class has collected methods that belong elsewhere — moving them out is the first step of slimming it. The opposite also happens: a Lazy Class or a data-only class sits there with fields but no behaviour, because all its behaviour was written in other classes. Moving the methods home gives the data class a real job.

4. A planned bigger refactoring. Move Method is rarely a solo act. It is the basic "brick" used by Extract Class (move several fields and methods into a new class) and Inline Class (move everything from a dying class into its user). Learn this brick well and the bigger refactorings become easy.

Here is the underline test from the Callout above, done as a tally table for the gradeOf method you will meet in a moment:

Data touched by the methodBelongs toTouches
student.marksObtainedStudent1
student.totalMarksStudent1
percent (local variable)the method itself3
fields of ReportPrinter (its own class)ReportPrinter0

Zero touches on its own class, two on someone else's. The verdict writes itself. The same verdict as a chart:

Figure 3: The underline test as a pie — when the slice for another class dominates, the method is Arjun

When should you not move a method, even if it looks envious?

  • Printing, saving, and serialising code naturally reads another object's fields. Moving a toHtml() or saveToDb() into your domain class would glue your domain to the UI or the database — a worse problem than the envy.
  • Some design patterns separate behaviour from data on purpose. Strategy and Visitor keep the algorithm outside the data class deliberately. Do not "fix" them.
  • If the method is overridden in subclasses, moving it can break polymorphism. Check the hierarchy first.

That judgement — move or keep — depends on two questions: whose data does the method use, and how many callers depend on its current address. Plot them and the answer falls out:

Figure 4: The move-or-keep decision — methods that lean on foreign data with few callers are the easiest, safest transfers

A method in the bottom-right is Arjun on a quiet day: clearly in the wrong room, and easy to transfer. A method in the top-right is Arjun if half the school sent him letters daily — still worth moving, but you must redirect a lot of post, so keep a forwarder for a while. Methods on the left already live with their data; leave them in peace.

College corner: automated smell detectors make this quadrant precise. The classic Lanza–Marinescu detection strategy flags Feature Envy when three metrics fire together: ATFD (Access To Foreign Data — the method reads more than a few attributes of other classes), LAA (Locality of Attribute Accesses — less than one third of the data it touches is its own), and FDP (Foreign Data Providers — the foreign data comes from very few classes, ideally one, so there is an obvious target). Tools like SonarQube, NDepend, and the research tool JDeodorant implement variations of exactly this rule, and JDeodorant will even suggest the Move Method target for you. When FDP is one class, the transfer certificate practically signs itself.

👀 Before and after at a glance

Here is a tiny TypeScript example. A ReportPrinter computes a student's grade — but every line reads Student data.

// BEFORE — the grade rule lives far away from the marks
class Student {
  constructor(
    public name: string,
    public marksObtained: number,
    public totalMarks: number,
  ) {}
}
 
class ReportPrinter {
  gradeOf(student: Student): string {
    const percent = (student.marksObtained / student.totalMarks) * 100;
    if (percent >= 90) return "A";
    if (percent >= 75) return "B";
    if (percent >= 50) return "C";
    return "D";
  }
}

gradeOf is Arjun. Its register entry says ReportPrinter, but its whole life happens inside Student. Now we transfer it:

// AFTER — the grade rule lives beside the marks it uses
class Student {
  constructor(
    public name: string,
    private marksObtained: number,
    private totalMarks: number,
  ) {}
 
  grade(): string {
    const percent = (this.marksObtained / this.totalMarks) * 100;
    if (percent >= 90) return "A";
    if (percent >= 75) return "B";
    if (percent >= 50) return "C";
    return "D";
  }
}
 
class ReportPrinter {
  gradeOf(student: Student): string {
    return student.grade(); // simple delegation — or remove this entirely
  }
}

Notice two bonuses. First, marksObtained and totalMarks could become private, because nobody outside needs them any more — encapsulation improved for free. Second, any other class that needs a grade can now ask student.grade() instead of re-writing the formula.

The class structure, before and after:

Figure 5: Before the move — ReportPrinter reaches into Student's data for every line of the grade rule
Figure 6: After the move — the grade rule sits beside its data, and the printer asks one polite question

🪜 Step-by-step, the safe way

Never move a method in one giant jump. Follow small steps, and keep the program working after every single step. Mrs. Rao did not throw Arjun's bag out of the window into 6B; there was a register entry, a quiet week of settling in, and only then was his old hook reassigned. Here is the same care, as a procedure, using the gradeOf example.

Figure 7: The five safe steps of Move Method — the program compiles and passes tests after every arrow

Step 1 — Study what the method uses. Underline every field and method the body touches. Decide the target class (here: Student). Also check: is gradeOf declared in any superclass or overridden in any subclass of ReportPrinter? If yes, stop and think — moving it may break polymorphism.

Step 2 — Copy the method into the target class. Do not delete anything yet. Create the new method in Student, and adjust the body to use this instead of the parameter:

// INTERMEDIATE STATE — both copies exist; program still works
class Student {
  // ...fields...
  grade(): string {
    const percent = (this.marksObtained / this.totalMarks) * 100;
    if (percent >= 90) return "A";
    if (percent >= 75) return "B";
    if (percent >= 50) return "C";
    return "D";
  }
}
 
class ReportPrinter {
  gradeOf(student: Student): string {
    // old body still here for one more moment
    const percent = (student.marksObtained / student.totalMarks) * 100;
    if (percent >= 90) return "A";
    if (percent >= 75) return "B";
    if (percent >= 50) return "C";
    return "D";
  }
}

Compile. Run the tests. Everything still green? Good.

Step 3 — Turn the old method into a forwarder. Replace the old body with a single call:

class ReportPrinter {
  gradeOf(student: Student): string {
    return student.grade(); // delegation only
  }
}

Compile and test again. The behaviour is identical, but now there is only one copy of the real logic. This is the "settling in" week of the transfer: old callers still knock on the 6A door, and the door politely points them down the corridor.

Figure 8: Life during the delegation phase — old callers still reach the old address, which quietly forwards to the new home

Step 4 — Decide the fate of the forwarder. If ReportPrinter.gradeOf has many callers spread across the codebase, you may keep the forwarder for a while. But usually the right finish is to update each caller to say student.grade() directly, and then delete gradeOf completely. A forwarder that lives forever becomes a Middle Man smell — a 6A desk kept empty "in case Arjun visits".

Step 5 — Tighten access. Any getter or public field that existed only to feed the old method can now become private. This is the hidden prize of Move Method: the target class gets to close its doors.

The whole journey of the method, from wrong home to settled, looks like this as a state machine:

Figure 9: The lifecycle of one moved method — each state is a safe, compiling, tested resting point

What if the method also needs data from its old class? Then you have choices: pass that data as a parameter to the new method, pass the whole source object as a parameter, or move that data too with Move Field. Prefer passing plain values — it keeps the new method independent. If the method needs a lot from both classes, that is the two-rooms problem from the FAQ: split it with Extract Method first, and send each piece where it belongs.

⚠️

Run your tests after every small step — after copying, after delegating, after each caller update, after the final delete. Move Method feels mechanical, but a tiny slip (using the wrong field, forgetting one caller) changes behaviour silently. If you have no tests around the method, write a couple of simple ones before you start. A refactoring without tests is just hopeful editing.

🏏 A bigger real-life example

Let us code the full Arjun story. A school has section objects, and someone wrote the "activity report" for Arjun inside SectionA — but every line of it uses SectionB's things.

// BEFORE
class SectionB {
  constructor(
    public footballTeam: string[],
    public dramaGroup: string[],
    public scienceProjects: Map<string, string>, // student -> project
  ) {}
}
 
class SectionA {
  constructor(public students: string[]) {}
 
  // Arjun's method: registered in 6A, but living in 6B
  activityReportFor(name: string, other: SectionB): string {
    const lines: string[] = [];
    if (other.footballTeam.includes(name)) {
      lines.push(`${name} plays football with 6B`);
    }
    if (other.dramaGroup.includes(name)) {
      lines.push(`${name} acts in the 6B drama group`);
    }
    const project = other.scienceProjects.get(name);
    if (project) {
      lines.push(`${name} works on the 6B project: ${project}`);
    }
    return lines.join("\n");
  }
}

Look at activityReportFor. It uses other.footballTeam, other.dramaGroup, other.scienceProjects — three touches on SectionB — and from its own class it uses nothing at all except its address. In ATFD terms: foreign accesses three, own accesses zero, and all the foreign data comes from one provider. It is pure Feature Envy with an obvious target. Transfer order, please:

// AFTER
class SectionB {
  constructor(
    private footballTeam: string[],
    private dramaGroup: string[],
    private scienceProjects: Map<string, string>,
  ) {}
 
  activityReportFor(name: string): string {
    const lines: string[] = [];
    if (this.footballTeam.includes(name)) {
      lines.push(`${name} plays football with 6B`);
    }
    if (this.dramaGroup.includes(name)) {
      lines.push(`${name} acts in the 6B drama group`);
    }
    const project = this.scienceProjects.get(name);
    if (project) {
      lines.push(`${name} works on the 6B project: ${project}`);
    }
    return lines.join("\n");
  }
}
 
class SectionA {
  constructor(public students: string[]) {}
  // nothing about 6B activities lives here any more
}
 
// Any caller now writes:
//   sectionB.activityReportFor("Arjun");

See what improved:

  • SectionB's three collections became private. Before the move that was impossible, because SectionA needed to read them.
  • The report logic sits beside the data it reads. When 6B adds a chess club, only SectionB changes.
  • SectionA shrank to its real job. It no longer recompiles or breaks when 6B's inner shape changes.

And the numbers agree. Count every place where code in one class touches a member of the other class — every "border crossing" Arjun used to make daily:

Figure 10: Border crossings between the two section classes, counted before and after the transfer

Sixteen crossings collapse to three — and the three that remain are polite, single-method calls through a public door, not rummaging through another class's cupboard. This is what "reducing coupling" looks like when you actually count it.

💻 The same refactoring in C# and Python

The shape is identical in every object-oriented language. Here a courier company's DeliveryPlanner decides the delivery zone — but every line interrogates the Address.

// BEFORE
class Address
{
    public string State { get; set; }
    public string Pincode { get; set; }
    public bool IsMetroCity { get; set; }
}
 
class DeliveryPlanner
{
    public string ZoneFor(Address address)
    {
        if (address.IsMetroCity) return "Metro";
        if (address.Pincode.StartsWith("1")) return "North";
        if (address.State == "Kerala" || address.State == "Tamil Nadu")
            return "South";
        return "Standard";
    }
}

ZoneFor knows the whole inner shape of Address. If tomorrow the company stores pincode differently, this far-away planner breaks. Move the method home:

// AFTER
class Address
{
    public string State { get; set; }
    public string Pincode { get; set; }
    public bool IsMetroCity { get; set; }
 
    public string Zone()
    {
        if (IsMetroCity) return "Metro";
        if (Pincode.StartsWith("1")) return "North";
        if (State == "Kerala" || State == "Tamil Nadu") return "South";
        return "Standard";
    }
}
 
class DeliveryPlanner
{
    public string ZoneFor(Address address) => address.Zone();
}

If DeliveryPlanner was the only caller, the final polish is to delete ZoneFor and let everyone ask address.Zone() directly.

Python students, the move is the same dance — and Python's habit of reaching into objects freely makes the envy even easier to spot:

# BEFORE — the fine rule lives in the desk, but uses only book data
class Book:
    def __init__(self, title, daily_fine, max_borrow_days):
        self.title = title
        self.daily_fine = daily_fine
        self.max_borrow_days = max_borrow_days
 
class LibraryDesk:
    def fine_for(self, book, days_kept):
        late_days = days_kept - book.max_borrow_days
        return max(0, late_days * book.daily_fine)
 
# AFTER — the rule moves home; the desk just asks
class Book:
    def __init__(self, title, daily_fine, max_borrow_days):
        self.title = title
        self._daily_fine = daily_fine
        self._max_borrow_days = max_borrow_days
 
    def fine_for(self, days_kept):
        late_days = days_kept - self._max_borrow_days
        return max(0, late_days * self._daily_fine)

Same lesson in all three languages: after the move, the data could turn private (private in TypeScript and C#, the underscore convention in Python), because the only method that needed it now lives inside.

🧰 IDE support

Good news: you rarely have to do this with bare hands. Modern IDEs automate Move Method and update every caller for you.

ToolSupportHow
IntelliJ IDEA / JetBrains familyFullSelect method, press F6 (Refactor → Move), or Ctrl+Alt+Shift+T for "Refactor This"
JetBrains Rider (C#)FullDedicated Move Instance Method refactoring rewrites every usage
ReSharper in Visual StudioFullAdds Move Instance Method / Move Static Method to Visual Studio
Visual Studio (plain)Partial"Move type to file", pull-up/push-down — full instance move is manual
VS Code (TypeScript)Partial"Move to a new file" and extracts; the five safe steps work perfectly by hand

Even with a smart IDE, keep your tests running. The IDE guarantees the code compiles; only your tests guarantee the behaviour survived.

⚖️ Benefits and risks

Benefit ✅Risk / cost ⚠️
ReadabilityBehaviour sits beside its data; reading one class tells the full story of that conceptA long chain of moves can confuse teammates mid-review — move in small commits
EncapsulationThe target class can make fields private and delete feed-me gettersIf the moved method still needs source data, you may add parameters and grow the signature
CouplingRemoves a dependency edge; the two classes stop changing togetherMoving to the wrong target just flips the envy in the other direction
ReuseOther clients can call the method at its natural home instead of duplicating the formulaA kept-forever delegating wrapper becomes a Middle Man smell
Design patternsOften the first brick of Extract Class and Inline ClassMoving methods out of Strategy/Visitor structures destroys a deliberate design

One balance to remember: Move Method moves one member. If you find yourself moving five methods and four fields to the same place, stop — you are actually doing Extract Class (if the place is new) or Inline Class (if a whole class is emptying out). Use the bigger named refactoring so your intent is clear. And remember the seesaw those two sit on: extract when a class does too much, inline when it does too little — Move Method is the plank both of them stand on.

College corner: a good way to measure whether your move helped is to compute CBO and LCOM before and after on the two classes involved. A successful Move Method should drop the source class's CBO by at least one (the dependency on the target disappears or weakens to a single call), and the target's LCOM should fall, because the incoming method genuinely uses the target's fields. If instead the target's CBO rises — the moved method dragged in three new imports — you probably picked the wrong target, and the quadrant chart in Figure 4 would have warned you: that method was top-left, not bottom-right.

🧪 Which smells does it cure?

SmellHow Move Method helps
Feature EnvyThe direct cure — the envious method moves to the class it loves
Inappropriate IntimacyMethods that poke another class's privates move there, and the doors close
Shotgun SurgeryScattered pieces of one rule gather into one class; one change, one file
Large ClassMoving out methods that never belonged starts the slimming diet
Data ClassA fields-only class receives its rightful behaviour and becomes a real object

📦 Quick revision box

+--------------------------------------------------------------+
|                    MOVE METHOD — CHEAT CARD                  |
+--------------------------------------------------------------+
| Story    : Arjun sits in 6A but lives in 6B -> transfer him  |
| Problem  : method in class A uses class B's data all day     |
| Smell    : Feature Envy (main), Shotgun Surgery, Intimacy    |
| Fix      : create method in B -> copy body -> delegate from  |
|            A -> update callers -> delete old -> privatise    |
| Rule     : a method belongs where its DATA lives             |
| Test     : run tests after EVERY small step                  |
| Metrics  : CBO down, LCOM down = the move worked             |
| Don't    : move UI/DB logic into domain; break Strategy /    |
|            Visitor; ignore overridden methods in subclasses  |
| Friends  : Move Field, Extract Method, Extract Class         |
+--------------------------------------------------------------+

✏️ Practice exercise

Try this yourself before peeking at any solution.

class Book {
  constructor(
    public title: string,
    public dailyFine: number,
    public maxBorrowDays: number,
  ) {}
}
 
class LibraryDesk {
  fineFor(book: Book, daysKept: number): number {
    const lateDays = daysKept - book.maxBorrowDays;
    if (lateDays <= 0) return 0;
    return lateDays * book.dailyFine;
  }
}

Your tasks:

  1. Underline every piece of data fineFor uses. Whose data is it — LibraryDesk's or Book's? Draw your own version of the Figure 3 pie for this method.
  2. Move the method to its true home using the five safe steps. Keep an intermediate delegating version of fineFor and run a small test (e.g. daysKept = 20, maxBorrowDays = 14, dailyFine = 2 should give 12) after each step.
  3. After the move, make dailyFine and maxBorrowDays private. Does the program still compile? What does that tell you about who was using them?
  4. Place fineFor on the Figure 4 quadrant chart: how far right is it (foreign data use), and how high (number of callers)? Does its quadrant agree with your decision?
  5. Bonus thinking question: suppose fineFor also gave a 50% discount when the LibraryDesk had a festivalWeek flag set. Now the method uses data from both classes. Which refactoring would you do first, and where could the discount rule live?

If you can answer task 5 out loud in two sentences, you have truly understood Move Method — not just the steps, but the judgement behind them. And if your answer mentioned "split it first, then move each piece" — congratulations, you are already thinking like Mrs. Rao with her cup of tea in the corridor.

Frequently asked questions

What is the Move Method refactoring in simple words?
Move Method means lifting a method out of one class and placing it inside another class — the class whose data the method actually uses. After the move, the old class either deletes the method or keeps a one-line forwarder. The behaviour of the program does not change at all; only the home of the method changes.
How do I know which class a method should move to?
Count whose data the method touches. If it reads three or four fields of class B but almost nothing from its own class A, then class B is its true home. Fowler's rule of thumb: a method belongs in the class whose data it uses the most.
Which code smell does Move Method fix?
It is the number one cure for Feature Envy — a method that is more interested in another class's data than its own. It also helps with Inappropriate Intimacy and Shotgun Surgery, because once related logic gathers in one class, a change touches one file instead of many.
What if the method uses data from two classes equally?
Then moving it to either class just creates envy towards the other one. First use Extract Method to split the method into parts, then move each part to the class it loves. Sometimes the answer is a brand-new class that owns the whole calculation — that is Extract Class.
Is it safe to move a method that is overridden in subclasses?
Be careful. If the method is part of an inheritance contract — declared in a superclass or overridden in subclasses — moving it can break polymorphism. Check the whole class hierarchy first. If the method participates in overriding, either leave it or redesign the hierarchy before moving.

Further reading

Related Lessons