为什么说不要用参数控制代码逻辑
什么叫参数控制代码逻辑?我们可能经常在项目中看到过类似这种代码:
func Worker(isA bool) {
if isA {
// codeBlockA
}
// repeatedCodeBlock
}
上面示例代码就叫做参数控制代码逻辑,它经常发生在我们想复用代码的时候。然而它并不是复用代码正确的“姿势”,它是我们代码中的“坏味道”,为什么这么说呢?我举两个简单的例子:
如果这段代码不继续恶化,保持仅有一个 if 条件控制代码逻辑,这种复用代码的方式还可以忍受。
但是如果未来某一天业务需求变更,新增了一个条件 isB,恰巧 isB 可以复用repeatedCodeBlock
代码块,那么代码很可能写成这样。
func Worker(isA bool, isB bool) {
if isA {
// codeBlockA
}
if isB {
// codeBlockB
}
// repeatedCodeBlock
}
甚至有可能会发生参数穿透(参数传递到子函数)
func Worker(isA bool) {
if isA {
// codeBlockA
}
subWorker(isA) // 参数传递到子函数
// repeatedCodeBlock
}
func subWorker(isA bool) {
// codeBlock
}
我们可以很容易的发现,随着需求的迭代,函数将变得越来越复杂臃肿,这带来了哪些影响呢?
- 低可维护性、低可扩展性、低复用性。某一天,isB 业务需求改变,某些代码不能复用了,请问示例函数修改起来是否难度很大?函数内的代码,变量依赖错综复杂,说不定一个小小的改动就引入了 Bug。
- 低可测试性。我们都清楚,单元测试中最让人头疼的就是大量的 Mock,而针对这种庞大的函数做单测,恰巧需要大量的 Mock。
那么我们该如何合理的复用代码呢?很简单,将重复的业务逻辑抽离成独立的子函数即可。
func WorkerA() {
// codeBlockA 独立业务逻辑
// 重复业务逻辑 -> 函数复用
repeatedCodeWorker()
}
func WorkerB() {
// codeBlockB 独立业务逻辑
// 重复业务逻辑 -> 函数复用
repeatedCodeWorker()
}
func repeatedCodeWorker() {
// codeBlock
}
对比之前的实现方式,我们可以很容易的发现这种新写法的好处:
- 高可测试性,
repeatedCodeWorker
函数很方便测试,并且因为复用程度高,针对此函数写单测性价比特别高。与此同时,因为业务 A、B 从一个大函数分别被抽离成较小的子函数,所以 Mock 的工作量锐减。 - 高可维护性、高可扩展性、高复用性。未来如果业务需求有变动,那么修改对应的 Worker 即可,不会影响其他业务。并且因为有完善的单元测试,改动代码引入 Bug 的风险也大大降低了。
这种思想有点类似软件工程领域中用组合代替继承的概念,代码不死板,更具有灵活性。