Skip to main content
CleanCodeMastery

Shotgun Surgery: One Small Change, Ten Offices to Visit

Learn the Shotgun Surgery code smell with an address-change story, simple definitions, TypeScript and C# examples, a clear comparison with Divergent Change, and practice.

27 min read Updated June 11, 2026beginner
shotgun surgerycode smellschange preventersmove methodduplicationtypescript

🏠 The day Meera's family shifted house

Meera's family lived in Indira Nagar, Lucknow. Last month, her father got a transfer, and the family shifted to a new flat in Gomti Nagar. New house, new paint smell, new neighbours. Everyone was happy — until Papa took out a notebook and started writing a list.

"What is that list, Papa?" Meera asked.

"Beta, this is the list of every office where our address is written," he sighed. And he started counting on his fingers:

  1. Aadhaar card — visit the Aadhaar Seva Kendra, take a token, wait two hours.
  2. Bank — fill the KYC form, attach proof, sign in three places.
  3. Meera's school records — write an application to the principal's office.
  4. Gas connection — go to the agency, show the new rent agreement.
  5. Ration card — another office, another queue, another photocopy.
  6. Electricity bill, post office, scooter RC... the list kept growing.

The next two days were a tour of the city. Token machines, photocopies, "come after lunch", "the server is down, come tomorrow". Watch Papa's mood through the journey — the scores tell the whole story:

Figure 1: One address change, ten offices — Papa's two-day pilgrimage of queues and photocopies

One small change in real life — the family lives in a new flat — but ten different offices must each be updated, one by one, by hand. And here is the scary part Papa mentioned at dinner: "If I forget even one office, trouble comes later. Last time, your uncle forgot the bank. His debit card got blocked because the address on file did not match."

Notice the shape of this pain. The change is one. The places to update are many. No office talks to the others. Nobody sends Papa a checklist. Whether every record gets updated depends completely on Papa's memory. And look at the last row of the journey above — even careful Papa missed the scooter RC, and only found out a week later.

Meera, who had just started learning to code, asked the golden question: "Papa, why is there no one office where you change the address once, and it tells all the others?"

Papa smiled. "Beta, the day you build that, you will have fixed a very famous problem."

Software has the exact same disease. Sometimes one tiny change — a new tax rate, a new date format, a renamed status — forces a programmer to hunt through ten files and edit each one by hand. Miss one file, and a bug quietly waits in production, just like uncle's blocked debit card. This smell is called Shotgun Surgery.

🤔 What is this smell?

Shotgun Surgery happens when a single change forces you to make many small edits in many different classes.

The name is vivid on purpose. A rifle makes one clean hole. A shotgun sprays many small pellets across a wide area. When this smell is present, one conceptual change does not land in one clean place — it sprays edits all over the codebase. And each edit is a small "surgery": careful, manual, and easy to get wrong.

Martin Fowler describes it in Refactoring: every time you make a kind of change, you have to make lots of little edits to lots of different classes. When the changes are all over the place, they are hard to find, and it is easy to miss an important one.

The root cause is almost always the same: a concept never got a home. Think about "how we format an address" or "how late fine is calculated". If that idea lives in one class, changing it means one edit. But if it was written inline — copy-pasted, re-typed, slightly varied — at every place that needed it, then the idea has no owner. It is smeared across the system like butter scraped over too much bread. Changing the idea now means finding every smear.

Shotgun Surgery is the second member of the Change Preventers family. Like its sibling Divergent Change, it does not crash your program. It slows your changes. Teams suffering from it start saying things like "oh no, not the address format again" — dread before a simple task is the emotional signature of this smell.

💡

Easy memory trick: Shotgun Surgery = one house shift, ten offices. One reason to change, many classes to edit. The cure is to build "one office that informs all others" — gather the scattered logic into a single owning class.

One more sharp observation from Fowler: with Divergent Change, at least everything is in one place and easy to find — the problem is that it is too much in one place. With Shotgun Surgery, the problem is worse in one way: the pieces are hard to even find. You cannot fix what you cannot locate, and the compiler usually will not help you locate string formats and sprinkled if-conditions.

Here is the entire smell on one revision map:

Figure 2: The Shotgun Surgery mind map — signs, cures, and the one root cause

🔍 How to spot it

