Shotgun surgery is a notorious code smell that strikes fear into the hearts of developers everywhere. The mere mention of it is enough to trigger memories of late nights debugging mysterious breaks caused by a simple one-line change.
But what exactly is shotgun surgery, and how can you identify and eliminate it from your codebase? This comprehensive guide has the answers.
What Is Shotgun Surgery?
The shotgun surgery code smell refers to a situation where making a single change to one part of a codebase necessitates a myriad of small modifications scattered across multiple classes, components, or modules.
When faced with a case of shotgun surgery, making one tweak breaks code in many different places. This is because the code is tightly coupled, needlessly duplicative, and not modularized properly.
Martin Fowler first coined the colorful term “shotgun surgery” in his 1999 book _Refactoring: Improving the Design of Existing Code_. The imagery it evokes is very fitting – having to make incisions across various parts of the codebase to implement a single change.
Key Characteristics of Shotgun Surgery
There are a few telltale signs that point to the shotgun surgery code smell in a codebase:
- Making one small code change causes failures and bugs in multiple places, often seemingly unrelated ones
- High coupling and dependencies between classes or components
- Duplicate code scattered across classes/modules/files
- Difficulty reasoning about and predicting the impact of a change
- Fear of changing legacy code because of potential wide-spread breakage
The more interconnected and duplicative the code, the more susceptible it is to shotgun surgery. This makes the code rigid and fragile in the face of changes.
Common Causes of Shotgun Surgery
There are a few root causes that lead to shotgun surgery cropping up:
- Lack of abstraction– No abstraction over common logic leads to copy-pasted code across the codebase. This duplication then causes shotgun surgery effects when changed.
- Violation of SRP – The single responsibility principle (SRP) is violated, with too much logic crammed into single functions/classes/modules. Teasing out one responsibility requires hacking at multiple places.
- Laziness– It takes longer to figure out the right abstractions. Copying and pasting code is quicker in the short term, but leads to technical debt and shotgun surgery down the road.
- Lack of insight– The original programmers lacked the insight to foresee how code should be properly decomposed. What seemed expedient at the time leads to shotgun surgery as code evolves.
Identifying the root cause helps guide refactoring efforts to eliminate shotgun surgery.
Real World Examples of Shotgun Surgery
To get a better idea of how shotgun surgery presents itself, let’s look at some real code examples.
Example 1 – Duplicated Logic
Imagine we have an online banking application with a `SavingsAccount` class containing methods like `withdraw` and `transfer`. Each of these methods contains the following logic:
// Inside SavingsAccount class
function withdraw(amount) {
if (this.balance < 1000) {
throw new Error("Balance cannot be below $1000!");
}
// ... withdrawal logic
}
function transfer(amount, otherAccount) {
if (this.balance < 1000) {
throw new Error("Balance cannot be below $1000!");
}
// ... transfer logic
}
The `balance` threshold check is duplicated across methods. If we ever want to change the threshold, we have to modify multiple methods – a clear shotgun surgery situation!
Example 2 – Non-DRY CSS
Consider CSS code structured like:
.btn-primary {
background-color: #ec407a;
}
.menu-primary {
background-color: #ec407a;
}
.modal-primary {
background-color: #ec407a;
}
Here the background color `#ec407a` is repeated across multiple CSS selectors. If we ever need to change the primary brand color, we’ll need to modify several rules – another example of shotgun surgery!
Example 3 – God Classes and Spaghetti Code
Imagine a legacy PHP application with no abstraction and classes that are thousands of lines long, chock full of duplicated logic and hard-coded dependencies. Changing one piece results in breakages throughout the tangled dependency web – the ultimate nightmare of shotgun surgery.
While simplistic, these examples demonstrate the essence of shotgun surgery clearly. Now let’s learn how to put an end to it!
Refactoring to Eliminate Shotgun Surgery
Refactoring shotgun surgery may seem intimidating, but following a systematic approach can tame even the wildest of codebases. Here are the steps:
1. Identify Duplicated Logic
Scan through the codebase and highlight any duplicated logic. This is often the root of shotgun surgery, so it’s an important first step.
For example, in our first example, we would identify the duplicated `balance` threshold check in multiple methods of the `SavingsAccount` class.
2. Extract the Logic into a Shared Method/Component/Module
Next, extract the identified duplication into a shared function, component, service, module, or method.
For our duplicated threshold check, we can extract it into a `validateMinimumBalance()` method on `SavingsAccount`:
// Inside SavingsAccount class
function validateMinimumBalance() {
if (this.balance < 1000) {
throw new Error("Balance cannot be below $1000!");
}
}
function withdraw(amount) {
this.validateMinimumBalance();
// ... withdrawal logic
}
function transfer(amount, otherAccount) {
this.validateMinimumBalance();
// ... transfer logic
}
Now there is a single source of truth for this logic.
3. Replace Duplicated Logic with Calls to Extracted Code
With the logic extracted, replace the original duplicate code snippets with calls to the new shared function/component/module.
For example, we replaced the inline threshold checks with calls to `validateMinimumBalance()` in each method.
4. Apply Extracted Method to Other Areas with Same Logic
Scan the entire codebase for any other duplicate instances of the same logic, and replace those with calls to the extracted shared method/module.
This eliminates duplication across the full codebase and completes the shotgun surgery refactoring process.
5. Repeat Steps for Other Duplicated Logic
Lather, rinse and repeat the extraction steps for any other duplicated logic contributing to shotgun surgery.
With each refactoring, the code becomes more modular and less duplicative. The shotgun surgery decay is progressively remedied bit by bit.
Leveraging Automated Tools
Automated static analysis tools can help accelerating identifying duplicate code and extracting it into shared modules. For example:
- ESLint – JavaScript linters like ESLint have built-in rules to detect code duplication.
- RIPS This PHP-focused static analysis tool flags duplicated code snippets.
- SonarQube– Checks for duplicated code across many languages.
- Visual Studio Built-in duplication detection for .NET codebases.
These tools complement manual code scanning and accelerate refactoring of large codebases.
Benefits of Refactoring Shotgun Surgery
Putting in the effort to carefully refactor shotgun surgery pays dividends through:
- Lower maintenance overhead** – Far easier to modify code in one place instead of scattered everywhere.
- Increased flexibility** – No longer afraid to tweak legacy code.
- Improved readability** – Eliminates messy duplication.
- Better testability** – Shared logic only needs to be tested once.
- Enables reuse** – Shared logic can be reused anywhere.
- Higher productivity** – No more wasted time debugging mystifying breaks.
- Reduced technical debt** – Eliminates a major maintainability headache.
- Increased team velocity** – Developers can move faster and fearlessly.
The benefits ripple through the entire software development lifecycle, from planning through coding, testing and deployment.
Avoiding New Shotgun Surgery
Once you’ve eradicated existing shotgun surgery through refactoring, it’s equally important to avoid reintroducing it. Here are some proactive measures:
- Abstraction** – Build proper abstractions around common logic from the start.
- Modularity** – Decompose code into modules with clear separation of concerns.
- DRY coding** – Watch for copy-pasted code and immediately extract it.
- Code reviews** – Peer reviews help spot shotgun surgery red flags early.
- Static analysis** – Use tools as part of CI to flag duplication issues.
With the right vigilance and practices, shotgun surgery can be avoided even as code evolves.
Conclusion
The shotgun surgery code smell is an infamous maintainability killer in many legacy codebases. But by identifying points of high coupling and duplication, then systematically extracting logic into shared modules, the problem can be cured.
Refactoring shotgun surgery leads to code that is more modular, reusable and readable. The resulting benefits range from easier maintenance through improved agility and productivity.
While daunting at first, with patience and the right approach, shotgun surgery can be successfully refactored. The codebase will thank you!
So next time you need to make a simple one-line change that breaks code throughout the system, you’ll know you have shotgun surgery on your hands. Armed with this guide, you can start the process of excising it from your codebase and regaining maintainability.