Surgical code refactoring to improve maintainability without changing behavior. Covers extracting functions, renaming variables, breaking down god functions, improving type safety, eliminating code smells, and applying design patterns. Less drastic than repo-rebuilder; use for gradual improvements.
Surgical code refactoring to improve maintainability without changing behavior. Covers extracting functions, renaming variables, breaking down god functions, improving type safety, eliminating code smells, and applying design patterns. Less drastic than repo-rebuilder; use for gradual improvements.
license
MIT
Refactor
Overview
Improve code structure and readability without changing external behavior. Refactoring is gradual evolution, not revolution. Use this for improving existing code, not rewriting from scratch.
When to Use
Choose the mode that matches your goal:
Mode
Trigger
Changes
Structural Refactor
Functions too large, god objects, design smells
Function extraction, class decomposition, design patterns
Simplification Mode
Names unclear, logic hard to follow, no structural change needed
Rename, inline, clarify — no structural reshaping
Performance Mode
Measured bottleneck exists, profiler data available
Targeted optimization with before/after benchmark
Use this skill when:
Code is hard to understand or maintain
Functions/classes are too large
Code smells need addressing
Adding features is difficult due to code structure
User asks "clean up this code", "refactor this", "improve this"
Refactoring Principles
The Golden Rules
Behavior is preserved - Refactoring doesn't change what the code does, only how
Small steps - Make tiny changes, test after each
Version control is your friend - Commit before and after each safe state
Tests are essential - Without tests, you're not refactoring, you're editing
One thing at a time - Don't mix refactoring with feature changes
When NOT to Refactor
- Code that works and won't change again (if it ain't broke...)
- Critical production code without tests (add tests first)
- When you're under a tight deadline
- "Just because" - need a clear purpose
# BAD: Same logic in multiple places
- function calculateUserDiscount(user) {- if (user.membership === 'gold') return user.total * 0.2;- if (user.membership === 'silver') return user.total * 0.1;- return 0;- }-- function calculateOrderDiscount(order) {- if (order.user.membership === 'gold') return order.total * 0.2;- if (order.user.membership === 'silver') return order.total * 0.1;- return 0;- }
# GOOD: Extract common logic
+ function getMembershipDiscountRate(membership) {+ const rates = { gold: 0.2, silver: 0.1 };+ return rates[membership] || 0;+ }++ function calculateUserDiscount(user) {+ return user.total * getMembershipDiscountRate(user.membership);+ }++ function calculateOrderDiscount(order) {+ return order.total * getMembershipDiscountRate(order.user.membership);+ }
3. Large Class/Module
# BAD: God object that knows too much
- class UserManager {- createUser() { /* ... */ }- updateUser() { /* ... */ }- deleteUser() { /* ... */ }- sendEmail() { /* ... */ }- generateReport() { /* ... */ }- handlePayment() { /* ... */ }- validateAddress() { /* ... */ }- // 50 more methods...- }
# GOOD: Single responsibility per class
+ class UserService {+ create(data) { /* ... */ }+ update(id, data) { /* ... */ }+ delete(id) { /* ... */ }+ }++ class EmailService {+ send(to, subject, body) { /* ... */ }+ }++ class ReportService {+ generate(type, params) { /* ... */ }+ }++ class PaymentService {+ process(amount, method) { /* ... */ }+ }
4. Long Parameter List
# BAD: Too many parameters
- function createUser(email, password, name, age, address, city, country, phone) {- /* ... */- }
# GOOD: Group related parameters
+ interface UserData {+ email: string;+ password: string;+ name: string;+ age?: number;+ address?: Address;+ phone?: string;+ }++ function createUser(data: UserData) {+ /* ... */+ }
# EVEN BETTER: Use builder pattern for complex construction
+ const user = UserBuilder+ .email('test@example.com')+ .password('secure123')+ .name('Test User')+ .address(address)+ .build();
5. Feature Envy
# BAD: Method that uses another object's data more than its own
- class Order {- calculateDiscount(user) {- if (user.membershipLevel === 'gold') {+ return this.total * 0.2;+ }+ if (user.accountAge > 365) {+ return this.total * 0.1;+ }+ return 0;+ }+ }
# GOOD: Move logic to the object that owns the data
+ class User {+ getDiscountRate(orderTotal) {+ if (this.membershipLevel === 'gold') return 0.2;+ if (this.accountAge > 365) return 0.1;+ return 0;+ }+ }++ class Order {+ calculateDiscount(user) {+ return this.total * user.getDiscountRate(this.total);+ }+ }
6. Primitive Obsession
# BAD: Using primitives for domain concepts
- function sendEmail(to, subject, body) { /* ... */ }- sendEmail('user@example.com', 'Hello', '...');- function createPhone(country, number) {- return `${country}-${number}`;- }
# GOOD: Use domain types
+ class Email {+ private constructor(public readonly value: string) {+ if (!Email.isValid(value)) throw new Error('Invalid email');+ }+ static create(value: string) { return new Email(value); }+ static isValid(email: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); }+ }++ class PhoneNumber {+ constructor(+ public readonly country: string,+ public readonly number: string+ ) {+ if (!PhoneNumber.isValid(country, number)) throw new Error('Invalid phone');+ }+ toString() { return `${this.country}-${this.number}`; }+ static isValid(country: string, number: string) { /* ... */ }+ }++ // Usage+ const email = Email.create('user@example.com');+ const phone = new PhoneNumber('1', '555-1234');
# BAD: Arrow code
- function process(order) {- if (order) {- if (order.user) {- if (order.user.isActive) {- if (order.total > 0) {- return processOrder(order);+ } else {+ return { error: 'Invalid total' };+ }+ } else {+ return { error: 'User inactive' };+ }+ } else {+ return { error: 'No user' };+ }+ } else {+ return { error: 'No order' };+ }+ }
# GOOD: Guard clauses / early returns
+ function process(order) {+ if (!order) return { error: 'No order' };+ if (!order.user) return { error: 'No user' };+ if (!order.user.isActive) return { error: 'User inactive' };+ if (order.total <= 0) return { error: 'Invalid total' };+ return processOrder(order);+ }
# EVEN BETTER: Using Result type
+ function process(order): Result<ProcessedOrder, Error> {+ return Result.combine([+ validateOrderExists(order),+ validateUserExists(order),+ validateUserActive(order.user),+ validateOrderTotal(order)+ ]).flatMap(() => processOrder(order));+ }
9. Dead Code
# BAD: Unused code lingers
- function oldImplementation() { /* ... */ }- const DEPRECATED_VALUE = 5;- import { unusedThing } from './somewhere';- // Commented out code- // function oldCode() { /* ... */ }
# GOOD: Remove it
+ // Delete unused functions, imports, and commented code+ // If you need it again, git history has it
10. Inappropriate Intimacy
# BAD: One class reaches deep into another
- class OrderProcessor {- process(order) {- order.user.profile.address.street; // Too intimate- order.repository.connection.config; // Breaking encapsulation+ }+ }
# GOOD: Ask, don't tell
+ class OrderProcessor {+ process(order) {+ order.getShippingAddress(); // Order knows how to get it+ order.save(); // Order knows how to save itself+ }+ }
# Before: Nested validation
- function validate(user) {- const errors = [];- if (!user.email) errors.push('Email required');+ else if (!isValidEmail(user.email)) errors.push('Invalid email');+ if (!user.name) errors.push('Name required');+ if (user.age < 18) errors.push('Must be 18+');+ if (user.country === 'blocked') errors.push('Country not supported');+ return errors;+ }
# After: Chain of responsibility
+ abstract class Validator {+ abstract validate(user: User): string | null;+ setNext(validator: Validator): Validator {+ this.next = validator;+ return validator;+ }+ validate(user: User): string | null {+ const error = this.doValidate(user);+ if (error) return error;+ return this.next?.validate(user) ?? null;+ }+ }++ class EmailRequiredValidator extends Validator {+ doValidate(user: User) {+ return !user.email ? 'Email required' : null;+ }+ }++ class EmailFormatValidator extends Validator {+ doValidate(user: User) {+ return user.email && !isValidEmail(user.email) ? 'Invalid email' : null;+ }+ }++ // Build the chain+ const validator = new EmailRequiredValidator()+ .setNext(new EmailFormatValidator())+ .setNext(new NameRequiredValidator())+ .setNext(new AgeValidator())+ .setNext(new CountryValidator());
Refactoring Steps
Safe Refactoring Process
1. PREPARE
- Ensure tests exist (write them if missing)
- Commit current state
- Create feature branch
2. IDENTIFY
- Find the code smell to address
- Understand what the code does
- Plan the refactoring
3. REFACTOR (small steps)
- Make one small change
- Run tests
- Commit if tests pass
- Repeat
4. VERIFY
- All tests pass
- Manual testing if needed
- Performance unchanged or improved
5. CLEAN UP
- Update comments
- Update documentation
- Final commit
Refactoring Checklist
Code Quality
Functions are small (< 50 lines)
Functions do one thing
No duplicated code
Descriptive names (variables, functions, classes)
No magic numbers/strings
Dead code removed
Structure
Related code is together
Clear module boundaries
Dependencies flow in one direction
No circular dependencies
Type Safety
Types defined for all public APIs
No any types without justification
Nullable types explicitly marked
Testing
Refactored code is tested
Tests cover edge cases
All tests pass
Common Refactoring Operations
Operation
Description
Extract Method
Turn code fragment into method
Extract Class
Move behavior to new class
Extract Interface
Create interface from implementation
Inline Method
Move method body back to caller
Inline Class
Move class behavior to caller
Pull Up Method
Move method to superclass
Push Down Method
Move method to subclass
Rename Method/Variable
Improve clarity
Introduce Parameter Object
Group related parameters
Replace Conditional with Polymorphism
Use polymorphism instead of switch/if
Replace Magic Number with Constant
Named constants
Decompose Conditional
Break complex conditions
Consolidate Conditional
Combine duplicate conditions
Replace Nested Conditional with Guard Clauses
Early returns
Introduce Null Object
Eliminate null checks
Replace Type Code with Class/Enum
Strong typing
Replace Inheritance with Delegation
Composition over inheritance
Simplification Mode(輕量模式)
Use when code is hard to read but structure is sound — improve naming, reduce cognitive load, and inline redundancy. Do NOT reshape structure or add new logic.
Difference from Structural Refactor:
Structural Refactor
Simplification Mode
Extract functions, decompose classes
Rename variables, inline trivial helpers
Change call hierarchies
Improve comments, remove noise
Apply design patterns
Reorder code within a function
Chesterton's Fence Principle
Before removing any code, you MUST understand why it exists. If you don't know why it's there, don't remove it.
Steps to apply:
Read the code — understand the original intent
Check git history (git log -p) for context
Search for callers or side effects that might not be obvious
Only then decide: simplify, document, or leave it
Rule of 500
If a single function exceeds 500 lines: do NOT attempt crude deletion. Use automated refactoring tools (IDE rename/extract, rope for Python, Roslyn for C#) to split safely.
Prerequisite: Tests First
If no tests exist for the target code:
Add minimal coverage first (happy path + error path)
Confirm tests pass (Green)
Then simplify
Verification
All existing tests pass (dotnet test / pytest / npm test, exit code 0)
Build succeeds with no new warnings
No feature logic or structural changes mixed into this commit
git diff HEAD --stat confirms only renaming / inline / clarity changes
Performance Mode(效能優化模式)
Hard prerequisite — Measure First: Do NOT touch performance-related code without measurement data.
"Premature optimization is the root of all evil." — Knuth