Testing & Code Quality

[Code Smell #5] Shotgun Surgery vs Divergent Change

Shotgun Surgery is a smell where one change is scattered across multiple files, while Divergent Change is its mirror image: one file changes for multiple reasons. This article explains how to measure and distinguish these opposite smells using commit history.

6 min read
Cover image for [Code Smell #5] Shotgun Surgery vs Divergent Change

“Why is adding just one payment method taking so long?”

This continues from the previous article Code Smell #4.

This is a common question from product planning. It is also a difficult one for developers to answer.

Because there are actually twelve places to fix: adding an enum case, updating five switches, the icon mapping table, string constants, analytics event names, server-parameter conversion, and test fixtures.

Each change is only two or three lines, and none is difficult. The difficult part is finding all twelve places without missing one.

Martin Fowler lists a name for this situation in 『Refactoring』: Shotgun Surgery (Original by the author).

Why the Name Fits

When a shotgun fires, its pellets spread widely. The term means that one change appears as small modifications scattered throughout the codebase.

Each wound is shallow, but there are many of them, and missing even one causes problems.

The real problem appears when you miss one. Fix eleven of twelve places and deploy, and the remaining one may surface only under a specific condition.

The payment screen works, but the receipt screen shows “Unknown payment method.” The cost of Shotgun Surgery is not editing time but the probability of omission.

There Is a Mirror Image

The same list contains the opposite smell: Divergent Change (called “Tangled Change” in some translations).

  • Divergent Change: one module changes frequently for several different reasons. Change the database, and you edit this file; change the payment rules, and you edit this file again.
  • Shotgun Surgery: a change for one reason affects multiple modules (Original Shotgun Surgery article).

Draw their relationship, and the axes are reversed.

Divergent Change Shotgun Surgery
Concerns and code Multiple concerns in one place One concern in multiple places
Symptom This file keeps changing This change keeps spreading
Remedy Separate Consolidate

The opposite remedies are the key point. Discussions of code smells often lead to “split it up,” but the answer to Shotgun Surgery is consolidation.

If you identify the problem incorrectly, the remedy moves in exactly the opposite direction.

There is one axis that separates them: the reason for change.

This is why the Single Responsibility Principle is defined not as “should do only one thing,” but as “should have one reason to change.” The number of tasks varies by observer, but reasons for change can be verified in actual commit history.

Code-smell comparison diagram contrasting the change directions of Divergent Change and Shotgun Surgery
Separate Divergent Change; consolidate Shotgun Surgery

Measuring It with Commit History

You can use data instead of intuition. Shotgun Surgery appears as files that always change together.

This is called change coupling or logical coupling.

importThe key is that this coupling is invisible in relationships. If two files never reference each other but always appear in the same commit, an undocumented rule is binding them together.

Count file pairs that changed together in recent commits to find candidates.

git log --format='%H' --since=6.months.ago | while read c; do
  git show --format= --name-only "$c" | grep '\.swift$' | sort | \
    awk 'NR==FNR{a[NR]=$0;n=NR} END{for(i=1;i<n;i++)for(j=i+1;j<=n;j++)print a[i]" + "a[j]}'
done | sort | uniq -c | sort -rn | head -20

Then check whether the pairs at the top really represent code that belongs together.

Of course, some pairs naturally change together, such as an implementation and its test file.

The remaining pairs after filtering are what you should investigate.

How to Consolidate

Consolidating scattered conditional branches into a single type is the most common form.

// Before: knowledge of payment methods switch is scattered throughout the project
func iconName(for method: PaymentMethod) -> String {
    switch method {
    case .card: return "creditcard"
    case .transfer: return "building.columns"
    }
}
func displayName(for method: PaymentMethod) -> String { ... }
func serverCode(for method: PaymentMethod) -> String { ... }

If these three functions are in different files, every new payment method requires finding three places. Consolidating them into one type reduces that to one place.

// After: look in only one place
extension PaymentMethod {
    var iconName: String { ... }
    var displayName: String { ... }
    var serverCode: String { ... }
}

Let the compiler detect omissions. In Swift, switch must handle every case.

Adding an enum case makes every default without a switch produce a compile error. It does not eliminate Shotgun Surgery, but moves omissions from runtime to compile time.

Since the real cost mentioned earlier was the probability of omission, this alone changes the situation.

That is why you should not habitually add switch to default: break that handle enums. It cuts the safety net yourself.

Turn string keys into types. If analytics event names, user-default keys, and notification names are scattered as string literals, the compiler cannot help.

Collecting them as constants in one place or wrapping them in a type creates a single point for additions and changes.

Make configuration data. If each payment method needs an icon, name, and code, you can define them as one array of structs instead of code.

Adding a new method then means adding one item to the array.

If consolidation is difficult, leave a marker. Some things cannot be physically consolidated.

For example, the server and client may need to share the same rule.

In that case, add comments at each point referring to the others, or add a test that fails when values diverge. If you must rely on human memory, at least write down where to remember.

Concept image of refactoring branches scattered across multiple places into one place
Consolidating scattered rules into one type leaves a single point for additions

Between Separating and Consolidating

This connects to the earlier articles. Code-smell remedies generally move in only two directions—separate or consolidate—but pushing either direction too far creates another smell.

  • Separating to fix Divergent Change → Ravioli Code
  • Consolidating to fix Shotgun Surgery → God Object
  • Separating into layers → Lasagna Code

So the goal should not be the size or number of pieces. There is one goal.

Keep things that change together together, and keep things that change separately separate. This is ultimately what cohesion and coupling are saying.

There is a useful question when the answer is unclear: ask yourself about three times, “Why did I most recently change this code?”

If the reason was different each time, it is time to separate; if you changed multiple places for the same reason each time, it is time to consolidate.

Summary

  • Shotgun Surgery is a state where one change is scattered across small edits in multiple files.
  • The real cost is not editing time but the probability of omission.
  • Its opposite, Divergent Change, is a state where one file changes for multiple reasons, and the remedy is also opposite. One is consolidated; the other is separated.
  • Extracting file pairs that always change together from commit history lets you measure candidates.
  • In Swift, exhaustive checking of enums and switch turns omissions into compile errors. Not habitually adding default is how you preserve that safety net.

The next article covers the metaphor that gave all these smells a single name: technical debt.

Ward Cunningham’s original meaning was not “messy code.”

Sources and Verification Criteria

  • Refactoring — Martin Fowler · Original by the author · Verified 2026-08-17 · Basis: Shotgun Surgery and Divergent Change smells and refactoring principles
  • The Shotgun Surgery Problem — Martin Fowler · Original by the author · Verified 2026-08-17 · Basis: examples of Shotgun Surgery in which changes are scattered across modules

Continue reading

Code Smell Series