skip to content

Smells & Refactoring

Recognising when code has degraded and improving it without changing behavior: the smell catalogue, the refactoring moves, technical debt, and surviving in legacy code. Interviewers use it to check that you can improve a codebase incrementally rather than demanding a rewrite.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

page 1 of 2

In software engineering, what is the "Boy Scout Rule" (also called the campsite rule), and what does following it look like in an ordinary day-to-day code change?

level: juniorimportance: must knowfreq 55%

answer

  1. Leave the campground cleaner
  2. Uncle Bob, Clean Code
  3. Cleanup amortized into normal work
  4. Touch-frequency targets the hot files
  5. Separate commit from the behaviour change

basics

~20 s

Always leave code a little better than you found it. Whenever you touch a file, make one small safe improvement — a clearer name, a deleted unused line, an extracted helper — so quality slowly rises instead of decaying.

solid answer

~50 s

The Boy Scout Rule, popularized by Robert C. Martin in *Clean Code*, adapts the scouting maxim "leave the campground cleaner than you found it": every time you open a file to do real work, make a small, safe improvement before you leave. Typical moves are renaming a misleading variable, deleting dead code or a stale comment, extracting a well-named function from a long one, replacing a magic number with a named constant, or adding a missing test for the path you just touched. The point is economics: cleanup is amortized across normal work instead of requiring a separately-funded "quality sprint" that never gets scheduled. The rule is deliberately bounded — improvements should be behaviour-preserving refactorings, verifiable by existing tests, and small enough that a reviewer can still see the actual change. It counteracts entropy: without it, every edit adds a little mess and none removes any.

code

text · 9 lines
text
# One fix, two commits — the reviewer can read each on its own.

commit 1 (refactor, no behaviour change):
  - rename `d` -> `daysSinceLastLogin`
  - extract `isDormant(user)` from the 40-line `process()`
  - delete unused helper `oldFormat()`

commit 2 (the actual fix):
  - isDormant(): use `>= 90` instead of `> 90`   # off-by-one bug

go deeper

for a junior

State the rule and give two concrete cheap examples (rename a confusing variable, delete dead code). Mention you'd keep it small and run the tests.

for a middle

Add the discipline: behaviour-preserving only, separate commits, don't inflate the diff, add a test if the file has none, let a formatter own style so review stays about meaning.

for a senior

Frame it economically — cleanup amortized into funded work, automatically targeting the highest-churn files — and name the boundary conditions and failure modes (merge conflicts, blame damage, scope creep, frozen/hotfix code).

for a principal

Position it on the spectrum from opportunistic tidying to Strangler Fig migration, and talk about making it systemic: automated formatting, ratcheting quality gates on changed lines only, hotspot analysis to direct attention, and review norms that reward small cleanups.

