06
3

Edsger Wybe Dijkstra was born in Rotterdam in 1930. He survived the bombing of Rotterdam during World War II, along with the German occupation of the Netherlands, and in 1948 graduated from high school with the highest possible marks in math, physics, chemistry, and biology. In March 1952, at the age of 21 (and just 9 months before I was born), Dijkstra took a job with the Mathematical Center of Amsterdam as the Netherlands’ very first programmer.

Edsger Wybe Dijkstra 于 1930 年出生在荷兰鹿特丹。生于乱世,他亲身经历了第二次世界大战中的鹿特丹大轰炸、德国占领荷兰等事件。1948 年,他以数学、物理、化学以及生物全满分的成绩高中毕业。1952 年 3 月,年仅 21 岁的 Dijkstra (此时距离我出生还有 9 个月时间)入职荷兰阿姆斯特丹数学中心,成为了荷兰的第一个程序员。

In 1955, having been a programmer for three years, and while still a student, Dijkstra concluded that the intellectual challenge of programming was greater than the intellectual challenge of theoretical physics. As a result, he chose programming as his long-term career.

1955 年,在从事编程工作 3 年之后,当时还是一个学生的 Dijkstra 就认为编程相比理论物理更有挑战性,因此他选择将编程作为终身职业。

In 1957, Dijkstra married Maria Debets. At the time, you had to state your profession as part of the marriage rites in the Netherlands. The Dutch authorities were unwilling to accept “programmer” as Dijkstra’s profession; they had never heard of such a profession. To satisfy them, Dijkstra settled for “theoretical physicist” as his job title.

1957 年,Dijkstra 与 Maria Debets 结婚了。在当时的荷兰,新郎新娘必须在结婚仪式上公布自己的职业。而当时的荷兰官方政府拒绝承认“程序员”这一职业,因为他们从来没有听说过。最终 Dijkstra 不得不继续使用"理论物理学家这一职位名称。

As part of deciding to make programming his career, Dijkstra conferred with his boss, Adriaan van Wijngaarden. Dijkstra was concerned that no one had identified a discipline, or science, of programming, and that he would therefore not be taken seriously. His boss replied that Dijkstra might very well be one of the people who would discover such disciplines, thereby evolving software into a science.

Dijkstra 和他的老板 Adriaan van Wijingaarden 曾经讨论过将“程序员”当作终身职业这件事,Dijkstra 最担心的是由于没有人认真地对待过编程这件事或者将它当作是一门学术学科对待,他的科研成果可能将不会得到认真对待。而 Adriaan 则建议 Dijkstra:为什么不亲自去开创这门学科呢?

Dijkstra started his career in the era of vacuum tubes, when computers were huge, fragile, slow, unreliable, and (by today’s standards) extremely limited. In those early years, programs were written in binary, or in very crude assembly language. Input took the physical form of paper tape or punched cards. The edit/compile/test loop was hours—if not days—long.

当时还是真空管阶段。计算机体积巨大,运行缓慢,还非常容易出故障,功能(与今天对比)十分有限。人们还是直接使用二进制数,或者使用非常原始的汇编语言编程。计算机的输入方式则还是用纸卷带或者是打孔卡片。要想执行完整的编辑、编译、测试流程是非常耗时的,通常需要数小时或者数天才能完成。

It was in this primitive environment that Dijkstra made his great discoveries.

Dijkstra 就是在这样原始的条件下做出其非凡的成就的。

PROOF 可推导性
The problem that Dijkstra recognized, early on, was that programming is hard, and that programmers don’t do it very well. A program of any complexity contains too many details for a human brain to manage without help. Overlooking just one small detail results in programs that may seem to work, but fail in surprising ways.

Dijkstra 很早就得出的结论是:编程是一项难度很大的活动。一段程序无论复杂与否,都包含了很多的细节信息。如果没有工具的帮助,这些细节的信息是远远超过一个程序员的认知能力范围的。而在一段程序中,哪怕仅仅是一个小细节的错误,也会造成整个程序出错。

Dijkstra’s solution was to apply the mathematical discipline of proof. His vision was the construction of a Euclidian hierarchy of postulates, theorems, corollaries, and lemmas. Dijkstra thought that programmers could use that hierarchy the way mathematicians do. In other words, programmers would use proven structures, and tie them together with code that they would then prove correct themselves.

Dijkstra 提出的解决方案是采用数学推导方法。他的想法是借鉴数学中的公理(Postulate)、定理(Theorem)、推论(Corollary)和引理(Lemma),形成一种欧几里得结构。Dijkstra 认为程序员可以像数学家一样对自己的程序进行推理证明。换句话说,程序员可以用代码将一些已证明可用的结构串联起来,只要自行证明这些额外代码是正确的,就可以推导出整个程序的正确性。

Of course, to get this going, Dijkstra realized that he would have to demonstrate the technique for writing basic proofs of simple algorithms. This he found to be quite challenging.

当然,在这之前,必须先展示如何推导证明简单算法的正确性,这本身就是一件极具挑战性的工作。

During his investigation, Dijkstra discovered that certain uses of goto statements prevent modules from being decomposed recursively into smaller and smaller units, thereby preventing use of the divide-and-conquer approach necessary for reasonable proofs.

Dijkstra 在研究过程中发现了一个问题:goto 语句的某些用法会导致某个模块 无法被递归拆分成更小的、可证明的单元,这会导致无法采用分解法来将大型问题进一步拆分成更小的、可证明的部分。

Other uses of goto, however, did not have this problem. Dijkstra realized that these “good” uses of goto corresponded to simple selection and iteration control structures such as if/then/else and do/while. Modules that used only those kinds of control structures could be recursively subdivided into provable units.

goto 语句的其他用法虽然不会导致这种问题,但是 Dijkstra 意识到它们的实际效果其实和更简单的分支结构 if-then-else 以及循环结构 do-while 是一致的。如果代码中只采用了这两类控制结构,则一定可以将程序分解成更小的、可证明的单元。

Dijkstra knew that those control structures, when combined with sequential execution, were special. They had been identified two years before by Böhm and Jacopini, who proved that all programs can be constructed from just three structures: sequence, selection, and iteration.

事实上,Dijkstra 很早就知道将这些控制结构与顺序结构的程序组合起来很有用。因为在两年前,Bohm 和 Jocopini 刚刚证明了人们可以用顺序结构、分支结构、循环结构这三种结构构造出任何程序。

This discovery was remarkable: The very control structures that made a module provable were the same minimum set of control structures from which all programs can be built. Thus structured programming was born.

这个发现非常重要:因为它证明了我们构建可推导模块所需要的控制结构集与构建所有程序所需的控制结构集的最小集是等同的。这样—来,结构化编程就诞生了。

Dijkstra showed that sequential statements could be proved correct through simple enumeration. The technique mathematically traced the inputs of the sequence to the outputs of the sequence. This approach was no different from any normal mathematical proof.

Dijkstra 展示了顺序结构的正确性可以通过枚举法证明,其过程与其他一般的数学推导过程是一样的:针对序列中的每个输入,跟踪其对应的输出值的变化就可以了。

Dijkstra tackled selection through reapplication of enumeration. Each path through the selection was enumerated. If both paths eventually produced appropriate mathematical results, then the proof was solid.

同样的,Dijkstra 利用枚举法又证明了分支结构的可推导性。因为我们只要能用枚举法证明分支结构中每条路径的正确性,自然就可以推导出分支结构本身的正确性。

Iteration was a bit different. To prove an iteration correct, Dijkstra had to use induction. He proved the case for 1 by enumeration. Then he proved the case that if N was assumed correct, N + 1 was correct, again by enumeration. He also proved the starting and ending criteria of the iteration by enumeration.

循环结构的证明过程则有所不同,为了证明一段循环程序的正确性,Dijkstra 需要采用数学归纳法。具体来说就是,首先要用枚举法证明循环 1 次的正确性。接下来再证明如果循环 N 次是正确的,那么循环 N+1 次也同样也是正确的。最后还要用枚举法证明循环结构的起始与结束条件的正确性。

Such proofs were laborious and complex—but they were proofs. With their development, the idea that a Euclidean hierarchy of theorems could be constructed seemed reachable.

尽管这些证明过程本身非常复杂和烦琐,但确实是完备的。有了这样的证明过程,用欧几里得层级构造定理的方式来验证程序正确性的目标,貌似近在咫尺了。

