Skip to content

Refactoring Guide

This comprehensive guide covers refactoring techniques, tools, and best practices for improving code quality and maintainability in Trae.

Introduction to Refactoring

What is Refactoring?

Refactoring is the process of restructuring existing code without changing its external behavior. The goal is to improve code readability, reduce complexity, and enhance maintainability while preserving functionality.

Benefits of Refactoring

  • Improved Code Quality: Cleaner, more readable code
  • Enhanced Maintainability: Easier to modify and extend
  • Reduced Technical Debt: Eliminates code smells and anti-patterns
  • Better Performance: Optimized algorithms and data structures
  • Increased Developer Productivity: Easier to understand and work with

When to Refactor

  • Before adding new features
  • When fixing bugs
  • During code reviews
  • When code becomes difficult to understand
  • When performance issues arise
  • As part of regular maintenance

Refactoring Tools in Trae

Built-in Refactoring Features

Rename Symbol

typescript
// Before refactoring
function calculateTotal(items: Item[]): number {
    let sum = 0;
    for (const item of items) {
        sum += item.price;
    }
    return sum;
}

// After renaming 'sum' to 'total'
function calculateTotal(items: Item[]): number {
    let total = 0;
    for (const item of items) {
        total += item.price;
    }
    return total;
}

How to use:

  1. Right-click on symbol → "Rename Symbol" (F2)
  2. Type new name
  3. Press Enter to apply changes across all files

Extract Method

typescript
// Before extraction
class OrderProcessor {
    processOrder(order: Order): void {
        // 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');
        }
        
        // Calculate total
        let total = 0;
        for (const item of order.items) {
            total += item.price * item.quantity;
        }
        order.total = total;
        
        // Save order
        this.saveOrder(order);
    }
}

// After extracting methods
class OrderProcessor {
    processOrder(order: Order): void {
        this.validateOrder(order);
        this.calculateTotal(order);
        this.saveOrder(order);
    }
    
    private validateOrder(order: Order): void {
        if (!order.items || order.items.length === 0) {
            throw new Error('Order must have items');
        }
        if (!order.customer) {
            throw new Error('Order must have customer');
        }
    }
    
    private calculateTotal(order: Order): void {
        let total = 0;
        for (const item of order.items) {
            total += item.price * item.quantity;
        }
        order.total = total;
    }
}

How to use:

  1. Select code block
  2. Right-click → "Extract Method" (Ctrl+Shift+R)
  3. Enter method name
  4. Review and confirm changes

Extract Variable

typescript
// Before extraction
function calculateDiscount(order: Order): number {
    return order.total * 0.1 > 100 ? 100 : order.total * 0.1;
}

// After extracting variable
function calculateDiscount(order: Order): number {
    const discountRate = 0.1;
    const maxDiscount = 100;
    const calculatedDiscount = order.total * discountRate;
    return calculatedDiscount > maxDiscount ? maxDiscount : calculatedDiscount;
}

Move Symbol

typescript
// Before moving
// file: utils.ts
export function formatCurrency(amount: number): string {
    return `$${amount.toFixed(2)}`;
}

// After moving to currency.ts
// file: currency.ts
export function formatCurrency(amount: number): string {
    return `$${amount.toFixed(2)}`;
}

Advanced Refactoring Tools

Inline Variable/Method

typescript
// Before inlining
function processPayment(amount: number): boolean {
    const isValidAmount = amount > 0;
    if (isValidAmount) {
        return this.chargeCard(amount);
    }
    return false;
}

// After inlining variable
function processPayment(amount: number): boolean {
    if (amount > 0) {
        return this.chargeCard(amount);
    }
    return false;
}

Change Method Signature

typescript
// Before signature change
function createUser(name: string, email: string, age: number): User {
    return new User(name, email, age);
}

// After adding optional parameter
function createUser(name: string, email: string, age: number, role?: string): User {
    return new User(name, email, age, role || 'user');
}

Convert to Arrow Function

typescript
// Before conversion
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(function(n) {
    return n * 2;
});

// After conversion
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2);

Common Refactoring Patterns

1. Extract Class

typescript
// Before: God class with too many responsibilities
class User {
    name: string;
    email: string;
    address: string;
    city: string;
    zipCode: string;
    
