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

好,咱们接着聊第一组重构的下半部分。上一节讲了几个跟函数体内部相关的技巧,这一节我们聚焦在「接口」和「数据」上。说白了,就是函数怎么对外暴露、变量怎么藏起来、名字怎么起得更明白。

我个人习惯,重构时最先动的往往是函数签名。为什么?因为签名是函数的门面。门面歪了,里面再漂亮也没用。

改变函数声明

这个重构手法,以前叫「函数改名」。后来Martin Fowler觉得光改名不够,参数也得管,就改成了现在的名字。

改变函数声明,核心就两件事:改名字调参数

改名字:让函数名说人话

我在项目中遇到过最离谱的函数名,叫 processData。你猜它干了什么?它既做了数据校验,又做了格式转换,还顺带发了封邮件。这种名字,说白了就是「我懒得想名字,先写着再说」。

好的函数名应该像这样:

// 糟糕
function calc(a, b) {
  return a * b + a;
}

// 好多了
function calculateTotalPrice(unitPrice, quantity) {
  return unitPrice * quantity + unitPrice;
}

嗯,这里要注意:改名字不是让你把名字写成一篇文章。简洁、准确、一看就懂,就够了。

调参数:少即是多

参数多了,调用方就痛苦。我见过一个函数有7个参数,调用的时候得对着文档一个一个填。这种代码,重构的第一步就是减少参数。

怎么减?常见手法:

  • 合并参数:把相关的参数合并成一个对象
  • 移除标记参数:用两个函数代替一个带布尔参数的函数
  • 添加参数:有时候确实需要新参数,别怕加

核心原则:函数声明是契约。改契约的时候,要确保所有调用方都同步更新。IDE的全局重命名功能是你的好朋友。

封装变量

封装变量,说白了就是把全局变量或者模块级变量藏起来,只通过函数来访问。

为什么要这么做?你想想看,一个变量如果到处都能改,你根本不知道它什么时候变了、被谁变了。这种「幽灵变量」是bug的温床。

// 不封装
let defaultUserName = 'guest';

// 封装后
let _defaultUserName = 'guest';
function getDefaultUserName() {
  return _defaultUserName;
}
function setDefaultUserName(name) {
  _defaultUserName = name;
}

我曾经在一个遗留系统里调试过一个bug,花了整整两天。最后发现是一个全局变量在某个角落里被改了值。从那以后,我对全局变量就特别敏感。能封装的,一律封装。

小技巧:封装变量不只是为了安全,更是为了将来好改。哪天你想加个日志、加个校验,直接在setter里加就行了,调用方完全不用动。

变量改名

这个重构手法看起来简单,但做起来学问不小。变量改名,不只是把 a 改成 age 这么简单。

我建议你遵循一个原则:变量的名字要反映它的用途,而不是它的类型

// 糟糕:只反映了类型
let str = '2024-01-01';
let arr = [1, 2, 3];
let obj = { name: 'Tom' };

// 好多了:反映了用途
let startDate = '2024-01-01';
let scores = [1, 2, 3];
let userInfo = { name: 'Tom' };

嗯,这里有个坑要注意:临时变量的名字尤其重要。因为临时变量的作用域小,很多人就随便起名。但恰恰是这种变量,最容易让人看晕。

避坑指南:我曾经在一个循环里看到 let t = ...,然后后面又出现 let t2 = ...,再后面还有 let t3 = ...。这种命名方式,重构的时候一定要改掉。临时变量也要有名字,哪怕叫 tempResult 也比 t 强。

引入参数对象

这个手法,跟前面「改变函数声明」里的合并参数是配套的。当你发现一组参数经常一起出现,就把它们封装成一个对象。

// 重构前
function bookRoom(startDate, endDate, roomType, guestName) {
  // ...
}

// 重构后
function bookRoom(bookingInfo) {
  // bookingInfo.startDate
  // bookingInfo.endDate
  // bookingInfo.roomType
  // bookingInfo.guestName
}

你可能会问:这不就是把参数换了个形式吗?有什么好处?

好处多了:

  • 参数顺序不再重要:调用方不用记顺序了
  • 容易扩展:想加个参数,直接在对象里加,不用改函数签名
  • 可以复用:多个函数用同一组参数,直接传同一个对象

我在项目中遇到过这样一个场景:一个订单处理函数有6个参数,其中4个是地址相关的。我把这4个参数封装成 Address 对象后,不仅这个函数清爽了,其他几个处理地址的函数也跟着受益。

注意:引入参数对象不是让你把所有参数都塞进一个对象。只有那些逻辑上相关的参数才应该放一起。不相关的参数硬凑在一起,反而会让代码更乱。

知识体系总览

这一节讲的四个重构手法,其实都围绕着一个核心:让代码的意图更清晰。函数声明是接口,变量是数据,名字是标签。把这些整理好了,代码自然就好读了。

下面这张图,帮你理清它们之间的关系:

第一组重构(下)知识体系 代码意图清晰化 改变函数声明 封装变量 变量改名 引入参数对象 改名字 调参数 隐藏数据 提供访问 反映用途 避免模糊 合并参数 封装成类 目标:让代码的意图一目了然

这四个手法,你可以在日常开发中随时用。改个名字、封装个变量,花不了几分钟,但带来的收益是长期的。代码是写给人看的,机器只是顺便执行一下。把人的体验放在第一位,重构就有了方向。

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