## The rule > **"Always leave the code cleaner than you found it."** The phrase comes from the Boy Scouts of America's campsite guidance ("leave the campground cleaner than you found it") and was applied to software by **Robert C. Martin ("Uncle Bob")** in *Clean Code* (2008). It is also called the **campsite rule**. The operational form: *whenever you open a file for any reason — a bug fix, a feature, even a read-through while debugging — make at least one small improvement to it before you close it.* ## Terms used here - **Refactoring** — changing the internal structure of code **without changing its externally observable behaviour**. Renaming a variable is a refactoring; adding a validation check is not (it changes behaviour). - **Code smell** — a surface symptom that usually indicates a deeper design problem: a 400-line function, a variable named `tmp2`, duplicated blocks, a comment explaining what confusing code does. - **Technical debt** — the accumulated cost of shortcuts and decay; like financial debt, you pay "interest" as extra effort on every future change. - **Entropy / code rot** — the tendency of a codebase to get messier over time simply because many people make many local edits under time pressure. - **Diff / pull request (PR)** — the unit a reviewer sees: the set of lines you changed. Cleanup that inflates the diff makes review harder, which is the rule's main tension. ## Why it exists (the economic argument) Big cleanups need to be *scheduled*, *funded*, and *justified to a stakeholder who sees no user-visible benefit*. In practice they get deferred forever. The Boy Scout Rule sidesteps that by making cleanup a **side effect of work you were already doing and already paid for**. Two reinforcing properties: 1. **The code you touch is the code that matters.** Files change with a power-law distribution: a small fraction of files absorb most edits. Opportunistic cleanup automatically concentrates effort exactly there, because those are the files you keep opening. Untouched files stay ugly, but nobody pays interest on them. 2. **You already have the context.** The cheapest moment to fix a bad name is the moment you have just finished figuring out what it actually means. A week later that understanding is gone. ## What counts as a "clean-up" Cheap, low-risk, high-signal moves — roughly in ascending order of risk: | Move | Risk | Example | |---|---|---| | Delete dead code / commented-out block | very low | remove an unused private helper | | Delete or fix a stale/lying comment | very low | comment says "retries 3x", code retries 5 | | Rename a local variable/parameter | low (tool-assisted) | `d` → `daysSinceLastLogin` | | Replace magic value with named constant | low | `86400` → `SECONDS_PER_DAY` | | Extract a named function from a long one | low–medium | pull the 12-line validation block out | | Add a missing test for the path you touched | low, high value | characterization test around the bug | | Simplify a conditional / remove nesting | medium | early `return` instead of nested `if` | | Rename a public/exported symbol | **high** | ripples to every caller — usually out of scope | The rule is *not* a licence to redesign the module. "A little better" is literal. ## The built-in constraints The rule only works when three conditions hold, and a good answer names them: 1. **Behaviour preservation.** Cleanups must be refactorings, not silent behaviour changes. If you "fix" something while tidying, that is a separate, described change. 2. **A safety net.** Tests (or at minimum a type checker plus automated refactoring tools) must be able to catch you. In a file with no tests, the safest "cleanup" is often *adding a test*, not restructuring. 3. **Reviewability.** The functional change must stay visible. Standard practice: put cleanup in **separate commits** (or a separate PR) from the behavioural change, so a reviewer — and later a bisect or a blame — can separate "what changed" from "what moved". ## The failure modes - **Scope creep**: a two-line fix arrives as a 900-line diff. Reviewers rubber-stamp it, and a real bug hides in the noise. - **Blame/history damage**: mass reformatting destroys the usefulness of line-level history unless your tooling can ignore specific commits. - **Merge conflicts**: gratuitous restructuring of a file that three other people have open costs the team more than the mess did. - **Bikeshedding**: turning every review into a style argument. Style should be enforced by an auto-formatter, not by humans; the rule should be spent on *meaning*, not on brace placement. ## Relationship to neighbouring ideas - **Broken windows theory** (Hunt & Thomas, *The Pragmatic Programmer*): visible neglect invites more neglect; one unrepaired mess signals that mess is acceptable. The Boy Scout Rule is the counter-force — fix the broken window while you're there. - **Opportunistic refactoring** (Martin Fowler): the same idea framed as "refactor when you're near the code anyway", contrasted with scheduled refactoring projects. - **Continuous small improvement vs. big-bang rewrite**: the rule is the smallest granularity on that spectrum; larger interventions (Strangler Fig migrations, branch-by-abstraction) are what you escalate to when opportunistic cleanup provably cannot reach the problem. ## How to answer in an interview State the rule, give two or three *concrete* cheap cleanups, then immediately show judgement by naming the boundary: separate commits, behaviour-preserving only, don't balloon the diff, and skip it in code that is frozen, being deleted, or on a hotfix path.

  • Should the cleanup go in the same pull request as the bug fix?
    Ideally the same PR but in separate commits, so the reviewer can read the behavioural change alone; if the cleanup grows past roughly the size of the fix, split it into its own PR and land it first.
  • What do you do when the file you must touch has no tests at all?
    Make adding a characterization test — a test that pins down the current behaviour, right or wrong — your Boy Scout improvement. Structural cleanup without a safety net is a gamble, and the test is the highest-value thing that file is missing.
  • Doesn't reformatting a file destroy git blame?
    Yes, which is why formatting should be automated and applied repo-wide once, then the bulk-format commit added to an ignore-revs list so blame skips it. Ad-hoc reformatting inside a feature PR is a smell, not Boy Scouting.