    constructor(name: string, email: string, address: string, city: string, zipCode: string) {
        this.name = name;
        this.email = email;
        this.address = address;
        this.city = city;
        this.zipCode = zipCode;
    }
    
    validateEmail(): boolean {
        return this.email.includes('@');
    }
    
    formatAddress(): string {
        return `${this.address}, ${this.city} ${this.zipCode}`;
    }
    
    sendEmail(message: string): void {
        // Email sending logic
    }
}

// After: Extracted Address class
class Address {
    constructor(
        public street: string,
        public city: string,
        public zipCode: string
    ) {}
    
    format(): string {
        return `${this.street}, ${this.city} ${this.zipCode}`;
    }
}

class User {
    constructor(
        public name: string,
        public email: string,
        public address: Address
    ) {}
    
    validateEmail(): boolean {
        return this.email.includes('@');
    }
    
    sendEmail(message: string): void {
        // Email sending logic
    }
}

2. Replace Conditional with Polymorphism

typescript
// Before: Complex conditional logic
class PaymentProcessor {
    processPayment(type: string, amount: number): boolean {
        if (type === 'credit') {
            // Credit card processing logic
            return this.processCreditCard(amount);
        } else if (type === 'debit') {
            // Debit card processing logic
            return this.processDebitCard(amount);
        } else if (type === 'paypal') {
            // PayPal processing logic
            return this.processPayPal(amount);
        }
        throw new Error('Unknown payment type');
    }
}

// After: Using polymorphism
abstract class PaymentMethod {
    abstract process(amount: number): boolean;
}

class CreditCardPayment extends PaymentMethod {
    process(amount: number): boolean {
        // Credit card processing logic
        return true;
    }
}

class DebitCardPayment extends PaymentMethod {
    process(amount: number): boolean {
        // Debit card processing logic
        return true;
    }
}

class PayPalPayment extends PaymentMethod {
    process(amount: number): boolean {
        // PayPal processing logic
        return true;
    }
}

class PaymentProcessor {
    processPayment(paymentMethod: PaymentMethod, amount: number): boolean {
        return paymentMethod.process(amount);
    }
}

3. Replace Magic Numbers with Constants

typescript
// Before: Magic numbers
class TaxCalculator {
    calculateTax(amount: number, state: string): number {
        if (state === 'CA') {
            return amount * 0.0875;
        } else if (state === 'NY') {
            return amount * 0.08;
        } else if (state === 'TX') {
            return amount * 0.0625;
        }
        return amount * 0.05;
    }
}

// After: Named constants
class TaxCalculator {
    private static readonly TAX_RATES = {
        CA: 0.0875,
        NY: 0.08,
        TX: 0.0625,
        DEFAULT: 0.05
    };
    
    calculateTax(amount: number, state: string): number {
        const rate = TaxCalculator.TAX_RATES[state] || TaxCalculator.TAX_RATES.DEFAULT;
        return amount * rate;
    }
}

4. Introduce Parameter Object

typescript
// Before: Too many parameters
function createUser(
    firstName: string,
    lastName: string,
    email: string,
    phone: string,
    address: string,
    city: string,
    state: string,
    zipCode: string
): User {
    // Implementation
}

// After: Parameter object
interface UserData {
    firstName: string;
    lastName: string;
    email: string;
    phone: string;
    address: string;
    city: string;
    state: string;
    zipCode: string;
}

function createUser(userData: UserData): User {
    // Implementation
}

5. Replace Nested Conditionals with Guard Clauses

typescript
// Before: Nested conditionals
function processOrder(order: Order): void {
    if (order) {
        if (order.items && order.items.length > 0) {
            if (order.customer) {
                if (order.customer.isActive) {
                    // Process order
                    this.doProcessOrder(order);
                } else {
                    throw new Error('Customer is not active');
                }
            } else {
                throw new Error('Order must have a customer');
            }
        } else {
            throw new Error('Order must have items');
        }
    } else {
        throw new Error('Order cannot be null');
    }
}

// After: Guard clauses
function processOrder(order: Order): void {
    if (!order) {
        throw new Error('Order cannot be null');
    }
    
    if (!order.items || order.items.length === 0) {
        throw new Error('Order must have items');
    }
    
    if (!order.customer) {
        throw new Error('Order must have a customer');
    }
    
    if (!order.customer.isActive) {
        throw new Error('Customer is not active');
    }
    
    this.doProcessOrder(order);
}

