第一组重构(下):改变函数声明、封装变量、变量改名、引入参数对象

好,咱们接着聊第一组重构的下半部分。上一节我们讲了提炼函数和搬移语句,这次要聊的几个手法,说白了都是关于「名字」和「结构」的调整。你想想看,代码里最让人头疼的是什么?不是算法复杂,而是你看到一个变量名,完全猜不出它是干嘛的。

我个人习惯是,写代码的前十分钟,可能有一半时间在改名字。别笑,这很值得。

改变函数声明

这个重构手法,以前叫「函数改名」,后来 Martin Fowler 觉得它不止改名字,还包括改参数列表,所以改成了现在的名字。但我个人觉得,最核心的还是改名字。

函数的名字,就是它的说明书。一个函数叫 calc(),你看了完全不知道它在算什么。改成 calculateTotalPrice(),是不是清晰多了?

核心原则: 函数名应该回答「这个函数做什么」,而不是「它怎么做的」。

我在项目中遇到过一段代码,一个函数叫 process(),里面处理了订单、发送邮件、更新库存。后来新同事接手,完全不敢动,因为不知道这个「process」到底包含了什么。我们花了半天把它拆成三个函数,每个都取了明确的名字,问题迎刃而解。

改参数列表也是常见场景。比如一个函数需要三个参数,但调用方每次都只传两个,第三个永远是默认值。这时候就该考虑把第三个参数去掉,或者用参数对象替代。

// 改名前
function setDimensions(width, height, depth) {
  // ...
}

// 改名后
function setDimensions(dimensions) {
  // dimensions 是一个对象
}
小技巧: 改函数名时,先保留旧函数,让旧函数调用新函数。等所有调用方都迁移完,再删除旧函数。这叫「渐进式重构」,风险最小。

封装变量

封装变量,说白了就是把一个公开的变量藏起来,通过函数来访问。为什么要这么做?因为直接暴露变量,你就失去了对它的控制。

举个例子,你有一个全局变量 let userCount = 0;,到处都在直接修改它。有一天你想加个日志,记录每次修改的时间和调用方,怎么办?一个个去找?太累了。

封装之后,你只需要在 setter 里加一行代码:

let _userCount = 0;

function getUserCount() {
  return _userCount;
}

function setUserCount(value) {
  console.log(`userCount 被修改为 ${value}`);
  _userCount = value;
}

我曾经在一个遗留系统里看到过,一个全局变量被 47 个地方直接修改。你想加个校验,根本无从下手。后来我们花了两周,把所有直接访问都改成了函数调用。嗯,那两周确实痛苦,但改完之后,系统稳定了很多。

注意: 封装变量不是让你把所有变量都藏起来。只有那些需要控制访问逻辑的变量,才值得封装。局部变量、一次性使用的变量,没必要。

变量改名

这个重构手法看起来最简单,但实际做起来,影响往往最大。变量名是代码里出现频率最高的东西,一个坏名字会反复误导你。

我见过最离谱的变量名:let a = 10;,然后整个函数里用了 20 次这个 a。你根本不知道它代表什么。后来我们改成 let maxRetryCount = 10;,所有人都松了一口气。

变量改名的原则很简单:

  • 名字要能表达意图let d = 0; 不如 let elapsedDays = 0;
  • 避免缩写usrCnt 不如 userCount,省不了几个字符,但阅读成本高很多
  • 保持一致性:如果其他地方用 customer,这里就别用 client
// 坏名字
let t = new Date();

// 好名字
let startTime = new Date();
我的习惯: 写代码时先用临时名字,等逻辑写完了,再回头改名字。这时候你对变量的作用理解得更清楚,取的名字也更准确。

引入参数对象

这个重构手法,专门对付「参数过多」的问题。你想想看,一个函数有六七个参数,调用的时候谁记得住顺序?

引入参数对象,就是把一组经常一起出现的参数,封装成一个对象。比如:

// 重构前
function createBooking(customerName, customerEmail, customerPhone, startDate, endDate) {
  // ...
}

// 重构后
function createBooking(customer, dateRange) {
  // ...
}

这里 customer 包含了姓名、邮箱、电话,dateRange 包含了开始和结束日期。参数从 5 个变成了 2 个,清晰多了。

我在项目中遇到过最夸张的一个函数,有 12 个参数。每次调用都要翻文档,看看第 5 个参数是什么。后来我们引入了三个参数对象,函数签名变成了 3 个参数,调用方也开心了,维护方也开心了。

什么时候该引入参数对象?
  • 三个以上参数经常一起出现
  • 参数之间有逻辑关联(比如地址相关的几个字段)
  • 调用方经常需要查文档才能知道参数顺序

知识体系总览

下面这张图,把这四个重构手法的关系和适用场景梳理了一下。你可以把它当作一个快速参考。

第一组重构(下)知识体系 改变函数声明 封装变量 变量改名 引入参数对象 函数名不达意 参数列表不合理 全局变量失控 需要加访问控制 变量名模糊 缩写难以理解 参数过多 参数逻辑关联 核心思想 名字要清晰,结构要合理 小步重构,每次只改一个点 让代码自己说明自己

避坑指南

最后,分享几个我踩过的坑:

  • 改名字时不要只改一处:我曾经改了一个变量名,但忘了改注释,结果注释和代码对不上,比不改还糟糕。
  • 封装变量时不要过度:如果一个变量只在函数内部使用,没必要封装成 getter/setter,直接改名字就行。
  • 引入参数对象时不要塞太多:参数对象里的字段应该逻辑相关,不要把不相关的硬凑在一起。
我曾经犯过的错: 有一次我引入了一个参数对象,把 8 个参数塞了进去。结果这个对象本身变得很臃肿,调用方需要先构造这个对象,反而更麻烦了。后来我意识到,应该拆成两个参数对象,每个 4 个字段,这样才合理。

好了,这一组重构手法就聊到这里。记住,重构不是一次性的工作,而是日常开发的一部分。每次看到代码不顺眼,就顺手改一下。积少成多,代码质量会越来越好。


公众号:蓝海资料掘金营,微信deep3321