Project 38: “The Refactoring Surgeon” — Software Architecture

Attribute Value
File KIRO_CLI_LEARNING_PROJECTS.md
Main Programming Language TypeScript
Coolness Level Level 3: Genuinely Clever
Difficulty Level 3: Advanced
Knowledge Area Software Architecture

What you’ll build: A refactoring tool that identifies God Classes (classes with too many responsibilities) and uses Kiro to safely decompose them into focused services, utilities, and domain models while preserving all tests and behavior.

Why it teaches Safe Changes: This project shows how AI agents excel at mechanical refactoring tasks that require understanding code structure, dependency graphs, and test coverage. You’ll learn to use Kiro for large-scale architectural changes that would take days manually.

Core challenges you’ll face:

  • God Class detection → Maps to cyclomatic complexity analysis, SRP (Single Responsibility Principle) violations
  • Dependency extraction → Maps to identifying method dependencies, data flow analysis
  • Safe decomposition → Maps to extract class/method refactorings that preserve semantics
  • Test preservation → Maps to ensuring all tests pass after refactoring

Real World Outcome

You’ll have a CLI tool that analyzes a codebase, identifies bloated classes, and refactors them into clean, testable components:

$ cat src/services/OrderService.ts
// WARNING: God Class - 1,247 lines, 43 methods, cyclomatic complexity: 87

export class OrderService {
  constructor(
    private db: Database,
    private emailClient: EmailClient,
    private paymentGateway: PaymentGateway,
    private inventoryService: InventoryService,
    private shippingService: ShippingService,
    private taxCalculator: TaxCalculator,
    private discountEngine: DiscountEngine
  ) {}

  // Payment methods (8 methods, 312 lines)
  async processPayment(orderId: string) { /* ... */ }
  async refundPayment(orderId: string) { /* ... */ }
  async captureAuthorization(orderId: string) { /* ... */ }
  // ... 5 more payment methods

  // Email methods (6 methods, 189 lines)
  async sendOrderConfirmation(orderId: string) { /* ... */ }
  async sendShippingNotification(orderId: string) { /* ... */ }
  async sendCancellationEmail(orderId: string) { /* ... */ }
  // ... 3 more email methods

  // Inventory methods (7 methods, 234 lines)
  async reserveInventory(orderId: string) { /* ... */ }
  async releaseInventory(orderId: string) { /* ... */ }
  async checkStockAvailability(items: Item[]) { /* ... */ }
  // ... 4 more inventory methods

  // Tax/discount methods (9 methods, 278 lines)
  async calculateTax(order: Order) { /* ... */ }
  async applyDiscount(order: Order, code: string) { /* ... */ }
  // ... 7 more calculation methods

  // Shipping methods (5 methods, 156 lines)
  async calculateShipping(order: Order) { /* ... */ }
  async schedulePickup(orderId: string) { /* ... */ }
  // ... 3 more shipping methods

  // Core order methods (8 methods, 178 lines)
  async createOrder(data: CreateOrderDTO) { /* ... */ }
  async cancelOrder(orderId: string) { /* ... */ }
  async getOrderDetails(orderId: string) { /* ... */ }
  // ... 5 more order methods
}

$ kiro refactor --file src/services/OrderService.ts --analyze

[Kiro CLI Session]
🔧 Refactoring Surgeon Agent
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📊 Analyzing OrderService.ts...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Metrics:
  • Lines of Code: 1,247
  • Number of Methods: 43
  • Cyclomatic Complexity: 87 ⚠️  (threshold: 20)
  • Coupling (Dependencies): 7 classes
  • Cohesion: LOW (methods operate on different data)

🚨 God Class Detected!

Responsibility Clusters Identified:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

1. Payment Operations (8 methods, 312 lines)
   → processPayment, refundPayment, captureAuthorization, etc.
   → Dependencies: PaymentGateway
   → Suggested: Extract to PaymentService

2. Email Notifications (6 methods, 189 lines)
   → sendOrderConfirmation, sendShippingNotification, etc.
   → Dependencies: EmailClient
   → Suggested: Extract to OrderNotificationService

3. Inventory Management (7 methods, 234 lines)
   → reserveInventory, releaseInventory, checkStockAvailability
   → Dependencies: InventoryService
   → Suggested: Extract to OrderInventoryCoordinator

4. Tax & Discount Calculations (9 methods, 278 lines)
   → calculateTax, applyDiscount, validateCoupon, etc.
   → Dependencies: TaxCalculator, DiscountEngine
   → Suggested: Extract to OrderPricingService

5. Shipping Coordination (5 methods, 156 lines)
   → calculateShipping, schedulePickup, trackShipment
   → Dependencies: ShippingService
   → Suggested: Extract to ShippingCoordinator