A HARMFUL PROCLAMATION goto 是有害的
In 1968, Dijkstra wrote a letter to the editor of CACM, which was published in the March issue. The title of this letter was “Go To Statement Considered Harmful.” The article outlined his position on the three control structures.

1968 年,Dijkstra 曾经给 CACM 的编辑写过一封信。这封信后来发表于 CACM 3 月刊,标题是 Go To Statement Considered Harmful,Dijkstra 在信中具体描绘了他对三种控制结构的看法。

And the programming world caught fire. Back then we didn’t have an Internet, so people couldn’t post nasty memes of Dijkstra, and they couldn’t flame him online. But they could, and they did, write letters to the editors of many published journals.

这可捅了个大篓子。由于当时还没有互联网,大家还不能直接上网发帖来对 Dijkstra 进行冷嘲热讽,他们唯一能做的,也是大部分人的选择,就是不停地给各种公开发表的报刊的编辑们写信。

Those letters weren’t necessarily all polite. Some were intensely negative; others voiced strong support for his position. And so the battle was joined, ultimately to last about a decade.

可想而知,有的信件的措辞并不那么友善,甚至是非常负面的。但是,也不乏强烈支持者。总之,这场火热的争论持续了超过 10 年。

Eventually the argument petered out. The reason was simple: Dijkstra had won. As computer languages evolved, the goto statement moved ever rearward, until it all but disappeared. Most modern languages do not have a goto statement—and, of course, LISP never did.

当然,这场辩论最终还是逐渐停止了。原因很简单:Dijkstra 是对的。随着编程语言的演进,goto 语句的重要性越来越小,最终甚至消失了。如今大部分的现代编程语言中都已经没有了 goto 语句。哦,对了,LISP 里从来就没有过!

Nowadays we are all structured programmers, though not necessarily by choice. It’s just that our languages don’t give us the option to use undisciplined direct transfer of control.

现如今,无论是否自愿,我们都是结构化编程范式的践行者了,因为我们用的编程语言基本上都已经禁止了不受限制的直接控制转移语句。

Some may point to named breaks in Java or exceptions as goto analogs. In fact, these structures are not the utterly unrestricted transfers of control that older languages like Fortran or COBOL once had. Indeed, even languages that still support the goto keyword often restrict the target to within the scope of the current function.

或许有些人会指出,Java 中的带命名的 break 语句或者 Exception 都和 goto 很类似。这些语法结构与老的编程语言(类似 FORTRAN 和 COBOL)中的完全无限制的 goto 语句根本不一样。就算那些还支持 goto 关键词的编程语言也通常限制了 goto 的目标不能超出当前函数范围。

FUNCTIONAL DECOMPOSITION 功能性降解拆分
Structured programming allows modules to be recursively decomposed into provable units, which in turn means that modules can be functionally decomposed. That is, you can take a large-scale problem statement and decompose it into high-level functions. Each of those functions can then be decomposed into lower-level functions, ad infinitum. Moreover, each of those decomposed functions can be represented using the restricted control structures of structured programming.

既然结构化编程范式可将模块递归降解拆分为可推导的单元,这就意味着模块了可以按功能进行降解拆分。这样一来,我们就可以将一个大型问题拆分为一系列高级函数的组合,而这些高级函数各自又可以继续被拆分为一系列低级函数,如此无限递归。更重要的是,每个被拆分出来的函数也都可以用结构化编程范式来书写。

Building on this foundation, disciplines such as structured analysis and structured design became popular in the late 1970s and throughout the 1980s. Men like Ed Yourdon, Larry Constantine, Tom DeMarco, and Meilir Page-Jones promoted and popularized these techniques throughout that period. By following these disciplines, programmers could break down large proposed systems into modules and components that could be further broken down into tiny provable functions.

以此为理论基础,在 20 世纪 70 年代晚期到 10 年代中期出现的结构化分析与结构化设计工作才能广为人知。Ed Yourdon、Larry Constantine、Tom DeMarco 以及 Meilir Page Jones 在这期间为此做了很多推广工作。通过采用这些技巧,程序员可以将大型系统设计拆分成模块和组件,而这些模块和组件最终可以拆分为更小的、可证明的函数。

NO FORMAL PROOFS 形式化证明没有发生
But the proofs never came. The Euclidean hierarchy of theorems was never built. And programmers at large never saw the benefits of working through the laborious process of formally proving each and every little function correct. In the end, Dijkstra’s dream faded and died. Few of today’s programmers believe that formal proofs are an appropriate way to produce high-quality software.

但是,人人都用完整的形式化证明的一天没有到来。大部分人不会真的按照欧几里得结构为每个小函数书写冗长复杂的正确性证明过程。Dijkstra 的梦想最终并没有实现。没有几个程序员会认为形式化验证是产出高质量软件的必备条件。

Of course, formal, Euclidian style, mathematical proofs are not the only strategy for proving something correct. Another highly successful strategy is the scientific method.

当然,形式化的、欧几里得式的数学推导证明并不是证明结构化编程正确性的唯一手段。下面我们来看另外一个十分成功的策略:科学证明法。

SCIENCE TO THE RESCUE 科学来救场
Science is fundamentally different from mathematics, in that scientific theories and laws cannot be proven correct. I cannot prove to you that Newton’s second law of motion, F = ma, or law of gravity, F = Gm1m2/r2, are correct. I can demonstrate these laws to you, and I can make measurements that show them correct to many decimal places, but I cannot prove them in the sense of a mathematical proof. No matter how many experiments I conduct or how much empirical evidence I gather, there is always the chance that some experiment will show that those laws of motion and gravity are incorrect.

科学和数学在证明方法上有着根本性的不同,科学理论和科学定律通常是无法被证明的,譬如我们并没有办法证明牛顿第二运动定律 F=ma 或者万有引力定律 F=Gm1m2/r^2 是正确的,但我们可以用实际案例来演示这些定律的正确性,并通过高精度测量来证明当相关精度达到小数点后多少位时,被测量对象仍然一直满足这个定律。但我们始终没有办法像用数学方法一样推导出这个定律。而且,不管我们进行多少次正确的实验,也无法排除今后会存在某一次实验可以推翻牛顿第二运动定律与万有引力定律的可能性。

That is the nature of scientific theories and laws: They are falsifiable but not provable.

这就是科学理论和科学定律的特点:它们可以被证伪,但是没有办法被证明。

And yet we bet our lives on these laws every day. Every time you get into a car, you bet your life that F = ma is a reliable description of the way the world works. Every time you take a step, you bet your health and safety that F = Gm1m2/r2 is correct.

但是我们仍然每天都在依赖这些定律生活。开车的时候,我们就等于是在用性命担保 F=ma 是对世界运转方式的一个可靠的描述。每当我们迈出一步的时候,和等于在亲身证明 F=Gm1m2/r^2 是正确的。

Science does not work by proving statements true, but rather by proving statements false. Those statements that we cannot prove false, after much effort, we deem to be true enough for our purposes.

科学方法论不需要证明某条结论是正确的,只需要想办法证明它是错误的。如果某个结论经过一定的努力无法证伪,我们则认为它在当下是足够正确的。

Of course, not all statements are provable. The statement “This is a lie” is neither true nor false. It is one of the simplest examples of a statement that is not provable.

当然,不是所有的结论都可以被证明或者证伪的。举一个最简单的不可证明的例子:“这句话是假的”,非真也非伪。

Ultimately, we can say that mathematics is the discipline of proving provable statements true. Science, in contrast, is the discipline of proving provable statements false.

最终,我们可以说数学是要将可证明的结论证明,而与之相反,科学研究则是要将可证明的结论证伪。

TESTS 测试
Dijkstra once said, “Testing shows the presence, not the absence, of bugs.” In other words, a program can be proven incorrect by a test, but it cannot be proven correct. All that tests can do, after sufficient testing effort, is allow us to deem a program to be correct enough for our purposes.

Dijkstra 曾经说过“测试只能展示 Bug 的存在,并不能证明不存在 Bug”,换句话说,一段程序可以由一个测试来证明其错误性,但是却不能被证明是正确的。测试的作用是让我们得出某段程序已经足够实现当前目标这一结论。

The implications of this fact are stunning. Software development is not a mathematical endeavor, even though it seems to manipulate mathematical constructs. Rather, software is like a science. We show correctness by failing to prove incorrectness, despite our best efforts.

这一事实所带来的影响是惊人的。软件开发虽然看起来是在操作很多数学结构,其实不是一个数学研究过程。恰恰相反,软件开发更像是一门科学研究学科,我们通过无法证伪来证明软件的正确性。

