Rename Method: Fix the Shop Board So It Tells the Truth
Rename Method explained simply — why a method's name must say what the method really does, how to rename safely with a delegating old method, and how IDEs like VS Code (F2) and JetBrains (Shift+F6) make it a one-key job.
🪧 The Shop Board That Tells a Lie
Near my house there is a small shop. The board on top says, in big proud letters: "Sharma General Store". One hot Sunday afternoon my cousin Ravi walked twenty minutes in the sun to buy soap, toothpaste, and a packet of glucose biscuits from this "general store".
He came back empty-handed, sweating, and properly annoyed. Why? The shop sells only mobile recharges. No soap. No biscuits. Just recharges, SIM cards, and a small glass case of phone covers. The board was painted years ago, when Sharma uncle really did sell rice, dal, and washing powder. Then the supermarket opened two lanes away, the grocery business dried up, and he slowly switched to mobile recharges — a smart business move. The business changed completely. The board did not change one letter.
Now think about what that old board does, every single day:
- Ravi and people like him waste a full trip in the sun.
- Sharma uncle wastes half his day saying "no beta, only recharge" — he told Ravi he says it maybe fifty times a day.
- People who actually want a recharge walk past the shop, because the board does not mention recharges at all!
- One day a wholesale soap salesman wasted an hour trying to convince "the general store" to stock his brand.
The board is not just useless. It is actively harmful in both directions — it pulls in the wrong people and pushes away the right ones. And the fix? One afternoon, one painter, three hundred rupees: repaint the board to say "Sharma Mobile Recharge & SIM Point". After that, every passer-by understands the shop correctly from the street, without stepping inside, forever.
Methods in our code have boards too — their names. A caller reads the name from "the street" (the call site) and decides what the method does without opening it. If the name says check() but the method actually calculates a bill, the reader is poor Ravi, walking into the wrong shop again and again.
Rename Method is the act of repainting the board. We change the method's name so the name tells the truth about what the method does today — not what it did two years ago.
What is Rename Method? 🧐
Rename Method is a refactoring from Martin Fowler's Refactoring book. The idea is short enough to fit in one line:
Change a method's name so that the name alone explains what the method does.
That is the whole refactoring. No logic changes. No behaviour changes. The program does exactly the same thing after the rename as before. Only the label improves.
Why does such a small change deserve a whole chapter in famous books? Because code is read far more often than it is written. You write a method once, but you (and your teammates, and the new junior who joins next year) read its call sites hundreds of times over the years. Every time someone reads const x = get(customer) and has to open the method body to understand it, the bad name has charged a small tax. A precise name pays that tax off forever. It is the cheapest documentation in the world: it travels with every call, it never goes out of date with the signature, and the compiler checks it for you.
There is a second, quieter gift. Trying to find a good name is a design test. If you sit for five minutes and simply cannot find one honest name for a method, that is the method confessing: "I do two different jobs." A method that calculates and saves and notifies cannot be named truthfully with one verb. The failed naming attempt tells you to split the method first (using Extract Method or Separate Query from Modifier), and then name the clean pieces. Sharma uncle faced the same thing: if he had still sold groceries and recharges, no short board could say it honestly — the naming struggle would have told him his shop was doing two businesses.
A naming note for the exam and for real life: in the 2nd edition of Refactoring, Fowler merged several first-edition refactorings — Rename Method, Add Parameter, and Remove Parameter — into one umbrella refactoring called Change Function Declaration (the catalog lists "Rename Function", "Add Parameter", "Remove Parameter", and "Change Signature" as its aliases). The thinking is simple: a method's name and its parameter list together form its public "board", and changing any part of that board follows the same safe mechanics. When the change is only the name, Fowler still calls it "Rename Function" for clarity. So if you see "Change Function Declaration" on refactoring.com, do not be confused — Rename Method lives inside it.
One small honesty rule for ourselves: a rename is only a refactoring if behaviour stays identical. If you change the name and sneak in a logic change in the same step, you have done two things at once, and when a test fails you will not know which change broke it. One step, one purpose.
Here is the whole topic on one page, the way you might sketch it in the back of your notebook before an exam:
College corner: what we casually call "renaming" is, at industry scale, API evolution. A public method name is a contract — once another team, another service, or a published package depends on it, the name stops being yours alone. Mature ecosystems never delete a public name overnight; they run a deprecation lifecycle: announce the new name, mark the old one deprecated (Java's @Deprecated, C#'s [Obsolete], JavaScript's JSDoc @deprecated), keep both alive for one or more release cycles, and remove the old name only in the next major version (that is the "major" in semantic versioning — breaking changes only on major bumps). The humble gradual rename you will learn below is exactly this lifecycle, shrunk down to one codebase.
When Do We Need It? 🚨
Watch for these everyday signals. Each one is the shop board drifting away from the shop.
- The name is vague from birth. Names like
process(),handle(),doStuff(),manage(),get()say almost nothing. They are boards that just say "Shop". - The behaviour changed but the name did not. A method named
validateOrder()that now also saves the order is lying. This drift happens naturally as code grows — names rot silently, because nothing forces them to update. This is exactly Sharma uncle's board: true once, false now. - You need a comment to explain a call. If every call site carries a comment like
// this actually computes the discount, the comment is doing the name's job. The Comments smell teaches us: a comment that explains what code does is often a name begging to be fixed. - You explain the method differently than its name. In code review you say "this one fetches the latest invoice" — but the method is called
check(). Whatever words come out of your mouth naturally are usually the right name. - Searching fails. You search the project for "discount" to find the discount calculation, and find nothing, because the method is named
calc2(). Good names make code discoverable; bad names hide it. - The name describes how, not what.
loopThroughOrdersAndSum()describes the mechanism. Callers care about the result:totalOrderAmount(). If you later change the loop to a formula, the "how" name becomes a lie too.
The smell this refactoring attacks most directly is the Mysterious Name smell — Fowler's 2nd edition even lists it as the very first smell in the book, because unclear names are the most common and most fixable problem in real codebases.
Think about what the lying board did to the people who read it. If we surveyed everyone who acted on "Sharma General Store" last month, the picture would look roughly like this:
Only thirty percent of readers got what the name promised. In code, that ratio is a bug factory: seventy percent of developers who call your badly named method will call it for the wrong reason, or pass the wrong things, or handle its result wrongly.
So should you rename right now, or schedule it? Two questions decide: how badly does the name lie, and how many callers exist?
Read it like a doctor reads a chart: a name that actively lies (top half) is an emergency whether it has two callers or two hundred — the only question is the technique (one IDE keypress for few callers, the gradual dance for many). A name that is merely slightly off (bottom half) can wait for the next time you touch that file.
Before and After at a Glance 📋
Here is the lying shop board in TypeScript:
// BEFORE — the board says "general store"
class CustomerAccount {
private orders: Order[] = [];
private discountRate = 0.1;
// What does "get" get? Nobody knows without reading the body.
get2(): number {
const subtotal = this.orders.reduce((sum, o) => sum + o.amount, 0);
return subtotal * (1 - this.discountRate);
}
}
// At the call site, the reader learns nothing:
const x = account.get2(); // a lookup? a network call? a total? a mystery.And after repainting the board:
// AFTER — the board tells the truth
class CustomerAccount {
private orders: Order[] = [];
private discountRate = 0.1;
calculateDiscountedTotal(): number {
const subtotal = this.orders.reduce((sum, o) => sum + o.amount, 0);
return subtotal * (1 - this.discountRate);
}
}
// The call site now reads like a sentence:
const billAmount = account.calculateDiscountedTotal();Notice what did not change: the body, the return type, the behaviour, the tests. Only the label changed — and yet every future reader of every call site benefits.
Step-by-Step, the Safe Way 🪜
For a private method inside one file, your IDE can rename everything in one keypress (we will see that soon). But you should also know the manual, gradual rename, because it is the only safe path when the method is public, used by other teams, or reachable by code your tools cannot see. Fowler calls this the migration approach inside Change Function Declaration.
The whole plan fits in one picture:
Let us rename get2() to calculateDiscountedTotal() the careful way.
Step 1 — Check the inheritance family. Does any superclass or subclass declare a method with the same signature? If yes, polymorphism depends on the name. You must rename the whole family together, or callers holding a base-class reference will silently stop reaching your override.
Step 2 — Create the new method with the better name, and move the body into it.
class CustomerAccount {
// NEW — the truthful name, holding the real body
calculateDiscountedTotal(): number {
const subtotal = this.orders.reduce((sum, o) => sum + o.amount, 0);
return subtotal * (1 - this.discountRate);
}
// OLD — still here, but now just a signpost
get2(): number {
return this.calculateDiscountedTotal();
}
}This is the key trick: the old method now simply delegates to the new one. Every existing caller still works. Nothing is broken. The shop has two boards for a short while — the old faded one and the new clear one — and both lead to the same counter.
Step 3 — Compile and run the tests. Behaviour must be identical. If anything fails now, you made a typo, not a design mistake — easy to fix.
Step 4 — Migrate callers one by one. Find each call to get2() and change it to calculateDiscountedTotal(). After each caller (or each small batch), run the tests again.
// caller, before:
const x = account.get2();
// caller, after:
const billAmount = account.calculateDiscountedTotal();Step 5 — Delete the old method. When no callers remain, remove get2() completely. The old faded board comes down.
Step 6 — For public APIs, deprecate instead of delete. If outside consumers call get2(), do not remove it immediately. Mark it deprecated (@deprecated in TypeScript JSDoc, [Obsolete] in C#) so their tools warn them, and remove it in a later major version.
/** @deprecated Use calculateDiscountedTotal() instead. */
get2(): number {
return this.calculateDiscountedTotal();
}During the migration window, the delegation looks like this at runtime — the old name is just a polite receptionist forwarding visitors to the new office:
And seen from above, the whole codebase passes through three clean states. You can pause safely in the middle state for a day or for six months — nothing is broken there:
Run your tests after every small step — after creating the delegating method, and after migrating each caller. A rename feels too tiny to break anything, which is exactly why people skip testing and then ship a broken build. The compiler also cannot see callers that use the name as a string: reflection, JSON serialization, ORM column/method mappings, RPC routes, and dynamic lookups. After the rename, search the whole repository for the old name as plain text. This one habit catches the bugs that hurt the most.
College corner: notice that the middle state (BothNames) is what API designers call backward compatibility — new capability added without breaking a single existing consumer. Big platforms live in this middle state for years. The Java standard library still carries methods deprecated decades ago; HTTP APIs version themselves (/v1/, /v2/) for the same reason. The skill being practised is not "renaming" — it is changing a contract without breaking the people who signed it. Master it on one method today; you will reuse it on whole services later.
A Bigger Real-Life Example 🏫
A school fee portal has grown for three years. Look at this class — every name has drifted:
// BEFORE — three lying boards in one class
class FeePortal {
// "check" actually CALCULATES the pending amount
check(studentId: string): number {
const fees = this.feeRepo.findByStudent(studentId);
return fees
.filter((f) => !f.paid)
.reduce((sum, f) => sum + f.amount, 0);
}
// "data" actually SENDS a reminder SMS
data(studentId: string): void {
const pending = this.check(studentId);
if (pending > 0) {
this.sms.send(studentId, `Pending fee: Rs.${pending}`);
}
}
// "handleStudent" actually MARKS a fee as paid
handleStudent(studentId: string, receiptNo: string): void {
const fee = this.feeRepo.findOpenFee(studentId);
fee.paid = true;
fee.receiptNo = receiptNo;
this.feeRepo.save(fee);
}
}Now read a caller of this class and feel the pain:
// What is this code doing? Honestly — who can tell?
const a = portal.check(id);
if (a > 0) portal.data(id);
portal.handleStudent(id, receipt);Three calls, zero understanding. Each name forces the reader to open a body. Let us repaint all three boards, using the gradual method for each (new name, delegate, migrate, delete). The end result:
// AFTER — every board tells the truth
class FeePortal {
calculatePendingFee(studentId: string): number {
const fees = this.feeRepo.findByStudent(studentId);
return fees
.filter((f) => !f.paid)
.reduce((sum, f) => sum + f.amount, 0);
}
sendPendingFeeReminder(studentId: string): void {
const pending = this.calculatePendingFee(studentId);
if (pending > 0) {
this.sms.send(studentId, `Pending fee: Rs.${pending}`);
}
}
markFeeAsPaid(studentId: string, receiptNo: string): void {
const fee = this.feeRepo.findOpenFee(studentId);
fee.paid = true;
fee.receiptNo = receiptNo;
this.feeRepo.save(fee);
}
}And the same caller, after:
const pending = portal.calculatePendingFee(id);
if (pending > 0) portal.sendPendingFeeReminder(id);
portal.markFeeAsPaid(id, receipt);Read it aloud. It is almost an English paragraph: calculate the pending fee; if there is any, send a reminder; mark the fee as paid. Nothing about the program's behaviour changed — but the cost of understanding this code dropped from "open three method bodies" to "read three lines".
Compare the two class boards side by side as a class diagram — the structure is identical; only the truth content of the labels changed:
One more bonus: while renaming data(), we were forced to ask "what does this really do?" — and we noticed it both reads the pending amount and sends an SMS. Today one name (sendPendingFeeReminder) covers it honestly. But if it ever also starts recording the reminder in the database, no single name will fit, and that struggle will push us toward splitting it. Renaming keeps testing our design for free.
The Same Refactoring in C# 🎯
The mechanics are identical in C#. Here is a payroll example, including the deprecation step that C# does especially well with the [Obsolete] attribute.
// BEFORE — "Get" gets... what, exactly?
public class PayrollService
{
public decimal Get(Employee e)
{
var basePay = e.MonthlySalary;
var hra = basePay * 0.40m; // house rent allowance
var pf = basePay * 0.12m; // provident fund deduction
return basePay + hra - pf;
}
}Step 1 — add the truthful method; make the old one delegate and warn:
public class PayrollService
{
public decimal CalculateNetMonthlyPay(Employee e)
{
var basePay = e.MonthlySalary;
var hra = basePay * 0.40m;
var pf = basePay * 0.12m;
return basePay + hra - pf;
}
[Obsolete("Use CalculateNetMonthlyPay instead. Will be removed in v3.")]
public decimal Get(Employee e) => CalculateNetMonthlyPay(e);
}Now every project that calls Get() still compiles and runs, but the compiler shows a warning at each call site, gently pushing every caller toward the new name. When the team's build logs show zero Obsolete warnings, you delete Get().
// AFTER — final state
public class PayrollService
{
public decimal CalculateNetMonthlyPay(Employee e)
{
var basePay = e.MonthlySalary;
var hra = basePay * 0.40m;
var pf = basePay * 0.12m;
return basePay + hra - pf;
}
}C#-specific care points:
- If the method implements an interface member or overrides a virtual method, rename the declaration in the interface/base class too — the IDE's rename does this family-wide automatically.
- Watch for reflection (
typeof(PayrollService).GetMethod("Get")) and serializer attributes — these hold the old name as a string and will break silently.
A Quick Python Taste 🐍
Dynamic languages get the same medicine, with one extra caution: there is no compiler to list the broken callers for you, so the delegating step matters even more. Python's warnings module plays the role of [Obsolete]:
import warnings
class FeePortal:
def calculate_pending_fee(self, student_id: str) -> int:
fees = self.fee_repo.find_by_student(student_id)
return sum(f.amount for f in fees if not f.paid)
def check(self, student_id: str) -> int:
"""Deprecated: use calculate_pending_fee instead."""
warnings.warn(
"check() is deprecated; use calculate_pending_fee()",
DeprecationWarning,
stacklevel=2,
)
return self.calculate_pending_fee(student_id)Every old caller still works, but test runs now print a deprecation warning with the caller's file and line (stacklevel=2 does that trick) — your migration to-do list, generated at runtime instead of compile time.
Does Renaming Actually Pay Off? 📈
It feels like cosmetics, so let us talk results. Teams that track "wrong-use" bugs — a caller used a method for the wrong purpose because the name suggested the wrong thing — see the count collapse after honest renames, for one boring reason: the misunderstanding that caused the bug can no longer happen at the call site.
The remaining one-per-month? Usually the string-based callers the compiler cannot see — which is why the plain-text search habit from the warning box above earns its keep.
IDE Support 🛠️
Rename is the most automated refactoring in existence. Modern editors do the whole gradual dance for you, safely, in one keystroke:
| Tool | Shortcut | What it does |
|---|---|---|
| VS Code | F2 (Rename Symbol) | Renames the symbol and all references across the workspace, using the language server's understanding of the code — not text matching. |
| JetBrains IDEs (IntelliJ IDEA, Rider, WebStorm, PyCharm) | Shift+F6 (Rename) | Renames the method and every usage; offers to update related names in comments, strings, and inheritors. |
| JetBrains IDEs | Ctrl+F6 (Cmd+F6 on macOS) — Change Signature | The full "Change Function Declaration": rename and add/remove/reorder parameters in one dialog. |
| Visual Studio | Ctrl+R, Ctrl+R (Rename) | Solution-wide rename with preview, including comments and strings if you tick the options. |
Because the IDE understands scopes and types, it will not touch a different method that happens to share the name, and it will rename the whole polymorphic family together. This is why IDE rename is strictly safer than find-and-replace.
Still, remember the limits: no IDE can follow a name that lives inside a string (reflection, route tables, serialized payloads, SQL, config files). After the one-key rename, do a plain-text search for the old name. Two minutes of searching beats two days of debugging.
Benefits and Risks ⚖️
| Benefits | Risks / Costs |
|---|---|
| Call sites explain themselves; readers stop opening method bodies just to understand a call. | Renaming a public API breaks external consumers — must use delegate + deprecate + remove-later. |
| The cheapest, never-stale documentation: the name travels with every call and is compiler-checked. | String-based callers (reflection, serialization, ORM, RPC routes) are invisible to the compiler and to IDE rename. |
| Failed naming attempts expose hidden design problems — a method doing two jobs cannot be named honestly. | Renaming again and again ("name churn") pollutes git history and confuses teammates; converge on a good name, do not oscillate. |
Better searchability: grep "discount" actually finds the discount logic. | In a polymorphic family, renaming only one member silently breaks overriding — rename the whole family. |
| Nearly free with IDE support (F2 / Shift+F6). | Mixing a rename with a logic change in one commit makes failures hard to diagnose — keep renames pure. |
Which Smells Does It Cure? 🧹
| Smell | How Rename Method helps |
|---|---|
| Mysterious Name | The direct cure: replace check(), data(), get2() with names that state intent. |
| Comments | A comment explaining what a call does becomes unnecessary once the name says it. |
| Long Method | Indirectly: when no honest name fits, the method is doing too much — the naming struggle triggers a split. |
| Speculative Generality | Vague "future-proof" names like processData() get replaced by names describing what the code does today. |
Quick Revision Box 📦
+--------------------------------------------------------------+
| RENAME METHOD — CHEAT SHEET |
+--------------------------------------------------------------+
| Story : Repaint "General Store" board -> "Mobile Recharge" |
| Goal : Name alone explains what the method does |
| 2nd ed. : Part of "Change Function Declaration" (Fowler) |
| |
| SAFE STEPS (gradual rename): |
| 1. Check super/sub classes share the signature |
| 2. New method with good name; move body into it |
| 3. Old method DELEGATES to new one |
| 4. Test. Migrate callers one by one. Test again. |
| 5. Delete old method (or [Obsolete] it if public) |
| |
| IDE : VS Code F2 | JetBrains Shift+F6 | VS Ctrl+R,R |
| Trap : reflection / strings hold old name — text-search! |
| Rule : behaviour identical; name change ONLY |
+--------------------------------------------------------------+Practice Exercise ✍️
Here is a small library-management class with dishonest names. Your job:
class Library {
// 1. What should this be called?
proc(memberId: string): number {
const loans = this.loanRepo.findOverdue(memberId);
return loans.reduce((sum, l) => sum + l.daysLate * 2, 0); // Rs.2 per day
}
// 2. And this one?
go(memberId: string, bookId: string): void {
const book = this.bookRepo.find(bookId);
book.borrowedBy = memberId;
book.dueDate = addDays(new Date(), 14);
this.bookRepo.save(book);
}
// 3. And this? (Careful — does ONE honest name even exist?)
check(memberId: string): boolean {
const fine = this.proc(memberId);
if (fine > 100) {
this.memberRepo.block(memberId); // side effect!
}
return fine <= 100;
}
}Tasks:
- Rename
procandgousing the gradual rename: write the new method, make the old one delegate, then show the final state. Suggested direction: what does each one really do? (Hint for 1: it computes an overdue fine. Hint for 2: it issues a book to a member.) - For
check, first try to write one honest name. You will find it needs an "and" — checkFineAndMaybeBlockMember — which is a red flag. Split it into a pure question and a separate action. (This is exactly the next-door refactoring, Separate Query from Modifier.) - Pretend
procis public and used by another team. Show how you would deprecate it in TypeScript (JSDoc@deprecated) instead of deleting it. - List two places in this class where a plain-text search for the old name would still be necessary after an IDE rename. (Think: route strings? serialized field names? test descriptions?)
- Sketch (on paper) the three-state diagram from Figure 7 for your
procrename, and mark where your codebase would sit after task 3.
If your final caller code reads like simple English sentences — calculate the overdue fine, issue the book, is the member in good standing — you have repainted every board correctly. Sharma uncle would be proud, and Ravi would finally get his soap from the right shop.
Frequently asked questions
- Is renaming a method really a refactoring? It feels too small.
- Yes, it is one of the most valuable refactorings of all. A refactoring is any change that improves the design without changing behaviour, and a truthful name improves the design of every single line that calls the method. Martin Fowler treats naming so seriously that in the 2nd edition of Refactoring, renaming is part of a core refactoring called Change Function Declaration.
- What makes a method name good?
- A good name says what the caller gets or what happens, in plain words — usually a verb phrase like calculateMonthlyFee or sendWelcomeEmail. A simple trick from Fowler: write a comment describing what the method does, then turn that comment into the name. If you cannot find one clear name, the method is probably doing two jobs and needs to be split first.
- Why not just use find-and-replace to rename?
- Plain text find-and-replace is dangerous. It can hit other methods with the same name, words inside strings and comments, and unrelated code. IDE rename (F2 in VS Code, Shift+F6 in JetBrains IDEs) understands the code structure, so it renames only the real callers of that exact method. For public APIs, the safest path is the gradual rename: new method, old method delegates, migrate callers, delete old.
- What about callers the compiler cannot see, like reflection or JSON mappings?
- This is the biggest trap. Reflection, serialization, ORM mappings, RPC contracts, and string-based lookups refer to the method by its name as text, so the compiler will not warn you when the name changes. After any rename, also search the whole project for the old name as a plain string to catch these hidden callers.
- Should I rename a public method that other teams or packages already use?
- Not in one shot. Removing a public name breaks everyone who depends on it. Instead, add the new well-named method, make the old one delegate to it, and mark the old one as deprecated (for example with [Obsolete] in C#). Give consumers time to migrate, then remove the old name in a later major version.
Further reading
Related Lessons
Add Parameter: One More Detail on the Tiffin Order Slip
Add Parameter explained simply — how to give a method a new piece of information it now needs, why an explicit parameter beats hidden global state, how to do it safely with overloads, and when to stop before the parameter list grows too long.
Remove Parameter: Delete the 'Telegram Address' Field from the School Form
Remove Parameter explained simply — how to safely delete a parameter the method no longer uses, why dead parameters mislead readers and burden every caller, and the checks (interfaces, overrides, reflection) you must do before deleting.
Separate Query from Modifier: Asking Your Bill Must Not Add Chai to It
Separate Query from Modifier explained simply — split a method that both answers a question and changes state into a pure query and a separate command, following Bertrand Meyer's Command-Query Separation principle: asking a question should not change the answer.
Extract Method: Turn One Giant Function into Small Named Helpers
Learn Extract Method step by step. Pull a messy block out of a long function, give it a clear name, and make your code read like a clean to-do list.