Code Smells and Solutions

1. Long Method

Problem: Methods that are too long and do too much.

Solution: Extract smaller methods.

typescript
// Before: Long method
class ReportGenerator {
    generateReport(data: any[]): string {
        // Data validation (20 lines)
        // Data processing (30 lines)
        // Formatting (25 lines)
        // Output generation (15 lines)
        // Total: 90 lines
    }
}

// After: Extracted methods
class ReportGenerator {
    generateReport(data: any[]): string {
        const validatedData = this.validateData(data);
        const processedData = this.processData(validatedData);
        const formattedData = this.formatData(processedData);
        return this.generateOutput(formattedData);
    }
    
    private validateData(data: any[]): any[] {
        // Validation logic
    }
    
    private processData(data: any[]): any[] {
        // Processing logic
    }
    
    private formatData(data: any[]): any[] {
        // Formatting logic
    }
    
    private generateOutput(data: any[]): string {
        // Output generation logic
    }
}

2. Duplicate Code

Problem: Same code appears in multiple places.

Solution: Extract common functionality.

typescript
// Before: Duplicate code
class UserService {
    createUser(userData: UserData): User {
        // Validation
        if (!userData.email || !userData.email.includes('@')) {
            throw new Error('Invalid email');
        }
        if (!userData.name || userData.name.length < 2) {
            throw new Error('Invalid name');
        }
        
        return new User(userData);
    }
    
    updateUser(id: string, userData: UserData): User {
        // Same validation code repeated
        if (!userData.email || !userData.email.includes('@')) {
            throw new Error('Invalid email');
        }
        if (!userData.name || userData.name.length < 2) {
            throw new Error('Invalid name');
        }
        
        const user = this.findUser(id);
        user.update(userData);
        return user;
    }
}

// After: Extracted validation
class UserService {
    createUser(userData: UserData): User {
        this.validateUserData(userData);
        return new User(userData);
    }
    
    updateUser(id: string, userData: UserData): User {
        this.validateUserData(userData);
        const user = this.findUser(id);
        user.update(userData);
        return user;
    }
    
    private validateUserData(userData: UserData): void {
        if (!userData.email || !userData.email.includes('@')) {
            throw new Error('Invalid email');
        }
        if (!userData.name || userData.name.length < 2) {
            throw new Error('Invalid name');
        }
    }
}

3. Large Class

Problem: Classes that have too many responsibilities.

Solution: Split into smaller, focused classes.

typescript
// Before: Large class
class OrderManager {
    // Order management
    createOrder(orderData: OrderData): Order { /* ... */ }
    updateOrder(id: string, orderData: OrderData): Order { /* ... */ }
    deleteOrder(id: string): void { /* ... */ }
    
    // Payment processing
    processPayment(orderId: string, paymentData: PaymentData): boolean { /* ... */ }
    refundPayment(orderId: string): boolean { /* ... */ }
    
    // Inventory management
    checkInventory(items: Item[]): boolean { /* ... */ }
    updateInventory(items: Item[]): void { /* ... */ }
    
    // Notification
    sendOrderConfirmation(order: Order): void { /* ... */ }
    sendShippingNotification(order: Order): void { /* ... */ }
}

// After: Separated concerns
class OrderService {
    constructor(
        private paymentService: PaymentService,
        private inventoryService: InventoryService,
        private notificationService: NotificationService
    ) {}
    
    createOrder(orderData: OrderData): Order {
        const order = new Order(orderData);
        this.inventoryService.checkInventory(order.items);
        this.paymentService.processPayment(order.id, orderData.payment);
        this.notificationService.sendOrderConfirmation(order);
        return order;
    }
}

class PaymentService {
    processPayment(orderId: string, paymentData: PaymentData): boolean { /* ... */ }
    refundPayment(orderId: string): boolean { /* ... */ }
}

class InventoryService {
    checkInventory(items: Item[]): boolean { /* ... */ }
    updateInventory(items: Item[]): void { /* ... */ }
}

class NotificationService {
    sendOrderConfirmation(order: Order): void { /* ... */ }
    sendShippingNotification(order: Order): void { /* ... */ }
}

