Code Smell Diagnosis
by @quochungto
Scan a codebase or code fragment for the 22 named code smells from Fowler's refactoring catalog and produce a prioritized diagnosis report with the specific...
Example 1: Service Class Audit
Scenario: A developer inherits a PaymentService class (280 lines, 12 methods, 9 instance variables) and asks for a diagnosis before adding a new payment gateway.
Step 1 β Structural read:
gatewayUrl, gatewayApiKey, gatewayTimeout β Data Clumps candidate)processPayment, validateCard, logTransaction, formatCurrency, sendWebhook, retryWebhook, buildGatewayRequest, parseGatewayResponse β some methods clearly work on gateway concerns, others on local concernsStep 2 β Smell check (selected findings):
*Divergent Change (HIGH):* The class changes when the gateway changes (3 methods: buildGatewayRequest, parseGatewayResponse, processPayment) AND when the webhook logic changes (2 methods: sendWebhook, retryWebhook) AND when logging changes (1 method: logTransaction). Three axes of change in one class.
*Data Clumps (MEDIUM):* gatewayUrl, gatewayApiKey, gatewayTimeout appear together in every gateway-related method signature. Deleting gatewayUrl makes the others meaningless.
*Long Method (MEDIUM):* processPayment is 55 lines with embedded comments marking sections ("// validate", "// build request", "// process", "// log").
Step 3 β Prioritize: Divergent Change first (it's a change preventer). Data Clumps second (will simplify gateway extraction). Long Method third (the extracted sections will become methods in the new class).
Diagnosis report excerpt:
### Finding #1 β Divergent Change β HIGHLocation: PaymentService (entire class)
Evidence: The class changes for at least three distinct reasons:
(1) Gateway integration changes β buildGatewayRequest, parseGatewayResponse, processPayment
(2) Webhook delivery changes β sendWebhook, retryWebhook
(3) Logging changes β logTransaction
Prescription: Extract Class for each axis of change.
- Extract GatewayClient (gateway request/response)
- Extract WebhookDispatcher (send + retry logic)
- PaymentService retains orchestration only
Why this prescription: Divergent Change calls for Extract Class on each axis.
The test: "I have to change these N methods every time X happens."
Three different values of X = three different classes needed.
Next step: Extract GatewayClient first β it's the largest axis and will expose
the Data Clumps smell for remedy.
Example 2: Single Method Diagnosis
Scenario: A code reviewer asks what's wrong with this Python method:
def calculate_charge(customer_type, base_price, quantity, discount_code,
loyalty_years, is_weekend, tax_rate):
if customer_type == 'enterprise':
price = base_price * quantity * 0.85
elif customer_type == 'retail':
price = base_price * quantity
elif customer_type == 'wholesale':
price = base_price * quantity * 0.7
else:
price = base_price * quantity if discount_code == 'SUMMER10':
price *= 0.90
elif discount_code == 'VIP20':
price *= 0.80
if loyalty_years > 5:
price *= 0.95
if is_weekend:
price *= 1.05
return price * (1 + tax_rate)
Step 2 β Smell check:
*Long Parameter List (HIGH):* 7 parameters. Why this branch: there is no obvious existing object that holds these β customer_type, loyalty_years suggest a Customer object; base_price, quantity, discount_code suggest an Order. Two Introduce Parameter Object applications.
*Switch Statements (HIGH):* The customer_type branching recurs β any new customer type requires finding every switch on customer_type and adding a case. Why this branch: type code affects behavior, and customer type is unlikely to change at runtime β Replace Type Code with Subclasses on Customer, then Replace Conditional with Polymorphism for the pricing logic.
Diagnosis report excerpt:
### Finding #1 β Switch Statements β HIGHLocation: calculate_charge(), lines 2-9 (customer_type branching)
Evidence: A switch on customer_type determines pricing multiplier.
This switch will exist wherever customer pricing is computed.
Every new customer type requires finding every such switch.
Prescription: Replace Type Code with Subclasses (EnterpriseCustomer,
RetailCustomer, WholesaleCustomer); then Replace Conditional with
Polymorphism (each subclass implements its own price multiplier method).
Why this branch: type code affects behavior; customer type doesn't change
at runtime for a given customer β subclassing is appropriate.
(If type changed at runtime, use Replace Type Code with State/Strategy instead.)
Next step: Introduce Parameter Object first (Finding #2) to create a
Customer object; then apply type code replacement on that object.
Finding #2 β Long Parameter List β HIGH
Location: calculate_charge() signature (7 parameters)
Evidence: customer_type, loyalty_years β Customer data cluster.
base_price, quantity, discount_code β Order data cluster.
Deleting customer_type makes loyalty_years ambiguous without context.
Prescription: Introduce Parameter Object twice β Customer(customer_type,
loyalty_years) and Order(base_price, quantity, discount_code).
Then: Preserve Whole Object β pass Customer and Order, not their fields.
Next step: Define Customer and Order dataclasses; update the signature.
This unblocks the Switch Statement refactoring (Finding #1).
Refactoring sequence:
1. Introduce Parameter Object: Customer, Order
2. Replace Type Code with Subclasses on Customer
3. Replace Conditional with Polymorphism for pricing
4. Preserve Whole Object in calculate_charge signature
Example 3: Inheritance Hierarchy Diagnosis
Scenario: A codebase has Animal, Dog, Cat, Bird hierarchy. A developer notices that every time a new animal species is added, a parallel set of AnimalSound, DogSound, CatSound, BirdSound classes must also be added.
Step 2 β Smell check:
*Parallel Inheritance Hierarchies (HIGH):* Subclassing Animal always requires subclassing AnimalSound. The prefix pattern is exact (Dog/DogSound, Cat/CatSound).
*Prescription:* Make instances of one hierarchy refer to instances of the other (Strategy pattern). Move Method and Move Field from AnimalSound hierarchy into Animal hierarchy using a sound strategy object. Once all behavior is moved, the AnimalSound hierarchy disappears.
clawhub install bookforge-code-smell-diagnosis