Such proofs of incorrectness can be applied only to provable programs. A program that is not provable—due to unrestrained use of goto, for example—cannot be deemed correct no matter how many tests are applied to it.

注意,这种证伪过程只能应用于可证明的程序上。某段程序如果是不可证明的,例如,其中采用了不加限制的 goto 语句,那么无论我们为它写多少测试,也不能够证明其正确性。

Structured programming forces us to recursively decompose a program into a set of small provable functions. We can then use tests to try to prove those small provable functions incorrect. If such tests fail to prove incorrectness, then we deem the functions to be correct enough for our purposes.

结构化编程范式促使我们先将一段程序递归降解为一系列可证明的小函数,然后再编写相关的测试来试图证明这些函数是错误的。如果这些测试无法证伪这些函数,那么我们就可以认为这些函数是足够正确的,进而推导整个程序是正确的。

CONCLUSION 本章小结
It is this ability to create falsifiable units of programming that makes structured programming valuable today. This is the reason that modern languages do not typically support unrestrained goto statements. Moreover, at the architectural level, this is why we still consider functional decomposition to be one of our best practices.

结构化编程范式中最有价值的地方就是,它赋予了我们创造可证伪程序单元的能力。这就是为什么现代编程语言一般不支持无限制的 goto 语句。更重要的是,这也是为什么在架构设计领域,功能性降解拆分仍然是最佳实践之一。

At every level, from the smallest function to the largest component, software is like a science and, therefore, is driven by falsifiability. Software architects strive to define modules, components, and services that are easily falsifiable (testable). To do so, they employ restrictive disciplines similar to structured programming, albeit at a much higher level.

无论在哪一个层面上,从最小的函数到最大组件,软件开发的过程都和科学研究非常类似,它们都是由证伪驱动的。软件架构师需要定义可以方便地进行证伪(测试)的模块、组件以及服务。为了达到这个目的,他们需要将类似结构化编程的限制方法应用在更高的层面上。

It is those restrictive disciplines that we will study in some detail in the chapters to come.

我们在接下来的章节中将会深入研究这些限制性的方法。

05
3

The three paradigms included in this overview chapter are structured programming, object-orient programming, and functional programming.

本章将讲述三个编程范式,它们分别是结构化编程(structured programming)、 面向对象编程(object-oriented programming)以及函数式编程(functional programming)。

STRUCTURED PROGRAMMING 结构化编程
The first paradigm to be adopted (but not the first to be invented) was structured programming, which was discovered by Edsger Wybe Dijkstra in 1968. Dijkstra showed that the use of unrestrained jumps (goto statements) is harmful to program structure. As we’ll see in the chapters that follow, he replaced those jumps with the more familiar if/then/else and do/while/until constructs.

结构化编程是第一个普遍被采用的编程范式(但是却不是第一个被提出的),由 Edsger Wybe Dijkstra 于 1968 年最先提出。与此同时,Dijkstra 还论证了使用 goto 这样的无限制跳转语句将会损害程序的整体结构。接下来的章节我们还会说到,二是这位 Dijkstra 最先主张用我们现在熟知的 if/then/else 语句和 do/while/until 语句来代替跳转语句的。

We can summarize the structured programming paradigm as follows:

我们可以将结构化编程范式归结为一句话:

Structured programming imposes discipline on direct transfer of control.

结构化编程对程序控制权的直接转移进行了限制和规范。

OBJECT-ORIENTED PROGRAMMING 面向对象编程
The second paradigm to be adopted was actually discovered two years earlier, in 1966, by Ole Johan Dahl and Kristen Nygaard. These two programmers noticed that the function call stack frame in the ALGOL language could be moved to a heap, thereby allowing local variables declared by a function to exist long after the function returned. The function became a constructor for a class, the local variables became instance variables, and the nested functions became methods. This led inevitably to the discovery of polymorphism through the disciplined use of function pointers.

说到编程领域中第二个被广泛采用的编程范式,当然就是面向对象编程了:事实上,这个编程范式的提出比结构化编程还早了两年,是在 1966 年由 Ole Johan Dahl 和 Kriste Nygaard 在论文中总结归纳出来的。这两个程序员注意到在 ALGOL 语言中. 函数调用堆栈(call stack frame)可以被挪到堆内存区域里,这样函数定义的本地变量就可以在函数返回之后继续存在。这个函数就成为了一个类(class)的构造函数,而它所定义的本地变量就是类的成员变量,构造函数定义的嵌套函数就成为了成员方法(method)。这样一来,我们就可以利用多态(polymorphism)来限制用户对函数指针的使用。

We can summarize the object-oriented programming paradigm as follows:

在这里,我们也可以用一句话来总结面向对象编程:

Object-oriented programming imposes discipline on indirect transfer of control.

面向对象编程对程序控制权的间接转移进行了限制和规范。

FUNCTIONAL PROGRAMMING 函数式编程
The third paradigm, which has only recently begun to be adopted, was the first to be invented. Indeed, its invention predates computer programming itself. Functional programming is the direct result of the work of Alonzo Church, who in 1936 invented l-calculus while pursuing the same mathematical problem that was motivating Alan Turing at the same time. His l-calculus is the foundation of the LISP language, invented in 1958 by John McCarthy. A foundational notion of l-calculus is immutability—that is, the notion that the values of symbols do not change. This effectively means that a functional language has no assignment statement. Most functional languages do, in fact, have some means to alter the value of a variable, but only under very strict discipline.

尽管第三个编程范式是近些年才刚刚开始被采用的,但它其实是三个范式中最先被发明的。事实上,函数式编程概念是基于与阿兰·图灵同时代的数学家 Alonzo Church 在 1936 年发明的入演算的直接衍生物。1958 年 John Mccarthy 利用其作为基础发明了 LISP 语言。众所周知,λ 演算法的一个核心思想是不可变性——某个符号所对应的值是永远不变的,所以从理论上来说,函数式编程语言中应该是没有赋值语句的。大部分函数式编程语言只允许在非常严格的限制条件下,才可以更改某个变量的值。

We can summarize the functional programming paradigm as follows:

因此,我们在这里可以将函数式编程范式总结为下面这句话:

Functional programming imposes discipline upon assignment.

函数式编程对程序中的赋值进行了限制和规范。

FOOD FOR THOUGHT 仅供思考
Notice the pattern that I’ve quite deliberately set up in introducing these three programming paradigms: Each of the paradigms removes capabilities from the programmer. None of them adds new capabilities. Each imposes some kind of extra discipline that is negative in its intent. The paradigms tell us what not to do, more than they tell us what to do.

如你所见,我在介绍三个编程范式的时候,有意采用了上面这种格式,目的是凸显每个编程范式的实际含义——它们都从某一方面限制和规范了程序员的能力。没有一个范式是增加新能力的。也就是说,每个编程范式的目的都是设置限制。这些范式主要是为了告诉我们不能做什么,而不是可以做什么。

Another way to look at this issue is to recognize that each paradigm takes something away from us. The three paradigms together remove goto statements, function pointers, and assignment. Is there anything left to take away?

另外,我们应该认识到,这三个编程范式分别限制了 goto 语句、函数指针和赋值语句的使用。那么除此之外,还有什么可以去除的吗?

Probably not. Thus these three paradigms are likely to be the only three we will see—at least the only three that are negative. Further evidence that there are no more such paradigms is that they were all discovered within the ten years between 1958 and 1968. In the many decades that have followed, no new paradigms have been added.

可能没有了。因此这三个编程范式可能是仅有的三个了——如果单论去除能力的编程范式的话。支撑这一结论的另外一个证据是,三个编程范式都是在 1958 年到 1968 年这 10 年间被提出来的,后续再也没有新的编程范式出现过。

CONCLUSION 本章小结
What does this history lesson on paradigms have to do with architecture? Everything. We use polymorphism as the mechanism to cross architectural boundaries; we use functional programming to impose discipline on the location of and access to data; and we use structured programming as the algorithmic foundation of our modules.

大家可能会问,这些编程范式的历史知识与软件架构有关系吗?当然有,而目关系相当密切。譬如说,多态是我们跨越架构边界的手段,函数式编程是我们规范和限制数据存放位置与访问权限的手段,结构化编程则是各模块的算法实现基础。

Notice how well those three align with the three big concerns of architecture: function, separation of components, and data management.

这和软件架构的三大关注重点不谋而合:功能性、组件独立性以及数据管理。

04
3

Software architecture begins with the code—and so we will begin our discussion of architecture by looking at what we’ve learned about code since code was first written.

