code-smell-detector
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
More from mgd34msu/goodvibes-gemini
chakra-ui
Builds accessible React applications with Chakra UI v3 components, tokens, and recipes. Use when creating styled component systems, theming, or accessible form controls.
70fastify
Builds high-performance Node.js APIs with Fastify, TypeScript, schema validation, and plugins. Use when building fast REST APIs, microservices, or needing schema-based validation.
2playwright
Tests web applications with Playwright including E2E tests, locators, assertions, and visual testing. Use when writing end-to-end tests, testing across browsers, automating user flows, or debugging test failures.
2vitest
Tests JavaScript and TypeScript applications with Vitest including unit tests, mocking, coverage, and React component testing. Use when writing tests, setting up test infrastructure, mocking dependencies, or measuring code coverage.
2vite
Builds web applications with Vite including dev server, production builds, plugins, and configuration. Use when scaffolding projects, configuring build tools, optimizing bundles, or setting up development environments.
2valibot
Validates data with Valibot's modular, tree-shakable schema library for minimal bundle size. Use when bundle size matters, building form validation, or needing lightweight TypeScript validation.
2