Contrast the Divergent Change and Shotgun Surgery code smells: what causes each, how do you tell them apart, and which refactorings resolve them?
answer
- one module, many reasons = divergent
- one reason, many modules = shotgun
- split apart vs gather together
- SRP: one reason to change
- detect from commit history, not a snapshot
basics
~20 sDivergent 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.
solid answer
~60 sBoth are change-shaped smells; you detect them from the *history* of edits, not from one snapshot. **Divergent Change** — a single module is edited for several unrelated reasons: a new payment provider, a new report format, and a schema change all touch the same class. It signals weak cohesion and a violation of the Single Responsibility Principle ("a module should have one reason to change"). Fix by splitting along change axes: Extract Class, Split Phase, Extract Function then Move Function. **Shotgun Surgery** — one conceptual change requires many small edits scattered across many modules; you always forget one. It signals a responsibility with no home — knowledge duplicated or spread. Fix by gathering: Move Function/Move Field into one module, Inline Class to collapse a class that no longer pulls its weight, Combine Functions into Class/Transform. They are duals: Divergent Change = too many reasons in one place (low cohesion); Shotgun Surgery = one reason spread over too many places (high coupling / poor encapsulation). Over-correcting one produces the other, so the goal is a boundary that matches the real axes of change.
code
pseudocode · 8 lines// Shotgun Surgery generator: adding SHIPPED means editing every switch
switch(status) { case NEW: ...; case PAID: ... } // pricing.file
switch(status) { case NEW: ...; case PAID: ... } // ui.file
switch(status) { case NEW: ...; case PAID: ... } // export.file
// After Replace Conditional with Polymorphism: one new class, one place
interface OrderState { label(); canCancel(); exportCode() }
class Shipped implements OrderState { ... } // add a file, edit nonego deeper
State the two definitions correctly and the direction of the fix: split the over-loaded module; gather the scattered logic.
Add the SRP framing, name concrete refactorings (Extract Class, Move Function, Replace Conditional with Polymorphism), and give the enum-plus-switch example of Shotgun Surgery.
Explain the dual relationship and the over-correction trap, the two diagnostic questions, and detection through commit history and temporal coupling.
Tie boundaries to organisational and domain reality — Common Closure Principle, bounded contexts, Conway's Law — and discuss sequencing large restructurings safely with strangler-style incremental moves and behavioural code analysis to prioritise hotspots.
## The common frame: reasons to change Both smells are defined by *how the code changes over time*, which is why they are hard to see in a single file review and easy to see in version-control history. Robert Martin's formulation of the **Single Responsibility Principle** is the relevant lens: *a module should have one, and only one, reason to change* — where a "reason" means an actor or a policy that can request change (billing rules, a report format, a persistence schema, a UI layout). ## Divergent Change **Definition.** One module is changed in different ways for different reasons. **Symptom in practice.** "Every time we add a payment provider we edit `OrderService`. Every time marketing changes the invoice layout we edit `OrderService`. Every time the schema changes we edit `OrderService`." The commit history shows the same file appearing in unrelated feature branches. **Cause.** Weak cohesion. The module accumulated everything topically related to one *noun* rather than everything that changes together. **Consequences.** - Merge conflicts concentrate in the file; independent teams serialize on it. - Blast radius: a report tweak risks breaking payments because they share state and initialization. - Tests become slow and broad because the class needs everything set up. - Understanding requires holding several unrelated policies in your head. **Refactorings.** *Extract Class* along each change axis; *Split Phase* when the module does sequential stages (parse → compute → render) that change for different reasons; *Extract Function* + *Move Function* to relocate slices; introduce interfaces so variants (a payment provider, a report renderer) become pluggable rather than edited in place. ## Shotgun Surgery **Definition.** A single conceptual change forces many small edits across many different modules. **Symptom in practice.** "Adding one new order status means touching the enum, three switch statements, a validator, a serializer, two UI mappers, a database constraint, and four tests." Commits are wide and shallow — one or two lines each in a dozen files. **Cause.** A responsibility has no owner. The knowledge (a rule, a format, a set of cases) is duplicated or spread across modules, so each copy must be updated. Enum-plus-switch designs are the archetypal generator. **Consequences.** - Omission bugs: one of the twelve sites is missed and behaves as the old system. - No compiler help unless the language enforces exhaustiveness. - Reviewers cannot judge completeness; the change is easy to get 90% right. - Cost of change scales with the number of copies, so the system resists evolution. **Refactorings.** *Move Function* / *Move Field* to pull the scattered logic into one module; *Inline Class* when a class has been hollowed out and only adds a hop; *Combine Functions into Class* or *into Transform* when several functions operate on the same data; *Replace Conditional with Polymorphism* so adding a case means adding one class instead of editing N switches; *Extract Class* when the scattered knowledge is a missing concept. ## Telling them apart Ask two questions: 1. **"For how many different reasons does this file change?"** Many → Divergent Change. 2. **"For this one reason, how many files must change?"** Many → Shotgun Surgery. | | Divergent Change | Shotgun Surgery | |---|---|---| | Shape | one module, many reasons | one reason, many modules | | Underlying weakness | low cohesion | poor encapsulation / high coupling | | Commit signature | same file in unrelated features | wide, thin commits | | Direction of fix | split apart | gather together | ## The dual relationship and the trap They are **opposites**, and over-correcting one creates the other. Aggressively splitting a divergent class into many tiny classes can scatter a single responsibility, producing Shotgun Surgery. Aggressively gathering scattered code into one place to stop Shotgun Surgery can create a god class that changes for every reason — Divergent Change again. The resolution is not "more classes" or "fewer classes" but **aligning module boundaries with the real axes of change**. This is the same idea as: - **Cohesion/coupling**: put things that change together in the same place, keep things that change separately apart (Constantine's principle). - **Common Closure Principle** (package-level SRP): classes that change for the same reasons belong in the same package/module. - **Bounded contexts** in Domain-Driven Design: boundaries follow language and ownership, not data shape. - **Conway's Law**: if two teams keep editing the same module, or one team keeps editing twelve modules for one story, the boundary disagrees with the organisation. ## Detecting them with data Because both are historical, version control is the best detector: - **Change frequency per file** and **number of distinct feature areas touching a file** surface Divergent Change hotspots. - **Temporal/logical coupling** — files that repeatedly change in the same commit without a code dependency — surfaces Shotgun Surgery. Techniques popularised by Adam Tornhill's *behavioural code analysis* build on exactly this. - **Number of files per user-story-sized commit**, trending upward, is a leading indicator. These are heuristics; a shared file touched often may simply be a stable, well-factored hub. Confirm with the two questions above before restructuring.
- How would you find these smells in a large legacy codebase you have never seen?Mine the version-control history rather than reading code. Rank files by change frequency and by the number of distinct feature areas that touch them to find Divergent Change candidates; compute temporal coupling — sets of files that repeatedly change in the same commit without a static dependency — to find Shotgun Surgery. Then confirm each candidate against the two questions (how many reasons per file, how many files per reason) before restructuring.
- Can a single module exhibit both smells at once?Yes. A god class changes for many reasons (Divergent Change) while also being one of a dozen files edited for each individual change (Shotgun Surgery), because its responsibilities are simultaneously over-collected and half-duplicated elsewhere. Such modules are usually attacked by first extracting one change axis at a time, gathering that axis's scattered logic into the new module, and repeating.
Divergent Change is one drawer where you keep cutlery, batteries, and tax receipts — everyone opens it for unrelated errands. Shotgun Surgery is keeping one fork in every room, so changing cutlery means walking the whole house.
saying these in an interview costs you the question
- Swapping the definitions — describing Shotgun Surgery as "one class doing too much"
- Treating "many files changed in a commit" as automatic proof of Shotgun Surgery, ignoring genuinely wide but legitimate changes
- Prescribing "just split the class" for both smells, which turns Divergent Change into Shotgun Surgery
- Equating SRP's "reason to change" with "does only one thing" at the function level, losing the actor/policy meaning
- Claiming these smells are visible from a single snapshot without any history or change context