Automated Refactoring

Using Trae's Refactoring API

typescript
// Extension for automated refactoring
import * as trae from '@trae/extension-api';

class AutoRefactorer {
    async refactorLongMethods(): Promise<void> {
        const editor = trae.window.activeTextEditor;
        if (!editor) return;
        
        const document = editor.document;
        const text = document.getText();
        
        // Analyze code and find long methods
        const longMethods = this.findLongMethods(text);
        
        for (const method of longMethods) {
            await this.extractMethodParts(editor, method);
        }
    }
    
    private findLongMethods(text: string): MethodInfo[] {
        // Implementation to find methods longer than threshold
        return [];
    }
    
    private async extractMethodParts(editor: trae.TextEditor, method: MethodInfo): Promise<void> {
        // Implementation to extract method parts
    }
}

Custom Refactoring Rules

typescript
// Define custom refactoring rules
interface RefactoringRule {
    name: string;
    description: string;
    pattern: RegExp;
    replacement: string | ((match: string) => string);
}

class CustomRefactoringEngine {
    private rules: RefactoringRule[] = [
        {
            name: 'Replace var with const/let',
            description: 'Replace var declarations with const or let',
            pattern: /var\s+(\w+)\s*=/g,
            replacement: 'const $1 ='
        },
        {
            name: 'Convert to template literals',
            description: 'Convert string concatenation to template literals',
            pattern: /"([^"]*)"\s*\+\s*([^\+]+)\s*\+\s*"([^"]*)"/g,
            replacement: '`$1${$2}$3`'
        }
    ];
    
    applyRules(text: string): string {
        let result = text;
        for (const rule of this.rules) {
            result = result.replace(rule.pattern, rule.replacement as string);
        }
        return result;
    }
}

Testing During Refactoring

Test-Driven Refactoring

typescript
// 1. Write tests first
describe('OrderService', () => {
    let orderService: OrderService;
    
    beforeEach(() => {
        orderService = new OrderService();
    });
    
    test('should create order with valid data', () => {
        const orderData = {
            items: [{ id: '1', quantity: 2, price: 10 }],
            customer: { id: '1', name: 'John Doe' }
        };
        
        const order = orderService.createOrder(orderData);
        
        expect(order).toBeDefined();
        expect(order.total).toBe(20);
    });
    
    test('should throw error for invalid order data', () => {
        const invalidOrderData = {
            items: [],
            customer: null
        };
        
        expect(() => orderService.createOrder(invalidOrderData))
            .toThrow('Invalid order data');
    });
});

// 2. Refactor while keeping tests green
class OrderService {
    createOrder(orderData: OrderData): Order {
        this.validateOrderData(orderData);
        const order = new Order(orderData);
        this.calculateTotal(order);
        return order;
    }
    
    private validateOrderData(orderData: OrderData): void {
        if (!orderData.items || orderData.items.length === 0) {
            throw new Error('Invalid order data');
        }
        if (!orderData.customer) {
            throw new Error('Invalid order data');
        }
    }
    
    private calculateTotal(order: Order): void {
        order.total = order.items.reduce((sum, item) => {
            return sum + (item.price * item.quantity);
        }, 0);
    }
}

Regression Testing

typescript
// Automated regression testing
class RegressionTester {
    async runRegressionTests(): Promise<TestResult[]> {
        const testSuites = await this.discoverTestSuites();
        const results: TestResult[] = [];
        
        for (const suite of testSuites) {
            const result = await this.runTestSuite(suite);
            results.push(result);
            
            if (!result.passed) {
                console.error(`Regression detected in ${suite.name}`);
                break;
            }
        }
        
        return results;
    }
    
    private async discoverTestSuites(): Promise<TestSuite[]> {
        // Implementation to discover test suites
        return [];
    }
    
    private async runTestSuite(suite: TestSuite): Promise<TestResult> {
        // Implementation to run test suite
        return { passed: true, suite: suite.name, errors: [] };
    }
}

Performance Considerations

Measuring Performance Impact

typescript
// Performance monitoring during refactoring
class PerformanceMonitor {
    private metrics: Map<string, number[]> = new Map();
    
