前言
众所周知软件设计与开发中,追求模块的高内聚和低耦合。这两个原则适用在所有的软件类型的开发与设计中。遵循这个原则,就会使得工程师在软件设计中有意无意的使用“抽离”这个概念,最常见的场景就是将一团混在各种功能的代码,通过重构分离成各个功能独立的函数或者类。“抽离”除了会带来软件的高内聚和低耦合之外,还有一些重要的因素容易被设计者忽略,这些大部分不完全在软件开发的视角内,而是在软件开发与管理或产品的世界的交接处,亦或在软件开发自然人的精神世界与软件开发世界的交接处。把握这些被忽略要素,不仅能够提升于软件产品的价值或较低其成本,亦可以使开发者有效平衡内心世界与外部世界的关系。
一、抽离的作用
1. 抽离可以增加功能的纯度,使其易被超系统整合
“抽离”本身其最原始的初衷,就是满足软件模块的高内聚和低耦合。抽离出的实体可能是软件函数、类、模块或微服务等。这些实体功能是单一且对外依赖程度减少。这个过程就会使得这个实体其功能变的单一。如果与现实世界中的操作对比,这种操作可以理解为提纯,既从含有杂质的物质中提取出人们关注的物质。也可以换一种角度理解成化学上的还原,类似于从化合物中还原出单质。
 不论是虚拟的软件世界中抽离,还是现实的化学世界中提纯或还原。他们共同点都是使得他们抽离出的实体较原来实体相比,要不是概念变小、功能变少了、物质种类变少了,亦或是物质原子量变少了。这种变化的共同点,既抽离出的实体与原来相比,更小,微观化程度有所提升。这种微观化程度提升所带来的好处,既它易被超系统进行整合,从软件视角看,既它可被复用,A系统可以使用它,B系统亦可以。从现实世界中看,提纯的物质或单质可以再与其他物质掺杂或者化学反应,产生新的物质。新的物质拥有新的化学或物理特性,可以适用在新的场合
