03.吸积与持续迭代

吸积与持续迭代

复杂度是递增的,并不是某些特定事物让系统复杂,而是数十或数百个小事物的积累。吸积效应,即一个事物的生成过程,就是从一个核心开始,逐步吸附新的资源,渐渐积累而形成的。这本来是天文学上的一个词,用来解释星体的形成。比如太阳是从一个核心,逐渐吸收周围的宇宙物质逐渐形成的。

从字面上我们可以理解为吸附和积累。每次加一点,每次加一点,最后就有了一大堆。这句话来描述吸积效应最好不过,出自 1975 年出版的《人月神话》。无论一次代码迭代和升级看上去是多么独立,客观上导致的结果就是代码量从一点点变成一大堆,遗留代码和遗留系统就是这样形成的。遗留代码到底有什么问题?吸积效应导致代码十分庞大,没有人可以完全了解这个系统。当系统出现问题时,最了解这部分代码的人可能已经离职而杳无音讯。

软件本身如此易变(Malleable),因此软件设计是持续的过程,贯穿软件系统的整个生命周期。软件设计的这个特点,使它和现实世界中实体系统(例如建筑,船舶,桥梁)的设计差异很大。然而,人们并不是从一开始就认可软件设计设计的持续性特点。在软件开发历史上很大一部分时间里,设计被认为和其他的工程一样:设计过程集中在项目的开始阶段.这种方式的一个极端就是瀑布式开发模型。在这个模型中,项目被划分为分离(Discrete)的各个阶段:需求分析,设计,编码,测试和维护,前一个阶段在下一个阶段开始前结束,并且很多场景下,不同人负责不同的阶段。整个系统的设计工作在设计阶段一次性完成,这个阶段结束后,设计就冻结了。后面的各个阶段就是逐步实现设计的过程.

软件复杂性随着时间增加的衍化

不幸的是,瀑布流开发模型很少在软件开发中有效。软件系统比其他实体系统内在(Intrinsically)复杂性更高.大型软件系统,很难在开发前设想清楚系统设计个各个方面和准确理解各个设计决策的影响。结果是,初始的设计会有很多问题,这些问题一开始并不明显,在实现的过程中才变得更加清晰。瀑布开发模型没有组织上的保障来适应这种设计变化(例如:软件设计人员可能已经调整到其他项目上). 因此,开发人员尝试在不修改整体设计的前提下,通过补丁(Patch)的方式来修复问题.这导致复杂性的爆炸性增长.

因为有这些问题,今天大部分软件开发项目采用了增量式开发模式(例如敏捷开发模式)。在这种模式下,初始的设计聚焦在整体功能的一小部分上进行设计,实现和评估。初始设计的问题逐步被发现,并被纠正。随后更多的功能点被设计,实现和评估。每个迭代都暴露已有设计的问题,这些问题在下一个功能集的设计前得到解决。通过将设计的分散(Spread Out),初始设计的问题能够在系统还很小的阶段得到修复。得益于初始阶段获得的经验,后续功能设计问题会更少.

这种增量式开发模型之所以有效,是因为软件系统具有足够的可塑性(Malleable),使得在软件开发的中途进行重大的设计变更是可行的。做为对比来看,在实现过程中对实体系统的重大设计进行变更往往更有挑战。例如,在建筑过程中,增加/减少支撑桥梁的支柱的数量是不可行的。

增量式设计意味着软件设计永不结束。设计在系统的生命周期中持续发生:程序员要时刻考虑设计问题。增量开发也意味着持续重构。一个系统的初始设计几乎从来都不是最好的方案。随着经验的增加,必然会发现更好的设计方案。作为程序员,你需要时刻寻找机会来改进当前系统的设计,并且你也需要设计改进做计划,预留时间。遵从 John Ousterhout 老爷子的指导,作为程序员对设计和代码保留着必要的"洁癖",不断追求更好的设计,在面临"将就"和"重构"的时候会做出更好的选择。