    measureFunction<T>(name: string, fn: () => T): T {
        const start = performance.now();
        const result = fn();
        const end = performance.now();
        
        const duration = end - start;
        if (!this.metrics.has(name)) {
            this.metrics.set(name, []);
        }
        this.metrics.get(name)!.push(duration);
        
        return result;
    }
    
    getAverageTime(name: string): number {
        const times = this.metrics.get(name) || [];
        return times.reduce((sum, time) => sum + time, 0) / times.length;
    }
    
    comparePerformance(oldName: string, newName: string): void {
        const oldAvg = this.getAverageTime(oldName);
        const newAvg = this.getAverageTime(newName);
        const improvement = ((oldAvg - newAvg) / oldAvg) * 100;
        
        console.log(`Performance change: ${improvement.toFixed(2)}%`);
    }
}

// Usage
const monitor = new PerformanceMonitor();

// Before refactoring
monitor.measureFunction('oldImplementation', () => {
    return oldFunction(data);
});

// After refactoring
monitor.measureFunction('newImplementation', () => {
    return newFunction(data);
});

monitor.comparePerformance('oldImplementation', 'newImplementation');

Best Practices

1. Refactor in Small Steps

  • Make one change at a time
  • Run tests after each change
  • Commit frequently
  • Use version control effectively

2. Maintain Test Coverage

typescript
// Ensure tests cover refactored code
class TestCoverageChecker {
    async checkCoverage(filePath: string): Promise<CoverageReport> {
        // Run tests and collect coverage data
        const coverage = await this.runCoverageAnalysis(filePath);
        
        if (coverage.percentage < 80) {
            console.warn(`Low test coverage: ${coverage.percentage}%`);
        }
        
        return coverage;
    }
}

3. Document Refactoring Decisions

typescript
/**
 * Refactored: 2024-01-15
 * Reason: Extracted payment processing logic to improve separation of concerns
 * Previous implementation: Single OrderService class handled all operations
 * Current implementation: Separate PaymentService for payment-related operations
 * 
 * Breaking changes: None (internal refactoring only)
 * Performance impact: Negligible
 * Test coverage: Maintained at 95%
 */
class PaymentService {
    // Implementation
}

4. Use Static Analysis Tools

json
// .eslintrc.json - Configure linting rules
{
  "extends": ["@trae/eslint-config"],
  "rules": {
    "max-lines-per-function": ["error", 50],
    "max-params": ["error", 4],
    "complexity": ["error", 10],
    "max-depth": ["error", 4]
  }
}

Refactoring Checklist

Before Refactoring

  • [ ] Understand the existing code
  • [ ] Ensure comprehensive test coverage
  • [ ] Create a backup/branch
  • [ ] Identify the refactoring goal
  • [ ] Plan the refactoring steps

During Refactoring

  • [ ] Make small, incremental changes
  • [ ] Run tests after each change
  • [ ] Maintain functionality
  • [ ] Update documentation
  • [ ] Review code quality metrics

After Refactoring

  • [ ] Verify all tests pass
  • [ ] Check performance impact
  • [ ] Update documentation
  • [ ] Code review
  • [ ] Deploy and monitor

Common Pitfalls

1. Over-refactoring

Problem: Making unnecessary changes that don't improve the code.

Solution: Have clear goals and stop when they're achieved.

2. Breaking Functionality

Problem: Changing behavior while refactoring.

Solution: Maintain comprehensive tests and run them frequently.

3. Ignoring Performance

Problem: Refactoring that degrades performance.

Solution: Monitor performance metrics before and after refactoring.

4. Incomplete Refactoring

Problem: Leaving the codebase in an inconsistent state.

Solution: Complete the refactoring or revert changes.

Tools and Extensions

  • Trae Refactor Pro: Advanced refactoring tools
  • Code Metrics: Complexity analysis
  • Test Coverage: Coverage reporting
  • Performance Profiler: Performance monitoring

External Tools

  • SonarQube: Code quality analysis
  • ESLint: JavaScript/TypeScript linting
  • Prettier: Code formatting
  • Jest: Testing framework

Resources

Books

  • "Refactoring: Improving the Design of Existing Code" by Martin Fowler
  • "Clean Code" by Robert C. Martin
  • "Working Effectively with Legacy Code" by Michael Feathers

Online Resources

Your Ultimate AI-Powered IDE Learning Guide