Shotgun Surgery is felt before it is seen. Here is the checklist. Tick three or more, and you have it.

  • A "simple" requirement produces a pull request touching 8+ files, each with two or three changed lines.
  • Your team keeps a mental (or written!) checklist: "when X changes, remember to update A, B, C, and that one job in the legacy folder."
  • You have shipped bugs of the form "we updated it everywhere except one place."
  • The same constant, format string, or validation rule appears — slightly differently — in many files.
  • New developers ask "where do I change X?" and the honest answer is "everywhere, a little."
  • Code reviews for small changes take long, because the reviewer must verify nothing was missed — which is much harder than verifying what is present.

As a table for quick revision:

SignWhat you observeWhat it means
Confetti PRsOne ticket, ten files, two lines eachThe concept being changed has no single home
Tribal checklist"Remember to also update..." knowledgeCorrectness depends on human memory
Except-one bugsProduction bug in the one missed spotScattered edits are incomplete by default
Cloned rulesSame validation/format logic in many filesCopy-paste spread the concept everywhere
Slow reviewsReviewer must hunt for missing editsAbsence is harder to review than presence
Grep-driven workYou search the codebase by keyword to make a changeThe type system cannot find the sites for you

A practical detection trick: look at your version control history for commits that always travel together. If files A, D, and K change together in commit after commit, an invisible thread ties them. That thread is a homeless concept — and it is exactly what you should extract and give a home.

You can also make the scatter visible with a simple chart. Take one concept — say "how we write an address" — and count where its code currently lives:

Figure 3: Where does the address concept live today? Everywhere, a little — the signature of a homeless concept

A healthy concept produces a boring pie — one slice, one owner. Five nearly-equal slices means five offices, each holding its own photocopy of your address, each going stale independently.

College corner: Researchers who mine version-control histories call this change coupling (also "logical coupling"): files that have no compile-time dependency on each other, yet keep changing in the same commits. Tools like CodeScene rank file pairs by co-change frequency precisely to surface Shotgun Surgery that no static analyzer can see — because the coupling lives in the concept, not in the imports. If you ever do a software engineering mini-project, mining co-change pairs from a real repo's git log is a genuinely publishable exercise.

⚠️ Why it is a problem

1. Errors of omission. This is the killer. The danger is not the nine edits you make — it is the tenth one you forget. With scattered change-sites, forgetting is the default and completeness needs heroic memory. Papa's uncle forgot the bank; your team will forget LegacyExportJob.

2. Small changes become expensive. A one-line conceptual change becomes an afternoon of hunting, editing, and re-testing ten files. When small improvements cost this much, people stop making small improvements, and the codebase slowly stops getting better.

3. Reviews become weak. A reviewer can check that your ten edits are correct. But how can they check there is no eleventh place you both missed? Reviewing absence is nearly impossible, so the review becomes a hopeful guess.

4. The copies drift apart. Each scattered site gets edited independently over the years. One site rounds the fine up, another rounds down. One formats the address with a comma, another without. Tiny differences pile up into "why does the invoice show a different address than the receipt?" mysteries.

5. Knowledge gets locked in people. The full list of change-sites often exists only in a senior developer's head. When that person goes on leave, the team is stuck — a project risk hiding inside a code smell.

Here is the spray, drawn the way it feels on a Monday morning when the ticket says "small change":

Figure 4: One change, many cuts — every box is a separate file you must remember to open

And here is how the conversation actually goes inside a team living with this smell. Notice that the most important question — "is that all of them?" — has no confident answer:

Figure 5: The hunt for scattered sites — grep finds four, human memory must supply the fifth

College corner: This smell is the textbook case of change amplification — John Ousterhout's term from A Philosophy of Software Design for when a simple change requires code modifications in many places. Note how it pairs with its sibling smell: Divergent Change amplifies the understanding needed for one edit; Shotgun Surgery amplifies the number of edit sites. Both inflate the true cost of a change far beyond the size of the idea behind it. A good architecture is one where the cost of a change is proportional to the size of the idea, not to the age of the codebase.

💻 A real-life code example

Let us code Meera's address problem. A small shop's software keeps customer addresses. Watch how the idea of "how we write an address" was never given a home — every class that needed an address just built the string itself.

// The concept "format an address" is smeared across the system.
 
class CustomerView {
  renderProfile(c: Customer): string {
    return `${c.houseNo}, ${c.street}, ${c.city} - ${c.pincode}`;
  }
}
 
class InvoicePdf {
  billingBlock(c: Customer): string {
    // same idea, typed again, slightly different
    return `${c.houseNo} ${c.street}, ${c.city}-${c.pincode}`;
  }
}
 
