序言
软件架构(architecture)究竟是什么?
不论从哪个角度分析软件系统,都不可能面面俱到。如果从架构学角度来分析,在一定程度上能够做到抓大放小,把握住重点,但是也不可避免地会错失某些重要的细节信息。
软件架构学关注的的一个重点是组织结构(structure)。不管是讨论组件(Component)、类(Class)、函数(Function)、模块(Module),还是层级(Layer)、服务(Service)以及微观与宏观的软件开发过程,软件的组织结构都是我们的主要关注点。但是真实世界中的许多软件项目并不完全按照我们的信念和愿望生长——它们就像超大型国企那样,层层嵌套,缠绕成一团乱麻 。有的时候真的很难相信,软件项目的组织结构性也能像物理建筑那样一目了然,层次清晰。
物理建筑,不管其地基是石头还是水泥,形状是高大还是宽阔,风格是气势恢宏还是小巧玲珑,其组织结构都一目了然。物理建筑的组织结构必须遵守“受重力”这一自然规律,同时还要符合建筑材料自身的物理特性。软件项目则没有定律可以遵循。另外,物理建筑是用砖头、水泥、木头、钢铁或者玻璃等标准材料建成的,而大型软件项目往往是由小的软件组件构成的,这些软件组件又是由更小的软件组件构成的,层层堆叠,无穷无尽。
所以,当讨论软件架构时,要特别注意软件项目是具有递归(recursive)和分形(fractal)特点的,最终都要由一行行的代码组成。脱离了一行行的代码,脱离了具体的细节设计,架构设计就无从谈起。大型物理建筑通常可以用比例模型分层描述细节信息,但是软件项目内部结构是很难用模型分层描述的。软件项目也具有内部结构,但是其结构无论从数量上还是多样性上来说,都远远超过了物理建筑的结构。可以不夸张地说,软件开发比修建物理建筑需要更长、更专注的设计过程,软件架构师应该比建筑架构师更懂架构!
比例模型是深入人心的展示方式,但是不管某个PowerPoint图表中的彩色方块多么好看,多么简单易懂,它也无法完全代表一个软件的架构。它只能是该软件的架构的一个视图,而非全部。软件的架构并没有固定的展现形式,你所看到的每一个视图的背后都是架构师所做的层层抉择。一个视图包含了哪些部分,排除了哪些部分;用特殊形状和颜色强调了哪些部分,又有哪些部分被泛泛地一笔带过,甚至直接忽略,这些都是这个视图本身的特性。然而,每个视图都是对的,它们往往并没有优劣之分。
虽然软件无法很好地用比例模型展示,但它还是要在现实世界中运行的。在设计软件架构的过程中,我们必须理解和遵守现实的约束条件。CPU速度和网络带宽往往在很大程度上决定了系统的性能,而内存和存储空间的大小也会大幅影响代码的设计野心。
女士,这就是爱情的穷凶极恶之处,人的意愿是无穷的,而实际行动却处处受限。人的欲望是无止境的,行为却不得不遵从现实的限制。
——威廉·莎士比亚
人类的整个经济活动都是存在于现实世界中的,所以我们可以利用现实世界的一些准则来衡量和推理软件开发过程中那些不好量化和物化的因素。
软件架构是系统设计过程中的重要设计决定的集合,可以通过变更成本来衡量每个设计决定的重要程度。
——Grady Booch
需要付出的时间、金钱和人力成本是区分软件架构规模大小的衡量标准,也可以用来区分架构设计和细节设计。同时,我们还可以依据这个信息来判断某个特定架构设计是好还是坏:一个好的架构,不仅要在某一特定时刻满足软件用户、开发者和所有者的需求,更要在一段时间内持续满足他们的后续需求。
如果你觉得好架构的成本太高,那你可以试试选择差的架构加上返工重来的成本。
——Brian Foote 和 Joseph Yoder
一个系统的常规变更不应该是成本高昂的,也不应该需要难以决策的大型设计调整,更不应该需要单独立项来推进。这些常规变更应该可以融入每日或者每周的日常系统维护中去完成。
我们怎么能够预知某个系统未来的变更需求,以便提前做准备呢?我们怎么能在没有水晶球与时光穿梭机的情况下,未卜先知,降低未来的变更成本呢?
所谓软件架构,就是你希望在项目一开始就能做对,但是却不一定能够做得对的决策的集合。
——Ralph Johnson
了解历史已经够难了,我们对现实的认知也不够可靠,预言未来就更难了。
这就是不同的软件开发理论的主要分歧点。
其中一条比较悲观阴暗的路线认为,只有权威和刚性才能带来强壮与稳定。如果某项变更成本高昂,那么就应该忽视它——变更背后的需求要么应该被抑制,要么就应该被丢到官僚主义的大机器中去绞碎。架构师的决定永远是完整的、彻底的,软件架构就是全体开发人员的敌托邦噩梦(Dystopia),永远是所有人沮丧的源泉。
另外一条路线则到处充斥着大量的投机性的通用设计。在这样的软件项目中到处都是硬编码的猜测性代码,到处是无穷无尽的参数,存在着成篇累牍的无效代码。 维护这样的项目,肯定会遇到意外情况,而且无论预留多少资源都不够应付。
而本书试图探索的则是一条整洁路线。这条路线拥抱软件的灵活多变性,将其作为系统的一级设计目标。同时,我们也承认人类并不能全知全晓,但在信息不全的情况下人类仍然能够做出优良的决策。这条路线可以让我们多发挥优势,避开弱势。通过实际创造和探索,不停地提出问题和进行实验。优良的软件架构不是一成不变的,只有经过不断打磨和改进才能最终成就。
软件架构是一个猜想,只有通过实际实现和测量才能证实。
——Tom Gilb
遵循这条路线,我们需要用心,全神贯注,不停观察和思考,在原则指导下不断实践。虽然这可能听起来很麻烦、很慢,但是只要坚持走下去一定能够成功。
走快的唯一方法是先走好。
——Robert C. Martin
一起享受这个过程吧!
Kevlin Henney
2017年5月
前言
本书的名字叫作《架构整洁之道》,使用这个名字可谓是十分胆大,甚至可以说有点目中无人了。那么,为什么我会选择写这本书,并且使用这个名字呢?
自1964年,12岁的我写下了人生的第一行代码算起,到2016年,我已经编程超过50年。在这段时间里,我自认为学到了构建软件系统的一些方法——并且我相信这些方法和经验对其他人应该有些价值。
我学习的途径是实际构建一些大大小小的软件系统。我写过小型的嵌入式系统,也构造过大型的批处理系统;我构建过实时控制系统,也构建过Web网页系统;我写过命令行程序、图形界面程序、进程管理程序、游戏、计费系统、通信系统、设计工具、画图工具等。
我写过单线程程序,也写过多线程程序;我写过由几个重型进程组成的应用,也写过由大量轻型进程组成的应用;我写过跨多个处理器的应用,还有数据库类、数值计算类和几何计算类应用,以及很多很多其他类型的应用。
回首过去,经历了这么多应用和系统的构建过程,我最意外的领悟是:
软件架构的规则是相同的!
我所构建的这些系统是千差万别的,为什么所有这些差异巨大的系统都遵守同样的软件架构规则呢?这里我得出的结论是,软件架构规则和其他变量完全无关。
回顾过去这半个世纪以来硬件系统产生的巨大变革,这个结论就更惊人了。我的编程生涯起步于像家用冰箱那么大的巨型机时代,它的CPU频率只有0.5MHz,拥有4KB核心内存,32KB磁盘存储,以及每秒只能传输10个字符的电传打字机接口。而现在,我正在一辆游览南非的观光车上敲这篇前言。我正在用一个拥有4核i7的MacBook,每核2.8GHz。这台笔记本电脑有16GB内存,1TB SSD硬盘,可以用2880?1800虹膜显示屏展现高清视频。二者计算能力上的差距真的是天壤之别。粗略分析可知,这台MacBook至少比我半个世纪以前用的计算机强大1022倍。
22个数量级的差距是非常非常巨大的,从地球到半人马星系也只有1022埃(angstrom,长度单位,主要用来描述原子尺寸与波长),你口袋里的零钱加起来所包含的电子数量也差不多为1022个。而这个数字(注意还是至少)是我在一生中,所亲身经历的计算能力的提升。
计算能力发生了这么巨大的变化,但对我所写软件的影响有多大呢?软件尺寸当然变大了。我过去认为2000行的程序就很庞大了。毕竟这样的程序变成打孔卡片能装满一盒子,重量超过10磅。而现在,一个10万行的程序都不能算大程序了。
同时,软件性能当然也有大幅提升。我们现在可以轻轻松松地完成那些1960年只能幻想的事情。电影The Forbin Project、The Moon is a Harsh Mistress以及2001: A Space Odyssey都试图预言我们的现状,但是都没有成功。在这些电影中普遍展现的是获得了智能的巨型机器,而我们目前所拥有的计算机,虽然体积之小是当初难以想象的,却还仅仅只是机器。
同时,还有一点很重要,今天的软件与过去的软件本质上仍然是一样的。都是由if语句、赋值语句以及while循环组成的。
哦,你可能会抗议说我们现在有更好的编程语以及更先进的编程范式了。毕竟,我们现在都是用Java、C#、Ruby语言编写程序,并且大量采用面向对象编程方式。这没错,但是最终产生的代码仍然只是顺序结构、分支结构、循环结构的组合,这方面和20世纪60年代甚至50年代的程序是一模一样的。
如果深入研究计算机编程的本质,我们就会发现这50年来,计算机编程基本没有什么大的变化。编程语言稍微进步了一点,工具的质量大大提升了,但是计算机程序的基本构造没有什么变化。
如果一个1966年的计算机程序员时空穿梭来到2016年,在我的MacBook上用IntelliJ写Java程序,她 可能也就需要24小时来适应一下,然后很快就能照常工作了。Java其实和C区别并不大,和FORTRAN也没那么大区别。
同样,如果我把你——读者传送回1966年,告诉你如何在一个每秒处理10个字符的终端上通过打孔纸带来编辑PDP-8代码,估计你最多也只需要24小时的适应时间。毕竟编程还是编程,代码并没有本质的变化。
这就是秘密所在:计算机代码没有变化,软件架构的规则也就一直保持了一致。软件架构的规则其实就是排列组合代码块的规则。由于这些代码块本质上没有变化,因此排列组合它们的规则也就不会变化。
年轻的一代程序员可能认为这些都是胡说。他们可能坚持认为现在所有东西都是崭新的、从来没有过的,过去的规则已经过时,不再适用了。这是一个非常大的错误。这些规则一直都没有变。虽然我们有了新的编程语言、新的编程框架、新的编程范式,但是软件架构的规则仍然和1946年阿兰·图灵写下第一行机器代码的时候一样。
当然,不一样的是,那时候我们还不知道规则是什么。所以我们一次一次地颠覆了它,并且为此一次一次地付出了代价。半个世纪过去了,我们终于可以说,现在我们对这些规则有一定程度的了解了。
写这本书就是为了讲述这些规则,这些永恒的、不变的软件架构规则。