| name | code-smell-detector |
| description | Detects code smells, anti-patterns, and common bugs with quantified thresholds and severity scoring. Use when reviewing code quality, finding maintainability issues, detecting SOLID violations, or identifying technical debt. |
Code Smell Detector
Systematic detection of code smells, anti-patterns, and maintainability issues with quantified thresholds for scoring.
Quick Start
Full smell analysis:
Analyze this codebase for code smells with severity scoring and file:line references.
Specific category:
Check for SOLID principle violations in the services directory.
Severity Classification
| Severity | Multiplier | Impact | Action |
|---|---|---|---|
| Bloater | 1.5x | High cognitive load, hard to modify | Refactor soon |
| Object-Orientation Abuser | 1.25x | Poor extensibility, rigid design | Refactor next sprint |
| Change Preventer | 1.5x | Ripple effects, fear of change | Prioritize refactoring |
| Dispensable | 1.0x | Clutter, confusion | Clean up opportunistically |
| Coupler | 1.25x | Tight coupling, hard to test | Decouple incrementally |
Bloaters
Code that has grown excessively large and hard to work with.
Long Method / Function
Threshold: >50 lines (warning), >100 lines (violation) Deduction: 0.5 points base per 10 functions over threshold
Detection Commands:
# Find long functions (approximate)
# TypeScript/JavaScript
grep -n "function\|=>\|async" src/**/*.ts | wc -l
# Get function lengths using AST tools
npx escomplex src/ --format json 2>/dev/null | jq '.reports[].functions[] | select(.sloc.logical > 50)'
# Python
radon cc src/ -j 2>/dev/null | jq '.[] | .[] | select(.loc > 50)'
Manual Detection:
# Count lines between function start and end
awk '/function.*\{/{start=NR} /^\s*\}/{if(NR-start>50) print FILENAME":"start":"NR-start}' src/**/*.ts
Evidence Template:
Location: {file}:{startLine}-{endLine}
Function: {name}()
Lines: {count} (threshold: 50)
Deduction: 0.05 * ceil(({count} - 50) / 10) points
Large Class / Module
Threshold: >300 lines (warning), >500 lines (violation) Deduction: 1.0 point base per class over threshold
Detection Commands:
# Files over 300 lines
find src -name "*.ts" -exec wc -l {} \; | awk '$1 > 300 {print}'
# Count classes per file
grep -c "^class\|^export class" src/**/*.ts
Evidence Template:
Location: {file}
Lines: {count} (threshold: 300)
Classes: {classCount}
Deduction: 1.0 point
Primitive Obsession
Threshold: >5 primitive parameters in function Deduction: 0.5 points per occurrence
Detection Patterns:
// SMELL: Too many primitives
function createUser(
name: string,
email: string,
phone: string,
street: string,
city: string,
zip: string,
country: string
) { ... }
// BETTER: Use objects
interface Address {
street: string;
city: string;
zip: string;
country: string;
}
function createUser(
name: string,
email: string,
phone: string,
address: Address
) { ... }
Detection Commands:
# Find functions with many parameters
grep -rn "function.*,.*,.*,.*,.*," src/
grep -rn "=>.*,.*,.*,.*,.*," src/
Long Parameter List
Threshold: >4 parameters Deduction: 0.25 points per function
Detection Commands:
# Functions with 5+ parameters
grep -rEn "function\s+\w+\s*\([^)]*,[^)]*,[^)]*,[^)]*,[^)]*\)" src/
Fix Pattern:
// BEFORE: 6 parameters
function createOrder(userId, productId, quantity, address, paymentMethod, coupon) {}
// AFTER: Parameter object
interface CreateOrderParams {
userId: string;
productId: string;
quantity: number;
address: Address;
paymentMethod: PaymentMethod;
coupon?: string;
}
function createOrder(params: CreateOrderParams) {}
Data Clumps
Threshold: Same 3+ fields appearing together in 3+ places Deduction: 0.5 points per clump
Example:
// SMELL: Same fields everywhere
function validateAddress(street, city, zip) {}
function formatAddress(street, city, zip) {}
function saveAddress(userId, street, city, zip) {}
// BETTER: Extract class
class Address {
constructor(public street: string, public city: string, public zip: string) {}
validate() {}
format() {}
}
Object-Orientation Abusers
Incorrect application of OOP principles.
Switch Statements on Type
Threshold: Switch/if-else chain on type in multiple places Deduction: 0.75 points per violation
Detection Commands:
# Type-based switches
grep -rn "switch.*type\|switch.*kind" src/
grep -rn "if.*instanceof.*else.*instanceof" src/
grep -rn "\.type\s*===\s*['\"]" src/
SMELL Pattern:
// SMELL: Type switching
function calculateArea(shape) {
switch (shape.type) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'rectangle': return shape.width * shape.height;
case 'triangle': return 0.5 * shape.base * shape.height;
}
}
function draw(shape) {
switch (shape.type) { // Same switch again!
case 'circle': drawCircle(shape);
case 'rectangle': drawRectangle(shape);
case 'triangle': drawTriangle(shape);
}
}
FIX Pattern:
// BETTER: Polymorphism
interface Shape {
calculateArea(): number;
draw(): void;
}
class Circle implements Shape {
constructor(private radius: number) {}
calculateArea() { return Math.PI * this.radius ** 2; }
draw() { /* circle drawing */ }
}
Refused Bequest
Threshold: Subclass doesn't use inherited methods/properties Deduction: 0.5 points per occurrence
Detection Signs:
- Override methods that throw "Not Implemented"
- Empty implementations of parent methods
- Subclass only uses 1-2 of many inherited methods
SMELL Pattern:
class Bird {
fly() { /* flying logic */ }
sing() { /* singing logic */ }
eat() { /* eating logic */ }
}
class Penguin extends Bird {
fly() {
throw new Error("Penguins can't fly"); // SMELL!
}
}
Temporary Field
Threshold: Fields only used in some methods Deduction: 0.25 points per field
SMELL Pattern:
class Order {
private tempTotal: number; // Only used in calculateTotal
private tempDiscount: number; // Only used in calculateTotal
calculateTotal() {
this.tempTotal = this.items.reduce((sum, i) => sum + i.price, 0);
this.tempDiscount = this.coupon ? this.tempTotal * 0.1 : 0;
return this.tempTotal - this.tempDiscount;
}
}
FIX:
class Order {
calculateTotal() {
const total = this.items.reduce((sum, i) => sum + i.price, 0);
const discount = this.coupon ? total * 0.1 : 0;
return total - discount; // Local variables, not fields
}
}
Change Preventers
Code that makes changes difficult and risky.
Divergent Change
Threshold: Class changes for multiple unrelated reasons Deduction: 1.0 point per class
Detection Signs:
- Class has methods for different domains (UI + DB + business logic)
- Multiple developers edit same class for unrelated features
- Class name is generic ("Manager", "Handler", "Processor")
SMELL Pattern:
class UserManager {
// Database concerns
saveToDatabase(user) {}
loadFromDatabase(id) {}
// Email concerns
sendWelcomeEmail(user) {}
sendPasswordReset(user) {}
// Validation concerns
validateEmail(email) {}
validatePassword(password) {}
// Formatting concerns
formatForDisplay(user) {}
formatForExport(user) {}
}
Shotgun Surgery
Threshold: One change requires edits to 5+ files Deduction: 1.0 point per pattern
Detection Signs:
- Adding a new field requires changes in many files
- Same concept scattered across codebase
- Feature flags checked in 10+ places
Detection Commands:
# Find scattered concepts
grep -rln "userRole\|user\.role\|role.*user" src/ | wc -l
grep -rln "isAdmin\|is_admin\|admin.*true" src/ | wc -l
Parallel Inheritance Hierarchies
Threshold: Adding a subclass requires adding another subclass elsewhere Deduction: 0.75 points
SMELL Pattern:
Shape -> Circle, Rectangle, Triangle
ShapeRenderer -> CircleRenderer, RectangleRenderer, TriangleRenderer
ShapeSerializer -> CircleSerializer, RectangleSerializer, TriangleSerializer
Every new shape requires three new classes!
Dispensables
Code that adds no value.
Dead Code
Threshold: Any unreachable or unused code Deduction: 0.25 points per 100 lines of dead code
Detection Commands:
# Unused exports (TypeScript)
npx ts-prune 2>/dev/null
# Unused dependencies
npx depcheck 2>/dev/null
# Unused files
npx unimported 2>/dev/null
# Python
vulture src/ 2>/dev/null
Speculative Generality
Threshold: Unused abstractions "for future use" Deduction: 0.5 points per occurrence
Detection Signs:
- Interfaces with single implementation
- Abstract methods never overridden differently
- Parameters/hooks never used
- "TODO: implement when needed" comments
SMELL Pattern:
// YAGNI violation
interface DataStore {
save(data: any): void;
load(id: string): any;
delete(id: string): void;
query(filter: any): any[]; // Never used
backup(): void; // Never used
restore(backup: any): void; // Never used
}
class PostgresStore implements DataStore {
query(filter: any) { throw new Error("Not implemented"); }
backup() { throw new Error("Not implemented"); }
restore() { throw new Error("Not implemented"); }
}
Duplicate Code
Threshold: >10 similar lines in 2+ places Deduction: 0.5 points per duplication
Detection Commands:
# JavaScript/TypeScript duplicate detection
npx jscpd src/ --min-lines 10 --min-tokens 50
# Generic duplicate finder
npx copy-paste-detector src/
Detection Patterns:
# Find similar function names
grep -rn "function validate\|const validate" src/ | sort
# Find repeated patterns
grep -rn "try {" src/ | head -20
Comments (Unnecessary)
Threshold: Comments that explain "what" instead of "why" Deduction: 0.1 points per 10 bad comments
SMELL Patterns:
// UNNECESSARY: Explains what (obvious from code)
// Increment counter by 1
counter++;
// Add user to list
users.push(user);
// GOOD: Explains why (not obvious)
// Using += 1 instead of ++ for consistency with Python team's style
counter += 1;
// Storing raw user object for cache invalidation in afterSave hook
users.push(user);
Couplers
Code with excessive coupling.
Feature Envy
Threshold: Method uses more from other class than its own Deduction: 0.5 points per method
SMELL Pattern:
// SMELL: calculateTotal uses Order more than itself
class OrderPrinter {
calculateTotal(order: Order) {
let total = 0;
for (const item of order.items) {
total += item.price * item.quantity;
}
total -= order.discount;
total *= (1 + order.taxRate);
return total;
}
}
// BETTER: Move to Order
class Order {
calculateTotal() {
let total = this.items.reduce((sum, item) =>
sum + item.price * item.quantity, 0);
return (total - this.discount) * (1 + this.taxRate);
}
}
Inappropriate Intimacy
Threshold: Classes access each other's private/internal data Deduction: 0.75 points per occurrence
Detection Commands:
# Direct property access on other classes
grep -rn "\._\w\+\s*=" src/ # Accessing _private fields
grep -rn "\.#\w\+\s*=" src/ # Accessing #private fields
Message Chains
Threshold: >3 chained method calls accessing data Deduction: 0.25 points per occurrence
SMELL Pattern:
// SMELL: Long chain
const city = order.customer.address.city.name;
// BETTER: Tell, don't ask
const city = order.getDeliveryCity();
Detection Commands:
# Find long chains
grep -rn "\.\w\+\.\w\+\.\w\+\.\w\+" src/
Middle Man
Threshold: Class delegates most work to another class Deduction: 0.5 points per class
SMELL Pattern:
// SMELL: UserService just delegates to User
class UserService {
getName(user: User) { return user.getName(); }
getEmail(user: User) { return user.getEmail(); }
setEmail(user: User, email: string) { user.setEmail(email); }
}
SOLID Violations
Single Responsibility Principle (SRP)
Threshold: Class has >1 reason to change Deduction: 1.0 point per violation
Detection Heuristics:
- Class has methods in different domains
- Class imports from many unrelated modules
- Class has >300 lines
- Class name contains "And" or is very generic
Open/Closed Principle (OCP)
Threshold: Modifying existing code to add features Deduction: 0.75 points per violation
Detection Signs:
- Switch statements that grow with each feature
- If-else chains on type
- God functions with many conditionals
Liskov Substitution Principle (LSP)
Threshold: Subclass can't substitute for parent Deduction: 0.75 points per violation
Detection Signs:
- Override that throws NotImplementedException
- Override that changes behavior contract
- Type checking before calling inherited method
Interface Segregation Principle (ISP)
Threshold: Interface with >7 methods or unused methods Deduction: 0.5 points per violation
Detection Commands:
# Large interfaces
grep -A 20 "^interface\|^export interface" src/**/*.ts | grep -c "("
Dependency Inversion Principle (DIP)
Threshold: High-level module depends on low-level implementation Deduction: 0.75 points per violation
SMELL Pattern:
// SMELL: Direct instantiation
class OrderService {
private db = new PostgresDatabase(); // Concrete!
private mailer = new SendGridMailer(); // Concrete!
}
// BETTER: Inject interfaces
class OrderService {
constructor(
private db: Database, // Interface
private mailer: EmailService // Interface
) {}
}
Summary Scoring Table
| Category | Smell | Threshold | Base Deduction |
|---|---|---|---|
| Bloater | Long Method | >50 lines | 0.5 |
| Bloater | Large Class | >300 lines | 1.0 |
| Bloater | Primitive Obsession | >5 params | 0.5 |
| Bloater | Long Parameter List | >4 params | 0.25 |
| OO Abuser | Switch on Type | multiple places | 0.75 |
| OO Abuser | Refused Bequest | unused inheritance | 0.5 |
| Change Preventer | Divergent Change | multi-reason class | 1.0 |
| Change Preventer | Shotgun Surgery | 5+ file changes | 1.0 |
| Dispensable | Dead Code | any | 0.25/100 lines |
| Dispensable | Speculative Generality | unused abstractions | 0.5 |
| Dispensable | Duplicate Code | >10 similar lines | 0.5 |
| Coupler | Feature Envy | method uses other class | 0.5 |
| Coupler | Message Chains | >3 chained calls | 0.25 |
| SOLID | SRP Violation | multiple responsibilities | 1.0 |
| SOLID | OCP Violation | modifying for extension | 0.75 |
| SOLID | DIP Violation | concrete dependencies | 0.75 |
Report Template
## Code Smell Analysis: {project}
### Summary
| Category | Count | Total Deduction |
|----------|-------|-----------------|
| Bloaters | {n} | {points} |
| OO Abusers | {n} | {points} |
| Change Preventers | {n} | {points} |
| Dispensables | {n} | {points} |
| Couplers | {n} | {points} |
| SOLID Violations | {n} | {points} |
| **Total** | **{sum}** | **{totalPoints}** |
### Top Offenders
#### 1. {ClassName} - God Class
- **Location:** `{file}:{line}`
- **Lines:** {count}
- **Responsibilities:** {list}
- **Deduction:** {points}
{repeat for top 10 issues}
Integration with Brutal Reviewer
This skill contributes to multiple categories:
- Maintainability (8%): Bloaters, Dispensables
- SOLID/DRY (10%): SOLID violations, Couplers
- Organization (12%): Change Preventers, Large Classes