6. Core Order Domain (8 methods, 178 lines)
   → createOrder, cancelOrder, getOrderDetails
   → Dependencies: Database
   → Keep in OrderService (core domain logic)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Refactoring Plan:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Step 1: Extract PaymentService (8 methods)
Step 2: Extract OrderNotificationService (6 methods)
Step 3: Extract OrderInventoryCoordinator (7 methods)
Step 4: Extract OrderPricingService (9 methods)
Step 5: Extract ShippingCoordinator (5 methods)
Step 6: Update OrderService to use new services (8 core methods remain)
Step 7: Update all imports and tests

Impact:
  • 5 new service classes created
  • OrderService reduced from 1,247 to 178 lines
  • Cyclomatic complexity: 87 → 12
  • Test files to update: 14

Proceed with refactoring? (y/n): y

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Step 1: Extracting PaymentService...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Created: src/services/payments/PaymentService.ts

export class PaymentService {
  constructor(private paymentGateway: PaymentGateway) {}

  async processPayment(orderId: string, amount: number): Promise<PaymentResult> {
    // [Original logic moved here]
  }

  async refundPayment(orderId: string, amount: number): Promise<void> {
    // [Original logic moved here]
  }

  async captureAuthorization(orderId: string): Promise<void> {
    // [Original logic moved here]
  }

  // ... 5 more methods
}

✓ PaymentService extracted (8 methods, 312 lines)
✓ Tests updated: PaymentService.test.ts created

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Step 2: Extracting OrderNotificationService...
Created: src/services/notifications/OrderNotificationService.ts
✓ OrderNotificationService extracted (6 methods, 189 lines)
✓ Tests updated: OrderNotificationService.test.ts created

Step 3: Extracting OrderInventoryCoordinator...
Created: src/services/inventory/OrderInventoryCoordinator.ts
✓ OrderInventoryCoordinator extracted (7 methods, 234 lines)
✓ Tests updated: OrderInventoryCoordinator.test.ts created

Step 4: Extracting OrderPricingService...
Created: src/services/pricing/OrderPricingService.ts
✓ OrderPricingService extracted (9 methods, 278 lines)
✓ Tests updated: OrderPricingService.test.ts created

Step 5: Extracting ShippingCoordinator...
Created: src/services/shipping/ShippingCoordinator.ts
✓ ShippingCoordinator extracted (5 methods, 156 lines)
✓ Tests updated: ShippingCoordinator.test.ts created

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Step 6: Updating OrderService (core domain logic)...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Updated: src/services/OrderService.ts

export class OrderService {
  constructor(
    private db: Database,
    private paymentService: PaymentService,
    private notificationService: OrderNotificationService,
    private inventoryCoordinator: OrderInventoryCoordinator,
    private pricingService: OrderPricingService,
    private shippingCoordinator: ShippingCoordinator
  ) {}

  async createOrder(data: CreateOrderDTO): Promise<Order> {
    // 1. Reserve inventory
    await this.inventoryCoordinator.reserveInventory(data.items);

    // 2. Calculate pricing
    const pricing = await this.pricingService.calculateTotal(data);

    // 3. Create order record
    const order = await this.db.orders.create({
      ...data,
      totalAmount: pricing.total,
      status: 'pending'
    });

    // 4. Process payment
    const paymentResult = await this.paymentService.processPayment(
      order.id,
      pricing.total
    );

    // 5. Update order status
    await this.db.orders.update(order.id, { status: 'confirmed' });

    // 6. Send confirmation email
    await this.notificationService.sendOrderConfirmation(order.id);

    return order;
  }

  async cancelOrder(orderId: string): Promise<void> {
    const order = await this.db.orders.findById(orderId);

    // 1. Refund payment
    await this.paymentService.refundPayment(orderId, order.totalAmount);

    // 2. Release inventory
    await this.inventoryCoordinator.releaseInventory(order.items);

    // 3. Update order status
    await this.db.orders.update(orderId, { status: 'cancelled' });

    // 4. Send cancellation email
    await this.notificationService.sendCancellationEmail(orderId);
  }

  // ... 6 more core order methods (getOrderDetails, updateOrder, etc.)
}

New Metrics:
  • Lines of Code: 178 (was 1,247) — 86% reduction
  • Number of Methods: 8 (was 43)
  • Cyclomatic Complexity: 12 (was 87)
  • Coupling: 6 services (clean dependency injection)

✓ OrderService refactored successfully!

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Step 7: Running Tests...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

$ npm test