任何软件架构的实现都离不开具体的代码,所以我们对软件架构的讨论应该从第一行被写下的代码开始。

In 1938, Alan Turing laid the foundations of what was to become computer programming. He was not the first to conceive of a programmable machine, but he was the first to understand that programs were simply data. By 1945, Turing was writing real programs on real computers in code that we would recognize (if we squinted enough). Those programs used loops, branches, assignment, subroutines, stacks, and other familiar structures. Turing’s language was binary.

1938 年,阿兰·图灵为现代计算机编程打下了地基。尽管他并不是第一个发明可编程机器的人,但却是第一个提出“程序即数据”的人。到 1945 年时,图灵已经在真实计算机上编写真实的、我们现在也能看懂的计算机程序了。这些程序中用到了循环、分支、赋值、子调用、栈等如今我们都很熟悉的结构。而图灵用的编程语言就是简单的二进制数序列。

Since those days, a number of revolutions in programming have occurred. One revolution with which we are all very familiar is the revolution of languages. First, in the late 1940s, came assemblers. These “languages” relieved the programmers of the drudgery of translating their programs into binary. In 1951, Grace Hopper invented A0, the first compiler. In fact, she coined the term compiler. Fortran was invented in 1953 (the year after I was born). What followed was an unceasing flood of new programming languages—COBOL, PL/1, SNOBOL, C, Pascal, C++, Java, ad infinitum.

从那时到现在,编程领域历经了数次变革,其中我们都很熟悉的就是编程语言的变革。首先是在 20 世纪 40 年代末期出现了汇编器(assembler),它能自动将一段程序转化为相应的二进制数序列,大幅解放了程序员。然后是 1951 年,Grace Hopper 发明了 A0,这是世界上第一个编译器(compiler)。事实上,编译器这个名字就是他定义和推广使用的。再接着就到了 1953 年,那一年 FORTRAN 面世了(就在我出生的第二年)。接下来就是层出不穷的新编程语言了——COBOL、PL/1、SNOBOL、C、Pascal、C++、Java 等等,不胜枚举。

Another, probably more significant, revolution was in programming paradigms. Paradigms are ways of programming, relatively unrelated to languages. A paradigm tells you which programming structures to use, and when to use them. To date, there have been three such paradigms. For reasons we shall discuss later, there are unlikely to be any others.

除此之外,计算机编程领域还经历了另外一个更巨大、更重要的变革,那就果编程范式(paradigm)的变迁。编程范式指的是程序的编写模式,与具体的编程语言关系相对较小。这些范式会告诉你应该在什么时候采用什么样的代码结构。直到今天,我们也一共只有三个编程范式,而且未来几乎不可能再出现新的,接下来我们就看一下为什么。

03
3

Every software system provides two different values to the stakeholders: behavior and structure. Software developers are responsible for ensuring that both those values remain high. Unfortunately, they often focus on one to the exclusion of the other. Even more unfortunately, they often focus on the lesser of the two values, leaving the software system eventually valueless.

对于每个软件系统,我们都对以通过行为和架构两个维度来休现它的实际价值。软件研发人员应该确保自己的系统在这两个维度上的实际价值都能长时间维持在很高的状态。不幸的是,他们往往只关注一个维度,而忽视了另外一个维度。更不幸的是,他们常常关注的还是错误的维度,这导致了系统的价值最终趋降为零。

BEHAVIOR 行为价值
The first value of software is its behavior. Programmers are hired to make machines behave in a way that makes or saves money for the stakeholders. We do this by helping the stakeholders develop a functional specification, or requirements document. Then we write the code that causes the stakeholder’s machines to satisfy those requirements.

软件系统的行为是其最直观的价值维度。程序员的工作就是让机器按照某种指定方式运转,给系统的使用者创造或者提高利润。程序员们为了达到这个目的,往往需要帮助系统使用者编写一个对系统功能的定义,也就是需求文档。然后,程序员们再把需求文档转化为实际的代码。

When the machine violates those requirements, programmers get their debuggers out and fix the problem.

当机器出现异常行为时,程序员要负责调试,解决这些问题。

Many programmers believe that is the entirety of their job. They believe their job is to make the machine implement the requirements and to fix any bugs. They are sadly mistaken.

大部分程序员认为这就是他们的全部工作。他们的工作是且仅是:按照需求文档编写代码,并且修复任何 Bug。这真是大错特错。

ARCHITECTURE 架构价值
The second value of software has to do with the word “software”—a compound word composed of “soft” and “ware.” The word “ware” means “product”; the word “soft”… Well, that’s where the second value lies.

软件系统的第二个价值维度,就体现在软件这个英文单词上:software。“ware” 的意思是“产品”,而 “soft” 的意思,不言而喻,是指软件的灵活性。

Software was invented to be “soft.” It was intended to be a way to easily change the behavior of machines. If we’d wanted the behavior of machines to be hard to change, we would have called it hardware.

软件系统必须保持灵活。软件发明的目的,就是让我们可以以一种灵活的方式来改变机器的工作行为。对机器上那些很难改变的工作行为,我们通常称之为硬件(hardware)。

To fulfill its purpose, software must be soft—that is, it must be easy to change. When the stakeholders change their minds about a feature, that change should be simple and easy to make. The difficulty in making such a change should be proportional only to the scope of the change, and not to the shape of the change.

为了达到软件的本来目的,软件系统必须够“软” 也就是说,软件应该容易被修改。当需求方改变需求的时候,随之所需的软件变更必须可以简单而方便地实现。变更实施的难度应该和变更的范畴(scope)成等比关系,而与变更的具体形状(shape)无关。

It is this difference between scope and shape that often drives the growth in software development costs. It is the reason that costs grow out of proportion to the size of the requested changes. It is the reason that the first year of development is much cheaper than the second, and the second year is much cheaper than the third.

需求变更的范畴与形状,是决定对应软件变更实施成本高低的关键。这就是为什么有的代码变更的成本与其实现的功能改变不成比例。这也是为什么第二年的研发成本比第一年的高很多,第三年又比第二年更高。

From the stakeholders’ point of view, they are simply providing a stream of changes of roughly similar scope. From the developers’ point of view, the stakeholders are giving them a stream of jigsaw puzzle pieces that they must fit into a puzzle of ever-increasing complexity. Each new request is harder to fit than the last, because the shape of the system does not match the shape of the request.

从系统相关方(Stakeholder)的角度来看,他们所提出的一系列的变更需求的范畴都是类似的,因此成本也应该是固定的。但是从研发者角度来看,系统用户持续不断的变更需求就像是要求他们不停地用一堆不同形状的拼图块,拼成一个新的形状。整个拼图的过程越来越困难,因为现有系统的形状永远和需求的形状不一致.

I’m using the word “shape” here in a unconventional way, but I think the metaphor is apt. Software developers often feel as if they are forced to jam square pegs into round holes.

我们在这里使用了“形状”这个词,这可能不是该词的标准用法,但是其寓意应该很明确。毕竟,软件工程师们经常会觉得自己的工作就是把方螺丝拧到圆螺丝孔里面。

The problem, of course, is the architecture of the system. The more this architecture prefers one shape over another, the more likely new features will be harder and harder to fit into that structure. Therefore architectures should be as shape agnostic are practical.

问题的实际根源当然就是系统的架构设计。如果系统的架构设计偏向某种特定的“形状”,那么新的变更就会越来越难以实施。所以,好的系统架构设计应该尽可能做到与“形状”无关。

THE GREATER VALUE 哪个价值维度更重要
Function or architecture? Which of these two provides the greater value? Is it more important for the software system to work, or is it more important for the software system to be easy to change?

那么,究竟是系统行为更重要,还是系统架构的灵活性更重要?哪个价值更大?系统正常工作更重要,还是系统易于修改更重要?

If you ask the business managers, they’ll often say that it’s more important for the software system to work. Developers, in turn, often go along with this attitude. But it’s the wrong attitude. I can prove that it is wrong with the simple logical tool of examining the extremes.

如果这个问题由业务部门来回答,他们通常认为系统正常工作很重要。系统开发人员常常也就跟随采取了这种态度。但是这种态度是错误的。下面我就用简单的逻辑推导来证明这个态度的错误性。

