这个手法做什么
Extract Function(提炼函数)把一段能命名的逻辑取出来,放进一个以意图命名的函数里。它的直接收益是用函数名替代「这段在干什么」的注释,让外层函数变短、层次变清晰;片段一旦独立,也更容易被复用和单独测试。Fowler 给的判断很直接:如果必须花力气才能看懂一个片段在做什么,就把它提炼出来并按意图命名。
基本信息
| 适用场景 | 长函数里的独立片段、需要写注释解释的代码块 |
|---|---|
| 命名原则 | 按做什么命名,而不是按怎么做命名 |
| 最大障碍 | 片段依赖的局部变量过多,可先拆分临时变量或改用查询替代临时变量 |
| 配套手法 | Move Function 搬家、Inline Function 反向合并 |
操作步骤
- 先给新函数起名:用动词短语说明意图,而不是描述实现细节。
- 把片段复制到新函数体里,把用到的局部变量变成参数或返回值。
- 片段修改了多个局部变量时,优先想办法只返回一个值,减少接口面。
- 编译并运行测试,确认新函数行为与原来完全一致。
- 在原位置替换为对新函数的调用,再次运行测试。
- 若新函数明显属于另一个类的职责,用搬家手法把它移过去。
提炼前后的差别
| 维度 | 提炼前 | 提炼后 |
|---|---|---|
| 可读性 | 靠注释说明意图 | 函数名本身就是意图 |
| 复用 | 片段无法被其他位置调用 | 可直接调用 |
| 测试 | 只能整体测试外层函数 | 可单独测试该片段 |
| 改动风险 | 改动牵连整个长函数 | 改动范围局部化 |
Java 示例
Java 示例
// 提炼前
void printOwing(double amount) {
printBanner();
System.out.println("name: " + name);
System.out.println("amount: " + amount);
}
// 提炼后
void printOwing(double amount) {
printBanner();
printDetails(amount);
}
void printDetails(double amount) {
System.out.println("name: " + name);
System.out.println("amount: " + amount);
}提炼时若临时变量过多:先拆分临时变量或改用查询替代临时变量,减少片段对局部状态的依赖,提炼会顺很多。
一次只提炼一个片段并立刻跑测试:连续提炼多处再统一验证,出问题时很难判断是哪一步引入的。