class ShippingLabel {
  print(c: Customer): string {
    // and again, with its own little variation
    return `${c.houseNo}, ${c.street}\n${c.city} ${c.pincode}`;
  }
}
 
class SmsSender {
  confirmDelivery(c: Customer): void {
    smsGateway.send(c.phone, `Delivered to ${c.houseNo}, ${c.street}, ${c.city}`);
  }
}
 
// ...and somewhere in a dusty corner:
class LegacyExportJob {
  exportRow(c: Customer): string {
    return [c.houseNo, c.street, c.city, c.pincode].join("|");
  }
}

Now the requirement arrives: "Add the state name to every address, and put the pincode in the standard 'PIN: 226010' style." A one-sentence change. But look at what it costs: you must find and edit CustomerView, InvoicePdf, ShippingLabel, SmsSender, and LegacyExportJob — five files, five careful little surgeries. And did you even remember LegacyExportJob exists? Nothing in the code told you to look there. You found four sites by searching for pincode, but the export job joins fields without that word nearby on the same line. The fifth pellet is already lost.

Notice also the drift: three of the five formats already disagree about commas and dashes. They were not designed to differ — they just drifted, because each site was edited alone over time. That drift is the smell ageing.

🛠️ Cleaning it up, step by step

The cure is the opposite of the cure for Divergent Change: do not split — gather. Give the homeless concept one home, and make every old site delegate to it. The tools are Move Method, Move Field, and sometimes Inline Class.

Step 1 — Name the homeless concept. Ask: "What idea keeps forcing this multi-file sweep?" Here it is clearly "how we present an address". Say the name out loud; it usually sounds like a class begging to exist: Address.

Step 2 — Create (or choose) the owner. Sometimes a suitable class already exists and just lacks the behavior. Here, none exists — so we create one and use Move Field to bring the address data into it:

// One home for the concept.
class Address {
  constructor(
    private houseNo: string,
    private street: string,
    private city: string,
    private state: string,
    private pincode: string
  ) {}
 
  full(): string {
    return `${this.houseNo}, ${this.street}, ${this.city}, ${this.state} - PIN: ${this.pincode}`;
  }
 
  short(): string {
    return `${this.houseNo}, ${this.street}, ${this.city}`;
  }
 
  exportRow(): string {
    return [this.houseNo, this.street, this.city, this.state, this.pincode].join("|");
  }
}

Step 3 — Redirect each old site, one by one. Use Move Method thinking: the formatting behavior moves into Address, and each scattered site becomes a one-line delegation. Convert one site, run the tests, commit. Then the next.

// After: every site delegates to the one owner.
class CustomerView  { renderProfile(c: Customer)  { return c.address.full(); } }
class InvoicePdf    { billingBlock(c: Customer)   { return c.address.full(); } }
class ShippingLabel { print(c: Customer)          { return c.address.full(); } }
class SmsSender     {
  confirmDelivery(c: Customer) {
    smsGateway.send(c.phone, `Delivered to ${c.address.short()}`);
  }
}
class LegacyExportJob { exportRow(c: Customer)    { return c.address.exportRow(); } }

The structure after gathering is worth drawing. One owner in the middle; every old site reduced to a polite one-line visitor:

Figure 6: After the cure — one Address owner, and every consumer delegates instead of rebuilding the format

Step 4 — Let the compiler hunt for you. Here is a lovely bonus. Once Customer holds an Address object instead of four loose strings, every leftover site that still does c.pincode fails to compile. The compiler becomes your checklist. The "did I miss one?" fear — the worst part of this smell — simply disappears, because missed sites announce themselves with red underlines.

Step 5 — Check for over-fragmentation. Sometimes Shotgun Surgery exists because a concept was split into too many tiny classes that always change together. In that case the cure is Inline Class: fold the fragments back into one class so the concept stops being confetti.

The whole journey of the concept — from homeless, to drifting, to safely housed — in one state machine:

Figure 7: The life of a concept — once gathered and guarded by the compiler, the omission bug species goes extinct

Now replay the original requirement: "add state, use the PIN style." One edit, inside the full method of Address. One file, one test run, one easy review. Papa's dream: shift the house, update one office, and that office informs everyone else automatically.

The before-and-after is dramatic enough to chart:

Figure 8: The same one-sentence requirement, measured in files edited — before and after gathering

