尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

claude-howto 重构目录实战指南:Martin Fowler 重构手法完整参考与落地工具

claude-howto 重构目录实战指南:Martin Fowler 重构手法完整参考与落地工具 claude-howto 重构目录实战指南Martin Fowler 重构手法完整参考与落地工具【免费下载链接】claude-howtoA visual, example-driven guide to Claude Code — from basic concepts to advanced agents, with copy-paste templates that bring immediate value.项目地址: https://gitcode.com/GitHub_Trending/cl/claude-howto本文是一份可直接照做的重构手法目录Refactoring Catalog完整收录 Martin Fowler《Refactoring: Improving the Design of Existing Code》第 2 版中的核心重构技术覆盖最常见重构、移动特性、数据组织、简化条件逻辑、API 重构、继承相关与 Extract Class 共 20 余种手法。在 claude-howto 项目的 refactor skill 中这份目录与代码异味目录、重构计划模板 以及自动检测脚本共同构成一套完整的重构工作流。读完本文你将掌握每种重构手法的动机、精确操作步骤、前后对照示例并能结合仓库自带的 detect-smells.py 与 analyze-complexity.py 把识别异味 → 选择手法 → 按步执行 → 测试验证的闭环落到实际代码上。重构由它的操作步骤来定义也就是你执行这个变化时遵循的精确过程。 — Martin Fowler如何使用这份目录重构不是随机改代码而是一条有迹可循的执行路径。本目录给出的标准用法是四步识别异味先用代码异味目录定位问题长函数、重复代码、大类、Feature Envy 等找到对应重构在本目录中查找匹配的手法按步骤执行一步一步来绝不跳步每步都测试确保行为不变黄金法则如果某一步超过 10 分钟就把它拆得更小。这个小步 每步测试的原则同样贯穿于仓库的 skill 定义。在 refactor SKILL.md 中整个工作流被拆成六个阶段——研究与分析、测试覆盖评估、识别代码异味、创建重构计划、增量实施、评审与迭代并明确要求每次只做很小、可测试的改动把目录中的每个手法步骤与提交粒度一一对齐。最常见的重构这一组手法使用频率最高解决的是函数、变量、声明层面的日常问题是重构的基本功。Extract Method何时使用长函数、重复代码、需要为概念命名动机把一段代码抽成一个名字能说明意图的方法。步骤创建一个新方法名字应描述做什么而不是怎么做把代码片段复制到新方法里检查片段里用了哪些局部变量把局部变量作为参数传入或在方法内声明正确处理返回值用新方法调用替换原始片段测试前function printOwing(invoice) { let outstanding 0; console.log(***********************); console.log(**** Customer Owes ****); console.log(***********************); // Calculate outstanding for (const order of invoice.orders) { outstanding order.amount; } // Print details console.log(name: ${invoice.customer}); console.log(amount: ${outstanding}); }后function printOwing(invoice) { printBanner(); const outstanding calculateOutstanding(invoice); printDetails(invoice, outstanding); } function printBanner() { console.log(***********************); console.log(**** Customer Owes ****); console.log(***********************); } function calculateOutstanding(invoice) { return invoice.orders.reduce((sum, order) sum order.amount, 0); } function printDetails(invoice, outstanding) { console.log(name: ${invoice.customer}); console.log(amount: ${outstanding}); }从源码视角看Extract Method 也是仓库异味检测脚本中最常给出的修复建议。在 detect-smells.py 中长方法超过 30 行与重复代码同一行出现 3 次以上被检测到时其suggestion字段统一指向 Apply Extract Method to break down into smaller functions / Apply Extract Method to eliminate duplication说明这是消除这两类异味的第一选择。Inline Method何时使用方法体和名字一样清楚或者只是多余转发动机当方法没有额外价值时去掉不必要的间接层。步骤确认这个方法不是多态方法找到所有调用点用方法体替换每个调用每替换一次就测试一次删除方法定义前function getRating(driver) { return moreThanFiveLateDeliveries(driver) ? 2 : 1; } function moreThanFiveLateDeliveries(driver) { return driver.numberOfLateDeliveries 5; }后function getRating(driver) { return driver.numberOfLateDeliveries 5 ? 2 : 1; }Extract Variable何时使用复杂表达式难以理解动机给复杂表达式的一部分起名字。步骤确保表达式没有副作用声明一个不可变变量将表达式结果赋给变量用变量替换原表达式测试前return order.quantity * order.itemPrice - Math.max(0, order.quantity - 500) * order.itemPrice * 0.05 Math.min(order.quantity * order.itemPrice * 0.1, 100);后const basePrice order.quantity * order.itemPrice; const quantityDiscount Math.max(0, order.quantity - 500) * order.itemPrice * 0.05; const shipping Math.min(basePrice * 0.1, 100); return basePrice - quantityDiscount shipping;Inline Variable何时使用变量名没有比表达式更清楚动机移除没必要的间接层。步骤检查右侧表达式没有副作用如果变量不是不可变的先改成不可变并测试找到第一次引用并替换成表达式测试对所有引用重复删除变量声明和赋值测试Rename Variable何时使用名字没有清楚表达用途动机好名字是清晰代码的基础。步骤如果变量使用范围很广先考虑封装找到所有引用逐个修改引用测试提示使用能体现意图的名字避免缩写使用领域术语// Bad const d 30; const x users.filter(u u.a); // Good const daysSinceLastLogin 30; const activeUsers users.filter(user user.isActive);Change Function Declaration何时使用函数名不能清楚说明用途参数也需要变化动机好函数名能让代码自解释。步骤简单情况删除不需要的参数修改函数名添加需要的参数测试步骤迁移式适用于复杂改动如果要删除参数确保它没有被使用创建一个新函数声明符合新设计让旧函数调用新函数测试让调用方逐步改用新函数每改一个调用点就测试删除旧函数前function circum(radius) { return 2 * Math.PI * radius; }后function circumference(radius) { return 2 * Math.PI * radius; }Encapsulate Variable何时使用多个地方直接访问某个数据动机为数据访问提供明确入口。步骤创建 getter 和 setter找到所有引用用 getter 替换读取用 setter 替换写入每次改动后测试降低变量可见性前let defaultOwner { firstName: Martin, lastName: Fowler }; // Used in many places spaceship.owner defaultOwner;后let defaultOwnerData { firstName: Martin, lastName: Fowler }; function defaultOwner() { return defaultOwnerData; } function setDefaultOwner(arg) { defaultOwnerData arg; } spaceship.owner defaultOwner();Introduce Parameter Object何时使用一组参数经常一起出现动机把自然属于一起的数据打包起来。步骤为这组参数创建一个新类 / 结构测试用 Change Function Declaration 引入新对象测试逐个移除参数改用对象字段每次移除后测试前function amountInvoiced(startDate, endDate) { ... } function amountReceived(startDate, endDate) { ... } function amountOverdue(startDate, endDate) { ... }后class DateRange { constructor(start, end) { this.start start; this.end end; } } function amountInvoiced(dateRange) { ... } function amountReceived(dateRange) { ... } function amountOverdue(dateRange) { ... }这项手法与仓库检测脚本的阈值直接对应detect-smells.py中max_parameters 4一旦函数参数超过 4 个Python 中剔除self/cls后计数就会被标记为 Long Parameter List脚本给出的建议正是 Consider Introduce Parameter Object or Preserve Whole Object。参数越多说明数据越应该被打包成领域对象。Combine Functions into Class何时使用多个函数操作同一份数据动机把函数和它操作的数据放到一起。步骤对公共数据先做 Encapsulate Record把每个函数移动到类里每移动一次就测试用类字段替代数据参数前function base(reading) { ... } function taxableCharge(reading) { ... } function calculateBaseCharge(reading) { ... }后class Reading { constructor(data) { this._data data; } get base() { ... } get taxableCharge() { ... } get calculateBaseCharge() { ... } }Split Phase何时使用代码在处理两件不同的事动机把代码拆成边界清晰的两个阶段。步骤为第二阶段创建新函数测试在两个阶段之间引入中间数据结构测试把第一阶段提取成独立函数测试前function priceOrder(product, quantity, shippingMethod) { const basePrice product.basePrice * quantity; const discount Math.max(quantity - product.discountThreshold, 0) * product.basePrice * product.discountRate; const shippingPerCase (basePrice shippingMethod.discountThreshold) ? shippingMethod.discountedFee : shippingMethod.feePerCase; const shippingCost quantity * shippingPerCase; return basePrice - discount shippingCost; }后function priceOrder(product, quantity, shippingMethod) { const priceData calculatePricingData(product, quantity); return applyShipping(priceData, shippingMethod); } function calculatePricingData(product, quantity) { const basePrice product.basePrice * quantity; const discount Math.max(quantity - product.discountThreshold, 0) * product.basePrice * product.discountRate; return { basePrice, quantity, discount }; } function applyShipping(priceData, shippingMethod) { const shippingPerCase (priceData.basePrice shippingMethod.discountThreshold) ? shippingMethod.discountedFee : shippingMethod.feePerCase; const shippingCost priceData.quantity * shippingPerCase; return priceData.basePrice - priceData.discount shippingCost; }移动特性这类手法解决代码放错了地方的问题——函数或数据所在的位置与它的实际依赖不一致。Move Method何时使用方法更依赖另一个类的数据而不是自己类的数据动机把函数放到它最依赖的数据所在的类里。步骤检查方法使用到的所有程序元素确认方法不是多态的把方法复制到目标类调整上下文让原方法委托给目标方法测试视情况删除原方法这正对应代码异味目录中的Feature Envy一个方法大量调用另一个对象的 getter、使用别类数据多于本类数据时说明行为放错了地方。detect-smells.py也会通过链式调用模式连续 3 个以上点号调用检测这类倾向。Move Field何时使用字段更多被另一个类使用动机让数据和使用它的函数靠在一起。步骤如果还没有先封装字段测试在目标类中创建字段把引用改成使用目标字段测试删除原字段Move Statements into Function何时使用相同代码总是跟着函数调用一起出现动机把重复语句移入函数去掉重复。步骤如果还没有先把重复代码提取成函数把语句移入函数测试如果调用方不再需要独立语句就删除它们Move Statements to Callers何时使用不同调用方需要不同的行为动机当行为需要差异化时把它从函数里移出去。步骤先对要移动的代码做 Extract Method再对原函数做 Inline Method删除被内联后的调用把提取出的代码移到各个调用方测试数据组织这一组手法聚焦于数据本身的表达方式把裸数据升级为携带行为的对象。Replace Primitive with Object何时使用数据项需要的不只是简单值动机把数据和行为封装在一起。步骤先做 Encapsulate Variable创建一个简单值对象修改 setter让它创建新实例修改 getter让它返回值测试给新类增加更丰富的行为前class Order { constructor(data) { this.priority data.priority; // string: high, rush, etc. } } // Usage if (order.priority high || order.priority rush) { ... }后class Priority { constructor(value) { if (!Priority.legalValues().includes(value)) throw new Error(Invalid priority: ${value}); this._value value; } static legalValues() { return [low, normal, high, rush]; } get value() { return this._value; } higherThan(other) { return Priority.legalValues().indexOf(this._value) Priority.legalValues().indexOf(other._value); } } // Usage if (order.priority.higherThan(new Priority(normal))) { ... }这个例子展示了对象化带来的两个关键收益构造时校验非法值直接抛错和语义化比较higherThan取代散落的字符串比较。它与代码异味目录中的基础类型沉迷Primitive Obsession一一对应——用 string 表示邮箱、用 int 表示金额、用魔法字符串表示类型码都会让逻辑散落、缺少类型验证。detect-smells.py中的MAGIC_NUMBER检测过滤0/1/2/100/true/false/null等常见可接受值后仍出现在运算或比较中的数字字面量正是这类异味在代码层面的自动化信号。Replace Temp with Query何时使用临时变量保存的是一个表达式结果动机把表达式提取成函数让代码更清晰。步骤确保变量只被赋值一次把赋值右侧提取成一个方法用方法调用替换临时变量引用测试删除临时变量声明和赋值前const basePrice this._quantity * this._itemPrice; if (basePrice 1000) { return basePrice * 0.95; } else { return basePrice * 0.98; }后get basePrice() { return this._quantity * this._itemPrice; } // In the method if (this.basePrice 1000) { return this.basePrice * 0.95; } else { return this.basePrice * 0.98; }简化条件逻辑条件语句是最容易积累复杂度的位置这一组手法专门对付 if/else 与 switch。Decompose Conditional何时使用复杂条件语句动机把条件和分支动作分别提取出来让意图更清楚。步骤对条件做 Extract Method对 then 分支做 Extract Method对 else 分支也做 Extract Method如果有前if (!aDate.isBefore(plan.summerStart) !aDate.isAfter(plan.summerEnd)) { charge quantity * plan.summerRate; } else { charge quantity * plan.regularRate plan.regularServiceCharge; }后if (isSummer(aDate, plan)) { charge summerCharge(quantity, plan); } else { charge regularCharge(quantity, plan); } function isSummer(date, plan) { return !date.isBefore(plan.summerStart) !date.isAfter(plan.summerEnd); } function summerCharge(quantity, plan) { return quantity * plan.summerRate; } function regularCharge(quantity, plan) { return quantity * plan.regularRate plan.regularServiceCharge; }Consolidate Conditional Expression何时使用多个条件最后都返回同一个结果动机让人一眼看出这些条件其实是一道检查。步骤确认条件没有副作用用 and / or 合并条件视情况对合并后的条件做 Extract Method前if (employee.seniority 2) return 0; if (employee.monthsDisabled 12) return 0; if (employee.isPartTime) return 0;后if (isNotEligibleForDisability(employee)) return 0; function isNotEligibleForDisability(employee) { return employee.seniority 2 || employee.monthsDisabled 12 || employee.isPartTime; }Replace Nested Conditional with Guard Clauses何时使用深层嵌套条件让流程难以追踪动机用 guard clause 提前返回让正常流程更清楚。步骤找出特殊情况用提前返回的 guard clause 替换它们每改一步就测试前function payAmount(employee) { let result; if (employee.isSeparated) { result { amount: 0, reasonCode: SEP }; } else { if (employee.isRetired) { result { amount: 0, reasonCode: RET }; } else { result calculateNormalPay(employee); } } return result; }后function payAmount(employee) { if (employee.isSeparated) return { amount: 0, reasonCode: SEP }; if (employee.isRetired) return { amount: 0, reasonCode: RET }; return calculateNormalPay(employee); }这个手法与检测脚本的DEEPLY_NESTED检测互为表里detect-smells.py设置max_nesting_depth 4Python 按缩进层级每 4 空格一层统计、JS/TS 按花括号深度统计嵌套超过 4 层即触发告警其建议正是 Apply Replace Nested Conditional with Guard Clauses or Extract Method。Replace Conditional with Polymorphism何时使用按类型分支的 switch / 条件逻辑动机让对象自己处理自己的行为。步骤创建类层次如果还没有用 Factory Function 创建对象把条件逻辑移到超类方法里给每种情况创建子类方法删除原条件前function plumages(birds) { return birds.map(b plumage(b)); } function plumage(bird) { switch (bird.type) { case EuropeanSwallow: return average; case AfricanSwallow: return (bird.numberOfCoconuts 2) ? tired : average; case NorwegianBlueParrot: return (bird.voltage 100) ? scorched : beautiful; default: return unknown; } }后class Bird { get plumage() { return unknown; } } class EuropeanSwallow extends Bird { get plumage() { return average; } } class AfricanSwallow extends Bird { get plumage() { return (this.numberOfCoconuts 2) ? tired : average; } } class NorwegianBlueParrot extends Bird { get plumage() { return (this.voltage 100) ? scorched : beautiful; } } function createBird(data) { switch (data.type) { case EuropeanSwallow: return new EuropeanSwallow(data); case AfricanSwallow: return new AfricanSwallow(data); case NorwegianBlueParrot: return new NorwegianBlueParrot(data); default: return new Bird(data); } }仓库脚本同样支持这一手法的自动化定位detect-smells.py对 JS/TS 统计switch的case数量、对 Python 统计连续if/elif 分支数量当 case 数 ≥ 4 或连续条件 ≥ 4 时即标记 Switch Statement建议 Apply Replace Conditional with Polymorphism。Introduce Special Case (Null Object)何时使用重复出现 null 检查动机返回一个特殊对象来处理特殊情况。步骤创建一个具备预期接口的特殊情况类增加 isSpecialCase 检查引入工厂方法用特殊对象替换 null 检查测试前const customer site.customer; // ... many places checking if (customer unknown) { customerName occupant; } else { customerName customer.name; }后class UnknownCustomer { get name() { return occupant; } get billingPlan() { return registry.defaultPlan; } } // Factory method function customer(site) { return site.customer unknown ? new UnknownCustomer() : site.customer; } // Usage - no null checks needed const customerName customer.name;重构 API这一组手法改善的是函数签名与调用契约让 API 的意图更直白、副作用更可控。Separate Query from Modifier何时使用函数既返回值又有副作用动机明确哪些操作会产生副作用。步骤创建新的查询函数复制原函数的返回逻辑修改原函数让它只负责副作用替换依赖返回值的调用点测试前function alertForMiscreant(people) { for (const p of people) { if (p Don) { setOffAlarms(); return Don; } if (p John) { setOffAlarms(); return John; } } return ; }后function findMiscreant(people) { for (const p of people) { if (p Don) return Don; if (p John) return John; } return ; } function alertForMiscreant(people) { if (findMiscreant(people) ! ) setOffAlarms(); }Parameterize Function何时使用有多个做类似事情但数值不同的函数动机通过参数化减少重复。步骤选择一个函数为变化的字面值增加参数修改函数体使用该参数测试让调用方改用参数化版本删除不再使用的旧函数前function tenPercentRaise(person) { person.salary person.salary * 1.10; } function fivePercentRaise(person) { person.salary person.salary * 1.05; }后function raise(person, factor) { person.salary person.salary * (1 factor); } // Usage raise(person, 0.10); raise(person, 0.05);Remove Flag Argument何时使用布尔参数改变函数行为动机通过拆分成明确函数让行为更清楚。步骤针对不同旗标值创建显式函数替换每个调用点每次改动后测试删除原函数前function bookConcert(customer, isPremium) { if (isPremium) { // premium booking logic } else { // regular booking logic } } bookConcert(customer, true); bookConcert(customer, false);后function bookPremiumConcert(customer) { // premium booking logic } function bookRegularConcert(customer) { // regular booking logic } bookPremiumConcert(customer); bookRegularConcert(customer);继承相关继承滥用是结构性异味的重要来源这一组手法帮助你重新摆正继承层次。Pull Up Method何时使用多个子类里有相同方法动机去掉类层次中的重复。步骤检查方法是否完全相同确认签名一致在超类中新建方法从一个子类复制实现删除一个子类中的方法并测试删除其他子类中的方法并测试Push Down Method何时使用某个行为只适用于部分子类动机把方法放到真正使用它的地方。步骤把方法复制到需要它的子类从超类删除方法测试删除不需要该方法的子类副本测试这对应代码异味目录中的拒绝遗赠Refused Bequest——子类没用到继承来的方法、或重写只是为了不做任何事说明继承被当成了代码复用工具而非真正的 IS-A 关系此时应当考虑 Push Down Method/Field 或 Replace Subclass with Delegate。Replace Subclass with Delegate何时使用继承用得不对需要更灵活动机在合适的地方更偏向组合而不是继承。步骤创建一个空的委托类在宿主类中加一个持有委托的字段在宿主类构造委托把功能迁移到委托类每次迁移后测试用委托替代继承Extract Class何时使用大类里有多个职责动机拆分类以维持单一职责。步骤决定如何拆分职责创建新类把字段从原类移到新类测试把方法从原类移到新类每次移动后测试重新检查并命名两个类决定如何暴露新类前class Person { get name() { return this._name; } set name(arg) { this._name arg; } get officeAreaCode() { return this._officeAreaCode; } set officeAreaCode(arg) { this._officeAreaCode arg; } get officeNumber() { return this._officeNumber; } set officeNumber(arg) { this._officeNumber arg; } get telephoneNumber() { return (${this._officeAreaCode}) ${this._officeNumber}; } }后class Person { constructor() { this._telephoneNumber new TelephoneNumber(); } get name() { return this._name; } set name(arg) { this._name arg; } get telephoneNumber() { return this._telephoneNumber.toString(); } get officeAreaCode() { return this._telephoneNumber.areaCode; } set officeAreaCode(arg) { this._telephoneNumber.areaCode arg; } } class TelephoneNumber { get areaCode() { return this._areaCode; } set areaCode(arg) { this._areaCode arg; } get number() { return this._number; } set number(arg) { this._number arg; } toString() { return (${this._areaCode}) ${this._number}; } }Extract Class 的自动化触发条件在检测脚本中有明确量化large_class_lines 300、large_class_methods 10类行数超 300 或方法数超 10 即触发 LARGE_CLASS 告警建议 Apply Extract Class to split responsibilities。这与代码异味目录中大类的检测参考行数 300、方法 15、字段 10口径一致可作为拆分决策的客观依据。快速参考异味到重构在定位到异味之后用下面这张映射表快速找到首选手法与备选方案完整异味定义见代码异味目录代码异味主要重构备选方案长函数Extract MethodReplace Temp with Query重复代码Extract MethodPull Up Method大类Extract ClassExtract Subclass长参数列表Introduce Parameter ObjectPreserve Whole ObjectFeature EnvyMove MethodExtract Method Move数据泥团Extract ClassIntroduce Parameter Object基础类型沉迷Replace Primitive with ObjectReplace Type Codeswitch 语句Replace Conditional with PolymorphismReplace Type Code临时字段Extract ClassIntroduce Null Object消息链Hide DelegateExtract Method中间人Remove Middle ManInline Method发散式变化Extract ClassSplit Phase散弹式修改Move MethodInline Class死代码Remove Dead Code-臆想泛化Collapse HierarchyInline Class在 claude-howto 中落地从目录到闭环工作流这份目录不是孤立的知识库它在仓库的 refactor skill 中与工具链组成了一条可执行的闭环目录结构示例见 03-skills/README.md第一步自动检测异味。运行 detect-smells.py 对单个文件或整个目录扫描python 03-skills/refactor/scripts/detect-smells.py myfile.py # 分析单个文件 python 03-skills/refactor/scripts/detect-smells.py --dir src/ # 分析整个目录 python 03-skills/refactor/scripts/detect-smells.py -v myfile.py # 详细模式带代码片段 python 03-skills/refactor/scripts/detect-smells.py -j myfile.py # JSON 输出便于后续处理脚本内置了与本文各手法对应的可配置阈值THRESHOLDS 字典长方法 30 行、超长方法 50 行、最大参数 4 个、大类 300 行/10 个方法、最大嵌套深度 4 层、消息链 3 个调用、重复代码 3 次以上。每条告警都会附带严重性Low/Medium/High/Critical、行号区间和指向具体重构手法的建议——这正是异味 → 重构映射的机器可读版本。第二步选定手法、制定计划。对照本文的每种手法使用重构计划模板记录每个任务的 Target、Smell、Refactoring、Steps、Risks 与 Rollback并按 refactor SKILL.md 的阶段性策略推进先做低风险的快速收益重命名、删除死代码、提取重复再做中风险的结构改进提取方法、引入参数对象、移动方法最后处理高风险的架构改动多态替代条件、提取类、引入设计模式。第三步每步测试 指标验证。每个手法步骤执行后立即跑测试全部完成后运行 analyze-complexity.py 对比重构前后python 03-skills/refactor/scripts/analyze-complexity.py before.py after.py # 对比模式 python 03-skills/refactor/scripts/analyze-complexity.py -v myfile.py # 查看函数级明细对比模式会输出圈复杂度决策点数量、认知复杂度嵌套与流程中断的阅读成本、可维护性指数0-10085 以上为 Highly maintainable、65-84 为 Moderately maintainable、50-64 为 Difficult、50 以下为 Very difficult以及平均/最大函数长度在重构前后的变化以客观指标确认行为未变、结构更优。第四步评审与迭代。按 SKILL.md 的阶段 6 检查清单确认测试全部通过、无新警告、行为未变并沉淀剩余技术债安排下一轮重构。延伸阅读Fowler, M. (2018).Refactoring: Improving the Design of Existing Code第 2 版在线目录https://refactoring.com/catalog/更多重构前后的实践示例、严重性分级与快速检测清单可继续阅读仓库内的代码异味目录想要掌握六阶段重构流程的完整方法论可阅读 refactor skill 主文件。【免费下载链接】claude-howtoA visual, example-driven guide to Claude Code — from basic concepts to advanced agents, with copy-paste templates that bring immediate value.项目地址: https://gitcode.com/GitHub_Trending/cl/claude-howto创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表