重构读书笔记十——重构API

重构 Jan 18, 2021

概述

模块和函数是软件的骨肉,而API则是将骨肉连接起来的关节。易于理解和使用的API非常重要,但是也很难见到[1]。好的API应该将更新数据的函数与只是读取数据的函数清晰分开。

10.1 将查询函数和更新函数分离

描述

将数据的查询函数和更新分开

动机

或许在业务上两个操作要一起完成,但是也要将实现分开来,让调用方来掌控。如果某个函数只提供一个值,没有副作用。那么我们就可以任意调用这个函数。

10.2 函数参数化

描述

如果两个函数可以的逻辑是类似的,只有入参上不同,那么可以通过调整入参,合并两个函数。

代码

function tenPercentRaise(aPerson) {    aPerson.salary = aPerson.salary.multiply(1.1); } function fivePercentRaise(aPerson) {    aPerson.salary = aPerson.salary.multiply(1.05); }

refactor:

function raise(aPerson, factor) {   aPerson.salary = aPerson.salary.multiply(1 + factor); }

动机

在代码里面重复是bug的温床。代码逻辑上的重复应该被去掉。

10.3 移除标记参数

描述

把只用来标记逻辑分支的参数去掉,然后把逻辑分支拆分到不同的函数中去。

代码


function setDimension(name, value) {   
  if (name === "height") {   
    this._height = value;    
  return;  
  }  

  if (name === "width") {    
    this._width = value;    
  return;  
  }

refactor:

function setHeight(value) {this._height = value;}  
function setWidth (value) {this._width = value;}

动机

“标记参数”是这样的一种参数:调用者用它来指示被调函数应该执行哪一部分逻辑。作者不喜欢标记参数,因为它们让人难以理解到底有哪些函数可以调用、应该怎么调用。拿到一份API以后,我首先看到的是一系列可供调用的函数,但标记参数却隐藏了函数调用中存在的差异性。使用这样的函数,我还得弄清标记参数有哪些可用的值。布尔型的标记尤其糟糕,因为它们不能清晰地传达其含义——在调用一个函数时,我很难弄清true到底是什么意思。如果明确用一个函数来完成一项单独的任务,其含义会清晰得多。
[me]:这种我一般叫把业务逻辑隐藏到了数据中。是一种非常糟糕的代码体现。就像作者所说,只有当了解输入参数是什么,才能看懂这段代码,更糟糕的是用布尔,字符串类型和数字枚举等无法限制枚举对象的类型。很多时候我们是要根据参数来判断逻辑的,最好用工厂来抽象判断的逻辑,之后的代码就不太需要标记参数了。同时标记参数用可枚举的值,让阅读代码的人知道具体有哪些值。

10.4 保持对象完整(这点我不太同意)

描述

作者认为如果函数需要一个对象的某些值就将整个对象都传入进去。

动机

如果我看见代码从一个记录结构中导出几个值,然后又把这几个值一起传递给一个函数,我会更愿意把整个记录传给这个函数,在函数体内部导出所需的值。 “传递整个记录”的方式能更好地应对变化:如果将来被调的函数需要从记录中导出更多的数据,我就不用为此修改参数列表。并且传递整个记录也能缩短参数列表,让函数调用更容易看懂。如果有很多函数都在使用记录中的同一组数据,处理这部分数据的逻辑常会重复,此时可以把这些处理逻辑搬移到完整对象中去。
[me]:我觉得应该遵循最小化原则——只传递函数需要的数据。将整个对象都传递过去是一种冗余操作,并且在ide在追踪对象调用的时候会发现一些没有必要的调用。最小化传递数据,减少了两个函数之间的依赖,一旦另一方想要修改对象的字段,只要检查自己内部即可。如果把对象都传递过去,无法保证对方是否只使用了自己需要的字段。(另一个原则——永远不要相信你的上下游调用者)

10.5 以查询取代参数

就不应该写这种代码出来...

10.6 移除设值函数

不想让别人给,就不要给别人这个机会,所以设值函数,构造函数什么的就都不要暴露出来。

10.7 以工厂取代构造函数

和工厂函数类似

10.8 用命令模版对象取代函数

描述

设计模式里面的命令模式和模版模式来实现代替函数

动机

好处:灵活,强大的表现力。
坏处:复杂,实现麻烦。
[me]:除了在业务上是固定的模版化和流程化的逻辑,一般不推荐这么做。如果这么做一般代表流程基本不会变动,这种模式写的代码,大的给改动就和重写一样。变化频繁的化,可以引入规则引擎来实现。

[1] :这里的API应该是广义上的API。不仅仅包括http接口、rpc接口,还包括函数,sdk对外的暴露的接口等等。任何给第三方使用的接口,都可以算作API。