“抽离”出的软件实体,易被超系统整合性,使其对于企业和个人带来巨大的利益空间。从企业角度考虑,“抽离”的软件实体被模块化、标准化和通用化后,可以缩短软件设计、开发周期,降低软件产品成本。亦可复用到其他软件产品上,减少新软件的研发周期和前期投入,成为赢得竞争软件市场的关键能力。从个人角度看,个人精力是有限的,如果想通过个人来完成一个商业化系统开发并投入使用取得成功赢的荣誉与机会,机会渺茫。但是如果个人精力集中于抽取出来的软件模块的上对其进行打磨,使其亦被整合到一个甚至多个成功的软件系统系统。以此路径带来个人声誉的增长,其难度水平会大大低于前者。
2. 抽离可以缓解或调节系统矛盾
抽离本身带有分割的性质,从A模块中抽离出B模块,也意味着B模块与A模块的联系变少(这里要说明:如果没有变少,那么抽离时没有满足软件设计基本要求高内聚和低耦合)。这种联系的减低,意味抽离本身带有分割的作用,而分割可以缓解或调节系统中的矛盾,这里矛盾有软件世界中的矛盾亦有现实世界中的矛盾。软件世界中的矛盾,主要是指变与不变的矛盾,软件系统做为满足现实世界需求的载体,构成它的所有要素也和现实物质世界一样是变动的,但是每个部分变动的频次和大小是不一样的,例如软件与人的交互部分GUI变动远高于内部业务逻辑,内部逻辑中业务逻辑变动高于底层基础功能逻辑。如果所有的要素杂糅在一起,那么某一个部分的变动,都要牵动其他部分,这使得每次变动的成本太高(这里的成本是指操作成本、变动人思维成本和变动后的测试成本等因素)。使用抽离策略,可以将经常变动的模块,从杂糅系统抽出单独功能模块,使其每次变动都局限在某个或某几个模块,这样可以降低变动的成本。
至于缓解现实世界中的矛盾,依然是从软件系统做为满足现实世界需求的载体谈起,软件系统是现实世界一部分,又与现实世界有差别,其体现在工程师在设计软件系统时,其思维特点倾向于让软件系统满足理想设计的特征,例如对称性、均等性和变换传递特性。但是软件系统服务的现实环境,它不是一个对称的,各个需要软件处理的信息也不是等权重的。这种差异,使得软件世界因现实世界业务特点,做出改变的时候。就会引发工程师对于它构建的理想软件世界破缺而感到不满和抵触,这种现象在初中级工程师中表现由于突出。抽离可以缓解(是缓解,不是解决)这种现象,抽离出的模块构成的软件。每次变化局限在少部分要素中,以此来降低工程师对于理想世界被破坏抵触程度(因为只改变了某个局部)。亦会引发工程师对于经常变化点的思考,例如:是否在这些易变化点上,使用其他软件开发原则来改善这些易变动区域的特性。
3. 抽离是软件价值资产化的重要环节
抽离出的实体带来的优势明显:实体的高内聚和低耦合,软件复用度提升。但现实情况是即使是模块高内聚和低耦合,不代表它就能复用到其他软件上,这中间有一个环节就是功能模块的管理。抽离出的模块能够成为公司重要的有效的数据资产的前提是,1. 抽离出的实体有价值(软件价值和业务价值)。2. 上述价值被人所知所感所用。市面上大部分书籍都对第一条价值进行详细的介绍。这里着重对第二条进行展开,大部分的工程师对于自己抽离出的实体,是不进行描述或很少进行描述的。其原因又如下两点:
- 模块本身功能,自己很清楚这块业务也是自己在维护没有必要“多此一举”。
- 公司价值评估体系对于文档化重视度不够。
对于解决第一点,更多的是工程师个人对于“抽离”出的实体价值变现的认识不完整。软件由代码+文档组成,抽离出的模块亦是一种软件,他依然满足上述定义。顾模块的价值体现,如果想被更多的人认识或使用对齐进行自然语言的文档化工作是自己价值对外变现的关键一环,缺失次环节,工作价值对外体现会大打折扣。另文档化模块,亦是个人工作价值倍增器,一旦你的文档化你的成果,可以让更多的人了解这块的东西与价值,机会也在此出现。

