学习重构 -- 代码的坏味道

241 阅读5分钟

代码的坏味道

神秘命名(Mysterious Name)

改名不仅仅是修改名字而已.如果你想不出一个好名字,说明背后很可能潜藏着更深的设计问题.为一个恼人的名字所付出的纠结,常常能推动我们对代码进行精简.

改变函数声明 124 变量改名 137 字段改名 244

重复代码(Duplicated Code)

提炼函数 106 移动语句 223 函数上移 350

过长函数(Long Function)

小函数: 更好的阐释力、更易于分享、更多的选择 -- 都是由小函数来支持.

我们遵循这样一条原则: 每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名.我们可以对一组甚至短短一行代码做这件事.哪怕替换后的函数调用动作比函数自身还长,只要函数名称能够解释其用途,我们也该毫不犹豫地那么做.

百分之九十九的场合里,要把函数变短,只需要使用 提炼函数(106) .找到函数中设和集中在一起的部分,将他们提炼出来形成一个新函数.

如果函数内有大量的临时变量,可以运用 以查询取代临时变量(178) 来消除临时元素. 引入参数对象(140)保持对象完整(319) 则可以将过长的参数列表变得更简洁一些.

如何确定该提炼哪一段代码?

一个很好的技巧是: 寻找注释. 他们通常能指出代码用途和实现手法之间的语义距离.如果代码前方有一行注释,就是在提醒你: 可以将这段代码替换成一个函数,而且可以在注释的基础上给这个函数命名.就算只有一行代码,如果它需要以注释来说明,那也值得将它提炼到独立函数中去.

条件表达式和循环常常也是提炼的信号.你可以使用 分解条件表达式(260) 处理条件表达式.

对于庞大的 switch 语句, 其中的每个分支都应该通过 提炼函数(106) 变成独立的函数调用.

如果有多个 switch 语句基于同一个条件进行分支选择,就应该使用 多态取代条件表达式(272).

至于循环,应该将循环和循环内的代码提炼到一个独立的函数中.如果你发现提炼出的函数很难命名,可能是因为其中做了几件不同的事.如果是这种青款,请使用 拆分循环(227) 将其拆分成各自独立的任务.

过长参数列表(Long Paramerter List)

如果可以像某个参数发起查询而获得另一个参数的值,那么就可以使用 以查询取代参数(324) 去掉这第二个参数.

如果你发现自己正在从现有的数据结构中抽出很多数据项,就可以考虑使用 保持对象完整(319) 手法,直接传入原来的数据结构.

如果几个参数总是同时出现,可以用 引入参数对象(319) ,直接传入原来的数据结构.

如果某个参数被用作区分函数行为的标记(flag),可以使用 移除标记参数(314) .

如果多个函数有同样的几个参数,引入一个类就尤为有意义.你可以使用 函数组合成类(144) ,将这些共同的参数变成这个类的字段.

全局数据(Global Data)

首要的防御手段是 封装变量(132) , 每当我们看到可能被各处的代码污染的数据, 这总是我们应对的第一招.

可变数据(Mutable Data)

封装变量(132) 来确保所有数据更新操作都通过很少几个函数来进行,使其更容易监控和演进.

如果一个变量在不同时候被用于存储不通的东西, 可以使用 拆分变量(240) 将其拆分为各自不同用途的变量, 从而避免危险的更新操作.

使用 移动语句(223)提炼函数(106) 尽量把逻辑从处理更新操作的代码中搬移出来, 将没有副作用的代码与执行数据更新操作的代码分开.

设计 API 时, 可以使用 将查询函数和修改函数分离(306) 确保调用者不会调到有副作用的代码, 除非他们真的需要更新数据.

使用 移除设置函数(331) , 帮我们发现缩小变量作用域的机会.

如果可变数据的值能在其他地方计算出来, 这就是一个特别刺鼻的坏味道, 可以使用 以查询取代派生变量 (248) .

可以用 函数组合类(144) 或者 函数组合成变换(149) 来限制需要对变量进行修改的代码量.

如果一个变量在其内部结构中包含了数据, 通常最好不要直接修改其中的数据, 而是用 将引用对象改为值对象(252) 令其直接替换整个数据结构.

发散式变化(Divergent Change)

如果某个模块经常因为不同的原因在不同的方向上发生变化,发散式变化就出现了.

如果发生变化的两个方向自然地形成了先后次序. (比如说, 先从数据库取出数据, 再对其进行金融逻辑处理), 可以用 拆分阶段(154) 将两者分开. 如果两个方向之间有更多的来回调用, 用 搬移函数(198) 把处理逻辑分开. 如果函数内部混合了两类处理逻辑, 应先用 提炼函数(106) 将其分开, 然后再搬移. 如果模块是以类的形式定义的, 就可以用 提炼类(182) 来做拆分.

霰弹式修改(Shotgun Surgery)

TODO