Shotgun Surgery, Divergent Change, and Parallel Inheritance Hierarchies
What This Concept Is
Three smells that only become visible when you zoom out from one file to the system:
- Divergent Change. One module changes in several unrelated ways for several unrelated reasons. Every kind of change touches the same file; reviewers from different domains collide.
- Shotgun Surgery. One change requires edits in many modules. Every logical change fans out to a dozen scattered edits, any of which is easy to miss.
- Parallel Inheritance Hierarchies. Every time you add a subclass to hierarchy
A, you must also add one to hierarchyB. Two trees grow together, locked in step.
Divergent Change and Shotgun Surgery are Fowler's "opposites" pair. Divergent Change says the module does too many jobs. Shotgun Surgery says one job is spread too thin. Both are failures of change locality; one by concentrating too much, the other by scattering too much.
Parallel Inheritance Hierarchies is the structural signal of a missed abstraction: two taxonomies encode the same variation.
Why It Matters Here
Most module-level smells are local: you can see them by reading one file. These three only show up when you notice what it took to ship the last change. They are the clearest signals that the system's structure does not match the way change actually arrives.
They also connect directly to SRP (Divergent Change is SRP at the file level), OCP (Shotgun Surgery is the sign that extension points are missing), and LSP/composition (Parallel Hierarchies is inheritance doing work composition should be doing).
Concrete Example
Divergent Change -- one file, many reasons:
payments.py:
- methods for Stripe flow
- methods for PayPal flow
- tax calculation
- invoice PDF generation
Every Stripe change, tax-rate change, and PDF-layout change reopens this file. Three unrelated reviewers fight each other.
Shotgun Surgery -- one logical change, many files:
Adding a new "preferred language" field for users edits:
- user_model.py
- user_repository.py
- user_api.py
- user_admin_view.py
- user_email_templates.py
- user_export_csv.py
- user_analytics_event.py
None of these edits is hard; missing one is almost certain. The logical change has no home.
Parallel Inheritance:
class Employee { ... }
class SalariedEmployee extends Employee { ... }
class HourlyEmployee extends Employee { ... }
class EmployeePayCalculator { ... }
class SalariedPayCalculator extends EmployeePayCalculator { ... }
class HourlyPayCalculator extends EmployeePayCalculator { ... }
Adding a Contractor forces matching ContractorPayCalculator. The hierarchies have become one hidden axis of variation encoded twice.
Common Confusion / Misconception
"Divergent Change and Shotgun Surgery are the same thing." No -- they are opposites. Divergent Change is too many reasons to change, one place; Shotgun Surgery is one reason to change, too many places. The refactoring directions are opposite too: Divergent Change calls for splitting a module; Shotgun Surgery calls for pulling scattered behavior into one module.
A second confusion: "Parallel Hierarchies just mean two class trees." The smell is that the trees mirror each other. Two unrelated hierarchies are fine.
How To Use It
These smells are detected from changes, not from static reading:
- After a feature lands, check the PR: did it touch three unrelated concerns in one file? Divergent Change.
- Did it touch the same concern across six files? Shotgun Surgery.
- Watch the commit stream: if every new subclass in tree A demands a matching one in tree B, you have a Parallel Hierarchy.
- The moves are:
- Divergent Change ->
Extract Class/Split Phase. - Shotgun Surgery ->
Move Function/Move Field/Combine Functions into Class. - Parallel Hierarchies -> collapse one hierarchy, use composition, or express the axis once as strategies.
- Divergent Change ->
Check Yourself
- Why are Divergent Change and Shotgun Surgery considered opposite smells?
- Why are these three smells hard to spot by reading a single file?
- Why does Parallel Hierarchies often reveal a missing strategy or component?
Mini Drill or Application
Pick a recent non-trivial change to a codebase you know. Do all four:
- List every file that the change touched.
- Classify the pattern: concentrated (Divergent Change), scattered (Shotgun Surgery), mirrored (Parallel), or local (fine).
- For one of the smells, name the refactor move that would have made the change smaller.
- Describe the one-sentence "home" that the change is missing or should have.