
Refactoring Surgeon
- 185 installs
- 178 repo stars
- Updated July 14, 2026
- erichowens/some_claude_skills
Execute precise, low-risk refactors across a codebase—extract modules, rename safely, simplify conditionals, and remove dead paths—while preserving behavior and keeping diffs reviewable.
About
Acts as a disciplined refactoring specialist that improves internal code quality without feature churn. Emphasizes incremental transformations, explicit invariants, regression safety, and readable PRs for SaaS APIs, CLIs, and shared libraries maintained by senior engineers.
- Behavior-preserving structural changes
- Small reviewable diffs
- Dead code and smell removal
- Safe extraction and renaming
- Test-aligned refactor sequencing
Refactoring Surgeon by the numbers
- 185 all-time installs (skills.sh)
- Ranked #350 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/erichowens/some_claude_skills --skill refactoring-surgeonAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 185 |
|---|---|
| repo stars | ★ 178 |
| Last updated | July 14, 2026 |
| Repository | erichowens/some_claude_skills ↗ |
What it does
Execute precise, low-risk refactors across a codebase—extract modules, rename safely, simplify conditionals, and remove dead paths—while preserving behavior and keeping diffs reviewable.
Files
Refactoring Surgeon
Expert code refactoring specialist focused on improving code quality without changing behavior.
Quick Start
1. Ensure tests exist - Never refactor without a safety net 2. Identify the smell - Name the specific code smell you're addressing 3. Make small changes - One refactoring at a time, commit frequently 4. Run tests after each change - Behavior must remain identical 5. Don't add features - Refactoring ≠ enhancement 6. Document significant changes - Explain the "why" for future maintainers
Core Capabilities
| Category | Techniques |
|---|---|
| Extraction | Extract Method, Extract Class, Extract Interface |
| Movement | Move Method, Move Field, Inline Method |
| Simplification | Replace Conditional with Polymorphism, Decompose Conditional |
| Organization | Introduce Parameter Object, Replace Magic Numbers |
| Legacy Migration | Strangler Fig, Branch by Abstraction, Parallel Change |
Code Smells Reference
Bloaters
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Long Method │ │ Large Class │ │ Long Parameter │
│ > 20 lines? │ │ > 200 lines? │ │ List │
│ → Extract Method │ │ → Extract Class │ │ → Parameter Object │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘OO Abusers
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Switch Statements │ │ Refused Bequest │ │ Parallel │
│ Type-checking? │ │ Unused inheritance?│ │ Hierarchies │
│ → Polymorphism │ │ → Delegation │ │ → Move Method │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘Change Preventers
┌─────────────────────┐ ┌─────────────────────┐
│ Divergent Change │ │ Shotgun Surgery │
│ One class, many │ │ One change, many │
│ reasons to change? │ │ classes affected? │
│ → Extract Class │ │ → Move/Inline │
└─────────────────────┘ └─────────────────────┘Reference Examples
Complete refactoring examples in ./references/:
| File | Pattern | Use Case |
|---|---|---|
extract-method.ts | Extract Method | Long methods → focused functions |
replace-conditional-polymorphism.ts | Replace Conditional | switch/if → polymorphic classes |
introduce-parameter-object.ts | Parameter Object | Long params → structured objects |
strangler-fig-pattern.ts | Strangler Fig | Legacy code → gradual migration |
Anti-Patterns (10 Critical Mistakes)
1. Big Bang Refactoring
Symptom: Rewriting entire modules in one massive change Fix: Strangler fig pattern, small incremental changes with tests
2. Refactoring Without Tests
Symptom: Changing structure without test coverage Fix: Write characterization tests first, add coverage for affected areas
3. Premature Abstraction
Symptom: Creating generic frameworks "for future flexibility" Fix: Wait for three concrete examples before abstracting (Rule of Three)
4. Renaming Without IDE Support
Symptom: Find-and-replace that misses occurrences Fix: Use IDE refactoring tools, search for usages first
5. Mixing Refactoring and Features
Symptom: Adding new functionality while restructuring Fix: Separate commits - refactor first, then add features
6. Ignoring Code Reviews
Symptom: Large refactoring PRs that are hard to review Fix: Small, focused PRs with clear commit messages
7. Over-Abstracting
Symptom: Three layers of abstraction for a simple operation Fix: YAGNI - start concrete, abstract when patterns emerge
8. Incomplete Refactoring
Symptom: Starting Extract Method but leaving partial duplication Fix: Complete the refactoring or revert - no half-measures
9. Refactoring Production During Incidents
Symptom: "I'll just clean this up while I'm here..." Fix: Never refactor during incidents - fix the bug, create a ticket
10. Not Measuring Improvement
Symptom: Refactoring without knowing if it helped Fix: Track metrics: complexity, test coverage, build time
Safety Checklist
Before Refactoring:
- [ ] Code compiles/runs successfully
- [ ] All tests pass
- [ ] Test coverage is adequate for area being refactored
- [ ] Commit current state (can rollback)
During Refactoring:
- [ ] Make small, incremental changes
- [ ] Run tests after each change
- [ ] Keep behavior identical
- [ ] Don't add features while refactoring
After Refactoring:
- [ ] All tests still pass
- [ ] No new warnings/errors
- [ ] Code is more readable
- [ ] Complexity metrics improved
- [ ] Document significant changes
Quality Checklist
- [ ] No behavior changes (tests prove this)
- [ ] Improved readability
- [ ] Reduced complexity (cyclomatic, cognitive)
- [ ] Better adherence to SOLID principles
- [ ] Removed duplication (DRY)
- [ ] More testable code
- [ ] Clear naming
- [ ] Appropriate abstractions (not over-engineered)
Validation Script
Run ./scripts/validate-refactoring.sh to check:
- Test coverage presence
- Code smell indicators
- Duplication patterns
- Complexity metrics
- SOLID violations
- Refactoring safety (git, uncommitted changes)
External Resources
Changelog
All notable changes to the refactoring-surgeon skill will be documented in this file.
[2.0.0] - 2024-12-13
Changed
- BREAKING: Restructured SKILL.md from 655 lines to ~170 lines for progressive disclosure
- Moved all large code examples to
./references/directory - Expanded anti-patterns section from 5 to 10 patterns
Added
references/extract-method.ts- Complete Extract Method example with before/afterreferences/replace-conditional-polymorphism.ts- Polymorphism refactoring with Factory patternreferences/introduce-parameter-object.ts- Parameter Object pattern with Builder variantreferences/strangler-fig-pattern.ts- Legacy code migration strategy with parallel runscripts/validate-refactoring.sh- Pre-refactoring validation script- Version field in frontmatter for skill tracking
Improved
- Anti-patterns section now covers 10 common refactoring mistakes
- Safety checklist expanded with before/during/after phases
- Better cross-references to code smell diagrams
[1.0.0] - 2024-01-01
Added
- Initial refactoring-surgeon skill
- Code smell detection guidance (Bloaters, OO Abusers, Change Preventers)
- Extract Method, Replace Conditional, Introduce Parameter Object examples
- Strangler Fig pattern for legacy migration
- Refactoring safety checklist
// Extract Method Refactoring Example
// Transform long methods into focused, single-responsibility functions
// =============================================================================
// BEFORE: Long method with multiple responsibilities
// =============================================================================
// ❌ Long method with multiple responsibilities
function processOrderBefore(order: Order): OrderResult {
// Validate order
if (!order.items || order.items.length === 0) {
throw new Error('Order must have items');
}
if (!order.customer) {
throw new Error('Order must have customer');
}
if (!order.customer.email) {
throw new Error('Customer must have email');
}
// Calculate totals
let subtotal = 0;
for (const item of order.items) {
subtotal += item.price * item.quantity;
}
const tax = subtotal * 0.08;
const shipping = subtotal > 100 ? 0 : 10;
const total = subtotal + tax + shipping;
// Apply discounts
let discount = 0;
if (order.customer.loyaltyTier === 'gold') {
discount = total * 0.1;
} else if (order.customer.loyaltyTier === 'silver') {
discount = total * 0.05;
}
const finalTotal = total - discount;
// Create order record
const orderRecord = {
id: generateId(),
items: order.items,
customer: order.customer,
subtotal,
tax,
shipping,
discount,
total: finalTotal,
status: 'pending',
createdAt: new Date(),
};
// Send confirmation email
const emailContent = `
Dear ${order.customer.name},
Thank you for your order #${orderRecord.id}.
Items: ${order.items.length}
Subtotal: $${subtotal.toFixed(2)}
Tax: $${tax.toFixed(2)}
Shipping: $${shipping.toFixed(2)}
Discount: -$${discount.toFixed(2)}
Total: $${finalTotal.toFixed(2)}
Best regards
`;
sendEmail(order.customer.email, 'Order Confirmation', emailContent);
return orderRecord;
}
// =============================================================================
// AFTER: Clean, single-responsibility functions
// =============================================================================
// ✅ Main orchestration function - reads like a story
function processOrder(order: Order): OrderResult {
validateOrder(order);
const pricing = calculatePricing(order);
const discount = calculateLoyaltyDiscount(order.customer, pricing.total);
const finalTotal = pricing.total - discount;
const orderRecord = createOrderRecord(order, pricing, discount, finalTotal);
sendOrderConfirmation(order.customer, orderRecord, pricing, discount);
return orderRecord;
}
// ✅ Focused validation with custom error type
function validateOrder(order: Order): void {
if (!order.items?.length) {
throw new OrderValidationError('Order must have items');
}
if (!order.customer) {
throw new OrderValidationError('Order must have customer');
}
if (!order.customer.email) {
throw new OrderValidationError('Customer must have email');
}
}
// ✅ Clear interface for pricing data
interface OrderPricing {
subtotal: number;
tax: number;
shipping: number;
total: number;
}
// ✅ Pricing calculation with extracted sub-functions
function calculatePricing(order: Order): OrderPricing {
const subtotal = calculateSubtotal(order.items);
const tax = calculateTax(subtotal);
const shipping = calculateShipping(subtotal);
return {
subtotal,
tax,
shipping,
total: subtotal + tax + shipping,
};
}
// ✅ Pure function - easy to test
function calculateSubtotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// ✅ Constants replace magic numbers
function calculateTax(subtotal: number): number {
const TAX_RATE = 0.08;
return subtotal * TAX_RATE;
}
// ✅ Business rules are clear
function calculateShipping(subtotal: number): number {
const FREE_SHIPPING_THRESHOLD = 100;
const STANDARD_SHIPPING = 10;
return subtotal > FREE_SHIPPING_THRESHOLD ? 0 : STANDARD_SHIPPING;
}
// ✅ Lookup table replaces conditional chain
const LOYALTY_DISCOUNTS: Record<LoyaltyTier, number> = {
gold: 0.10,
silver: 0.05,
bronze: 0,
none: 0,
};
function calculateLoyaltyDiscount(customer: Customer, total: number): number {
const discountRate = LOYALTY_DISCOUNTS[customer.loyaltyTier] ?? 0;
return total * discountRate;
}
// ✅ Factory function with clear parameters
function createOrderRecord(
order: Order,
pricing: OrderPricing,
discount: number,
finalTotal: number
): OrderRecord {
return {
id: generateId(),
items: order.items,
customer: order.customer,
...pricing,
discount,
total: finalTotal,
status: 'pending',
createdAt: new Date(),
};
}
// ✅ Email building extracted for testability
function sendOrderConfirmation(
customer: Customer,
orderRecord: OrderRecord,
pricing: OrderPricing,
discount: number
): void {
const emailContent = buildOrderConfirmationEmail(
customer,
orderRecord,
pricing,
discount
);
sendEmail(customer.email, 'Order Confirmation', emailContent);
}
// =============================================================================
// Key Benefits
// =============================================================================
// 1. Each function has ONE responsibility
// 2. Functions are small enough to understand at a glance
// 3. Business rules (tax rate, shipping threshold) are named constants
// 4. Easy to test each function in isolation
// 5. Main function reads like documentation
// 6. Changes to one concern don't affect others
// =============================================================================
// Type Definitions (for completeness)
// =============================================================================
interface Order {
items: OrderItem[];
customer: Customer;
}
interface OrderItem {
price: number;
quantity: number;
}
interface Customer {
name: string;
email: string;
loyaltyTier: LoyaltyTier;
}
type LoyaltyTier = 'gold' | 'silver' | 'bronze' | 'none';
interface OrderRecord {
id: string;
items: OrderItem[];
customer: Customer;
subtotal: number;
tax: number;
shipping: number;
discount: number;
total: number;
status: string;
createdAt: Date;
}
type OrderResult = OrderRecord;
class OrderValidationError extends Error {
constructor(message: string) {
super(message);
this.name = 'OrderValidationError';
}
}
// Stubs for external dependencies
declare function generateId(): string;
declare function sendEmail(to: string, subject: string, body: string): void;
declare function buildOrderConfirmationEmail(
customer: Customer,
order: OrderRecord,
pricing: OrderPricing,
discount: number
): string;
// Introduce Parameter Object
// Replace long parameter lists with structured objects
// =============================================================================
// BEFORE: Long parameter list
// =============================================================================
// ❌ Long parameter list - hard to read, easy to make mistakes
function searchProductsBefore(
query: string,
category: string,
minPrice: number,
maxPrice: number,
inStock: boolean,
sortBy: string,
sortOrder: 'asc' | 'desc',
page: number,
pageSize: number
): Product[] {
// Implementation...
return [];
}
// Calling code is hard to read and error-prone
const productsBefore = searchProductsBefore(
'laptop', // query
'electronics', // category
500, // minPrice? maxPrice? Who knows!
2000, // Is this min or max?
true, // What is this boolean?
'price', // sortBy
'asc', // sortOrder
1, // page
20 // pageSize
);
// Problems:
// 1. Easy to swap min/max by accident
// 2. Boolean without context
// 3. Can't skip optional parameters
// 4. Hard to add new parameters
// 5. No IDE autocomplete help
// =============================================================================
// AFTER: Parameter objects with clear intent
// =============================================================================
// ✅ Separate "what to find" from "how to return it"
interface ProductSearchCriteria {
query: string;
category?: string;
priceRange?: {
min?: number;
max?: number;
};
inStockOnly?: boolean;
brand?: string;
rating?: {
min?: number;
};
}
interface ProductSearchOptions {
sortBy?: 'price' | 'name' | 'rating' | 'date' | 'relevance';
sortOrder?: 'asc' | 'desc';
pagination?: {
page: number;
pageSize: number;
};
}
interface ProductSearchResult {
products: Product[];
total: number;
page: number;
pageSize: number;
hasMore: boolean;
}
// ✅ Clean function signature
function searchProducts(
criteria: ProductSearchCriteria,
options: ProductSearchOptions = {}
): ProductSearchResult {
// Destructure with defaults
const {
sortBy = 'relevance',
sortOrder = 'desc',
pagination = { page: 1, pageSize: 20 }
} = options;
// Implementation uses clear, named properties
let results = findProducts(criteria.query);
if (criteria.category) {
results = results.filter(p => p.category === criteria.category);
}
if (criteria.priceRange) {
const { min = 0, max = Infinity } = criteria.priceRange;
results = results.filter(p => p.price >= min && p.price <= max);
}
if (criteria.inStockOnly) {
results = results.filter(p => p.inStock);
}
// Sort and paginate
results = sortProducts(results, sortBy, sortOrder);
const paged = paginateResults(results, pagination);
return paged;
}
// ✅ Calling code is self-documenting
const products = searchProducts(
{
query: 'laptop',
category: 'electronics',
priceRange: { min: 500, max: 2000 },
inStockOnly: true,
},
{
sortBy: 'price',
sortOrder: 'asc',
pagination: { page: 1, pageSize: 20 },
}
);
// ✅ Easy to use partial criteria
const simpleSearch = searchProducts({ query: 'keyboard' });
const categoryBrowse = searchProducts(
{ query: '', category: 'monitors' },
{ sortBy: 'rating' }
);
// =============================================================================
// Builder Pattern for Complex Objects
// =============================================================================
// For even more complex scenarios, use a builder
class ProductSearchBuilder {
private criteria: ProductSearchCriteria = { query: '' };
private options: ProductSearchOptions = {};
query(query: string): this {
this.criteria.query = query;
return this;
}
category(category: string): this {
this.criteria.category = category;
return this;
}
priceRange(min?: number, max?: number): this {
this.criteria.priceRange = { min, max };
return this;
}
inStockOnly(): this {
this.criteria.inStockOnly = true;
return this;
}
sortBy(field: ProductSearchOptions['sortBy'], order: 'asc' | 'desc' = 'desc'): this {
this.options.sortBy = field;
this.options.sortOrder = order;
return this;
}
page(page: number, pageSize: number = 20): this {
this.options.pagination = { page, pageSize };
return this;
}
execute(): ProductSearchResult {
return searchProducts(this.criteria, this.options);
}
}
// ✅ Fluent interface for complex searches
const builderSearch = new ProductSearchBuilder()
.query('laptop')
.category('electronics')
.priceRange(500, 2000)
.inStockOnly()
.sortBy('price', 'asc')
.page(1, 20)
.execute();
// =============================================================================
// Validation with Parameter Objects
// =============================================================================
// Parameter objects make validation cleaner too
function validateSearchCriteria(criteria: ProductSearchCriteria): void {
if (!criteria.query && !criteria.category) {
throw new Error('Must provide query or category');
}
if (criteria.priceRange) {
const { min = 0, max = Infinity } = criteria.priceRange;
if (min < 0) throw new Error('Min price cannot be negative');
if (max < min) throw new Error('Max price must be >= min price');
}
if (criteria.rating?.min !== undefined) {
if (criteria.rating.min < 0 || criteria.rating.min > 5) {
throw new Error('Rating must be between 0 and 5');
}
}
}
// =============================================================================
// When to Use Parameter Objects
// =============================================================================
/*
✅ USE WHEN:
- 3+ parameters (some say 2+)
- Parameters are logically related
- Same parameter groups appear in multiple functions
- Optional parameters create awkward signatures
- You want to add parameters without breaking callers
❌ DON'T USE WHEN:
- Only 1-2 simple parameters
- Parameters are truly independent
- Would create one-off objects with no reuse
- Function is private/internal with few callers
BONUS BENEFITS:
1. Easier to test - create test fixtures
2. Easier to document - describe the object shape
3. Easier to validate - centralized validation
4. Easier to evolve - add properties without breaking
5. IDE autocomplete - shows available options
*/
// =============================================================================
// Type Definitions
// =============================================================================
interface Product {
id: string;
name: string;
category: string;
price: number;
inStock: boolean;
rating: number;
brand: string;
}
// Stub functions
declare function findProducts(query: string): Product[];
declare function sortProducts(
products: Product[],
sortBy: string,
sortOrder: 'asc' | 'desc'
): Product[];
declare function paginateResults(
products: Product[],
pagination: { page: number; pageSize: number }
): ProductSearchResult;
// Replace Conditional with Polymorphism
// Transform type-checking switch/if statements into polymorphic classes
// =============================================================================
// BEFORE: Type checking with switch/if
// =============================================================================
// ❌ Type checking with switch/if - violates Open/Closed Principle
class EmployeeBefore {
type: 'engineer' | 'manager' | 'salesperson';
baseSalary: number;
commission?: number;
teamSize?: number;
calculatePay(): number {
switch (this.type) {
case 'engineer':
return this.baseSalary;
case 'manager':
return this.baseSalary + (this.teamSize ?? 0) * 100;
case 'salesperson':
return this.baseSalary + (this.commission ?? 0);
default:
throw new Error('Unknown employee type');
}
}
getTitle(): string {
switch (this.type) {
case 'engineer':
return 'Software Engineer';
case 'manager':
return 'Engineering Manager';
case 'salesperson':
return 'Sales Representative';
default:
return 'Employee';
}
}
// Every new employee type requires modifying EVERY switch statement
// This violates the Open/Closed Principle
}
// =============================================================================
// AFTER: Polymorphic solution
// =============================================================================
// ✅ Abstract base class defines the contract
abstract class Employee {
constructor(protected baseSalary: number) {}
abstract calculatePay(): number;
abstract getTitle(): string;
// Shared behavior stays in base class
getBaseSalary(): number {
return this.baseSalary;
}
}
// ✅ Each type is its own class with specific behavior
class Engineer extends Employee {
calculatePay(): number {
return this.baseSalary;
}
getTitle(): string {
return 'Software Engineer';
}
}
class Manager extends Employee {
constructor(baseSalary: number, private teamSize: number) {
super(baseSalary);
}
calculatePay(): number {
const BONUS_PER_REPORT = 100;
return this.baseSalary + this.teamSize * BONUS_PER_REPORT;
}
getTitle(): string {
return 'Engineering Manager';
}
// Manager-specific methods
getTeamSize(): number {
return this.teamSize;
}
}
class Salesperson extends Employee {
constructor(baseSalary: number, private commission: number) {
super(baseSalary);
}
calculatePay(): number {
return this.baseSalary + this.commission;
}
getTitle(): string {
return 'Sales Representative';
}
// Salesperson-specific methods
getCommission(): number {
return this.commission;
}
}
// ✅ Factory centralizes creation logic - ONE place for type switching
interface EmployeeData {
baseSalary: number;
teamSize?: number;
commission?: number;
}
class EmployeeFactory {
static create(type: string, data: EmployeeData): Employee {
switch (type) {
case 'engineer':
return new Engineer(data.baseSalary);
case 'manager':
return new Manager(data.baseSalary, data.teamSize ?? 0);
case 'salesperson':
return new Salesperson(data.baseSalary, data.commission ?? 0);
default:
throw new Error(`Unknown employee type: ${type}`);
}
}
}
// =============================================================================
// Adding New Types - The Payoff
// =============================================================================
// ✅ Adding a new type is EASY - just add a new class
class Contractor extends Employee {
constructor(
baseSalary: number,
private hourlyRate: number,
private hoursWorked: number
) {
super(baseSalary);
}
calculatePay(): number {
return this.hourlyRate * this.hoursWorked;
}
getTitle(): string {
return 'Independent Contractor';
}
}
// Just update the factory
class EmployeeFactoryV2 {
static create(type: string, data: any): Employee {
switch (type) {
case 'engineer':
return new Engineer(data.baseSalary);
case 'manager':
return new Manager(data.baseSalary, data.teamSize ?? 0);
case 'salesperson':
return new Salesperson(data.baseSalary, data.commission ?? 0);
case 'contractor':
return new Contractor(0, data.hourlyRate, data.hoursWorked);
default:
throw new Error(`Unknown employee type: ${type}`);
}
}
}
// =============================================================================
// Usage Examples
// =============================================================================
function demonstratePolymorphism() {
// Create different employee types
const employees: Employee[] = [
EmployeeFactory.create('engineer', { baseSalary: 100000 }),
EmployeeFactory.create('manager', { baseSalary: 120000, teamSize: 5 }),
EmployeeFactory.create('salesperson', { baseSalary: 60000, commission: 25000 }),
];
// Polymorphic behavior - no type checking needed!
for (const employee of employees) {
console.log(`${employee.getTitle()}: $${employee.calculatePay()}`);
}
// Output:
// Software Engineer: $100000
// Engineering Manager: $120500
// Sales Representative: $85000
// Calculate total payroll - works with ANY employee type
const totalPayroll = employees.reduce(
(sum, emp) => sum + emp.calculatePay(),
0
);
console.log(`Total Payroll: $${totalPayroll}`);
}
// =============================================================================
// When to Use This Pattern
// =============================================================================
/*
✅ USE WHEN:
- Same switch/if on type appears in multiple places
- Each type has different behavior for multiple operations
- New types are added frequently
- You want to follow Open/Closed Principle
❌ DON'T USE WHEN:
- Switch appears in only one place
- Types rarely change
- Behavior differences are minimal
- Would create too many small classes
RULE OF THUMB:
If you have switch(type) in 3+ places, or adding new types
requires changing multiple files, consider polymorphism.
*/
// =============================================================================
// Alternative: Strategy Pattern for Behavior Injection
// =============================================================================
// When you need to swap behavior at runtime, use Strategy
interface PayCalculationStrategy {
calculate(baseSalary: number, context: any): number;
}
class HourlyPayStrategy implements PayCalculationStrategy {
calculate(baseSalary: number, context: { hoursWorked: number }): number {
const HOURLY_RATE = baseSalary / 2080; // Annual to hourly
return HOURLY_RATE * context.hoursWorked;
}
}
class SalaryPayStrategy implements PayCalculationStrategy {
calculate(baseSalary: number): number {
return baseSalary / 12; // Monthly salary
}
}
class EmployeeWithStrategy {
constructor(
private baseSalary: number,
private payStrategy: PayCalculationStrategy
) {}
calculateMonthlyPay(context?: any): number {
return this.payStrategy.calculate(this.baseSalary, context);
}
// Can change strategy at runtime
setPayStrategy(strategy: PayCalculationStrategy): void {
this.payStrategy = strategy;
}
}
// Strangler Fig Pattern
// Gradually replace legacy code without big-bang rewrites
// =============================================================================
// THE PROBLEM: Monolithic Legacy Code
// =============================================================================
// ❌ Monolithic order processor - 500+ lines of tangled logic
class LegacyOrderProcessor {
processOrder(orderData: any): any {
// Validation mixed with business logic
if (!orderData.items) throw new Error('No items');
if (!orderData.customer) throw new Error('No customer');
// Pricing calculation intertwined
let total = 0;
for (const item of orderData.items) {
// Complex pricing rules embedded here
let price = item.basePrice;
if (orderData.customer.type === 'wholesale') {
price *= 0.8;
}
if (item.quantity > 10) {
price *= 0.95;
}
total += price * item.quantity;
}
// Inventory check with database calls
// Payment processing with external API calls
// Shipping calculation with carrier integration
// Email notification with template rendering
// Audit logging
// ... 400 more lines of tightly coupled code
return { orderId: 'generated', total };
}
}
// Problems:
// 1. Can't test individual components
// 2. Can't modify one thing without risk to everything
// 3. Can't understand what it does without reading all 500 lines
// 4. Can't rewrite all at once (too risky)
// =============================================================================
// STRANGLER FIG PATTERN: Step-by-Step Migration
// =============================================================================
// Step 1: Create a facade that delegates to legacy
// -------------------------------------------------
class OrderProcessorV1 {
private legacy = new LegacyOrderProcessor();
async processOrder(order: Order): Promise<OrderResult> {
// Initially just delegate - establishes the new interface
const legacyResult = this.legacy.processOrder(order);
return this.adaptLegacyResult(legacyResult);
}
private adaptLegacyResult(legacy: any): OrderResult {
return {
orderId: legacy.orderId,
total: legacy.total,
status: 'completed',
};
}
}
// Step 2: Extract first component (validation)
// -------------------------------------------------
class OrderValidator {
validate(order: Order): ValidationResult {
const errors: string[] = [];
if (!order.items?.length) {
errors.push('Order must have items');
}
if (!order.customer) {
errors.push('Order must have customer');
}
if (!order.customer?.email) {
errors.push('Customer must have email');
}
// Can add new validations without touching legacy
if (order.items?.some(item => item.quantity <= 0)) {
errors.push('All items must have positive quantity');
}
return {
valid: errors.length === 0,
errors,
};
}
}
class OrderProcessorV2 {
private legacy = new LegacyOrderProcessor();
private validator = new OrderValidator(); // NEW!
async processOrder(order: Order): Promise<OrderResult> {
// Use NEW validation
const validation = this.validator.validate(order);
if (!validation.valid) {
throw new ValidationError(validation.errors);
}
// Still delegate rest to legacy
const legacyResult = this.legacy.processOrder(order);
return this.adaptLegacyResult(legacyResult);
}
private adaptLegacyResult(legacy: any): OrderResult {
return {
orderId: legacy.orderId,
total: legacy.total,
status: 'completed',
};
}
}
// Step 3: Extract pricing service
// -------------------------------------------------
interface PricingResult {
subtotal: number;
tax: number;
shipping: number;
discount: number;
total: number;
}
class PricingService {
calculate(order: Order): PricingResult {
const subtotal = this.calculateSubtotal(order);
const discount = this.calculateDiscount(order, subtotal);
const tax = this.calculateTax(subtotal - discount);
const shipping = this.calculateShipping(order);
return {
subtotal,
tax,
shipping,
discount,
total: subtotal - discount + tax + shipping,
};
}
private calculateSubtotal(order: Order): number {
return order.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
}
private calculateDiscount(order: Order, subtotal: number): number {
// Wholesale discount
if (order.customer.type === 'wholesale') {
return subtotal * 0.2;
}
// Volume discount
const totalItems = order.items.reduce((sum, i) => sum + i.quantity, 0);
if (totalItems > 10) {
return subtotal * 0.05;
}
return 0;
}
private calculateTax(taxableAmount: number): number {
return taxableAmount * 0.08;
}
private calculateShipping(order: Order): number {
const weight = order.items.reduce(
(sum, i) => sum + (i.weight || 0) * i.quantity,
0
);
if (weight === 0) return 0;
if (weight < 5) return 5.99;
if (weight < 20) return 12.99;
return 24.99;
}
}
class OrderProcessorV3 {
private legacy = new LegacyOrderProcessor();
private validator = new OrderValidator();
private pricingService = new PricingService(); // NEW!
async processOrder(order: Order): Promise<OrderResult> {
const validation = this.validator.validate(order);
if (!validation.valid) {
throw new ValidationError(validation.errors);
}
// Use NEW pricing
const pricing = this.pricingService.calculate(order);
// Delegate rest to legacy, but pass in our pricing
const legacyResult = this.legacy.processOrderWithPricing(order, pricing);
return this.adaptLegacyResult(legacyResult, pricing);
}
private adaptLegacyResult(legacy: any, pricing: PricingResult): OrderResult {
return {
orderId: legacy.orderId,
total: pricing.total,
status: 'completed',
};
}
}
// Step 4: Continue extracting until legacy is gone
// -------------------------------------------------
// Fully migrated - no more legacy!
class OrderProcessor {
constructor(
private validator: OrderValidator,
private pricingService: PricingService,
private inventoryService: InventoryService,
private paymentService: PaymentService,
private shippingService: ShippingService,
private notificationService: NotificationService,
private auditService: AuditService
) {}
async processOrder(order: Order): Promise<OrderResult> {
// Validate
const validation = this.validator.validate(order);
if (!validation.valid) {
throw new ValidationError(validation.errors);
}
// Calculate pricing
const pricing = this.pricingService.calculate(order);
// Reserve inventory
const inventoryReservation = await this.inventoryService.reserve(order.items);
try {
// Process payment
const payment = await this.paymentService.charge(
order.customer,
pricing.total
);
// Schedule shipping
const shipping = await this.shippingService.schedule(order);
// Create order record
const orderResult = await this.createOrder(order, pricing, payment, shipping);
// Send confirmation
await this.notificationService.sendConfirmation(order.customer, orderResult);
// Audit log
await this.auditService.log('order_created', orderResult);
return orderResult;
} catch (error) {
// Release inventory on failure
await this.inventoryService.release(inventoryReservation);
throw error;
}
}
private async createOrder(
order: Order,
pricing: PricingResult,
payment: PaymentResult,
shipping: ShippingResult
): Promise<OrderResult> {
return {
orderId: generateOrderId(),
total: pricing.total,
status: 'confirmed',
paymentId: payment.transactionId,
trackingNumber: shipping.trackingNumber,
};
}
}
// =============================================================================
// BRANCH BY ABSTRACTION: Feature Flag Approach
// =============================================================================
// Use feature flags to safely switch between old and new
class OrderProcessorWithFeatureFlag {
constructor(
private legacy: LegacyOrderProcessor,
private newProcessor: OrderProcessor,
private featureFlags: FeatureFlags
) {}
async processOrder(order: Order): Promise<OrderResult> {
// Gradual rollout by customer segment
const useNewProcessor = this.featureFlags.isEnabled(
'new_order_processor',
{ customerId: order.customer.id }
);
if (useNewProcessor) {
return this.newProcessor.processOrder(order);
} else {
const legacyResult = this.legacy.processOrder(order);
return this.adaptResult(legacyResult);
}
}
private adaptResult(legacy: any): OrderResult {
return {
orderId: legacy.orderId,
total: legacy.total,
status: 'completed',
};
}
}
// =============================================================================
// PARALLEL RUN: Verify New System
// =============================================================================
// Run both systems and compare results (shadow mode)
class OrderProcessorParallelRun {
constructor(
private legacy: LegacyOrderProcessor,
private newProcessor: OrderProcessor,
private comparisonLogger: ComparisonLogger
) {}
async processOrder(order: Order): Promise<OrderResult> {
// Always use legacy for real result
const legacyResult = this.legacy.processOrder(order);
// Run new processor in shadow mode (don't affect real state)
try {
const newResult = await this.newProcessor.processOrderDryRun(order);
// Log comparison for analysis
this.comparisonLogger.logComparison({
orderId: legacyResult.orderId,
legacyTotal: legacyResult.total,
newTotal: newResult.total,
match: Math.abs(legacyResult.total - newResult.total) < 0.01,
});
} catch (error) {
this.comparisonLogger.logError({
orderId: legacyResult.orderId,
error: error.message,
});
}
return this.adaptResult(legacyResult);
}
private adaptResult(legacy: any): OrderResult {
return {
orderId: legacy.orderId,
total: legacy.total,
status: 'completed',
};
}
}
// =============================================================================
// Type Definitions
// =============================================================================
interface Order {
customer: Customer;
items: OrderItem[];
}
interface Customer {
id: string;
email: string;
type: 'retail' | 'wholesale';
}
interface OrderItem {
productId: string;
price: number;
quantity: number;
weight?: number;
}
interface OrderResult {
orderId: string;
total: number;
status: string;
paymentId?: string;
trackingNumber?: string;
}
interface ValidationResult {
valid: boolean;
errors: string[];
}
interface PaymentResult {
transactionId: string;
}
interface ShippingResult {
trackingNumber: string;
}
class ValidationError extends Error {
constructor(public errors: string[]) {
super(errors.join(', '));
this.name = 'ValidationError';
}
}
// Service interfaces
interface InventoryService {
reserve(items: OrderItem[]): Promise<string>;
release(reservationId: string): Promise<void>;
}
interface PaymentService {
charge(customer: Customer, amount: number): Promise<PaymentResult>;
}
interface ShippingService {
schedule(order: Order): Promise<ShippingResult>;
}
interface NotificationService {
sendConfirmation(customer: Customer, order: OrderResult): Promise<void>;
}
interface AuditService {
log(event: string, data: any): Promise<void>;
}
interface FeatureFlags {
isEnabled(flag: string, context?: any): boolean;
}
interface ComparisonLogger {
logComparison(data: any): void;
logError(data: any): void;
}
declare function generateOrderId(): string;
#!/bin/bash
# Refactoring Surgeon Skill Validation Script
# Validates code for refactoring readiness and quality
set -e
ERRORS=0
WARNINGS=0
echo "═══════════════════════════════════════════════════════════════"
echo "Refactoring Surgeon Validator"
echo "═══════════════════════════════════════════════════════════════"
echo ""
# Check for test coverage before refactoring
check_test_coverage() {
echo "🧪 Checking test coverage..."
# Look for test files
test_count=0
for pattern in "*.test.ts" "*.test.js" "*.spec.ts" "*.spec.js" "__tests__/*.ts" "__tests__/*.js"; do
count=$(find . -name "$pattern" 2>/dev/null | wc -l)
test_count=$((test_count + count))
done
if [ "$test_count" -eq 0 ]; then
echo "❌ ERROR: No test files found - refactoring without tests is risky!"
((ERRORS++))
else
echo " Found $test_count test files"
fi
# Check for test runner config
if [ -f "jest.config.js" ] || [ -f "jest.config.ts" ] || [ -f "vitest.config.ts" ]; then
echo " ✅ Test runner configured"
elif grep -q '"test"' package.json 2>/dev/null; then
echo " ✅ Test script found in package.json"
else
echo "⚠️ WARN: No test runner configuration found"
((WARNINGS++))
fi
}
# Check for code smells
check_code_smells() {
echo ""
echo "👃 Checking for code smells..."
# Long files (potential god classes)
echo " Checking for long files..."
long_files=$(find . -name "*.ts" -o -name "*.js" 2>/dev/null | while read -r file; do
lines=$(wc -l < "$file" 2>/dev/null || echo 0)
if [ "$lines" -gt 300 ]; then
echo "$file ($lines lines)"
fi
done)
if [ -n "$long_files" ]; then
echo "⚠️ WARN: Files over 300 lines (potential god classes):"
echo "$long_files" | sed 's/^/ /'
((WARNINGS++))
fi
# Long functions (check for function declarations with many lines)
echo " Checking for long functions..."
# This is a simple heuristic - real analysis would need AST parsing
# Check for switch statements (potential polymorphism candidates)
switch_count=$(grep -r "switch\s*(" --include="*.ts" --include="*.js" . 2>/dev/null | wc -l)
if [ "$switch_count" -gt 5 ]; then
echo "⚠️ WARN: Found $switch_count switch statements - consider Replace Conditional with Polymorphism"
((WARNINGS++))
fi
# Check for long parameter lists
long_params=$(grep -rE "function\s+\w+\s*\([^)]{100,}\)" --include="*.ts" --include="*.js" . 2>/dev/null | wc -l)
if [ "$long_params" -gt 0 ]; then
echo "⚠️ WARN: Found $long_params functions with long parameter lists - consider Introduce Parameter Object"
((WARNINGS++))
fi
}
# Check for duplication
check_duplication() {
echo ""
echo "📋 Checking for code duplication..."
# Simple check for identical consecutive lines (very basic)
dup_count=0
for file in $(find . -name "*.ts" -o -name "*.js" 2>/dev/null | head -20); do
# Check for blocks that repeat
if [ -f "$file" ]; then
repeats=$(sort "$file" | uniq -d | wc -l)
if [ "$repeats" -gt 10 ]; then
((dup_count++))
fi
fi
done
if [ "$dup_count" -gt 0 ]; then
echo "⚠️ WARN: $dup_count files may have duplicated code"
((WARNINGS++))
else
echo " No obvious duplication detected"
fi
# Check for copy-paste comments
if grep -rE "(copy|paste|duplicate|TODO.*refactor)" --include="*.ts" --include="*.js" . 2>/dev/null | grep -v node_modules | head -5 | grep -q .; then
echo "⚠️ WARN: Found comments mentioning copy/paste or refactoring TODOs"
((WARNINGS++))
fi
}
# Check for complexity indicators
check_complexity() {
echo ""
echo "🔄 Checking complexity indicators..."
# Nested callbacks (callback hell)
nested=$(grep -rE "\)\s*=>\s*\{" --include="*.ts" --include="*.js" . 2>/dev/null | wc -l)
if [ "$nested" -gt 50 ]; then
echo "⚠️ WARN: High arrow function usage ($nested) - check for callback nesting"
((WARNINGS++))
fi
# Deep nesting (multiple levels of indentation)
deep_nesting=$(grep -rE "^\s{16,}" --include="*.ts" --include="*.js" . 2>/dev/null | wc -l)
if [ "$deep_nesting" -gt 20 ]; then
echo "⚠️ WARN: Found $deep_nesting deeply nested lines (4+ levels) - consider Extract Method"
((WARNINGS++))
fi
# Multiple return statements
multi_return=$(grep -rE "return\s" --include="*.ts" --include="*.js" . 2>/dev/null | wc -l)
echo " Found $multi_return return statements across codebase"
}
# Check SOLID principles violations
check_solid_violations() {
echo ""
echo "📐 Checking for SOLID violations..."
# Single Responsibility: files doing too many things
# Check for files with many different imports (heuristic)
# Open/Closed: check for type checking
type_checks=$(grep -rE "typeof|instanceof" --include="*.ts" --include="*.js" . 2>/dev/null | wc -l)
if [ "$type_checks" -gt 20 ]; then
echo "⚠️ WARN: $type_checks type checks found - may violate Open/Closed Principle"
((WARNINGS++))
fi
# Dependency Inversion: check for direct instantiation
new_calls=$(grep -rE "new\s+[A-Z]" --include="*.ts" --include="*.js" . 2>/dev/null | grep -v "Error\|Date\|Map\|Set\|Promise\|Array" | wc -l)
if [ "$new_calls" -gt 30 ]; then
echo "⚠️ WARN: $new_calls direct instantiations - consider dependency injection"
((WARNINGS++))
fi
}
# Check refactoring safety
check_refactoring_safety() {
echo ""
echo "🔒 Checking refactoring safety..."
# Check for version control
if [ -d ".git" ]; then
echo " ✅ Git repository found"
# Check for uncommitted changes
if git diff --quiet 2>/dev/null && git diff --staged --quiet 2>/dev/null; then
echo " ✅ No uncommitted changes"
else
echo "⚠️ WARN: Uncommitted changes - commit before refactoring!"
((WARNINGS++))
fi
else
echo "❌ ERROR: No git repository - can't safely refactor without version control"
((ERRORS++))
fi
# Check for CI/CD
if [ -f ".github/workflows/ci.yml" ] || [ -f ".github/workflows/test.yml" ] || [ -f ".gitlab-ci.yml" ]; then
echo " ✅ CI/CD configuration found"
else
echo "⚠️ WARN: No CI/CD found - tests may not run automatically"
((WARNINGS++))
fi
}
# Check for magic numbers and strings
check_magic_values() {
echo ""
echo "🔢 Checking for magic numbers and strings..."
# Magic numbers (excluding common ones like 0, 1, 2)
magic_nums=$(grep -rE "[^0-9][3-9][0-9]{2,}[^0-9]" --include="*.ts" --include="*.js" . 2>/dev/null | grep -v "node_modules\|\.d\.ts" | wc -l)
if [ "$magic_nums" -gt 10 ]; then
echo "⚠️ WARN: Found $magic_nums potential magic numbers - consider named constants"
((WARNINGS++))
fi
}
# Run all checks
check_test_coverage
check_code_smells
check_duplication
check_complexity
check_solid_violations
check_refactoring_safety
check_magic_values
# Summary
echo ""
echo "═══════════════════════════════════════════════════════════════"
echo "Validation Complete"
echo "═══════════════════════════════════════════════════════════════"
echo "Errors: $ERRORS"
echo "Warnings: $WARNINGS"
echo ""
if [ $ERRORS -gt 0 ]; then
echo "❌ Validation FAILED - address errors before refactoring"
exit 1
elif [ $WARNINGS -gt 5 ]; then
echo "⚠️ Validation PASSED with warnings - many refactoring opportunities!"
exit 0
else
echo "✅ Validation PASSED - code is in good shape"
exit 0
fi