If you give me a program that works perfectly but is impossible to change, then it won’t work when the requirements change, and I won’t be able to make it work. Therefore the program will become useless.
If you give me a program that does not work but is easy to change, then I can make it work, and keep it working as requirements change. Therefore the program will remain continually useful.
如果某程序可以正常工作,但是无法修改,那么当需求变更的时候它就不再能够正常工作了,我们也无法通过修改让它能继续正常工作。因此,这个程序的价值将成为 0。
如果某程序目前无法正常工作,但是我们可以很容易地修改它,那么将它改好,并且随着需求变化不停地修改它,都应该是很容易的事。因此,这个程序会持续产生价值。
You may not find this argument convincing. After all, there’s no such thing as a program that is impossible to change. However, there are systems that are practically impossible to change, because the cost of change exceeds the benefit of change. Many systems reach that point in some of their features or configurations.

当然,上面的逻辑论断可能不足以说服大家,修改的。但是,现实中有一些系统确实无法更改,因为其变更实施的成本会远远超过变更带来的价值。你在实际工作中一定遇到过很多这样的例了。

If you ask the business managers if they want to be able to make changes, they’ll say that of course they do, but may then qualify their answer by noting that the current functionality is more important than any later flexibility. In contrast, if the business managers ask you for a change, and your estimated costs for that change are unaffordably high, the business managers will likely be furious that you allowed the system to get to the point where the change was impractical.

如果你问业务部门,是否想要能够变更需求,他们的回答一般是肯定的,而且他们会增加一句:完成现在的功能比实现未来的灵活度更重要。但讽刺的是,如果事后业务部门提出了一项需求,而你的预估工作量大大超出他们的预期,这帮家伙通常会对你放任系统混乱到无法变更的状态而勃然大怒。

EISENHOWER’S MATRIX 艾森豪威尔矩阵
Consider President Dwight D. Eisenhower’s matrix of importance versus urgency (Figure 2.1). Of this matrix, Eisenhower said:

我们来看美国前总统艾森豪威尔的紧急/重要矩阵(参见图 2.1),面对这个矩阵,艾森豪威尔曾说道:

I have two kinds of problems, the urgent and the important. The urgent are not important, and the important are never urgent.1

我有两种难题:紧急的和重要的,而紧急的难题永远是不重要的,重要的难题永远是不紧急的。

Eisenhower matrix

There is a great deal of truth to this old adage. Those things that are urgent are rarely of great importance, and those things that are important are seldom of great urgency.

虽然老调重弹,但其中的道理依然成立。确实,紧急的事情常常没那么重要,而重要的事情则似乎永远也排不上优先级。

The first value of software—behavior—is urgent but not always particularly important.

软件系统的第一个价值维度:系统行为,是紧急的,但是并不总是特别重要。

The second value of software—architecture—is important but never particularly urgent.

软件系统的第二个价值维度:系统架构,是重要的,但是并不总是特别紧急。

Of course, some things are both urgent and important. Other things are not urgent and not important. Ultimately, we can arrange these four couplets into priorities:

当然,我们会有些重要且紧急的事情,也会有一些事情不重要也不紧急。最终我们应将这四类事情进行如下排序:

Urgent and important
Not urgent and important
Urgent and not important
Not urgent and not important
重要且紧急
重要不紧急
不重要但紧急
不重要且不紧急
Note that the architecture of the code—the important stuff—is in the top two positions of this list, whereas the behavior of the code occupies the first and third positions.

在这里你可以看到,软件的系统架构——那些重要的事情——占据了该列表的前两位,而系统行为——那些紧急的事情——只占据了第一和第三位。

The mistake that business managers and developers often make is to elevate items in position 3 to position 1. In other words, they fail to separate those features that are urgent but not important from those features that truly are urgent and important. This failure then leads to ignoring the important architecture of the system in favor of the unimportant features of the system.

业务部门与研发人员经常犯的共同错误就是将第三优先级的事情提到第一优先级去做。换句话说,他们没有把真正紧急并且重要的功能和紧急但是不重要的功能分开。这个错误导致了重要的事被忽略了,重要的系统架构问题让位给了不重要的系统行为功能。

The dilemma for software developers is that business managers are not equipped to evaluate the importance of architecture. That’s what software developers were hired to do. Therefore it is the responsibility of the software development team to assert the importance of architecture over the urgency of features.

但研发人员还忘了一点,那就是业务部门原本就是没有能力评估系统架构的重要程度的,这本来就应该是研发人员自己的工作职责!所以,平衡系统架构的重要性与功能的紧急程度这件事,是软件研发人员自己的职责。

FIGHT FOR THE ARCHITECTURE 为好的软件架构而持续斗争
Fulfilling this responsibility means wading into a fight—or perhaps a better word is “struggle.” Frankly, that’s always the way these things are done. The development team has to struggle for what they believe to be best for the company, and so do the management team, and the marketing team, and the sales team, and the operations team. It’s always a struggle.

为了做好上述职责,软件团队必须做好斗争的准备——或者说“长期抗争”的准备。现状就是这样。研发团队必须从公司长远利益出发与其他部门抗争,这和管理团队的工作一样,甚至市场团队、销售团队、运营团队都是这样。公司内部的抗争本来就是无止境的。

Effective software development teams tackle that struggle head on. They unabashedly squabble with all the other stakeholders as equals. Remember, as a software developer, you are a stakeholder. You have a stake in the software that you need to safeguard. That’s part of your role, and part of your duty. And it’s a big part of why you were hired.

有成效的软件研发团队会迎难而上,毫不掩饰地与所有其他的系统相关方进行平等的争吵。请记住,作为—名软件开发人员,你也是相关者之一。软件系统的可维护性需要由你来保护,这是你角色的一部分,也是你职责中不可缺少的一部分。公司雇你的很大一部分原因就是需要有人来做这件事。

This challenge is doubly important if you are a software architect. Software architects are, by virtue of their job description, more focused on the structure of the system than on its features and functions. Architects create an architecture that allows those features and functions to be easily developed, easily modified, and easily extended.

如果你是软件架构师,那么这项工作就加倍重要了。软件架构师这一职责本身就应更关注系统的整体结构,而不是具体的功能和系统行为的实现,软件架构师必须创建出一个可以让功能实现起来更容易、修改起来更简单、扩展起来更轻松的软件架构。

Just remember: If architecture comes last, then the system will become ever more costly to develop, and eventually change will become practically impossible for part or all of the system. If that is allowed to happen, it means the software development team did not fight hard enough for what they knew was necessary.

请记住:如果忽视软件架构的价值,系统将会变得越来越难以维护,终会有一天,系统将会变得再也无法修改。如果系统变成了这个样子,那么说明软件开发团队没有和需求方做足够的抗争,没有完成自己应尽的职责。

02
3

There has been a lot of confusion about design and architecture over the years. What is design? What is architecture? What are the differences between the two?

一直以来,设计(Design)与架构(Architecture)这两个概念让大多数人十分迷惑——什么是设计?什么是架构?二者究竟有什么区别?

One of the goals of this book is to cut through all that confusion and to define, once and for all, what design and architecture are. For starters, I’ll assert that there is no difference between them. None at all.

本书的一个重要的目标就是要清晰、明确地对二者进行定义。首先我要明确地说,二者没有任何区别。一丁点区别都没有!

The word “architecture” is often used in the context of something at a high level that is divorced from the lower-level details, whereas “design” more often seems to imply structures and decisions at a lower level. But this usage is nonsensical when you look at what a real architect does.

“架构”这个词往往使用于“高层级”的讨论中。这类讨论一般都把“底层”的实现细节排除在外。而“设计”一词,往往用来指代具体的系统底层组织结构和实现的细节。但是,从一个真正的系统架构师的日常工作来看,这样的区分是根本不成立的。

Consider the architect who designed my new home. Does this home have an architecture? Of course it does. And what is that architecture? Well, it is the shape of the home, the outward appearance, the elevations, and the layout of the spaces and rooms. But as I look through the diagrams that my architect produced, I see an immense number of low-level details. I see where every outlet, light switch, and light will be placed. I see which switches control which lights. I see where the furnace is placed, and the size and placement of the water heater and the sump pump. I see detailed depictions of how the walls, roofs, and foundations will be constructed.

以给我设计新房子的建筑设计师要做的事情为例。新房子当然是存在着既定架构的,但这个架构具体包含哪些内容呢?首先,它应该包括房屋的形状、外观设计、垂直高度、房间的布局,等等。但是,如果查看建筑设计师使用的图纸,会发现其中也充斥着大量的设计细节。譬如,我们可以看到每个插座、开关以及每个电灯具体的安装位置,同时也可以看到某个开关与所控制的电灯的具体连接信息;我们也能看到壁炉的具体安装位置,热水器的大小和位置信息,甚至是污水泵的位置;同时也可以看到关于墙体、屋顶和地基都有非常详细的建造说明。