Like tidying the kitchen as you cook: you wipe the counter and put away the one pan you used, every time. Nobody has to schedule a deep-clean day, and the kitchen never reaches the state where a deep clean is needed.

saying these in an interview costs you the question

  • Treating the rule as permission to rewrite or redesign a module inside a bug-fix PR
  • Mixing behaviour changes into "just cleanup" commits, so a reviewer can't tell what actually changed
  • Restructuring untested legacy code without first adding a characterization test
  • Ad-hoc reformatting that wrecks line-level history and creates merge conflicts
  • Claiming the rule means "the code must be perfect before you leave" rather than "a little better"
  • Spending the cleanup budget on style nits that an auto-formatter should own

context

open as a page

In Michael Feathers' book "Working Effectively with Legacy Code", how is "legacy code" defined, and what is the "Legacy Code Dilemma" that follows from that definition?

level: juniorimportance: must knowfreq 60%

basics

~20 s

Feathers defines legacy code as code without tests, regardless of its age or quality. The dilemma: to change code safely you want tests first, but to make the code testable you usually have to change it first.

open as a page

What does it mean that refactoring is "behavior-preserving", and which kinds of code changes are therefore NOT refactorings?

level: juniorimportance: must knowfreq 82%

basics

~20 s

Refactoring changes how code is structured, never what it does. Callers see the same observable results before and after. Adding a feature, fixing a bug, or changing output is not refactoring — it is a behavior change, done separately.

open as a page

What is the Extract Function refactoring (also called Extract Method), what are its mechanical steps, and what signals tell you a fragment is worth extracting?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Take a chunk of code inside a function, move it into a new function whose name says what it does, and call that new function from the original spot. Behaviour stays identical; only the structure changes.

open as a page

Describe the red-green-refactor cycle of test-driven development, and explain exactly what work belongs in each of its three steps.

level: juniorimportance: must knowfreq 74%

basics

~20 s

Red: write one small failing test for behavior you want. Green: write the simplest code that makes it pass. Refactor: clean up the code and the test while all tests stay passing. Then repeat with the next small behavior.

open as a page

What is a "code smell" in the Fowler/Beck sense, and how would you describe the three classic size-related smells: Duplicated Code, Long Method, and Long Parameter List?

level: juniorimportance: must knowfreq 82%

basics

~20 s

A code smell is a surface hint that code may need restructuring — it is not a bug. Duplicated Code = the same logic in several places. Long Method = one function doing too much. Long Parameter List = a call taking too many arguments.

open as a page

What is the "technical debt" metaphor introduced by Ward Cunningham, and what do "principal" and "interest" mean in it?

level: juniorimportance: must knowfreq 72%

basics

~20 s

It compares design shortcuts to borrowing money. The principal is the one-time effort to fix the shortcut properly; the interest is the extra effort every future change costs while the shortcut remains. Shipping fast borrows; refactoring repays.

open as a page

You need a one-line bug fix in a 600-line file full of dead code, misleading names and no tests. How do you honour "leave it cleaner than you found it" without making the change risky to ship or painful to review?

level: middleimportance: must knowfreq 50%

basics

~20 s

Fix the bug first in its own small commit. Then add one or two safe, behaviour-preserving cleanups — delete dead code, rename a local, add a test — as separate commits. Leave the rest; note it, don't do it all.

open as a page

What is a characterization test, how do you write one for code you do not understand, and what should you do when it reveals behaviour that looks like a bug?

level: middleimportance: must knowfreq 58%

basics

~20 s

A characterization test pins down what code actually does today, not what it should do. You call the code, assert a deliberately wrong value, read the failure message to learn the real value, and change the assertion to match. If the real behaviour looks like a bug, you still record it, then ask before changing it.

open as a page

Explain the refactorings "Replace Nested Conditional with Guard Clauses" and "Decompose Conditional". How do they differ, and when do you reach for each?

level: middleimportance: must knowfreq 62%

basics

~20 s

