Skip to content
On this page

第一章 介绍

(全是关于复杂度)

编写计算机软件是人类历史上最纯粹的创造性活动之一。程序员不受物理定律等实际限制的约束;我们可以用现实世界中永远不存在的行为来创造令人兴奋的虚拟世界。编程不像芭蕾舞或篮球需要很好的体能和协调。编程所需要的是创造性的头脑和组织思维能力。如果你可以构想一个系统,那么你多半可以在计算机程序中实现它。

这意味着编写软件的最大限制是我们理解我们创造的系统的能力。程序的进化和获得新功能会使它变复杂,因为组件之间微妙的依赖关系。随着时间的推移,复杂度不断累积,程序员在修改系统时越来越难以将所有的相关因素理清思绪。这会减慢开发速度并且导致错误,从而进一步减慢开发速度并增加其成本。在任何程序的整个生命周期过程中复杂度增加是不可避免的。程序体量越大,参与工作的人越多,就越难管理复杂度。

好的开发工具可以帮助我们处理复杂度,并且在过去几十年中已经创造了很多出色的工具。但是我们仅使用工具所能做的是有限的。如果我们想让编写软件变得更容易,从而可以更便宜地构建更强大的系统,我们必须找到软件简化的方法。尽管我们尽了最大努力,复杂度仍随着时间的推移而增加,但更简单的设计使我们能够在复杂度变得不堪重负前构建体量更大功能更强的系统。 攻克复杂度有两种通用方法,这两种都会在本书中讨论。第一种方法是通过使代码更简单和更浅显来消除复杂度。例如,可以通过消除特殊情况或者使用统一标识符来降低复杂度。

解决复杂度的第二种方法是将其封装,那么程序员在为一个系统工作时不用同时遭受所有的复杂情况。这种方法被称作模块化设计。在模块化设计中,软件系统被划分为模块,例如面向对象语言中的类。模块被设计成彼此相对独立,以便程序员可以在一个模块上工作而不需要详细了解其他模块。

由于软件具有很好的可塑性,因此软件设计是一个持续过程贯穿软件系统的整个生命周期;这使得软件设计不同于实体系统的设计,比如建筑、舰船或桥梁。但软件设计并不是始终被重视。在编程过去的大部分时间里,设计集中在项目开始阶段,像其他工程学科一样。这种极端的方法称为瀑布模型,项目被划分为独立的阶段,比如需求定义,设计,编码,测试和维护。在瀑布模型中,每个阶段需要在下个阶段开始前完成,多数情况下每个阶段由不同的人负责。在设计阶段一次性完成整个系统设计。设计在这个阶段结束时被冻结,后续阶段是为了丰满和实现此设计。

遗憾的是,瀑布模型很少能很好的适用于软件开发。软件系统本身比实体系统要复杂的多;在动工前,无法把一个大型软件系统的设计具象化地足够好来了解所有可能的影响。因此,初始设计会有很多问题。这些问题并不会显现直到实施过程中。但是瀑布模型无法组织做较大的设计变更在当前时间点(例如,设计人员已经继续去其他项目了)。因此,开发人员设法在不改变总体设计下来修补问题。结果导致复杂度爆炸式增长。

由于这些问题,现在大多数软件项目使用增量方法,如敏捷开发,初始设计只聚焦整体功能的一小部分。这部分被设计,被实现,然后被评估。原始设计的问题会被发现和修正,然后再设计、实现和评估更多特性。每个迭代会暴露现有设计的问题,在设计下一组特性前会得到修复。

通过这种方式开展设计,初始设计问题可以在系统还很小的时候就被修复掉;后期特性收益于前期特性实现时积累的经验,所以问题会较少。

增量方法适用于软件,因为软件可塑性强,可以在实现过程中做重大的设计变更。相比之下,重大的设计变更对于实体系统来说更具挑战性,例如在建造过程中改变支撑桥塔的数量是不现实的。

增量开发意味着软件设计永远无法完结。设计会持续贯穿系统的整个生命周期,开发人员要一直思考设计问题。增量开发也意味着不断的重新设计。系统或组件的初始设计向来不是最好的,实践出真知。做为软件开发人员,你总是在寻找机会来改进正在开发系统的设计,并计划花费部分时间在设计改进上。

如果开发人员需要一直思考设计问题,而降低复杂度是软件设计中最重要的元素,那么软件开发人员就需要一直思考复杂度。这本书是关于如何使用复杂度来指导软件在其整个生命周期中的设计。

这本书有两个总体目标。第一个是描述软件复杂度的本质:“复杂度”是什么意思,为什么它很重要,以及如何识别程序什么情况下具有不必要的复杂度?这本书的第二个更具挑战性的目标是介绍可以在软件开发过程中最大限度地降低复杂度的方法。遗憾的是,并没有简单的方法可以保证出色的软件设计。相反,我将提出一系列接近哲学的更高层次的理念,如“类要有深度”和“定义不存在的错误”。这些理念不能立即确定最佳设计,但可以使用它们来比较备选方案和指引探索设计空间。

1.1 如何使用这本书

这里描述的许多设计原则有些抽象,因此如果不看真实的代码可能很难理解。寻找示例是一个挑战,示例要很小可以放到本书中,却很大程度可以阐明真实系统的问题(如果你遇到好的示例,请发给我)。因此,本书本身可能不足以让你学习如何应用这些原则。

本书的最佳使用方式是与代码检视结合。当你阅读其他人的代码时,思考是否符合此处讨论的理念和如何影响代码复杂度。在别人的代码中比自己的代码更容易看到设计问题。你可以使用此处描述的危险信号来识别问题并提出改进建议。检视代码还可以接触到新的设计方法和编程技术。

改善设计技能的最好方法之一是学会识别危险信号:标志一段代码很可能比需要的复杂得多。在本书中我将指出每个重要设计问题的危险信号,最重要的内容会总结在书的末尾。然后你可以在编码时使用:当你看到危险信号时,停下来并寻找消除问题的替代设计。当你第一次尝试这种方法时,你可能需要尝试数种替代设计,才能找到可消除危险信号的那个。不要轻易放弃:解决问题前你尝试的替代越多,你收获的就越多。随着时间的推移,你会发现你代码中的危险信号越来越少,你的设计也越来越干净。你的经验也会展示其他的可以用来识别设计问题的危险信号(我很乐意听到关于这些)。

在应用本书中思想时,适度和谨慎是很重要的。每一条规则都有例外,而且每一条原则都有局限。如果你把任何设计思想都发挥到极致,你很可能会陷入困境。巧妙的设计体现了思想与方法间冲突的平衡。有几章里有标题为“渐行渐远,”的段落,描述了如何意识到过为已甚。

本书中几乎所有的示例都使用Java或C++,而且许多讨论是面向对象语言中如何设计类。不过,这些思想也适用于其他领域。几乎所有与方法相关的思想同样可以应用于非面向对象语言的函数,例如C。设计思想也适用于除了类以外的模块,如子系统或网络服务。

在此背景下,让我们更详细地讨论复杂度的起因,以及如何简化软件系统。