code-smell · git:20260614.2f67b30 · 2026-06-14 · sha256 5bffae871f3814c3

code-smell git:20260614.2f67b30A

Immutable. This exact content is served forever at /api/v1/blob/5bffae871f3814c3.

---
name: code-smell
description: Code smell detection and remediation. Identifies long methods, god classes, feature envy, primitive obsession, shotgun surgery, dead code, magic numbers, and other structural anti-patterns. Goes beyond linters — understands semantic coupling and design intent. Use when code is hard to change, PRs are consistently risky, or technical debt is growing faster than features.
---

# Code Smell

Find structural problems before they calcify into permanent pain.

## Quick Start

```
/code-smell                     — full codebase smell analysis
/code-smell --file src/user.ts  — analyze single file
/code-smell --category god-class — hunt specific smell type
/code-smell --fix               — apply safe refactors (extract method, rename)
/code-smell --rank              — sort files by smell density
```

## Smell Catalog

### Bloaters (things that grew too big)
| Smell | Signal | Fix |
|-------|--------|-----|
| **Long Method** | >20 lines, multiple levels of abstraction | Extract Method |
| **Large Class** | >300 lines, too many responsibilities | Extract Class / SRP |
| **Long Parameter List** | >4 params | Introduce Parameter Object |
| **Data Clumps** | Same 3+ vars always appear together | Extract Class |
| **Primitive Obsession** | `string` for email/money/status | Value Object |

### OO Abusers
| Smell | Signal | Fix |
|-------|--------|-----|
| **Switch Statements** | Switch/if-chain on type | Polymorphism |
| **Temporary Field** | Field only set in one method | Extract Class |
| **Refused Bequest** | Child ignores most of parent | Replace Inheritance with Delegation |
| **Alternative Classes** | Two classes do the same thing | Merge |

### Change Preventers
| Smell | Signal | Fix |
|-------|--------|-----|
| **Divergent Change** | Changing feature X always touches Class Y | Split class |
| **Shotgun Surgery** | One change → 8 files | Move Method / Inline Class |
| **Parallel Inheritance** | Adding subclass A requires subclass B | Merge hierarchies |

### Dispensables (things to delete)
| Smell | Signal | Fix |
|-------|--------|-----|
| **Dead Code** | Unreachable / never called | Delete |
| **Speculative Generality** | `AbstractFactory` for one use case | Simplify |
| **Lazy Class** | Class does almost nothing | Inline Class |
| **Duplicate Code** | Copy-paste across files | Extract + reuse |

### Couplers
| Smell | Signal | Fix |
|-------|--------|-----|
| **Feature Envy** | Method uses other class's data more than own | Move Method |
| **Inappropriate Intimacy** | Two classes read each other's privates | Move / Extract |
| **Message Chains** | `a.b().c().d().e()` | Introduce delegate method |
| **Middle Man** | Class delegates everything to another | Inline Class |

## Output Format

```
CODE SMELL ANALYSIS — 2026-06-14
===================================
Files analyzed: 147  |  Smell density: 2.3 smells/100 lines (HIGH)

CRITICAL
  [C1] src/services/UserService.ts — GOD CLASS (847 lines, 23 methods)
       Handles: auth, profile, billing, notifications, admin, analytics
       → Extract: AuthService, BillingService, NotificationService
       Estimated effort: 2 days | Risk: HIGH (many callers)

  [C2] src/api/checkout.ts:145 — LONG METHOD (94 lines, 7 abstraction levels)
       → Extract: validateCart(), applyDiscounts(), chargePayment(), sendConfirmation()

HIGH
  [H1] src/models/Product.ts:12 — PRIMITIVE OBSESSION
       price: number (no currency, no precision)
       → type Money = { amount: number; currency: Currency }

  [H2] src/components/AdminPanel.tsx — FEATURE ENVY
       References user.subscription.plan.features 14 times
       → Move canAccessFeature(featureId) to User or Subscription model

MEDIUM
  [M1] src/utils/helpers.ts — DEAD CODE (3 exported functions, 0 callers)
       formatLegacyDate(), parseOldSKU(), migrateV1User()
       → Safe to delete (confirmed by grep + git log)

  [M2] src/api/ — DUPLICATE CODE
       validateEmail() defined in auth.ts, users.ts, and checkout.ts
       → Extract to src/utils/validation.ts

LOW
  [L1] 12 occurrences of magic numbers (HTTP status codes, timeout values)
       → Extract to src/constants/http.ts and src/config/timeouts.ts
```

## Safe Refactor Patterns

### Extract Method
```typescript
// ❌ 60-line method
async processOrder(cart: Cart, user: User) {
  // validate cart
  if (!cart.items.length) throw new Error('Empty cart')
  const inventory = await this.checkInventory(cart.items)
  // ... 50 more lines
}

// ✅ decomposed
async processOrder(cart: Cart, user: User) {
  await this.validateCart(cart)
  const payment = await this.chargePayment(cart, user)
  await this.fulfillOrder(cart, payment)
  await this.sendConfirmation(user, payment)
}
```

### Introduce Value Object
```typescript
// ❌ primitive obsession
function charge(amount: number, currency: string) { ... }

// ✅ value object
class Money {
  constructor(readonly amount: number, readonly currency: 'USD' | 'BRL' | 'EUR') {}
  plus(other: Money): Money {
    if (this.currency !== other.currency) throw new Error('Currency mismatch')
    return new Money(this.amount + other.amount, this.currency)
  }
}
```

### Replace Switch with Map
```typescript
// ❌ switch smells
function getDiscount(tier: string): number {
  switch (tier) {
    case 'bronze': return 0.05
    case 'silver': return 0.10
    case 'gold': return 0.20
    default: return 0
  }
}

// ✅ data-driven
const DISCOUNTS: Record<string, number> = { bronze: 0.05, silver: 0.10, gold: 0.20 }
const getDiscount = (tier: string) => DISCOUNTS[tier] ?? 0
```