对于第二点,其本质是公司或部门,对于数字资产重视程度不够,或者根本没有意识到数字资产这个概念。技术部门核心业务承载实体是软件。大部分情况,把承载软件认为是一种资产。但是这种认识会使得其资产的价值大打折扣,因为一个具体的软件他特定性较强,泛化性不够。会导致其复用性较差,使其资产价值的倍增特性不够。而核心业务被模块化之后,它的微观化特点凸显,易被更多超系统整合,那么此时他资产倍增效应凸显。顾模块化的业务载体是一个组织重要的资产,使其业务可以模块化的前提除了软件代码层面的分离,亦有与其一起文档化工作。前者工程师普遍可以自发的进行,而后者需要公司价值评估手段的引导,使其进行。
软件模块的统一化呈现形式,使其易被别人感知和易被管理。而亦被感知和被管理,意味着其容易被超系统整合。多个统一呈现化形式的软件模块,构成的软件资产,其价值远大于一个零散的软件模块,即使此模块已被彻底文档化。这种统一的多业务模块集合软件资产,其可以根据不同的外部业务被单个使用或配合使用,此时此资产已有了承载某些特定领域的解决方案的物理基础了。
二、抽离方式
工程师抽离出软件功能实体或者模块的前提是识别,即识别出某个概念。比如在面向对象设计与开发中,经常封装成一个类,这个动作前提是,能够识别出这个类要代表的实体是什么。比如一个汽车进行建模的时候,可能会划分成轮子类,底盘类,车壳类等等,这些类背后带代表着一种概念。由此可知,对于实体抽取的前提是,工程师大脑中有相应的概念,那么如何才能识别出概念呢。一种直观的认识是,在众多事务中,抽取某个实体事物把对它的定义当做一个概念。这种方式符合直觉,把它套用到上面的汽车划分中的例子没有任何的问题。但是这种符合直觉的认识,只是在大脑中已经有对应的概念的时候,用这种概念去寻找实体的时候才成立。  对于那么大脑中没有的概念,上述方式很难奏效。因为上述方式中抽取“Extract”前提是我们知道了事物的边界,知道了他的特点,才能从众多事物中抽取它。但是在不知道它边界和特点时,无法完成上述步骤。尤其是在新的业务领域或者工程师不熟悉的业务领域。这里可以采用“Substract”来提取一个概念,例如看到红色布与红色铁皮,从这里选择一个实体,把这个实体中与其他实体相同部分减掉。这里就是红色,才能看出这个东西的本质是一种通过触觉感觉到的坚硬或者柔软的材质,而不是视觉上的红色。
三、抽离示例
这里用GUI界面开发中常见的模式进行举例。如果你在写界面为主的应用时,不刻意进行考虑重用和可测性,那么大部分你的代码都是杂糅性质的代码,既界面布局代码和业务逻辑是混在在一起的(这里要说明,目前主流的界面框架和IDE已经把界面布局进行抽离,所以这里杂糅更多的是指思维上和逻辑上杂糅,而不是形式上的)。下面是一个android 加法计算器代码。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv1 = (TextView) this.findViewById(R.id.textView1);
EditText et1 = (EditText) this.findViewById(R.id.editText1);
EditText et2 = (EditText) this.findViewById(R.id.editText2);
Button btn = (Button) this.findViewById(R.id.button1);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String inputText1 = et1.getText().toString();
String inputText2 = et2.getText().toString();
int num1 = Integer.valueOf(inputText1).intValue();
int num2 = Integer.valueOf(inputText2).intValue();
num1 = num1 + num2;
inputText1 = String.valueOf(num1);
tv1.setText(inputText1);
}
});
}
}
这里面最核心的业务逻辑,两个数字求和逻辑,混杂在了见面交互逻辑内部。使得这块的业务逻辑的可测性大幅下降,且这块逻辑因为其混在其他界面逻辑中,它的复用性和扩展性也不高。更不用说他的资产衍生价值。为了解决GUI应用业务逻辑与界面逻辑混杂问题,人们提出了常见的GUI架构模式MVC和MVP,这里并不详细讨论这两种模式的差异点,顾直接点名这个模式差异在于MVC中的控制器不能直接更新View,而MVP中的控制器(Present)可以更新View。
为了解决上述GUI应用业务逻辑与界面逻辑混在现象,这块需要使用抽离技术,既将两个或多个混杂在一起的实体分离开。上述情景中的实体就是业务逻辑与界面逻辑。需要将两个实体进行分离开,在软件中的体现,就可以表现为将业务逻辑放到一个单独类或模块中,将界面窗体划分到单独类或模块中。这种划分之后,逻辑层面清晰了不少。但是两种实体分割开之后,两种表现性质就已经有了显著的区别,日常生活中两种独立的实体(尤其是两种性质差别较大实体)如果想结合在一起,往往不能直接组合。需要采用连接件,这里用生活中的铜导线和铝导线进行链接的场景,举例铜线与铝线如果直接连接,因为二者化学性质不同,二者接触传递电流的时候会使得铝线接头处加速氧化,导致铝线接口电阻率上升,进而发热产生安全隐患,故日常生活中采用专用的链接端子将两种材质的导线进行连接。生活中两种金属因相对活跃性不同,尚且会出现上述情况。
那么软件世界中,两种功能截然不同的实体如果直接连接,带来的问题更大。一个最直观的例子既如果直接连接,界面代码必须引用业务逻辑代码,这就会造成退化现象,既本意是将两个独立实体分开,最后造成分开后的实体,在开发过程中,失去了独立性,使其又将融合在一起。
所以效仿现实物理世界中的例子,使两个功能独立的模块配合工作的时候,需要引入一个拎一个模块,这个模块的作用是充到一种连接器的形式,连接两个独立模块,于此同时这个模块也是起到分割界面代码和业务代码作用,调节两个模块的矛盾,使二者可以相对独立演化。这种独立出的充当桥梁的模块,在GUI设计中普遍叫做控制器(MVC中叫Controller,MVP模块叫Present)。由此GUI设计中常见划分形式分别是View(界面代码或模块)、Model(业务代码或模块)和Controller(控制器模块)。这种形式带来的好处View代码可以进行独立演化,并使用到不同GUI程序中,甚至可以独立成一个公司的产品对外出售。例如常见的GUI控件商DevExpress、Telerik和Infragistics等知名GUI控件商。Model层的业务代码由于其被Controller隔绝,使其可以进行独立演化和测试,传统业务代码测试如不进行gui分割,使其必须配合界面一起测试业务逻辑,这会造成自动化测试困难加重,测试效率下降等情况。但是业务代码从界面当中剥离以后,对外界依赖降低,可以更好的被测试夹具附着(类似于汽车发动机,可以不安装的汽车上进行测试),便于其进行自动化测试。而Controller存在,使得gui程序便于扩展,这里举一个。例子:传统gui程序,按照这种GUI设计模式划分,可以在View和Model代码保持不变的情况下,通过Controller的替换或者修改使其成为互联网联机程序。这种变化只发生在Controller模块,而其他模块基本无需修改。
四、抽离困难之处
如上文描述,抽离本质,是将软件同一概念的代码或者功能进行抽取使其单一化,这个操作类似于现实世界中的提纯。所以现实提纯时遇到困难,软件世界中都会遇到。现实中提纯工艺大部分是有一些列化学和物理反应,在一定的容器按照一定的顺序进行。这个过程中不可能提出到100%,提纯过程只能是不断让其纯度接近100%。软件世界中的过程提出过程,由一些列脑力活动和代码调整组成。这个过程不可控性比现实世界更强,因为核心抽离过程,必须先有人脑进行设计,这个过程依赖工程师经验、情绪和它所处的利益位置等诸多因素。这些因素都会影响软件分离的结果。故此软件抽离过程,重点放在已经对公司产生价值的业务和功能,因为这些功能它的价值点更高且这些功能更需要被平移到其他系统中。在这些点投入人力和物力进行抽离,这个投入的相较于所有功能都进行划分或抽离,投入与产出比更高。 在面试或者向与此系统无利益相干者介绍软件架构优势时,可以考虑所有功能都是进行了详细设计与抽离的高内聚和低耦合的模块。因为这些场景的介绍其核心目的是让对方认同此项目优势,人的心灵趋向于追求纯粹、完美和秩序,当向对方介绍系统功能可组合、完美与有条不紧的秩序时,会让其心灵产生一种美感,这种美感有助于达成此番介绍的目的
五、总结
本文对工程设计当中的“抽离”方法进行简要介绍,重点阐述这种方法被人们忽视的三种重要的作用:
- 抽离可以增加功能的纯度,使其易被超系统整合
- 抽离可以缓解或调节系统矛盾
- 抽离是软件价值资产化的重要环节
之后论述“抽离”方法运用方式,即采用“Substract”思想从众多实体中抽离出单独的概念。然后使用GUI界面开发中常见的模式进行,论述展示"抽离"方法的实际应用。最后对"抽离"方法的困难点进行了产出,即完美的“抽离”是不存在的,工程时间中的“抽离”出的模块或者实体的内聚程度,受到了研发人员、业务特点和工程环境等诸多方面的影响。所以“抽离”方法不能创建完美的模块,只能创建在特定环境下最适合的模块。