Guard clauses flatten deeply nested if/else by returning early for the unusual or invalid cases first, leaving the normal path unindented at the end. Decompose Conditional instead replaces a complicated condition and its branch bodies with well-named function calls.

open as a page

Explain the Feature Envy and Inappropriate Intimacy code smells: how do you recognise each, how do they differ, and what refactorings address them?

level: middleimportance: must knowfreq 71%

basics

~20 s

Feature Envy: a function is more interested in another object's data than its own — it keeps calling that object's getters. Inappropriate Intimacy: two classes know too much about each other's internals. Fix by moving behaviour to the data (Move Function) or separating the classes.

open as a page

Explain Martin Fowler's technical debt quadrant (deliberate vs inadvertent, prudent vs reckless) and give an example of each of the four cells.

level: middleimportance: must knowfreq 58%

basics

~10 s

Fowler classifies debt on two axes: was it chosen on purpose (deliberate) or discovered later (inadvertent), and was it a sensible call (prudent) or careless (reckless). The four combinations need very different responses.

open as a page

When is a stream of small, continuous in-place improvements the right strategy for a decaying system, and when do you need a larger planned effort such as a Strangler Fig migration or a full rewrite? How do you decide?

level: seniorimportance: must knowfreq 45%

basics

~20 s

Incremental cleanup works when the architecture is sound and the pain is spread across code you touch often. You need a bigger planned effort when the problem is structural — wrong boundaries, dead platform — so no local edit can reach it.

open as a page

In legacy-code refactoring, what is a "seam" and its "enabling point", and what are the main kinds of seam available (object, link, and preprocessing/build seams)?

level: seniorimportance: must knowfreq 50%

basics

~20 s

A seam is a place where you can change what code does without editing that code in that place. Its enabling point is where you make the choice (a constructor argument, a build configuration, a linker path). Seams let you swap real collaborators for fakes so untestable code becomes testable.

open as a page

You must restructure an important module that has no automated tests. How do you make that refactoring safe? Explain characterization tests and seams.

level: seniorimportance: must knowfreq 62%

basics

~20 s

Don't refactor blind. First pin the current behavior with characterization tests — tests that record what the code actually does today, bugs included. To get the code under test, introduce seams: small, low-risk points where you can substitute a dependency. Then refactor in small steps, running the tests each time.

open as a page

Contrast the Divergent Change and Shotgun Surgery code smells: what causes each, how do you tell them apart, and which refactorings resolve them?

level: seniorimportance: must knowfreq 66%

basics

~20 s

Divergent Change: one module keeps changing for many unrelated reasons — it does too much. Shotgun Surgery: one change forces small edits across many modules — a responsibility is scattered. They are opposites: split the module, or gather the scattered pieces.

open as a page

What strategies exist for paying down technical debt, and how do you choose between opportunistic refactoring, incremental replacement, and a big-bang rewrite?

level: seniorimportance: must knowfreq 52%

basics

~20 s

Small debt: clean up as you pass through the code, leaving it better than you found it. Medium debt: replace it piece by piece behind a stable interface while the system keeps running. Full rewrites are a last resort - slow, risky, and they freeze delivery.

open as a page

What is the "broken windows" theory applied to software quality, and what concrete signals would tell you a codebase is suffering from it?

level: middleimportance: should knowfreq 32%

basics

~20 s

The idea that visible neglect invites more neglect: one unfixed mess signals that mess is acceptable, so people stop caring and decay accelerates. Signals include ignored warnings, disabled tests, copy-pasted hacks and TODOs nobody ever removes.

open as a page

Explain the Sprout Method, Sprout Class, Wrap Method and Wrap Class techniques for adding behaviour to untested legacy code, and when you would choose each.

level: middleimportance: should knowfreq 45%

basics

~20 s

All four let you add new, tested code without untangling the old code. Sprout puts the new logic in a new method or class and calls it from one place in the old code. Wrap renames the old method and puts a new method in its place that calls both, or puts the old object behind a decorator that adds behaviour.

open as a page

What is the "Introduce Parameter Object" refactoring, what smell does it address, and what does it enable beyond a shorter parameter list?