College corner: What we just built is the single source of truth — the practical face of the DRY principle ("Don't Repeat Yourself", from The Pragmatic Programmer). But note the precise wording of DRY: every piece of knowledge must have a single, authoritative representation. It is about knowledge, not text. Five visually different lines that all encode the same business rule violate DRY even though no two lines are identical — which is why "find duplicate code" tools catch only the easy cases of Shotgun Surgery. Conversely, two identical-looking lines that encode different decisions (a coincidence) should NOT be merged. Gather by meaning, not by appearance.

⚠️

Do not over-gather! If you pull unrelated things into one "common" class just to reduce files, you will build a god class — and unrelated reasons will start hitting it. That is Divergent Change, the mirror smell. Gather only what truly changes together for the same reason.

🧪 The same smell in C# and Python

The C# version of the same disease, in a payroll setting. The rule "how professional tax is computed" was never given a home:

// Before: the tax rule lives in three places.
public class PayslipPrinter
{
    public string Footer(Employee e) =>
        $"Prof. Tax: {(e.GrossPay > 15000 ? 200 : 0)}";
}
 
public class SalaryCalculator
{
    public decimal NetPay(Employee e) =>
        e.GrossPay - (e.GrossPay > 15000 ? 200 : 0) - e.Pf;
}
 
public class ComplianceReport
{
    public decimal TotalProfTax(IEnumerable<Employee> all) =>
        all.Sum(e => e.GrossPay > 15000 ? 200m : 0m);
}

When the state changes the slab — say, tax becomes 208 above 21,000 — three files must change identically. Give the rule one home:

// After: one owner; everyone else delegates.
public static class ProfessionalTax
{
    public static decimal For(Employee e) => e.GrossPay > 15000 ? 200m : 0m;
}
 
public class PayslipPrinter   { public string Footer(Employee e) => $"Prof. Tax: {ProfessionalTax.For(e)}"; }
public class SalaryCalculator { public decimal NetPay(Employee e) => e.GrossPay - ProfessionalTax.For(e) - e.Pf; }
public class ComplianceReport { public decimal TotalProfTax(IEnumerable<Employee> a) => a.Sum(ProfessionalTax.For); }

The next slab change is a single edit in ProfessionalTax. The payslip, the salary, and the report can never disagree again, because they all read from the same rule.

Python projects catch this disease just as easily — usually through f-strings retyped at every print site:

# Before: the late-fine rule retyped in three modules
# library_desk.py
def fine_for(days_late):
    return days_late * 5
 
# fee_counter.py
def receipt_line(days_late):
    return f"Late fine: Rs.{days_late * 5}"
 
# parent_sms.py
def remind(days_late, phone):
    sms.send(phone, f"Pending fine: Rs.{days_late * 5}. Please pay.")
 
 
# After: one home — fine_policy.py
FINE_PER_DAY = 5
 
def fine_for(days_late: int) -> int:
    return days_late * FINE_PER_DAY
 
# Every other module imports fine_for. The next fine change is ONE line.

The language never matters. The question is always the same: when this rule changes, how many files open? If the answer is more than one, the rule is homeless.

⚖️ Divergent Change vs Shotgun Surgery

Here is the comparison that every student of code smells must master. These two smells are perfect mirror images, and choosing the wrong diagnosis leads to the wrong medicine.

QuestionShotgun SurgeryDivergent Change
Shape of the painONE reason, MANY classes to changeONE class, MANY reasons to change
Story versionOne address change, ten offices to visitOne clerk disturbed by every department
What is wrongThe concept is smeared with no single homeThe class is overloaded with unrelated jobs
You notice it whenA "small" change becomes a ten-file huntThe same file appears in every kind of PR
Biggest dangerForgetting one of the scattered sitesBreaking job B while editing job A
The cure directionGather — collect pieces into one homeSplit — break the class apart
Main refactoringsMove Method, Move Field, Inline ClassExtract Class, Move Method
Danger of over-curingOver-gathering creates Divergent ChangeOver-splitting creates Shotgun Surgery
Figure 9: The two Change Preventers are mirror opposites — gather for Shotgun Surgery, split for Divergent Change

The two diagnostic questions, side by side:

  1. For one kind of change, how many classes do I edit? Many → Shotgun Surgery → gather.
  2. For one class, how many kinds of change hit it? Many → Divergent Change → split.

Plot any confusing situation on this map and the diagnosis reads itself off the quadrant:

