1.4 易于演进
软件之所以叫作“软”件,是因为它天生就应该是便于修改的。演进是软件的最本质特征。
1.4.1 演进是软件的最本质特征
软件的演进特征是软件和人类创作的其他东西的根本区别所在。例如,我们生产一个汽车零件,在生产完成之后它就是预期的样子。如果对这个零件的要求发生了变化,会去生产一个新的型号,而不太会在原来的基础上重新加工。但是,软件是不一样的。在实现了早期功能之后,软件并不会停止“生长”,而是会在后续的业务发展中被持续注入新的功能或者放入新的使用场景中。软件应该是“软”的。
要做到软件是“软”的并不容易。设计不良的软件,在经过多次修改之后,往往会变得混乱不堪,看起来就像是补丁摞补丁的衣服。甚至对于初始设计良好的软件,如果缺乏有效的演进策略,结果也同样是糟糕的。这也就是人们常说的,代码在演进过程中容易变得腐化。
1.4.2 为演进而设计
为了让软件具有好的演进能力,我们需要让它能方便地演进、安全地演进。良好的设计结构、自动化测试和简单设计的理念都是实现易于演进的设计的重要方面。
良好的设计结构
一个软件中有许多不同的关注点。例如,在电商系统中,订单的支付行为意味着成交,而成交可能带来积分。如果改动一个关注点,如支付,还会影响到用户积分,就会让问题变得更加复杂。好的设计,能够把那些似乎藕断丝连的逻辑巧妙地分解开,形成正交的设计,让它们互不影响。正交设计是增强代码演进能力最重要的手段,如图1.3所示。
图1.3 正交设计:一个维度的变化不会影响其他维度
有一些好的设计原则有助于形成正交设计。例如,观察设计中相关因素的变化频率,把容易变化和不容易变化的部分分离开,就能减少变化影响。再比如,根据变化的方向来识别设计对象的职责,这就是面向对象设计中的单一职责原则。此外,通过恰当的抽象,让代码在面临新的业务场景时更容易扩展,甚至完全无须修改原来的代码,这就是面向对象设计中的开放-封闭原则。本书在第5章将会讨论这些和演进能力密切相关的设计原则。
自动化测试
设计必须安全地演进,增加的新功能不可以破坏既有的功能。当业务场景变得越来越复杂,靠人记住所有的业务场景,或者靠人进行回归测试,显然是一个不可能完成的任务。因此,具有自动化测试的代码,往往在演进方面的表现也更好。本书的第7章和第10章将会介绍如何使用自动化测试保障设计的演进能力。
简单设计
要警惕一种可能会伤害到设计演进能力的“前瞻性设计”做法——为未来所做的“预留设计”。有时候,有些开发者喜欢在设计中留下某些“前瞻性”,如在数据库中加入几个叫作“预留1、预留2、预留3”的字段,在代码中加入一些想象出的未来需要的扩展点等。可惜事与愿违,它们往往不会增强软件设计的适应性,还可能适得其反。其道理在于:今天你所做的任何决定,都是软件未来变化的约束。预留设计当然也是一样。只有那些恰好产生在预留方向上的新需求,才可以方便地演进,对于其他的变化方向,则无能为力。这是把“软”件当成“硬”件的一种思维模式。这种错误的设计方式,也被称为过度设计或大规模预先设计。本书的11.2节将会讨论与此相对的简单设计原则。