> Running 247 tests...

  OrderService.test.ts
    ✓ creates order successfully (45ms)
    ✓ cancels order and refunds payment (67ms)
    ✓ handles payment failure gracefully (34ms)
    ... 23 more tests

  PaymentService.test.ts
    ✓ processes payment via gateway (23ms)
    ✓ refunds payment correctly (29ms)
    ... 14 more tests

  [All other service tests passing...]

Test Suites: 14 passed, 14 total
Tests:       247 passed, 247 total
Time:        8.456s

✅ All tests passing! Behavior preserved.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📊 Refactoring Summary:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Files Created: 5
  • src/services/payments/PaymentService.ts
  • src/services/notifications/OrderNotificationService.ts
  • src/services/inventory/OrderInventoryCoordinator.ts
  • src/services/pricing/OrderPricingService.ts
  • src/services/shipping/ShippingCoordinator.ts

Files Modified: 15
  • src/services/OrderService.ts (refactored)
  • 14 test files updated

Before vs After:
  • OrderService: 1,247 lines → 178 lines (86% reduction)
  • Cyclomatic Complexity: 87 → 12 (86% reduction)
  • Single Responsibility: ✓ Each service has one clear purpose
  • Testability: ✓ Services can be tested in isolation
  • Maintainability: ✓ Changes are now localized

✅ Refactoring complete! Your God Class is now 6 focused services.

Exactly what happens:

  1. Kiro analyzes the class and calculates complexity metrics
  2. It identifies clusters of methods that operate on similar data/dependencies
  3. It extracts each cluster into a new service class
  4. It updates the original class to delegate to the new services
  5. It updates all tests to match the new structure
  6. It runs the full test suite to verify behavior is preserved

The Core Question You’re Answering

“How do you use AI to perform safe, large-scale refactoring that would take humans days but preserves all behavior and tests?”

This is about teaching Kiro architectural thinking:

  • Recognizing code smells (God Classes, tight coupling)
  • Understanding the Single Responsibility Principle
  • Safely extracting classes without breaking tests
  • Maintaining dependency injection patterns

Concepts You Must Understand First

Stop and research these before coding:

  1. SOLID Principles (especially SRP)
    • What is the Single Responsibility Principle?
    • How do you identify when a class has too many responsibilities?
    • What is coupling vs cohesion?
    • Book Reference: “Clean Architecture” by Robert C. Martin - Ch. 7-8
  2. Refactoring Patterns
    • Extract Class, Extract Method, Move Method
    • How do you safely refactor without changing behavior?
    • What is the “red-green-refactor” cycle?
    • Book Reference: “Refactoring” by Martin Fowler - Ch. 6-7
  3. Code Metrics
    • What is cyclomatic complexity and why does it matter?
    • How do you measure coupling and cohesion?
    • What’s the difference between LOC and meaningful complexity?
    • Book Reference: “Code Complete” by Steve McConnell - Ch. 19
  4. Dependency Injection
    • Why use constructor injection vs property injection?
    • How does DI make code testable?
    • What is inversion of control (IoC)?
    • Book Reference: “Dependency Injection Principles, Practices, and Patterns” by Steven van Deursen & Mark Seemann - Ch. 1-2

Questions to Guide Your Design

Before implementing, think through these:

  1. Cluster Detection
    • How do you identify which methods belong together?
    • Should you cluster by data dependencies, control flow, or domain concepts?
    • What if a method uses dependencies from multiple clusters?
    • Should you use static analysis or dynamic runtime profiling?
  2. Safe Extraction
    • How do you ensure the extracted class has the same behavior?
    • What if the original methods had side effects or shared mutable state?
    • Should you extract one service at a time or all at once?
    • How do you handle private methods that are called by methods in different clusters?
  3. Test Preservation
    • Should you update tests to match the new structure or keep them as integration tests?
    • What if some tests break after refactoring?
    • How do you ensure test coverage doesn’t decrease?
    • Should you add new unit tests for the extracted services?
  4. Naming and Structure
    • How do you name the new service classes?
    • Where should they live in the directory structure?
    • Should you group them by layer (services/, repositories/) or by domain (orders/, payments/)?
    • What if the God Class is already named “OrderService” — do you rename it?

Thinking Exercise

Refactoring Decision Challenge

You have this class:

class UserService {
  // Authentication methods
  async login(email, password) { /* ... */ }
  async logout(userId) { /* ... */ }
  async resetPassword(email) { /* ... */ }

  // Profile management
  async updateProfile(userId, data) { /* ... */ }
  async uploadAvatar(userId, file) { /* ... */ }

  // Notification preferences
  async updateEmailPreferences(userId, prefs) { /* ... */ }
  async updatePushPreferences(userId, prefs) { /* ... */ }