Figure 10: The diagnosis map — the address case sits firmly in the Shotgun Surgery corner

The address case sits low and to the right: one reason (address presentation), many classes touched — pure Shotgun Surgery. After the cure, the Address class moves to the calm corner: one reason, one class. And the haunted top-right corner? That is a codebase where god classes and scattered rules coexist — both smells at once. It happens, and the treatment order matters: gather the scattered concepts first, then split the god classes, so you always know where things live.

And remember the see-saw warning: the cures pull in opposite directions. Gather too much and you create a god class that everyone edits for every reason (Divergent Change). Split too much and you scatter one concept across confetti classes (Shotgun Surgery). Healthy code sits at the balance point: one responsibility per class, one class per responsibility. Fowler's two smells are simply the two ways of falling off that see-saw.

College corner: In coupling/cohesion terms, Shotgun Surgery is what low cohesion at the system level feels like: the parts of one concept, which belong together (high cohesion demands it), are instead distributed across modules, creating hidden common coupling through shared knowledge rather than shared variables. The compiler sees five independent classes; the business sees one rule. Whenever the business's mental model and the code's module structure disagree, every business-driven change pays a translation tax. Domain-Driven Design people call the fix "aligning the model with the domain" — Meera calls it "one office that informs the others."

🕵️ Where this smell hides in real projects

