1.5.4 抽象层次一致性原则
抽象层次要保持一致,一致性可以减少混乱,并降低理解成本。比如,你把水果、苹果、香蕉归类放在一起,就会显得不协调,自己心里也会犯嘀咕:为什么要把水果和苹果、香蕉放在一起呢?水果和苹果、香蕉不是一个抽象层次的(水果比另两者高一个抽象层次)。同样,我们在写代码的时候,如果把不同抽象层次的代码放在一起,也会在无形中提高认知和理解成本。
鉴于此,抽象层次一致性原则(Single Level of Abstration Principle,SLAP)应运而生。SLAP是ThoughtWorks的总监级咨询师Neal Ford在《卓有成效的程序员》一书中提出来的概念,其思想源自Kent Beck提出的组合方法模式(Composed Method Pattern,CMP)。
SLAP强调每个方法中的所有代码都处于同一级抽象层次。如果高层次抽象和底层细节杂糅在一起,就会显得代码凌乱,难以理解,从而造成复杂性。
举个例子,假如有一个冲泡咖啡的原始需求,其制作咖啡的过程分为3步。
(1)倒入咖啡粉。
(2)加入沸水。
(3)搅拌。
其伪代码(pseudo code)如下:
这时新的需求来了,需要允许选择不同的咖啡粉,以及选择不同的风味。于是上述代码从一开始的“眉清目秀”变成了下面这样。
如果再有更多的需求过来,代码还会进一步恶化,最后就变成一个谁也看不懂的“逻辑迷宫”、一个难以维护的“焦油坑”。
我们再回来看一下,新需求的引入当然是根本原因,但是除此之外,另一个原因是新代码已经不再满足SLAP了。具体选择用什么样的咖啡粉是“倒入咖啡粉”这个步骤应该考虑的实现细节,和主流程步骤不在一个抽象层次上。同理,加糖、加奶也是实现细节。
因此,在引入新需求以后,制作咖啡的主要步骤从原来的3步变成了4步。
(1)倒咖啡粉,存在不同的选择。
(2)倒开水。
(3)调味,根据需求加糖或加奶。
(4)搅拌。
根据组合方法模式和SLAP,我们要在入口函数中只显示业务处理的主要步骤。其具体实现细节通过私有方法进行封装,并通过抽象层次一致性来保证,一个函数中的抽象应该在同一个水平上,而不是将高层抽象和实现细节混在一起。
根据SLAP,我们可以将代码重构为:
重构后的makeCoffee()又重新变得整洁如初了,实际上,这种代码重构也是一种结构化思维的体现。在结构化思维中,有一个要点就是结构的每一层要属于同一个逻辑范畴、同一个抽象层次。更多关于结构化思维的内容会在第3章中详细介绍。
接下来,我们看一个真实的案例。在Spring中,做上下文初始化的核心类AbstractApplicationContext的refresh()方法,可以说在如何遵循SLAP方面给我们做了一个很好的示范。
试想:如果上面的逻辑混乱、无序地平铺在refresh()方法中,其结果会是怎样的?