In short, I see all the little details that support all the high-level decisions. I also see that those low-level details and high-level decisions are part of the whole design of the house.

总的来说,架构图里实际上包含了所有的底层设计细节,这些细节信息共同支撑了顶层的架构设计,底层设计信息和顶层架构设计共同组成了整个房屋的架构文档。

And so it is with software design. The low-level details and the high-level structure are all part of the same whole. They form a continuous fabric that defines the shape of the system. You can’t have one without the other; indeed, no clear dividing line separates them. There is simply a continuum of decisions from the highest to the lowest levels.

软件设计也是如此。底层设计细节和高层架构信息是不可分割的。它们组合在一起,共同定义了整个软件系统,缺一不可。所谓的底层和高层本身就是一系列决策组成的连续体,并没有清晰的分界线。

THE GOAL? 目标是什么
And the goal of those decisions? The goal of good software design? That goal is nothing less than my utopian description:

所有这些决策的终极目标是什么呢? 一个好的软件设计的终极目标是什么呢?就像我之前描述过的:

The goal of software architecture is to minimize the human resources required to build and maintain the required system.

软件架构的终极目标是,用最小的人力成本来满足构建和维护该系统的需求。

The measure of design quality is simply the measure of the effort required to meet the needs of the customer. If that effort is low, and stays low throughout the lifetime of the system, the design is good. If that effort grows with each new release, the design is bad. It’s as simple as that.

一个软件架构的优劣,可以用它满足用户需求所需要的成本来衡量。如果该成本很低,并且在系统的整个生命周期内一直都能维持这样的低成本,那么这个系统的设计就是优良的。如果该系统的每次发布都会提升下一次变更的成本,那么这个设计就是不好的。就这么简单。

CASE STUDY 案例分析
As an example, consider the following case study. It includes real data from a real company that wishes to remain anonymous.

下面来看一个真实案例,该案例中的数据均来源于一个要求匿名的真实公司。

First, let’s look at the growth of the engineering staff. I’m sure you’ll agree that this trend is very encouraging. Growth like that shown in Figure 1.1 must be an indication of significant success!

首先,我们来看一下工程师团队规模的增长。你肯定认为这个增长趋势是特别可喜的,像图 1.1 中的这种增长线条一定是公司业务取得巨大成功的直观体现。

Growth of the engineering staff

Reproduced with permission from a slide presentation by Jason Gorman

Now let’s look at the company’s productivity over the same time period, as measured by simple lines of code (Figure 1.2).

现在再让我们来看一下整个公司同期的生产效率(productivity),这里用简单的代码行数作为指标(参见图 1.2)。

Productivity over the same period of time

Clearly something is going wrong here. Even though every release is supported by an ever-increasing number of developers, the growth of the code looks like it is approaching an asymptote.

这明显是有问题的。伴随着产品的每次发布,公司的工程师团队在持续不断地扩展壮大,但是仅从代码行数的增长来看,该产品却正在逐渐陷入困境。

Now here’s the really scary graph: Figure 1.3 shows how the cost per line of code has changed over time.

还有更可怕的:图 1.3 展示的是同期内每行代码的变更成本。

These trends aren’t sustainable. It doesn’t matter how profitable the company might be at the moment: Those curves will catastrophically drain the profit from the business model and drive the company into a stall, if not into a downright collapse.

显然,图中展示的趋势是不可持续的。不管公司现在的利润率有多高,图中线条表明,按这个趋势下去,公司的利润会被一点点榨干,整个公司会因此陷入困境,甚至直接关门倒闭。

What caused this remarkable change in productivity? Why was the code 40 times more expensive to produce in release 8 as opposed to release 1?

究竟是什么因素造成生产力的大幅变化呢?为什么第 8 代产品的构建成本要 比第 1 代产品高 40 倍?

Cost per line of code over time

THE SIGNATURE OF A MESS 乱麻系统的特点
What you are looking at is the signature of a mess. When systems are thrown together in a hurry, when the sheer number of programmers is the sole driver of output, and when little or no thought is given to the cleanliness of the code or the structure of the design, then you can bank on riding this curve to its ugly end.

我们在这里看到的是一个典型的乱麻系统。这种系统一般都是没有经过设计,匆匆忙忙被构建起来的。然后为了加快发布的速度,拼命地往团队里加入新人,同时加上决策层对代码质量提升和设计结构优化存在着持续的、长久的忽视,这种状态能持续下去就怪了。

Figure 1.4 shows what this curve looks like to the developers. They started out at nearly 100% productivity, but with each release their productivity declined. By the fourth release, it was clear that their productivity was going to bottom out in an asymptotic approach to zero.

图 1.4 展示了系统开发者的切身体会。他们一开始的效率都接近 100%,然而伴随着每次产品的发布,他们的生产力直线下降。到了产品的第 4 版本时,很明显大家的生产力已经不可避免地趋近为零了。

Productivity by release

From the developers’ point of view, this is tremendously frustrating, because everyone is working hard. Nobody has decreased their effort.

对系统的开发者来说,这会带来很大的挫败感,因为团队中并没有人偷懒,每个人还都是和之前一样在拼命工作。

And yet, despite all their heroics, overtime, and dedication, they simply aren’t getting much of anything done anymore. All their effort has been diverted away from features and is now consumed with managing the mess. Their job, such as it is, has changed into moving the mess from one place to the next, and the next, and the next, so that they can add one more meager little feature.

然而,不管他们投入了多少个人时间,救了多少次火,加了多少次班,他们的产出始终上不去。工程师的大部分时间都消耗在对现有系统的修修补补上,而不是真正完成实际的新功能。这些工程师真正的任务是:拆了东墙补西墙,周而往复,偶尔有精力能顺便实现一点小功能。

THE EXECUTIVE VIEW 管理层视角
If you think that’s bad, imagine what this picture looks like to the executives! Consider Figure 1.5, which depicts monthly development payroll for the same period.

如果你觉得开发者们这样就已经够苦了,那么就再想想公司髙管们的感受吧! 请看图 1.5,该部门月工资同期图。

Monthly development payroll by release

Release 1 was delivered with a monthly payroll of a few hundred thousand dollars. The second release cost a few hundred thousand more. By the eighth release monthly payroll was $20 million, and climbing.

如你所见,产品的第 1 版是在月总工资 10 万美元左右的时候上线的。第 2 版又花掉了几十万美元。当发布第 8 版的时候,部门月工资已经达到了 2 千万美元,而且还在持续上升。

Just this chart alone is scary. Clearly something startling is happening. One hopes that revenues are outpacing costs and therefore justifying the expense. But no matter how you look at this curve, it’s cause for concern.

也许我们可以指望该公司的营收增长远远超出成本增长,这样公司就还能维持正常运转。但是这么惊人的曲线还是值得我们深入挖掘其中存在的巨大问题的。

But now compare the curve in Figure 1.5 with the lines of code written per release in Figure 1.2. That initial few hundred thousand dollars per month bought a lot of functionality—but the final $20 million bought almost nothing! Any CFO would look at these two graphs and know that immediate action is necessary to stave off disaster.

现在,只要将图 1.5 的月工资曲线和图 1.2 的每次发布代码行数曲线对比一下,任何一个理性的 CEO 都会一眼看出其中的问题:最开始的十几万美元工资给公司带来了很多新功能、新收益,而最后的 2 千万美元几乎全打了水漂。应立刻采取行动解决这个问题,刻不容缓。

But which action can be taken? What has gone wrong? What has caused this incredible decline in productivity? What can executives do, other than to stamp their feet and rage at the developers?

但是具体采取什么样的行动才能解决问题呢?究竟问题出在哪里?是什么造成了工程师生产力的直线下降?高管们除了跺脚、发飙,还能做什么呢?

WHAT WENT WRONG? 问题到底在哪里
Nearly 2600 years ago, Aesop told the story of the Tortoise and the Hare. The moral of that story has been stated many times in many different ways:

大约 2600 年前,《伊索寓言》里写到了龟兔赛跑的故事。这个故事的主题思想可以归纳为以下几种:

“Slow and steady wins the race.”
“The race is not to the swift, nor the battle to the strong.”
“The more haste, the less speed.”
慢但是稳,是成功的秘诀。
该比赛并不是拼谁开始跑得快,也不是拼谁更有力气的。
心态越急,反而跑得越慢。
The story itself illustrates the foolishness of overconfidence. The Hare, so confident in its intrinsic speed, does not take the race seriously, and so naps while the Tortoise crosses the finish line.

