Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Surgical code refactoring to improve maintainability—extracting functions, removing code smells, improving types.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: refactor3description: '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.'4license: MIT5---67# Refactor89## Overview1011Improve code structure and readability without changing external behavior. Refactoring is gradual evolution, not revolution. Use this for improving existing code, not rewriting from scratch.1213## When to Use1415Use this skill when:1617- Code is hard to understand or maintain18- Functions/classes are too large19- Code smells need addressing20- Adding features is difficult due to code structure21- User asks "clean up this code", "refactor this", "improve this"2223---2425## Refactoring Principles2627### The Golden Rules28291. **Behavior is preserved** - Refactoring doesn't change what the code does, only how302. **Small steps** - Make tiny changes, test after each313. **Version control is your friend** - Commit before and after each safe state324. **Tests are essential** - Without tests, you're not refactoring, you're editing335. **One thing at a time** - Don't mix refactoring with feature changes3435### When NOT to Refactor3637```38- Code that works and won't change again (if it ain't broke...)39- Critical production code without tests (add tests first)40- When you're under a tight deadline41- "Just because" - need a clear purpose42```4344---4546## Common Code Smells & Fixes4748### 1. Long Method/Function4950```diff51# BAD: 200-line function that does everything52- async function processOrder(orderId) {53- // 50 lines: fetch order54- // 30 lines: validate order55- // 40 lines: calculate pricing56- // 30 lines: update inventory57- // 20 lines: create shipment58- // 30 lines: send notifications59- }6061# GOOD: Broken into focused functions62+ async function processOrder(orderId) {63+ const order = await fetchOrder(orderId);64+ validateOrder(order);65+ const pricing = calculatePricing(order);66+ await updateInventory(order);67+ const shipment = await createShipment(order);68+ await sendNotifications(order, pricing, shipment);69+ return { order, pricing, shipment };70+ }71```7273### 2. Duplicated Code7475```diff76# BAD: Same logic in multiple places77- function calculateUserDiscount(user) {78- if (user.membership === 'gold') return user.total * 0.2;79- if (user.membership === 'silver') return user.total * 0.1;80- return 0;81- }82-83- function calculateOrderDiscount(order) {84- if (order.user.membership === 'gold') return order.total * 0.2;85- if (order.user.membership === 'silver') return order.total * 0.1;86- return 0;87- }8889# GOOD: Extract common logic90+ function getMembershipDiscountRate(membership) {91+ const rates = { gold: 0.2, silver: 0.1 };92+ return rates[membership] || 0;93+ }94+95+ function calculateUserDiscount(user) {96+ return user.total * getMembershipDiscountRate(user.membership);97+ }98+99+ function calculateOrderDiscount(order) {100+ return order.total * getMembershipDiscountRate(order.user.membership);101+ }102```103104### 3. Large Class/Module105106```diff107# BAD: God object that knows too much108- class UserManager {109- createUser() { /* ... */ }110- updateUser() { /* ... */ }111- deleteUser() { /* ... */ }112- sendEmail() { /* ... */ }113- generateReport() { /* ... */ }114- handlePayment() { /* ... */ }115- validateAddress() { /* ... */ }116- // 50 more methods...117- }118119# GOOD: Single responsibility per class120+ class UserService {121+ create(data) { /* ... */ }122+ update(id, data) { /* ... */ }123+ delete(id) { /* ... */ }124+ }125+126+ class EmailService {127+ send(to, subject, body) { /* ... */ }128+ }129+130+ class ReportService {131+ generate(type, params) { /* ... */ }132+ }133+134+ class PaymentService {135+ process(amount, method) { /* ... */ }136+ }137```138139### 4. Long Parameter List140141```diff142# BAD: Too many parameters143- function createUser(email, password, name, age, address, city, country, phone) {144- /* ... */145- }146147# GOOD: Group related parameters148+ interface UserData {149+ email: string;150+ password: string;151+ name: string;152+ age?: number;153+ address?: Address;154+ phone?: string;155+ }156+157+ function createUser(data: UserData) {158+ /* ... */159+ }160161# EVEN BETTER: Use builder pattern for complex construction162+ const user = UserBuilder163+ .email('[email protected]')164+ .password('secure123')165+ .name('Test User')166+ .address(address)167+ .build();168```169170### 5. Feature Envy171172```diff173# BAD: Method that uses another object's data more than its own174- class Order {175- calculateDiscount(user) {176- if (user.membershipLevel === 'gold') {177+ return this.total * 0.2;178+ }179+ if (user.accountAge > 365) {180+ return this.total * 0.1;181+ }182+ return 0;183+ }184+ }185186# GOOD: Move logic to the object that owns the data187+ class User {188+ getDiscountRate(orderTotal) {189+ if (this.membershipLevel === 'gold') return 0.2;190+ if (this.accountAge > 365) return 0.1;191+ return 0;192+ }193+ }194+195+ class Order {196+ calculateDiscount(user) {197+ return this.total * user.getDiscountRate(this.total);198+ }199+ }200```201202### 6. Primitive Obsession203204```diff205# BAD: Using primitives for domain concepts206- function sendEmail(to, subject, body) { /* ... */ }207- sendEmail('[email protected]', 'Hello', '...');208209- function createPhone(country, number) {210- return `${country}-${number}`;211- }212213# GOOD: Use domain types214+ class Email {215+ private constructor(public readonly value: string) {216+ if (!Email.isValid(value)) throw new Error('Invalid email');217+ }218+ static create(value: string) { return new Email(value); }219+ static isValid(email: string) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); }220+ }221+222+ class PhoneNumber {223+ constructor(224+ public readonly country: string,225+ public readonly number: string226+ ) {227+ if (!PhoneNumber.isValid(country, number)) throw new Error('Invalid phone');228+ }229+ toString() { return `${this.country}-${this.number}`; }230+ static isValid(country: string, number: string) { /* ... */ }231+ }232+233+ // Usage234+ const email = Email.create('[email protected]');235+ const phone = new PhoneNumber('1', '555-1234');236```237238### 7. Magic Numbers/Strings239240```diff241# BAD: Unexplained values242- if (user.status === 2) { /* ... */ }243- const discount = total * 0.15;244- setTimeout(callback, 86400000);245246# GOOD: Named constants247+ const UserStatus = {248+ ACTIVE: 1,249+ INACTIVE: 2,250+ SUSPENDED: 3251+ } as const;252+253+ const DISCOUNT_RATES = {254+ STANDARD: 0.1,255+ PREMIUM: 0.15,256+ VIP: 0.2257+ } as const;258+259+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;260+261+ if (user.status === UserStatus.INACTIVE) { /* ... */ }262+ const discount = total * DISCOUNT_RATES.PREMIUM;263+ setTimeout(callback, ONE_DAY_MS);264```265266### 8. Nested Conditionals267268```diff269# BAD: Arrow code270- function process(order) {271- if (order) {272- if (order.user) {273- if (order.user.isActive) {274- if (order.total > 0) {275- return processOrder(order);276+ } else {277+ return { error: 'Invalid total' };278+ }279+ } else {280+ return { error: 'User inactive' };281+ }282+ } else {283+ return { error: 'No user' };284+ }285+ } else {286+ return { error: 'No order' };287+ }288+ }289290# GOOD: Guard clauses / early returns291+ function process(order) {292+ if (!order) return { error: 'No order' };293+ if (!order.user) return { error: 'No user' };294+ if (!order.user.isActive) return { error: 'User inactive' };295+ if (order.total <= 0) return { error: 'Invalid total' };296+ return processOrder(order);297+ }298299# EVEN BETTER: Using Result type300+ function process(order): Result<ProcessedOrder, Error> {301+ return Result.combine([302+ validateOrderExists(order),303+ validateUserExists(order),304+ validateUserActive(order.user),305+ validateOrderTotal(order)306+ ]).flatMap(() => processOrder(order));307+ }308```309310### 9. Dead Code311312```diff313# BAD: Unused code lingers314- function oldImplementation() { /* ... */ }315- const DEPRECATED_VALUE = 5;316- import { unusedThing } from './somewhere';317- // Commented out code318- // function oldCode() { /* ... */ }319320# GOOD: Remove it321+ // Delete unused functions, imports, and commented code322+ // If you need it again, git history has it323```324325### 10. Inappropriate Intimacy326327```diff328# BAD: One class reaches deep into another329- class OrderProcessor {330- process(order) {331- order.user.profile.address.street; // Too intimate332- order.repository.connection.config; // Breaking encapsulation333+ }334+ }335336# GOOD: Ask, don't tell337+ class OrderProcessor {338+ process(order) {339+ order.getShippingAddress(); // Order knows how to get it340+ order.save(); // Order knows how to save itself341+ }342+ }343```344345---346347## Extract Method Refactoring348349### Before and After350351```diff352# Before: One long function353- function printReport(users) {354- console.log('USER REPORT');355- console.log('============');356- console.log('');357- console.log(`Total users: ${users.length}`);358- console.log('');359- console.log('ACTIVE USERS');360- console.log('------------');361- const active = users.filter(u => u.isActive);362- active.forEach(u => {363- console.log(`- ${u.name} (${u.email})`);364- });365- console.log('');366- console.log(`Active: ${active.length}`);367- console.log('');368- console.log('INACTIVE USERS');369- console.log('--------------');370- const inactive = users.filter(u => !u.isActive);371- inactive.forEach(u => {372- console.log(`- ${u.name} (${u.email})`);373- });374- console.log('');375- console.log(`Inactive: ${inactive.length}`);376- }377378# After: Extracted methods379+ function printReport(users) {380+ printHeader('USER REPORT');381+ console.log(`Total users: ${users.length}\n`);382+ printUserSection('ACTIVE USERS', users.filter(u => u.isActive));383+ printUserSection('INACTIVE USERS', users.filter(u => !u.isActive));384+ }385+386+ function printHeader(title) {387+ const line = '='.repeat(title.length);388+ console.log(title);389+ console.log(line);390+ console.log('');391+ }392+393+ function printUserSection(title, users) {394+ console.log(title);395+ console.log('-'.repeat(title.length));396+ users.forEach(u => console.log(`- ${u.name} (${u.email})`));397+ console.log('');398+ console.log(`${title.split(' ')[0]}: ${users.length}`);399+ console.log('');400+ }401```402403---404405## Introducing Type Safety406407### From Untyped to Typed408409```diff410# Before: No types411- function calculateDiscount(user, total, membership, date) {412- if (membership === 'gold' && date.getDay() === 5) {413- return total * 0.25;414- }415- if (membership === 'gold') return total * 0.2;416- return total * 0.1;417- }418419# After: Full type safety420+ type Membership = 'bronze' | 'silver' | 'gold';421+422+ interface User {423+ id: string;424+ name: string;425+ membership: Membership;426+ }427+428+ interface DiscountResult {429+ original: number;430+ discount: number;431+ final: number;432+ rate: number;433+ }434+435+ function calculateDiscount(436+ user: User,437+ total: number,438+ date: Date = new Date()439+ ): DiscountResult {440+ if (total < 0) throw new Error('Total cannot be negative');441+442+ let rate = 0.1; // Default bronze443+444+ if (user.membership === 'gold' && date.getDay() === 5) {445+ rate = 0.25; // Friday bonus for gold446+ } else if (user.membership === 'gold') {447+ rate = 0.2;448+ } else if (user.membership === 'silver') {449+ rate = 0.15;450+ }451+452+ const discount = total * rate;453+454+ return {455+ original: total,456+ discount,457+ final: total - discount,458+ rate459+ };460+ }461```462463---464465## Design Patterns for Refactoring466467### Strategy Pattern468469```diff470# Before: Conditional logic471- function calculateShipping(order, method) {472- if (method === 'standard') {473- return order.total > 50 ? 0 : 5.99;474- } else if (method === 'express') {475- return order.total > 100 ? 9.99 : 14.99;476+ } else if (method === 'overnight') {477+ return 29.99;478+ }479+ }480481# After: Strategy pattern482+ interface ShippingStrategy {483+ calculate(order: Order): number;484+ }485+486+ class StandardShipping implements ShippingStrategy {487+ calculate(order: Order) {488+ return order.total > 50 ? 0 : 5.99;489+ }490+ }491+492+ class ExpressShipping implements ShippingStrategy {493+ calculate(order: Order) {494+ return order.total > 100 ? 9.99 : 14.99;495+ }496+ }497+498+ class OvernightShipping implements ShippingStrategy {499+ calculate(order: Order) {500+ return 29.99;501+ }502+ }503+504+ function calculateShipping(order: Order, strategy: ShippingStrategy) {505+ return strategy.calculate(order);506+ }507```508509### Chain of Responsibility510511```diff512# Before: Nested validation513- function validate(user) {514- const errors = [];515- if (!user.email) errors.push('Email required');516+ else if (!isValidEmail(user.email)) errors.push('Invalid email');517+ if (!user.name) errors.push('Name required');518+ if (user.age < 18) errors.push('Must be 18+');519+ if (user.country === 'blocked') errors.push('Country not supported');520+ return errors;521+ }522523# After: Chain of responsibility524+ abstract class Validator {525+ abstract validate(user: User): string | null;526+ setNext(validator: Validator): Validator {527+ this.next = validator;528+ return validator;529+ }530+ validate(user: User): string | null {531+ const error = this.doValidate(user);532+ if (error) return error;533+ return this.next?.validate(user) ?? null;534+ }535+ }536+537+ class EmailRequiredValidator extends Validator {538+ doValidate(user: User) {539+ return !user.email ? 'Email required' : null;540+ }541+ }542+543+ class EmailFormatValidator extends Validator {544+ doValidate(user: User) {545+ return user.email && !isValidEmail(user.email) ? 'Invalid email' : null;546+ }547+ }548+549+ // Build the chain550+ const validator = new EmailRequiredValidator()551+ .setNext(new EmailFormatValidator())552+ .setNext(new NameRequiredValidator())553+ .setNext(new AgeValidator())554+ .setNext(new CountryValidator());555```556557---558559## Refactoring Steps560561### Safe Refactoring Process562563```5641. PREPARE565- Ensure tests exist (write them if missing)566- Commit current state567- Create feature branch5685692. IDENTIFY570- Find the code smell to address571- Understand what the code does572- Plan the refactoring5735743. REFACTOR (small steps)575- Make one small change576- Run tests577- Commit if tests pass578- Repeat5795804. VERIFY581- All tests pass582- Manual testing if needed583- Performance unchanged or improved5845855. CLEAN UP586- Update comments587- Update documentation588- Final commit589```590591---592593## Refactoring Checklist594595### Code Quality596597- [ ] Functions are small (< 50 lines)598- [ ] Functions do one thing599- [ ] No duplicated code600- [ ] Descriptive names (variables, functions, classes)601- [ ] No magic numbers/strings602- [ ] Dead code removed603604### Structure605606- [ ] Related code is together607- [ ] Clear module boundaries608- [ ] Dependencies flow in one direction609- [ ] No circular dependencies610611### Type Safety612613- [ ] Types defined for all public APIs614- [ ] No `any` types without justification615- [ ] Nullable types explicitly marked616617### Testing618619- [ ] Refactored code is tested620- [ ] Tests cover edge cases621- [ ] All tests pass622623---624625## Common Refactoring Operations626627| Operation | Description |628| --------------------------------------------- | ------------------------------------- |629| Extract Method | Turn code fragment into method |630| Extract Class | Move behavior to new class |631| Extract Interface | Create interface from implementation |632| Inline Method | Move method body back to caller |633| Inline Class | Move class behavior to caller |634| Pull Up Method | Move method to superclass |635| Push Down Method | Move method to subclass |636| Rename Method/Variable | Improve clarity |637| Introduce Parameter Object | Group related parameters |638| Replace Conditional with Polymorphism | Use polymorphism instead of switch/if |639| Replace Magic Number with Constant | Named constants |640| Decompose Conditional | Break complex conditions |641| Consolidate Conditional | Combine duplicate conditions |642| Replace Nested Conditional with Guard Clauses | Early returns |643| Introduce Null Object | Eliminate null checks |644| Replace Type Code with Class/Enum | Strong typing |645| Replace Inheritance with Delegation | Composition over inheritance |646