Practitioners (Fowler's Refactoring, refactoring.guru, the NDepend blog, and countless team retrospectives) report the same hiding spots again and again:

  • Magic values sprinkled by hand. Tax rates, fee amounts, limits, URLs, and format strings typed directly at each use site. The day the value changes, the hunt begins.
  • Cross-cutting concerns without a wrapper. Logging formats, permission checks, audit entries, and error-response shapes written manually at every endpoint. Changing the policy means sweeping every endpoint.
  • Primitive Obsession fallout. When "phone number", "money", or "address" is passed around as raw strings and numbers, every consumer reimplements the formatting and validation. The missing value-object is the missing home.
  • Switch statements on the same enum, everywhere. Five different files each switch on OrderStatus. Add one status, and all five switches need a new case — and the compiler may warn you about none of them.
  • Layered architectures with rigid ceremony. Adding one field can require touching the entity, the DTO, the mapper, the validator, the API contract, and the UI form. Some of this is unavoidable layering; but when each layer adds no judgement of its own, the ceremony is Shotgun Surgery wearing a suit.
  • Microservices sharing a hidden concept. When several services each hard-code the same business rule, a rule change becomes a multi-repo, multi-deploy sweep — the distributed, expensive edition of this smell.

😌 When it is okay to ignore

Honesty time. Not every multi-file change is a disease, and not every scatter is worth consolidating today.

SituationIgnore it?Why
The change is genuinely cross-cutting (a new parameter must pass through real layers)YesSome sweeps are essential structure, not smell; no gathering removes them
The scattered rule changes once in five yearsUsuallyConsolidation costs more than the rare sweep saves
You do not yet understand the concept wellWaitGathering too early builds a wrong abstraction, which becomes its own magnet for trouble
The "scatter" is two places, both obviousProbablyTwo well-known sites are cheap; a new abstraction may not pay rent
The same sweep happens every month and someone missed a site last quarterNo — fix itThe smell is actively producing bugs and dread
Each scattered site disagrees slightly with the othersNo — fix itDrift has started; it only gets worse with time

Rule of thumb: fix the scatter when the change is frequent or the miss is costly. A rare, low-stakes sweep can wait. A monthly sweep that once caused a production bug should be consolidated this sprint — the payoff is immediate and permanent.

💊 Which refactorings cure it

RefactoringWhat it does hereWhen to reach for it
Move MethodMoves the scattered logic into the one class that should own the conceptThe primary gathering tool
Move FieldBrings the related data into the owning class, next to its behaviorWhen the moved methods keep reaching back for data
Inline ClassFolds over-fragmented tiny classes back into oneWhen the scatter came from splitting too aggressively
Extract ClassCreates the missing home when no suitable owner existsWhen the concept (like Address) has no class at all yet
Replace Primitive with ObjectTurns a raw string/number into a concept-owning typeWhen Primitive Obsession caused the scatter

The working rhythm: name the concept, create or choose its owner, move behavior and data in, convert call sites one at a time with tests passing after each, and finish only when the next change of that kind will touch exactly one file.

📦 Quick revision box

+--------------------------------------------------------------+
|              SHOTGUN SURGERY — QUICK REVISION                |
+--------------------------------------------------------------+
| Story    : House shifted once -> Aadhaar, bank, school,      |
|            gas, ration card... ten offices to update         |
| Smell    : ONE change forces edits in MANY classes           |
| Family   : Change Preventers                                 |
| Root     : A concept was never given a single home           |
| Spot it  : Confetti PRs; tribal checklists; "missed one      |
|            place" bugs; cloned rules drifting apart          |
| Costs    : Errors of omission, costly small changes,         |
|            weak reviews, inconsistency, locked knowledge     |
| Cure     : GATHER -> Move Method, Move Field, Inline Class   |
|            (one home; every old site delegates to it)        |
| Opposite : Divergent Change (one class, many reasons)        |
| Memory   : Shotgun = one shot, many pellets -> GATHER        |
| Ignore   : Rare changes; genuine cross-cutting layers        |
+--------------------------------------------------------------+

✏️ Practice exercise

Be the officer who builds "one office that updates all others"!

Exercise 1 — Find the pellets. A school app stores the late-fine rule in several places. List every file you would need to edit if the fine changes from 5 to 10 rupees per day, and identify the homeless concept.

class LibraryDesk {
  fineFor(daysLate: number): number { return daysLate * 5; }
}
class FeeCounter {
  receiptLine(daysLate: number): string { return `Late fine: Rs.${daysLate * 5}`; }
}
class ParentSms {
  remind(daysLate: number, phone: string): void {
    smsGateway.send(phone, `Pending fine: Rs.${daysLate * 5}. Please pay.`);
  }
}
class AnnualReport {
  totalFines(records: { daysLate: number }[]): number {
    return records.reduce((s, r) => s + r.daysLate * 5, 0);
  }
}

Exercise 2 — Build the home. Create a LateFinePolicy class with a single method fineFor(daysLate). Then convert all four classes to delegate to it. After your refactor, the rule change should be a one-line edit.

Exercise 3 — Catch the drift. Suppose ParentSms had quietly used daysLate * 4 because of an old typo. In the scattered version, how long might that bug hide? In the gathered version, can it even exist? Write two sentences explaining why gathering kills this whole bug family.

Exercise 4 — Chart your own scatter. Pick one rule from any project you have (a discount, a date format, a validation regex). Grep for it and draw a pie like Figure 3 of where it lives. If you get more than one slice, write down the name the concept is begging for — that name is your missing class.

Exercise 5 — The diagnosis drill. Shotgun Surgery, Divergent Change, or healthy?

  1. Adding a new report type means editing only ReportFactory.
  2. Changing the GST rate requires edits in 7 files across 3 folders.
  3. AppManager is edited for login changes, billing changes, and UI text changes.
  4. Adding a database column requires changing the entity, the migration, and the form — each with its own real logic.

(Think it through: 1 is healthy — one reason, one place. 2 is Shotgun Surgery — gather the rate into one home. 3 is Divergent Change — split the class. 4 is mostly essential layering — each layer adds its own judgement, so the sweep may be irreducible.)

When you can confidently say "this one needs gathering, that one needs splitting", you have mastered both mirror smells. Next, meet the structural special case of this smell: Parallel Inheritance Hierarchies.

Frequently asked questions

What is Shotgun Surgery in one line?
Shotgun Surgery is when one small conceptual change — like changing an address format — forces you to make many tiny edits scattered across many different classes and files. One reason, many places to cut.
Why is it called Shotgun Surgery?
A shotgun does not make one clean hole; it sprays many small pellets over a wide area. This smell is the same: one change sprays many small edits across the codebase. The 'surgery' part reminds us that each edit is a careful, risky cut.
How is Shotgun Surgery different from Divergent Change?
They are exact opposites. Shotgun Surgery is ONE reason that touches MANY classes — fix it by gathering the scattered code into one home. Divergent Change is ONE class hit by MANY reasons — fix it by splitting the class.
Which refactorings fix Shotgun Surgery?
Move Method and Move Field gather the scattered behavior and data into one owning class. If the scatter came from splitting things into too many tiny classes, Inline Class folds the fragments back together.
Is Shotgun Surgery the same as code duplication?
They are close friends but not the same. Duplicated Code is copies of the same logic; Shotgun Surgery is the pain you feel when changing a concept that has no single home. Duplication is the most common cause, but even non-duplicated, related edits scattered across files count as Shotgun Surgery.

Further reading

Related Lessons