腐败的软件系统

大项目的陷阱是,随着时间的推移,它们会变得非常复杂,以至于重写比培养新人来理解代码然后修改要容易得多。软件是纯粹脑力劳动的结果,极度依赖软件工程师本人。因此,软件的交接就不简单是生产工具的交接,更是知识的传递。而只要有知识的传递就必然有丢失,有变形。因此一个离职频繁的团队,知识的反复交接,必然会丢失大量的细节,以致于到最后,接手的程序员对系统的理解是面目全非。

而且最严重的不仅是知识的丢失,更有责任心的丢失。如果一个模块是由某个软件工程师一手开发并维护。在感情上,他自然的倾向于爱惜羽毛,认真对待。在责任上,他也无可推卸,因为这一切都源于他。但是,在一个离职率高的团队,一个软件反复转手,后来者除了感情不深外。出了问题,也自然的会把所有的缺陷归罪于前人,包括设计和开发。一个反复转手的软件,必定会出现经典的“破窗效应”:“反正系统已经这么烂了,而且也不是我的责任,我既没有能力,也没有必要去矫正它。”于是,有了 bug,接手者自不会去认真研究问题的根源,而是倾向于用快速且肮脏的“打补丁”方式来解决。反正是先把问题摁住,至于整个系统是否会越来越腐败,管它呢?因此,整个软件系统最终呈现的就是补丁摞补丁的“垃圾场”。

瀑布与增量

由于软件具有很好的延展性,因此软件设计是一个贯穿软件系统整个生命周期的连续过程。这使得软件设计与诸如建筑物,船舶或桥梁的物理系统的设计不同。但是,并非总是以这种方式查看软件设计。在编程的大部分历史中,设计都集中在项目的开始,就像其他工程学科一样。这种方法的极端称为瀑布模型,该模型将项目划分为离散的阶段,例如需求定义,设计,编码,测试和维护。在瀑布模型中,每个阶段都在下一阶段开始之前完成;在许多情况下,每个阶段都由不同的人负责。在设计阶段,立即设计整个系统。

不幸的是,瀑布模型很少适用于软件。软件系统本质上比物理系统复杂。在构建任何东西之前,不可能充分具象化出大型软件系统的设计,以了解其所有含义。结果,初始设计将有许多问题。在实施良好之前,问题不会变得明显。但是,瀑布模型的结构此时无法适应主要的设计更改(例如,设计师可能已转移到其他项目)。因此,开发人员尝试在不改变整体设计的情况下解决问题。这导致复杂性的爆炸式增长。

由于这些问题,当今大多数软件开发项目都使用诸如敏捷开发之类的增量方法,其中初始设计着重于整体功能的一小部分。设计,实施和评估此子集。发现和纠正原始设计的问题,然后设计,实施和评估更多功能。每次迭代都会暴露现有设计的问题,这些问题在设计下一组功能之前就已得到解决。通过以这种方式扩展设计,可以在系统仍然很小的情况下解决初始设计的问题。较新的功能受益于较早功能的实施过程中获得的经验,因此问题较少。

增量方法适用于软件,因为软件具有足够的延展性,可以在实施过程中进行重大的设计更改。相比之下,对物理系统而言,主要的设计更改更具挑战性:例如,在建筑过程中更改支撑桥梁的塔架数量不切实际。增量开发意味着永远不会完成软件设计。设计在系统的整个生命周期中不断发生:开发人员应始终在思考设计问题。增量开发还意味着不断的重新设计。系统或组件的初始设计几乎从来都不是最好的。经验不可避免地显示出更好的做事方式。作为软件开发人员,您应该始终在寻找机会来改进正在开发的系统的设计,并且应该计划将部分时间花费在设计改进上。

如果软件开发人员应始终考虑设计问题,而降低复杂性是软件设计中最重要的要素,则软件开发人员应始终考虑复杂性。这本书是关于如何使用复杂性来指导软件设计的整个生命周期。

上一页
下一页