本文翻译自John Ousterhout所著的A Philosophy of Software Design一书。
Preface 前言
People have been writing programs for electronic computers for more than 80 years, but there has been surprisingly little conversation about how to design those programs or what good programs should look like. There has been considerable discussion about software development processes such as agile development and about development tools such as debuggers, version control systems, and test coverage tools. There has also been extensive analysis of programming techniques such as object-oriented programming and functional programming, and of design patterns and algorithms. All of these discussions have been valuable, but the core problem of software design is still largely untouched. David Parnas’ classic paper “On the Criteria to be used in Decomposing Systems into Modules” appeared in 1971, but the state of the art in software design has not progressed much beyond that paper in the ensuing 45 years.
80 多年来,人们一直在为电子计算机编写程序,但令人惊讶的是,很少有关于如何设计这些程序或好的程序应该是什么样子的讨论。相反,关于软件开发过程(例如敏捷开发)和开发工具(例如调试器、版本控制系统和测试覆盖工具)的讨论相当多,对编程技术(例如面向对象编程和函数式编程)以及设计模式和算法亦有广泛的分析。所有这些讨论都是有价值的,但软件设计的核心问题在很大程度上仍未触及。David Parnas的经典论文“关于将系统分解为模块的标准”发表于 1971 年,但在随后的 45 年中,软件设计的最新进展并没有超出那篇论文。
The most fundamental problem in computer science is problem decomposition: how to take a complex problem and divide it up into pieces that can be solved independently. Problem decomposition is the central design task that programmers face every day, and yet, other than the work described here, I have not been able to identify a single class in any university where problem decomposition is a central topic. We teach for loops and object-oriented programming, but not software design.
计算机科学中最基本的问题是问题分解:如何将一个复杂的问题分解成可以独立解决的部分。问题分解是程序员每天面临的核心设计任务,然而,除了本书描述的工作之外,我还无法在任何一所大学中找出一个以问题分解为中心主题的课程。我们教授循环和面向对象编程,但不教授软件设计。
In addition, there is a huge variation in quality and productivity among programmers, but we have made little attempt to understand what makes the best programmers so much better or to teach those skills in our classes. I have talked with several people I consider to be great programmers, but most of them had difficulty articulating specific techniques that give them their advantage. Many people assume that software design skill is an innate talent that cannot be taught. However, there is quite a bit of scientific evidence that outstanding performance in many fields is related more to high-quality practice than innate ability (see, for example, Talent is Overrated by Geoff Colvin).
此外,程序员的质量和生产力存在巨大差异,但我们并未明确是什么让最优秀的程序员变得如此出色,也没有再我们的课程中教授这些技能。我与几个我认为是伟大的程序员的人进行了交谈,但他们中的大多数人都难以阐明赋予他们优势的特定技术。许多人认为软件设计技能是一种无法传授的天赋。然而,有相当多的科学证据表明,在许多领域的出色表现更多地与高质量的实践相关,而不是与生俱来的能力(例如,参见 Geoff Colvin 的 Talent is Overrated)。
For many years these issues have perplexed and frustrated me. I have wondered whether software design can be taught, and I have hypothesized that design skill is what separates great programmers from average ones. I finally decided that the only way to answer these questions was to attempt to teach a course on software design. The result is CS 190 at Stanford University. In this class I put forth a set of principles of software design. Students then work through a series of projects to assimilate and practice the principles. The class is taught in a fashion similar to a traditional English writing class. In an English class, students use an iterative process where they write a draft, get feedback, and then rewrite to make improvements. In CS 190, students develop a substantial piece of software from scratch. We then go through extensive code reviews to identify design problems, and students revise their projects to fix the problems. This allows students to see how their code can be improved by applying design principles.
多年来,这些问题让我感到困惑和沮丧。我想知道是否可以教授软件设计,并且我假设设计技能是优秀程序员与普通程序员的区别。我最终决定,回答这些问题的唯一方法是尝试教授一门软件设计课程。斯坦福大学的CS 190课程应运而生。在该课程中,我提出了一套软件设计原则,然后学生通过一系列项目来吸收和实践这些原则。该课程的授课方式类似于传统的英语写作课程。在英语课上,学生使用一个迭代过程,他们编写草稿,获得反馈,然后重写以进行改进。在CS 190中,学生从头开始开发功能丰富且有一定复杂性的软件。然后,我们通过广泛的代码审查来识别设计问题,学生修改他们的项目以解决这些问题,这使学生可以了解如何通过应用设计原则来改进他们的代码。
I have now taught the software design class three times, and this book is based on the design principles that emerged from the class. The principles are fairly high level and border on the philosophical (“Define errors out of existence”), so it is hard for students to understand the ideas in the abstract. Students learn best by writing code, making mistakes, and then seeing how their mistakes and the subsequent fixes relate to the principles.
我现在已经教了3次软件设计课,这本书就是基于课堂上出现的设计原则。这些原则的描述大多是高层次的哲学层面描述(如“将错误定义为不存在”),因此学生很难理解抽象的想法,学生需要通过编写代码、犯错误、然后查看他们的错误以及随后的修复与原则之间的关系来学习。
At this point you may well be wondering: what makes me think I know all the answers about software design? To be honest, I don’t. There were no classes on software design when I learned to program, and I never had a mentor to teach me design principles. At the time I learned to program, code reviews were virtually nonexistent. My ideas about software design come from personal experience writing and reading code. Over my career I have written about 250,000 lines of code in a variety of languages. I’ve worked on teams that created three operating systems from scratch, multiple file and storage systems, infrastructure tools such as debuggers, build systems, and GUI toolkits, a scripting language, and interactive editors for text, drawings, presentations, and integrated circuits. Along the way I’ve experienced firsthand the problems of large systems and experimented with various design techniques. In addition, I’ve read a considerable amount of code written by other people, which has exposed me to a variety of approaches, both good and bad.
在这一点上,您可能想知道:是什么让我认为我知道有关软件设计的所有答案?老实说,我没有。当我学习编程时,没有关于软件设计的课程,也没有导师教我设计原则。在我学习编程的时候,几乎没有代码审查。我对软件设计的想法来自个人编写和阅读代码的经验。在我的职业生涯中,我用各种语言编写了大约250,000行代码。我工作过的团队从头开始创建了三个操作系统、多个文件和存储系统、基础设施工具(如调试器、构建系统和 GUI 工具包)、脚本语言以及用于文本、绘图、演示文稿和集成电路的交互式编辑器。在这些过程中,我亲身体验了大型系统的问题并尝试了各种设计技术。此外,我阅读了大量其他人编写的代码,这让我接触到了各种方法,有好有坏。
Out of all of this experience, I’ve tried to extract common threads, both about mistakes to avoid and techniques to use. This book is a reflection of my experiences: every problem described here is one that I have experienced personally, and every suggested technique is one that I have used successfully in my own coding.
我试图从上面这些经验中提取共同的线索,包括要避免的错误和要使用的技术。这本书反映了我的经验:这里描述的每一个问题都是我亲身经历的,每一个建议的技术都是我在自己编程中成功使用的技术。
I don’t expect this book to be the final word on software design; I’m sure there are valuable techniques that I’ve missed, and some of my suggestions may turn out to be bad ideas in the long run. However, I hope that the book will start a conversation about software design. Compare the ideas in this book with your own experiences and decide for yourself whether the approaches described here really do reduce software complexity. This book is an opinion piece, so some readers will disagree with some of my suggestions. If you do disagree, try to understand why. I’m interested in hearing about things that work for you, things that don’t work, and any other ideas you may have about software design. I hope that the ensuing conversations will improve our collective understanding of software design. I will incorporate what I learn in future editions of this book.
我不希望这本书成为软件设计的最终定论。我确信我错过了一些有价值的技术,从长远来看,我的一些建议可能会变成坏主意。不过,我希望这本书能在软件设计领域抛砖引玉。您应该将本书中的想法与您自己的经验进行比较,并自行决定此处描述的方法是否真的能降低软件复杂性。这本书是一篇评论文章,所以有些读者会不同意我的一些建议。如果您不同意,请尝试刨根问底。我很想听听对你有用的东西、没用的东西,以及你对软件设计可能有的任何其他想法。我希望接下来的对话能增进我们对软件设计的集体理解。我将把我学到的东西融入本书的未来版本中。
The best way to communicate with me about the book is to send email to the following address:[email protected]. I’m interested in hearing specific feedback about the book, such as bugs or suggestions for improvement, as well as general thoughts and experiences related to software design. I’m particularly interested in compelling examples that I can use in future editions of the book. The best examples illustrate an important design principle and are simple enough to explain in a paragraph or two. If you would like to see what other people are saying on the email address and participate in discussions, you can join the Google Group software-design-book.
就本书与我交流的最佳方式是向以下地址发送电子邮件:[email protected]。
我期待听到关于这本书的具体反馈,例如错误或改进建议,以及与软件设计相关的一般想法和经验。我对可以在本书的未来版本中使用的引人入胜的例子特别感兴趣。这样的例子最好说明一个重要的设计原则,并且很简单,可以用一两段来解释。如果您想查看其他人在电子邮件地址上的评论并参与讨论,您可以加入 Google Group software-design-book。
If for some reason the software-design-book Google Group should disappear in the future, search on the Web for my home page; it will contain updated instructions for how to communicate about the book. Please don’t send book-related email to my personal email address.
如果将来由于某种原因software-design-book Google Group 消失了,请在Web上搜索我的主页,它将包含有关如何就本书进行交流的更新说明。请不要将与书籍相关的电子邮件发送到我的个人电子邮件地址。
I recommend that you take the suggestions in this book with a grain of salt. The overall goal is to reduce complexity; this is more important than any particular principle or idea you read here. If you try an idea from this book and find that it doesn’t actually reduce complexity, then don’t feel obligated to keep using it (but, do let me know about your experience; I’d like to get feedback on what works and what doesn’t).
我建议你对本书中的建议持保留态度。降低复杂性是最重要的目标,这比您在此处阅读的任何特定原则或想法更重要。如果你尝试了本书中的一个想法并发现它实际上并没有降低复杂性,那么不要觉得继续使用它(但是,一定要让我知道你的经验;我想得到原则有效和无效的反馈)。
Many people have offered criticisms or made suggestions that improved the quality of the book. The following people offered helpful comments on various drafts of the book: Jeff Dean, Sanjay Ghemawat, John Hartman, Brian Kernighan, James Koppel, Amy Ousterhout, Kay Ousterhout, Rob Pike, Partha Ranganathan, Keith Schwartz, and Alex Snaps. Christos Kozyrakis suggested the terms “deep” and “shallow” for classes and interfaces, replacing previous terms “thick” and “thin”, which were somewhat ambiguous. I am indebted to the students in CS 190; the process of
reading their code and discussing it with them has helped to crystallize my thoughts about design.致谢部分,不再翻译
Introduction(It’s All About Complexity) 引言(万物复杂)
Writing computer software is one of the purest creative activities in the history of the human race. Programmers aren’t bound by practical limitations such as the laws of physics; we can create exciting virtual worlds with behaviors that could never exist in the real world. Programming doesn’t require great physical skill or coordination, like ballet or basketball. All programming requires is a creative mind and the ability to organize your thoughts. If you can visualize a system, you can probably implement it in a computer program.
使用计算机编写程序是人类历史上最纯粹的创造性活动之一。程序员并不被物理定律这样的客观规律所限制,我们可以创造令人激动的虚拟世界,实现在现实世界中永远不可能存在的行为。编程也不像芭蕾舞或篮球那样需要出色的技能和协调性。创造性的大脑与组织想法的能力就是编程所需要的全部资源。如果一个系统是可观测的,那么你通常可用计算机程序来实现它。
This means that the greatest limitation in writing software is our ability to understand the systems we are creating. As a program evolves and acquires more features, it becomes complicated, with subtle dependencies between its components. Over time, complexity accumulates, and it becomes harder and harder for programmers to keep all of the relevant factors in their minds as they modify the system. This slows down development and leads to bugs, which slow development even more and add to its cost. Complexity increases inevitably over the life of any program. The larger the program, and the more people that work on it, the more difficult it is to manage complexity.
这意味我们在编写软件时面临的最大限制是我们对软件系统的理解能力。随着程序不断发展,更能的功能被加入,程序本身也变得愈加复杂,不同组件之间会出现许多微妙的依赖。复杂性随时间不断增长,在不断演化的系统中将所有相关细节牢记于心对程序员来说会变的愈加困难,这将程序开发速度变慢,引入更多的bug,而更多的bug又会进一步降低开发速度,最终陷入成本不断增长的恶性循环。几乎在所有程序的生命周期中都伴随这种复杂性的增长,程序规模越大,开发程序的相关人员越多,对复杂性的管控就越困难。
Good development tools can help us deal with complexity, and many great tools have been created over the last several decades. But there is a limit to what we can do with tools alone. If we want to make it easier to write software, so that we can build more powerful systems more cheaply, we must find ways to make software simpler. Complexity will till increase over time, in spite of our best efforts, but simpler designs allow us to build larger and more powerful systems before complexity becomes overwhelming.
好的开发工具可以帮助我们处理复杂性,在过去的几十年中人们已经创建了许多优秀的工具,但是我们仅使用工具所能做的事情是有限度的。如果我们想让编写软件变得更容易,以便我们可以用更少的成本构建更强大的系统,我们必须找到让软件开发更简单的方法。虽然竭尽全力不能阻止复杂性随时间推移而增加,但更简单的设计可以使我们在复杂性变得不可承受之前构建出更大规模、功能更丰富的系统。
There are two general approaches to fighting complexity, both of which will be discussed in this book. The first approach is to eliminate complexity by making code simpler and more obvious. For example, complexity can be reduced by eliminating special cases or using identifiers in a consistent fashion.
对抗复杂性有两种通用方法,这两种方法都将在本书中进行讨论。 其一,可以通过使代码更简单、更具可读性来消除复杂性。 例如,可以通过消除不必要的特殊情况判断或使用标识符让程序逻辑保持一致来降低复杂性。
The second approach to complexity is to encapsulate it, so that programmers can work on a system without being exposed to all of its complexity at once. This approach is called modular design. In modular design, a software system is divided up into modules, such as classes in an object-oriented language. The modules are designed to be relatively independent of each other, so that a programmer can work on one module without having to understand the details of other modules.
第二种的方法是把复杂性封装起来,这使得在不暴露系统内复杂细节时,程序员仍然可以在这个系统的基础上开展工作,这种方法称为模块化设计。 在模块化设计中,软件系统被分成模块,模块的一个典型例子是面向对象语言中的类。这些模块被设计成彼此相对独立,这样程序员就可以在一个模块上工作,而不必了解其他模块的细节。
Because software is so malleable, software design is a continuous process that spans the entire lifecycle of a software system; this makes software design different from the design of physical systems such as buildings, ships, or bridges. However, software design has not always been viewed this way. For much of the history of programming, design was concentrated at the beginning of a project, as it is in other engineering disciplines. The extreme of this approach is called the waterfall model, in which a project is divided into discrete phases such as requirements definition, design, coding, testing, and maintenance. In the waterfall model, each phase completes before the next phase starts; in many cases different people are responsible for each phase. The entire system is designed at once, during the design phase. The design is frozen at the end of this phase, and the role of the subsequent phases is to flesh out and implement that design.
鉴于软件的可塑性很强,软件设计是一个跨越软件系统全生命周期的连续过程,这使得软件设计不同于建筑物、船舶或桥梁等物理系统的设计。然而,软件设计本身并不总是得到特殊审视:绝大部分编程历史中,与其他工程学科类似,设计总被集中放在在项目的开始阶段。将这种方法推进到机制的策略被称为瀑布模型,项目被严格划分为多个离散的阶段,例如需求定义、设计、编码、测试和维护等。在瀑布模型中,每个阶段都需要在下一个阶段开始之前完成,在多数情况下,不同的人负责不同的阶段。整个系统在设计阶段完成一次设计,此阶段结束后,设计被冻结,由瀑布模型后续阶段完善和实现该设计。
Unfortunately, the waterfall model rarely works well for software. Software systems are intrinsically more complex than physical systems; it isn’t possible to visualize the design for a large software system well enough to understand all of its implications before building anything. As a result, the initial design will have many problems. The problems do not become apparent until implementation is well underway. However, the waterfall model is not structured to accommodate major design changes at this point (for example, the designers may have moved on to other projects). Thus, developers try to patch around the problems without changing the overall design. This results in an explosion of complexity.
不幸的是,瀑布模型通常很难适用于软件开发。软件系统本质上比物理系统复杂许多:在构建任何东西之前,几乎不可能将大型软件系统的设计完整可视化到足以理解其所有细节的程度。 这样一来,最初的设计必将隐藏很多问题。 这些问题往往会在编码实现阶段才逐渐暴露出来,此时瀑布模型本身已无法应对主要的设计更改(例如,设计阶段的人员可能已经转移到其他项目)。因此,开发人员试图在不改变整体设计的情况下解决这些问题。 这导致了复杂性爆炸式增长。
Because of these issues, most software development projects today use an incremental approach such as agile development, in which the initial design focuses on a small subset of the overall functionality. This subset is designed, implemented, and then evaluated. Problems with the original design are discovered and corrected, then a few more features are designed, implemented and evaluated. Each iteration exposes problems with the existing design, which are fixed before the next set of features is designed. By spreading out the design in this way, problems with the initial design can be fixed while the system is still small; later features benefit from experience gained during the implementation of earlier features, so they have fewer problems.
由于上述问题,当今大多数软件开发项目都或多或少使用了增量开发策略,以敏捷开发为例,初始设计侧重于整体功能的一小部分子集。在设计和实现过程中,这个子集被不断评估,原始设计中的问题被逐步发现并纠正,接着设计、实施和评估更多功能点。每次迭代都会暴露现有设计的问题,这些问题下一轮迭代之前被修复。通过这种方式展开设计,可以在系统规模还很小的时候解决初始设计的问题,后期功能受益于早期实现过程中获得的经验,因此它们的问题更少。、
The incremental approach works for software because software is malleable enough to allow significant design changes partway through implementation. In contrast, major design changes are much more challenging for physical systems: for example, it would not be practical to change the number of towers supporting a bridge in the middle of construction.
增量策略之所以适用于软件,是因为软件具有足够的可塑性,允许在实施过程中进行重大的设计更改。相比之下,对物理系统来说主要的设计调整更具挑战性:例如,在施工过程中改变桥梁承重塔的数量是不切实际的。
Incremental development means that software design is never done. Design happens continuously over the life of a system: developers should always be thinking about design issues. Incremental development also means continuous redesign. The initial design for a system or component is almost never the best one; experience inevitably shows better ways to do things. As a software developer, you should always be on the lookout for opportunities to improve the design of the system you are working on, and you should plan on spending some fraction of your time on design improvements.
增量开发策略意味着软件设计永无完成时。设计在系统的整个生命周期中不断发生,开发人员应该始终考虑设计问题。增量开发也意味着持续不断的重新设计。系统或组件的初始设计几乎从来都不是最好的,随着经验不断积累,更好的实现方式总会出现。作为一名软件开发人员,您应该尽可能在开发中不断改进系统设计,并且在计划项目进度时,将一部分时间投入在设计改进中。
If software developers should always be thinking about design issues, and reducing complexity is the most important element of software design, then software developers should always be thinking about complexity. This book is about how to use complexity to guide the design of software throughout its lifetime.
既然软件开发人员应该始终考虑设计问题,而降低复杂性是软件设计中最重要的部分,那么软件开发人员在设计过程中应该始终考虑复杂性。这本书将主要介绍软件生命周期中基于控制复杂性驱动的设计理念。
This book has two overall goals. The first is to describe the nature of software complexity: what does “complexity” mean, why does it matter, and how can you recognize when a program has unnecessary complexity? The book’s second, and more challenging, goal is to present techniques you can use during the software development process to minimize complexity. Unfortunately, there isn’t a simple recipe that will guarantee great software designs. Instead, I will present a collection of higher-level concepts that border on the philosophical, such as “classes should be deep” or “define errors out of existence.” These concepts may not immediately identify the best design, but you can use them to compare design alternatives and guide your exploration of the design space.
本书有两个总体目标:其一是描述软件复杂性的本质:“复杂性”是什么意思,为什么重要,如何识别程序何时具有不必要的复杂性? 其二也是更具挑战性的目标是描述在软件开发过程中使用的技术,这些技术旨在最大限度地降低复杂性。不幸的是,并没有一个简单的方法可以保证出色的软件设计,相反,本书将描述一系列哲学层面的基本概念,例如“类应当深层抽象”或“将错误定义为不存在”。这些概念可能不会立即确定最佳设计,但您可以使用它们来比较设计备选方案并指导您对设计的探索。
How to use this book 如何使用本书
Many of the design principles described here are somewhat abstract, so they may be hard to appreciate without looking at actual code. It has been a challenge to find examples that are small enough to include in the book, yet large enough to illustrate problems with real systems (if you encounter good examples, please send them to me). Thus, this book may not be sufficient by itself for you to learn how to apply the principles.
此处描述的许多设计原则较为抽象,因此如果不查看实际代码,您可能很难理解它们。找到小到足以包含在书中的示例是一个挑战,实例需要有足够大的规模才足以说明实际系统中的问题(如果您遇到好的示例,请将它们发送给我)。 因此,这本书本身可能不足以让您学习如何应用这些原则。
The best way to use this book is in conjunction with code reviews. When you read other people’s code, think about whether it conforms to the concepts discussed here and how that relates to the complexity of the code. It’s easier to see design problems in someone else’s code than your own. You can use the red flags described here to identify problems and suggest improvements. Reviewing code will also expose you to new design approaches and programming techniques.
使用本书的最佳方式是在代码审查中审视设计。当您阅读其他人的代码时,请考虑它是否符合此处讨论的概念以及这与代码的复杂性有何关系。在别人的代码中看到设计问题比在你自己的代码中更容易。您可以使用此处描述的危险信号来识别问题并提出改进建议,审查代码还将让您接触到新的设计方法和编程技术。
One of the best ways to improve your design skills is to learn to recognize red flags: signs that a piece of code is probably more complicated than it needs to be. Over the course of this book I will point out red flags that suggest problems related to each major design issue; the most important ones are summarized at the back of the book. You can then use these when you are coding: when you see a red flag, stop and look for an alternate design that eliminates the problem. When you first try this approach, you may have to try several design alternatives before you find one that eliminates the red flag. Don’t give up easily: the more alternatives you try before fixing the problem, the more you will learn. Over time, you will find that your code has fewer and fewer red flags, and your designs are cleaner and cleaner. Your experience will also show you other red flags that you can use to identify design problems (I’d be happy to hear about these).
提高设计技能的最佳方法之一是学习识别危险信号——这些信号表明一段代码可能比它需要的更复杂。在本书中,我将指出与每个主要设计问题相关的危险信号,并在最后给出重要的总结。您可以在编码中不断实践:当您看到危险信号时,停下来,寻找可以消除问题的替代设计。当您第一次尝试这种方法时,您可能必须尝试几种不同的替代方案,然后才能找到消除危险信号的最佳方案。不要轻易放弃:在解决问题之前尝试的替代方案越多,你学到的就越多。随着时间的推移,你会发现你的代码的危险信号越来越少,你的设计也越来越干净。您的经验还将向您展示可用于识别设计问题的其他未在本书中描述危险信号(我很高兴听到这些)。
When applying the ideas from this book, it’s important to use moderation and discretion. Every rule has its exceptions, and every principle has its limits. If you take any design idea to its extreme, you will probably end up in a bad place. Beautiful designs reflect a balance between competing ideas and approaches. Several chapters have sections titled “Taking it too far,” which describe how to recognize when you are overdoing a good thing.
在应用本书中的想法时,保持适度和谨慎是很重要的。每条规则都有其例外,每条原则都有其局限性。如果您将任何设计理念发挥到极致,您可能最终会陷入困境。漂亮的设计反映了相互竞争的想法和方法之间的平衡。有几章有标题为“走得太远”的部分,描述了如何识别你是否有时候做得过火。
Almost all of the examples in this book are in Java or C++, and much of the discussion is in terms of designing classes in an object-oriented language. However, the ideas apply in other domains as well. Almost all of the ideas related to methods can also be applied to functions in a language without object-oriented features, such as C. The design ideas also apply to modules other than classes, such as subsystems or network services.
本书中几乎所有示例都是用Java或C++编写的,并且大部分讨论都是关于用面向对象语言设计类的。然而,这些想法也适用于其他领域。几乎所有与方法相关的思想也可以应用于那些没有面向对象特性的语言,例如C。设计思想也适用于类以外的模块,例如子系统或网络服务。
With this background, let’s discuss in more detail what causes complexity, and how to make software systems simpler.
现在,让我们更详细地讨论导致复杂性的原因,以及如何使软件系统更简单。