这个故事本身揭露的是过度自信的愚蠢行为。兔子由于对自己速度的过度自信,没有把乌龟当回事,结果乌龟爬过终点线取得胜利的时候,它还在睡觉。

Modern developers are in a similar race, and exhibit a similar overconfidence. Oh, they don’t sleep—far from it. Most modern developers work their butts off. But a part of their brain does sleep—the part that knows that good, clean, well-designed code matters.

这和现代软件研发工作有点类似,现在的软件研发工程师都有点过于自信。哦,当然,他们确实不会偷懒,一点也不。但是他们真正偷懒的地方在于——持续低估那些好的、良好设计的、整洁的代码的重要性。

These developers buy into a familiar lie: “We can clean it up later; we just have to get to market first!” Of course, things never do get cleaned up later, because market pressures never abate. Getting to market first simply means that you’ve now got a horde of competitors on your tail, and you have to stay ahead of them by running as fast as you can.

这些工程师们普遍用一句话来欺骗自己:“我们可以未来再重构代码,产品上线最重要!”但是结果大家都知道,产品上线以后重构工作就再没人提起了。市场的压力永远也不会消退,作为首先上市的产品,后面有无数的竞争对手追赶,必须要比他们跑得更快才能保持领先。

And so the developers never switch modes. They can’t go back and clean things up because they’ve got to get the next feature done, and the next, and the next, and the next. And so the mess builds, and productivity continues its asymptotic approach toward zero.

所以,重构的时机永远不会再有了。工程师们忙于完成新功能,新功能做不完,哪有时间重构老的代码?循环往复,系统成了一团乱麻,主产效率持续直线下降,直至为零。

Just as the Hare was overconfident in its speed, so the developers are overconfident in their ability to remain productive. But the creeping mess of code that saps their productivity never sleeps and never relents. If given its way, it will reduce productivity to zero in a matter of months.

结果就像龟兔赛跑中过于自信的兔子一样,软件研发工程师们对自己保持高产出的能力过于自信了。但是乱成一团的系统代码可没有休息时间,也不会放松。如果不严加提防,在几个月之内,整个研发团队就会陷入困境。

The bigger lie that developers buy into is the notion that writing messy code makes them go fast in the short term, and just slows them down in the long term. Developers who accept this lie exhibit the hare’s overconfidence in their ability to switch modes from making messes to cleaning up messes sometime in the future, but they also make a simple error of fact. The fact is that making messes is always slower than staying clean, no matter which time scale you are using.

工程师们经常相信的另外一个错误观点是: “在工程中容忍糟糕的代码存在可以在短期内加快该工程上线的速度,未来这些代码会造成一些额外的工作量,但是并没有什么大不了。”相信这些鬼话的工程师对自己清理乱麻代码的能力过于自信了。但是更重要的是,他们还忽视了一个自然规律:无论是从短期还是长期来看,胡乱编写代码的工作速度其实比循规蹈矩更慢。

Consider the results of a remarkable experiment performed by Jason Gorman depicted in Figure 1.6. Jason conducted this test over a period of six days. Each day he completed a simple program to convert integers into Roman numerals. He knew his work was complete when his predefined set of acceptance tests passed. Each day the task took a little less than 30 minutes. Jason used a well-known cleanliness discipline named test-driven development (TDD) on the first, third, and fifth days. On the other three days, he wrote the code without that discipline.

图 1.6 展示的是 Jason Gorman 进行的一次为期 6 天的实验。在该实验中,Jason 每天都编写一段代码,功能是将一个整数转化为相应罗马数字的字符串。当事先定义好的一个测试集完全通过时,即认为当天工作完成。每天实验的时长不超过 30 分钟。第一天、第三天和第五天,Jason 在编写代码的过程中采用了业界知名的优质代码方法论:测试驱动开发(TDD),而其他三天他则直接从头开始编写代码。

Time to completion by iterations and use/non-use of TDD

First, notice the learning curve apparent in Figure 1.6. Work on the latter days is completed more quickly than the former days. Notice also that work on the TDD days proceeded approximately 10% faster than work on the non-TDD days, and that even the slowest TDD day was faster than the fastest non-TDD day.

首先,我们要关注的是图 1.6 中那些柱状图。很显然,日子越往后,完成工作所需的时间就越少。同时,我们也可以看到当人们采用了 TDD 方法编程后,一般就会比未采用 TDD 方法编程少用 10%的时间,并且采用 TDD 方法编程时最差的一天也比未采用 TDD 方法编程时最好的一天用时要短。

Some folks might look at that result and think it’s a remarkable outcome. But to those who haven’t been deluded by the Hare’s overconfidence, the result is expected, because they know this simple truth of software development:

对于这个结果,有些人可能会觉得挺意外的。但是对常年关注软件开发本质的人来说,它其实揭示了软件开发的一个核心特点:

The only way to go fast, is to go well.

要想跑得快,先要跑得稳。

And that’s the answer to the executive’s dilemma. The only way to reverse the decline in productivity and the increase in cost is to get the developers to stop thinking like the overconfident Hare and start taking responsibility for the mess that they’ve made.

综上所述,管理层扭转局面的唯一选择就是扭转开发者的观念,让他们从过度自信的兔子模式转变回来,为自己构建的乱麻系统负起责任来。

The developers may think that the answer is to start over from scratch and redesign the whole system—but that’s just the Hare talking again. The same overconfidence that led to the mess is now telling them that they can build it better if only they can start the race over. The reality is less rosy:

当然,某些软件研发工程师可能会认为挽救一个系统的唯一办法是抛弃现有系统,设计一个全新的系统来替代。但是这里仍然没有逃离过度自信。试问:如果是工程师的过度自信导致了目前的一团乱麻,开始,结果就会更好呢?

Their overconfidence will drive the redesign into the same mess as the original project.

那么,我们有什么理由认为让他们从头第 1 章设计与架构究竟是什么过度自信只会使得重构设计陷入和原项目一样的困局中。

CONCLUSION 本章小结
In every case, the best option is for the development organization to recognize and avoid its own overconfidence and to start taking the quality of its software architecture seriously.

不管怎么看,研发团队最好的选择是清晰地认识并避开工程师们过度自信的特点,开始认真地对待自己的代码架构,对其质量负责。

To take software architecture seriously, you need to know what good software architecture is. To build a system with a design and an architecture that minimize effort and maximize productivity, you need to know which attributes of system architecture lead to that end.

要想提高自己软件架构的质量,就需要先知道什么是优秀的软件架构。而为了在系统构建过程中采用好的设计和架构以便减少构建成本,提高生产力,又需要先了解系统架构的各种属性与成本和生产力的关系。

That’s what this book is about. It describes what good clean architectures and designs look like, so that software developers can build systems that will have long profitable lifetimes.

这就是这本书的主题。本书为读者描述了什么是优秀的、整洁的软件架构与设计,读者可以参考这些设计来构建一个长期稳定的、持久优秀的系统。

01
3

My professional career as a software developer began in the 1990s, at a time when the dinosaurs of Big Architecture ruled the world. To get ahead, you had to learn about objects and components, about design patterns, and about the Unified Modeling Language (and its precursors).

我的软件工程师生涯开始于 20 世纪 90 年代,那是一个恐龙级大型架构统治世界的时代。要想在那样的时代获得一席之地,我们必须学会对象及其组件、设计模式、统一建模语言(包括其前身)的相关知识。

Projects—and boy, should we rue the day when we decided to call them that?—started with long design phases, where detailed blueprints for our systems were laid out by “senior” programmers for more “junior” programmers to follow. Which, of course, they didn’t. Ever.

现在想起来,或许真的可以考虑把我们那段日子所做的事情叫作“童子军项目”。每个项目的开头都会有一段长长的设计阶段,以便等那些“高级”程序员为一些跟随他们的、较“低级”的程序员制订好系统的设计蓝图,当然,这些“高级”程序员似乎永远完不成这件事。

And so it was that, after rising to the lofty ranks of “software architect”—and then “lead architect,” “chief architect,” “Lord Architect of the Privy Council,” and all the other highfalutin titles we gave ourselves back in the day—I seemed doomed to spend my days connecting boxes with arrows and coding with PowerPoint, and having barely any impact on the real code itself.