level: middleimportance: should knowfreq 48%

basics

~10 s

When the same group of arguments keeps travelling together through many functions, bundle them into one small object and pass that instead. The parameter lists shrink and the group finally has a name.

open as a page

What is the "Replace Temp with Query" refactoring, what does it unlock, and when is it the wrong move?

level: middleimportance: should knowfreq 42%

basics

~20 s

Replace a local variable holding the result of an expression with a small function that computes that expression, then call the function wherever the variable was used. Fewer locals makes the surrounding code easier to extract into other functions.

open as a page

Explain Kent Beck's "two hats" rule for refactoring, and describe what you should do when a refactoring reveals a genuine bug in the code you are restructuring.

level: middleimportance: should knowfreq 44%

basics

~20 s

You wear one of two hats: adding functionality (new tests, new behavior) or refactoring (structure only, no behavior change, no new tests). Swap deliberately, never wear both at once. If a refactoring uncovers a bug, note it, finish or revert the structural step, then switch to the functionality hat to fix it.

open as a page

What are the Data Clumps and Primitive Obsession code smells, how are they related, and what refactorings remove them?

level: middleimportance: should knowfreq 63%

basics

~20 s

Data Clumps: the same group of values keeps travelling together (e.g. street, city, zip). Primitive Obsession: using built-in types like string or int for domain ideas such as money or email. Both are fixed by creating a small named type.

open as a page

Give concrete situations where you should deliberately NOT tidy code you're touching, and leave it exactly as you found it.

level: seniorimportance: should knowfreq 28%

basics

~20 s

During an urgent production hotfix, in code about to be deleted or replaced, in files with big in-flight branches, in frozen or audited code, in areas you don't understand, and when the "cleanup" would actually change behaviour.

open as a page

Walk through Michael Feathers' Legacy Code Change Algorithm and explain how effect sketching and pinch points help you decide where to put tests.

level: seniorimportance: should knowfreq 42%

basics

~20 s

The algorithm is: identify change points, find test points, break dependencies, write tests, then make your change and refactor. To find test points you sketch which values a change can affect, then look for a narrow place all those effects flow through — a pinch point — and test there.

open as a page

When should you apply "Replace Conditional with Polymorphism", how do you do it in safe steps, and when is it the wrong call?

level: seniorimportance: should knowfreq 55%

basics

~20 s

When the same switch on a type code appears in several places, create one subclass (or strategy object) per case, move each branch's body into the matching subclass, and let the language dispatch. Adding a new case then means adding a class instead of editing every switch.

open as a page

When is incremental refactoring the right choice versus a full rewrite, and how do the strangler fig pattern and branch by abstraction let you restructure a running system without a big-bang cutover?

level: seniorimportance: should knowfreq 56%

basics

~20 s

Prefer incremental refactoring: it keeps the system shippable and delivers value continuously. Rewrite only when the platform itself is untenable. Strangler fig routes traffic feature-by-feature from old to new until the old is unused; branch by abstraction puts an interface in front of a component so old and new implementations can coexist behind a toggle.

open as a page

What is the Message Chains code smell, how does it relate to the Law of Demeter, and why can "fixing" it with Hide Delegate produce the Middle Man smell?

level: seniorimportance: should knowfreq 54%

basics

~20 s

A message chain is code like a.getB().getC().getD() — the caller navigates a long path through objects. It breaks the Law of Demeter ("only talk to your immediate friends"). Hiding each hop behind a delegating method can leave classes that only forward calls: Middle Man.

open as a page

How would you make technical debt visible and decide which items to pay down first?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Write debt down where the team and stakeholders can see it, with the fix cost and the pain it causes. Then fix first the items in code you change often, because those cost you repeatedly.

open as a page

How do you budget refactoring against feature delivery and justify it to non-technical stakeholders?

level: principalimportance: should knowfreq 40%

basics

~10 s

Fold most cleanup into feature estimates instead of asking permission for it, keep a small explicit allocation for larger items, and argue in terms of delivery speed and risk rather than code beauty.

open as a page

showing 1–30 of 35