  // Analytics
  async trackLogin(userId) { /* ... */ }
  async trackProfileView(userId, viewerId) { /* ... */ }
}

Questions to reason through:

  1. How many services should you extract? (3? 4?)
  2. Should login and logout go in AuthenticationService or SessionService?
  3. The trackLogin method depends on login — which service should it live in?
  4. After refactoring, should the original UserService still exist? What should it contain?
  5. What if 50 files import UserService — how do you update them all safely?

Proposed structure:

AuthenticationService: login, logout, resetPassword
ProfileService: updateProfile, uploadAvatar
NotificationPreferenceService: updateEmailPreferences, updatePushPreferences
UserAnalyticsService: trackLogin, trackProfileView

Is this optimal? What would you change?

The Interview Questions They’ll Ask

Prepare to answer these:

  1. “What is a God Class and why is it a code smell?”
  2. “How do you safely refactor a class without breaking existing functionality?”
  3. “What’s the difference between coupling and cohesion? Give examples.”
  4. “You extract a service but tests start failing. What’s your debugging strategy?”
  5. “How would you refactor a God Class that has circular dependencies?”
  6. “What metrics do you use to identify classes that need refactoring?”

Hints in Layers

Hint 1: Start with Static Analysis Use TypeScript Compiler API to parse the class:

const sourceFile = ts.createSourceFile(filename, code, ts.ScriptTarget.Latest);
const classNode = findClassDeclaration(sourceFile, 'OrderService');
const methods = classNode.members.filter(m => ts.isMethodDeclaration(m));

// For each method, extract:
// - Dependencies used (from constructor params or method args)
// - Other methods called
// - Properties accessed

This gives you a dependency graph to cluster methods.

Hint 2: Cluster by Dependencies

For each method:
  1. Extract dependencies it uses (PaymentGateway, EmailClient, etc.)
  2. Extract other methods it calls
  3. Group methods that share >70% of dependencies

Example:
  processPayment → uses PaymentGateway
  refundPayment → uses PaymentGateway
  captureAuthorization → uses PaymentGateway
  → Cluster: PaymentService

Hint 3: Extract Class Refactoring For each cluster:

  1. Generate new class file
  2. Move methods to new class
  3. Add constructor with required dependencies
  4. Update original class to instantiate new service
  5. Replace direct calls with this.paymentService.processPayment(...)

Hint 4: Test Validation After each extraction:

  1. Run full test suite: npm test
  2. If tests fail, analyze failures:
    • Missing imports? → Add imports
    • Constructor signature changed? → Update DI container
    • Method signature changed? → Verify parameters match
  3. If all tests pass, commit the refactoring
  4. If tests fail repeatedly, rollback and try a different clustering

Books That Will Help

Topic Book Chapter
SOLID principles “Clean Architecture” by Robert C. Martin Ch. 7-8
Refactoring patterns “Refactoring” by Martin Fowler Ch. 6-7
Code metrics “Code Complete” by Steve McConnell Ch. 19
Dependency injection “Dependency Injection” by Steven van Deursen Ch. 1-2
Software design “Domain-Driven Design” by Eric Evans Ch. 5

Common Pitfalls and Debugging

Problem 1: “Tests fail after extraction”

  • Why: Constructor signature changed but DI container wasn’t updated
  • Fix: Update all places where OrderService is instantiated (DI config, test setup)
  • Quick test: Search for new OrderService( and verify all constructor calls match

Problem 2: “Extracted service has circular dependency”

  • Why: Two clusters reference each other’s methods
  • Fix: Introduce an interface or event bus to break the cycle
  • Quick test: Draw dependency graph — if there’s a cycle, refactor to one-way dependencies

Problem 3: “Method calls private methods from different clusters”

  • Why: Private method is utility logic used by multiple responsibilities
  • Fix: Extract private method to a separate utility class or make it public in the appropriate service
  • Quick test: Analyze private method dependencies — does it belong in one cluster more than others?

Problem 4: “Extracted services are too granular (over-engineering)”

  • Why: Created too many small services (e.g., separate service for each method)
  • Fix: Merge related services (e.g., PaymentService + RefundService → PaymentService)
  • Quick test: If a service has <3 methods and low complexity, consider merging

Definition of Done

  • God Class is identified (cyclomatic complexity >20, >500 lines, or >10 methods)
  • Responsibility clusters are detected programmatically (shared dependencies)
  • Each cluster is extracted into a new service class with focused responsibility
  • Original class delegates to new services via constructor injection
  • All tests pass after refactoring (behavior is preserved)
  • Cyclomatic complexity is reduced by >50%
  • Each new service has <10 methods and single responsibility
  • Test coverage remains the same or improves