于是乎,做这件事的人被升级到了“软件架构师”,接着是“首席架构师”“总架构师”“枢密院首席架构师”以及其他各种高不可言的头衔,最终,我们还是让一切回到了原点。而我似乎注定要把时间花在画那些带箭头的盒子和编写 PowerPoint 的事情上,而这些事对真实代码的影响近乎为零。

It struck me then that this was nonsense; every line of code contains at least one design decision, and therefore anyone who writes code has a much greater impact on the quality of the software than a PowerPoint jockey like me ever could.

这让我无比受挫,每一行代码本身都至少包含了一条设计决策,任何一个写代码的家伙对软件质量的影响都远在我这个 PowerPoint 专业户之上。

And then, thankfully, the Agile Software Development revolution arrived and put architects like me out of our misery. I’m a programmer. I like programming. And the best way I’ve found to have a positive impact on code is to write it.

幸运的是,接下来发生的敏捷软件开发革命终于让我们这些架构师脱离了苦海。毕竟我是一名程序员,喜欢的是编程。而且我也发现影响软件质量最好的方法还是后序编写代码。

The dinosaurs of Big Architecture—typically to be found wandering the primeval plains of Big Process—were wiped out by the asteroid of Extreme Programming. And it came as a blessed relief.

这些大型架构像恐龙一样在大进程式的原始平原上游荡,然后被一颗叫作“敏捷开发”的小行星灭绝了,真是老天开眼啊!

Development teams were set free to focus on what matters and to concentrate their efforts on things that add value. Instead of waiting weeks or months for a Big Architecture document so they could dutifully ignore it and write the code they were going to write anyway, teams could just agree to a test with their customer, have a quick design session to get their bearings, and then write the code they were going to write anyway.

现在!开发团队可以自由地专注于真正重要的内容,并思考如何为他们所做的事情添加更多价值了。也就是说,他们现在再也不需要浪费几周或几个月的时间等待那些大型架构的设计文档了,他们可以名正言顺地忽略这些设计,直接按照自己的想法编写代码。然后,开发团队只需要安排客户直接参与测试,并在快速设计会议上得到用户的支持,然后他们就可以继续写代码了。

The Big Architecture dinosaurs were gone, and small, nimble Just-Enough-Design-Up-Front-with-Plenty-of-Refactoring mammals replaced us. Software architecture became responsive.

大型架构像恐龙一样消失了,前期设计够用、后期进行大量重构的设计思想如小巧玲珑的哺乳动物一样代替了它们,软件架构迎来了响应式设计的时代。

Well, that was the theory, anyway.

好吧,无论如何,以上这些都属于理论。

The problem with leaving architecture to programmers is that programmers have to be able to think like architects. It turns out that not all of the stuff we learned during the Big Architecture era was of no value. The way that software is structured can have a profound impact on our ability to keep adapting and evolving it, even in the short term.

把架构设计工作交给程序员的问题就是,程序员必须学会像架构师一样思考问题。事实证明,我们在大型架构时代学到的东西也并非一文不值。其设计软件结构的方法依然在我们保持软件的适应和扩展能力方面有着深远的影响,即使在短期开发中也是如此。

Every design decision needs to leave the door open for future changes. Like playing pool, each shot isn’t just about sinking that ball; it’s also about lining up the next shot. Writing working code that doesn’t block future code is a non-trivial skillset. It takes years to master.

我们的每一项设计决策都必须为未来的变化敞开大门。就像打台球一样,我们的每一杆击球都不只是为了要把球打进洞里,它也事关下一杆击球时所在的位置。让我们现在编写的代码不对未来的代码产生阻碍是一项非常重要的技能,通常需要花费多年的时间才能掌握。

And so, the era of Big Architecture gave way to a new era of Fragile Architecture: designs that grew quickly to deliver value sooner, but that made sustaining that pace of innovation very difficult.

因此,在大型架构时代让位给易碎型架构(Fragile Architecture)的新时代之后,虽然设计创造的价值得到了快速发展,但这也让我们想要持续创新变得举步维艰。

It’s all very well talking about “embracing change,” but if it costs $500 to change a line of code, change ain’t happening.

这里所有关于“拥抱变革"的讨论都很美好,但如果每修改一行代码的代价是 500 美元的话,这些变革恐怕根本就不会发生。

Bob Martin’s original papers on OO design principles had a big impact on me as a young software developer. I looked at my code with a fresh perspective, and noticed problems that—until then—never seemed like problems to me.

当我还是一名年轻的软件开发者的时候,Bob Martin 那篇关于面向对象设计原则的论文对我产生了很大的影响,他让我以一种全新的视觉审视了自己的代码,并发现了其中的问题,在那之前,这些问题对我来说似乎从来都不是问题。

Now you’ve seen how it’s possible to write code that delivers value today without blocking future value tomorrow; the onus is on you to put in the practice so you can apply these principles to your own code.

现在,你们也看到了如何才能写出既能提供当前价值,又不会阻碍未来价值的代码,期待你们也能亲自实践这些设计原则,并将其应用到自己的代码中。

Like riding a bicycle, you can’t master software design just by reading about it. To get the best from a book like this, you need to get practical. Analyze your code and look for the kinds of problems Bob highlights, then practice refactoring the code to fix these problems. If you’re new to the refactoring discipline, then this will be a doubly valuable experience.

就像学习骑自行车一样,单纯靠阅读是无法掌握软件设计方法的。为了让我们从这本书中的获益最大化,亲自实践是必不对少的。我们需要亲自分析自己的代码,查看其中是否存在 Bob 所强调的各种问题,然后在重构代码的实践中修复它们。如果你在重构方面是个新手,那么你将从本书收获双重的宝贵学习经验。

Learn how you can incorporate design principles and Clean Architecture into your development processes, so that new code is less likely to cause pain. For example, if you’re doing TDD, make a point of having a little design review after passing each test, and clean up as you go. (It’s way cheaper than fixing bad designs later.) Perhaps, before you commit code, ask a colleague to review it with you. And look into the possibility of adding a code “quality gate” to your build pipeline as a last line of defense against unclean architecture. (And if you don’t have a build pipeline, maybe it’s time to create one?)

我们得学会将书中的这些设计原则以及整洁架构融入自己的开发过程中,这可以大大减少新代码给我们带来的麻烦。例如,如果我们现在正在进行一次测试驱动的开发(TDD),就可以在每一次测试之后做一些设计审查,并及时整理我们的设计(这比事后再修复这些不良设计要省时省力得多)。或者,在提交代码之前,我们也可以邀请同事一起审查代码。另外,我们也可以研究在构建软件的管道中引入一些代码的“质量把关”机制,以作为防止架构设计不够清晰分明的最后一道防线。(如果你还没有设置构建软件的管道,现在是否可以考虑设置一个了?)

Most important of all is to talk about Clean Architecture. Talk about it with your team. Talk about it with the wider developer community. Quality is everybody’s business, and it’s important to reach a consensus about the difference between good and bad architecture.

这一切的重中之重就是要讨论架构的整洁性,我们要在自己的团队中讨论它,在各种开发者社区中讨论它。保证软件质量是我们每个人的责任,在区分架构的好坏标准上达成共识是一件非常重要的事。

Be mindful that most software developers are not very architecture-aware, just as I wasn’t 25 years ago. More experienced developers clued me into it. Once you’ve wrapped your head around Clean Architecture, take the time to wrap someone else’s head around it. Pay it forward.

我们必须意识到,大部分的软件开发者是没有太多架构意识的。就像 25 年前的我一样,是更有经验的开发者让我了解了架构。一旦我们一头扎进了整洁架构中,就会花时间围绕着它思考问题,并玩转它。

While the technology landscape for developers evolves continuously, foundational principles like the ones described here rarely change. I have little doubt that this is a book that’s going to stay on your shelf for many years after your copy of Lean JSON Cloud NoSQL for Dummies has ended up in a yard sale. I hope it does for your Design Fu what Bob’s original papers did for mine.

虽然开发者所在的技术环境一直在不断地发展,但本书所讨论的这些基本设计原则几乎不会发生变化。我一点都不怀疑在你们把 Lean JSON Cloud NoSQL for Dummies 当废纸卖掉很多年之后,这本书会还留在你们的书架上。我希望这本书会 对你们有很大的帮助,就像 Bob 那篇原创论文对我的帮助一样。

The real journey starts here.

愿你们真正的编程设计之旅从这里开始!

—Jason Gorman

January 26, 2017

——Jason Gorman

2017 年 1 月 26 日

公告栏

欢迎大家来到我的博客,我是dodoro,希望我的博客能给你带来帮助。