干货 | 《重构》都不如这七种武器“速效”

观点|Mike Cohn强烈推荐:ScrumMaster该有蝙蝠侠的特质
2019年7月15日
敏捷团队怎么做,能让你的PO随叫随到
2019年7月22日
导语:能不能写出单元测试,在很多情况下,其实是设计问题。依据测试驱动开发方式(TDD)写出来的代码和单元测试,与不用TDD写出来的代码和单元测试,以绝大多数情况,都可以认为是两个截然不同的“物种”。

而对于我们普通程序员来说,写出具有易测试性的代码,是⾛向团队⾼效的必由之路

2006年的时候,为了让开发工程师能够写出良好的单元测试,谷歌还聘请了很多专职技术教练,对谷歌员工进行培训(目前在网上仍可以搜索到当年的一系列单元测试和代码设计的培训录像)。

正文


对于⼀个以前没有写过单元测试的同学来说,⽆论是修改旧代码,还是编写新代码,要想写出实⽤的单无测试,往往都意味着要对原有代码进行一次重构(它与重写的区别,请自行搜索)!

重构有很多技巧和⽅法,但是如果以写可测试代码为⽬的,其关键就只有⼀个字——“少”,包括以下两点:

1、函数代码⾏数要少,功能要单⼀,分⽀逻辑要少。

2、类或者函数的依赖要少,尽量不使⽤除参数外的任何其他外部依赖。

虽然只有一个“少”字诀,但还要有趁手的武器才行。下面就是在具体实践中⾮常实⽤的七种武器。

七种武器列表


1、函数不要超过 40 ⾏。

在它的多种代码规范中都做了这相同的规定,当然这不是个硬性标准,具体还是要看函数的复杂度,以能否轻松写成⾼覆盖率的单测为准。

2、每个函数只做⼀件事,

事的拆分不光服从逻辑关系也要考虑到拆分后的粒度。

把⼤象放在冰箱⾥,开⻔,塞⼤象,关⻔,这其实是三件事。

根据⽤户 ID 输出⽤户资料,从数据库查询,对查询结果处理,格式化输出,这也是三件事。

3、函数的依赖尽量通过参数来传递。

⽐如,⼀个函数依赖数据库查询,那么最好把数据库对象当参数传递进来,⽽不是在函数内部构造。当然更好的是传递数据库对象的抽象,⽐如是 DataProvider ⽽不是 SqlDataProvider。

4、尽量不要把复杂结构当做参数传递给函数。

有时这种复杂结构根本没有构造函数,或者构造函数依赖很多,那么这种情况就很难在测试时构造参数了。

可以只传递复杂结构中函数所必须的参数,或者是容易构造的参数。

5、函数间或者对象间依赖接⼝或者抽象,⽽不是依赖具体的对象。

参见依赖倒置原则。⽐如⼀个类 A 依赖另外⼀个类 B ,那么,最好是类 A 的抽象依赖类 B 的抽象,这样在写类 A 的单测时能⽅便地 Mock 类 B 。因为类 A 中对 B 的依赖都是 B 的接⼝或者抽象,那么就可以把 B 的 Mock对象 直接传递给 A 使⽤。

具体传递⽅法有三种:函数接⼝传递,构造⽅法传递和 setter ⽅法传递。

如果嫌代码太多麻烦不想⽤抽象或者接⼝建⽴依赖,某些情况下还可以⽤ 类B 的衍⽣类 FakeB 重写的⽅式来帮助单测。

6、旧瓶装新酒

旧类或者旧函数太⼤难以直接测试,可以考虑从中拆分出多个可测的类或者函数,同时保持旧有的类或函数接⼝不变。

7、不建议对私有函数写单元测试

⼀般私有函数的测试是通过公有函数的测试来覆盖的,如果覆盖不了,这意味着这个私有函数“管的有点宽”,即内聚度不⾼,可以考虑重新设计了。

如果确实需要对私有函数单测,也有一些办法,例如,C++可以⽤friend,Go 可以新建单测。

结束语


与遗留代码相比,新增代码的单元测测试相对好写⼀下。如果想对原来没有测试的遗留代码写单元测试的话,重构往往是第⼀步。《重构》第2版 经典名著,系统性地介绍了很多很多重构技巧,想要内功精进,必须得读。

(但是,那书里的教条太多啦,不如上⾯七武器来的实⽤快捷~哈哈哈~)

重磅推荐

《持续交付2.0》

硅谷顶级互联网公司的产品研发方法。

在未来十年里,

快速提升工程生产力,打造有战斗力的团队,

应对激烈的市场竞争,

这一本书就够了~

拨打免费咨询电话 021-63809913