Shotgun Surgery in Dart: Examples and Fixes
One feature request, five files changed, two bugs introduced. That scatter is the shotgun surgery code smell — here's a Dart example, the three-move fix, and how to keep it out.
Your "clean" architecture with dozens of tiny classes might be the problem. If every small feature request sends you on a treasure hunt across five files — changing the same rule in each one, missing one, shipping a bug — you don't have a testing problem. You have the shotgun surgery code smell: one logical change that forces edits scattered across your codebase. Here's the shotgun surgery Dart example that makes it concrete, the three-move refactor that kills it, and the decision table for choosing the right move. What you'll learn What shotgun surgery is and how to detect it in your own git history A full shotgun surgery Dart example: the smelly before-state and the clean after-state How it differs from its mirror image, divergent change The three-step refactor: Move Method, Extract Class, verify with dart analyze A decision table for picking extract method vs. move method vs. new class Four mistakes teams make when "fixing" the smell (and what to do instead) Shotgun surgery Dart example: one rule, five files A notification system. Every channel — push, email, SMS, in-app, digest — must respect quiet hours. The rule is duplicated in every channel: // ❌ BEFORE: the same rule copy-pasted into five classes (Dart 3.13) class PushService { void send(String userId, String body, UserPrefs prefs) { final now = DateTime.now(); if (prefs.quietStart != null && prefs.quietEnd != null) { final s = prefs.quietStart!; final e = prefs.quietEnd!; if (now.hour >= s && now.hour < e) return; // respect quiet hours } _deliver(userId, body); } } class EmailService { void send(String userId, String body, UserPrefs prefs) { final now = DateTime.now(); if (prefs.quietStart != null && prefs.quietEnd != null) { final s = prefs.quietStart!; final e = prefs.quietEnd!; if (now.hour…