理性设计过程
============================================================

.. We can produce an html file from this document using the following command:
   rst2html.py a.rst a.html

`Parnas and Clements. 1986. A rational design process: How and why to fake it. IEEE Trans. Softw. Eng. 12, 2 (February 1986), 251-257.  <https://link.springer.com/content/pdf/10.1007%2F3-540-15199-0_6.pdf>`_

.. |date| date::
.. |time| date:: %H:%M

文件更新时间 |date|.

.. contents:: 内容目录
   
   
通篇总结
-------------------------------------------------------------------------

读完全文之后,我感觉作者能把前面十几页的内容写出那么多字,真是十分了不
起,可能这就是科研工作者的特长吧,把一个东西反复讲述,使得论文的篇幅变
得很长,让人看起来好像内容很多。

.. 作者之所以能写那么多,是因为8年前有 A7E Aircraft 需求分析的经验。见
   第9篇参考文献:software requirements for the A-7E Aircraft

以下是我没有做第二遍阅读,单凭印象做出的理解。(而且我也相信第二遍和第
一遍不会有很多改变。)

作者核心观点是,一个合理的设计过程需要写一大堆东西,比如“设计文档/需求
分析/母子模型/接口说明”,在设计之初就开始写,在设计过程中每一次改变都
要清晰的更新。然而这是很难的,甚至一般都是不可能也做不到的,作者于是说,
那就假装(尽力)做一个。

有一个合理的设计过程有什么用呢? 在开发过程中,有助于估算一个过程的时
间与金钱花费,减少重复决策,减少大改次数,方便维护,防止因人员变动而对
接困难,等等。这些的前提都是有一个合理的设计过程,如果没有,或实施得不
那么周全,就不会有那么理想的辅助效果。

那么怎么假装呢? 这篇文章有很多方法论式的表述,但缺少举例 —— 可能也很
难举例,毕竟是理想状况 —— 总之就是多花时间写文档。具体而言,在最开始的
时候就照着自己理想目标去写,不要去考虑实际实现的过程,以免后期忘掉了最
开始的想法。开发的过程中,要坚守 **Write Everything Down** 的原则,无
论有什么决策改变或者修改,都要如实的写下原因。

在我看来能做到自然是一件很理想的事情,或许在一个长期的软件开发过程中,
这是重要的。根据我的个人经验,比较多的开发者会写 change log/update log
之类的东西,这也算文档的一种,但是可能更面向用户一些。 像这篇论文中的
文档可能对于短期的私人项目开发太繁琐了,也是有局限性的。

作者在最后几页分析一些他不认同的行为。比如软件做完了再写各种文档。文档
要么是意识流要么是面向实现。或者干脆文档很少,都靠代码注释,这样的文档
很难看懂,过多的注释也会导致后续的修改很麻烦(改了一个注释,还有多少注
释要受牵连呢?)



(Page 1)

理性设计过程: 如何、为何要佯装它
------------------------------------------------------------------

... 理性设计过程: 如何、为何要佯装它

**大卫L.帕纳斯(David L.Parnas)**

(加拿大维多利亚大学计算机科学系,维多利亚BC V8W2Y2)

(美国海军研究实验室计算机科学和系统分支,华盛顿特区,20375)

**保罗C·克莱门茨(Paul C. Clements)**

(美国海军研究实验室计算机科学与系统分支,华盛顿特区,20375)


摘要


软件工程师一直在寻找理想的软件开发过程: 在此过程中,程序从软件规格说
明中派生,就像引理与定理从已发表的公理中派生那样。在解释了为何我们
永远无法做到这一点后,本文描述了这样的一个过程。这个过程由应该在软件开
发中产生的一系列文档来描述。 这些文档能服务几个目的:作为初步设计评审
的基础,作为编程的参考资料,指导运维程序员的工作。我们探讨用与指导软件
设计相同的原则来编写文档。如此产生的文档远比常见的“马后炮” 文档有价值。
如果我们注意保持所有文档都是最新的,那么就创造了一个看起来完全理性的设
计过程。



(Page 2)


I. 寻找魔法石: 我们为什么想要理性设计过程?
`````````````````````````````````````````````````````````````

理性的人做事总有好的理由。 他走的每一步都可以被证明是实现明确目标的最佳选择。我们大多
数人都喜欢自认为是理性的专业人士。 然而,在观察家看来,通常软件设计过程显得相当不
理性。程序员似乎经常做决定时没有理由。他们在没有清晰声明要构建什么
时就开始了。他们做一长串的设计决定,却没有清晰声明为什么那么做。他们不定
义目标,也很少解释理由。


我们很多人不满意这样的设计过程。 这就是为什么会有对软件设计、编程方法、结
构化编程以及相关主题的研究。 理想情况下,我们希望像从公开出版的公理中
推出定理那样,从需求中推出程序。 所有能被归类为“自顶向下” 的方法, 都
是我们渴望拥有理性的、系统的软件设计方式的结果。



(Page 3)

本文同时带来了好消息和坏消息。坏消息是,在我们看来,我们将永远找不到魔法石。
我们将永远找不到能让我们以完美地理性方式去设计软件的过程。好消息是我们
可以伪造它。  我们能向别人呈现我们的系统, 就像理性设计师那样。更好的消息是,这样做是值得的。

II. 为什么软件设计“过程”总只是理想化?
`````````````````````````````````````````````````````````````

我们永远不会看到软件项目像上面的建议进行。 一些原因罗列如下:


1. 大多数情况下,委托构建软件系统的人并不确切知道自己想要什么,也无
   法告知我们他们确实知道的东西。

2. 即使我们知道了需求,为了设计软件,我们还需要知道很多其它事实。 许多
   细节只有在我们实施过程中才知道。 我们学到的东西会使我们
   的设计失效,我们必须回溯。

3. 即使我们在开始之前就知道了所有相关事实,经验表明,人类无法完全理解
   过多必须要考虑的细节,以设计构建正确的系统。设计软件的过程是我们试
   图分离关注点的过程,以便我们工作时信息量易管理。然而,在我们到达那
   点前,我们必然会犯错误。

4. 即便我们能够掌握所需的所有细节,除了最不重要的项目,所有项目都会由
   于外部原因发生变化。 其中一些变化会使以前的设计决策失效。



(Page 4)


5. 除非不用人,否则无法避免人为错误(Human Errors)。无论我们的决策过程多么理性,无论我们对相关事实
   的收集与组织有多好,我们都会犯错。

6. 我们经常被先入为主的设计想法所拖累,这些想法是我们发明的,在相关项目中获得的,或者在课上听到的。
   有时候我们会为尝试或使用自己喜欢的想法而去从事某个项目。 这些想法也
   许不是经由理性过程从我们的需求中导出的;它们可能从其他来源自发地产生。

7. 由于经济因素,我们常被鼓励去使用为其他项目开发的软件。在其他情况下,
   我们也许会被鼓励与另一个正在进行的项目共享我们的软件。 如此产生的软
   件可能不是任一个项目的理想软件,即,不是单纯地根据需求开发的软件,但是这
   样的软件也足够好且节省精力。

由于所有这些原因,软件设计者以理性、无错误的方式从需求陈述中推导出他的设计是相当不现实的。
我们相信从未有过任何系统以这种方式开发,而且将来也不会有。
即使是教科书和论文中各种小程序开发也不是真实的。这些小程序已经过修改和
打磨的,直到作者向我们展示了他希望看到的样子,而非实际发生的情况。


III . 尽管如此, 为什么描述理性的理想化过程有用呢?
````````````````````````````````````````````````````````````````````````````````

我们上文讲的东西甚为明显,为每个细心的思考者所知,为坦诚的人们所承认。尽管如此,我们还是看到以软件设计过程为主题的会议,关于软件设计方法论的工作组以及一个获利丰厚的、声称用描述逻辑的方法来设计软件的课程市场。这些人想要达到什么目的呢?


(Page 5)


..
   (与吴贞娴翻译的最后一段同: 上文所提及的内容,很明显为每个深思熟虑过
   的人所知、为坦诚的人所承认。尽管如此,我们仍旧能看到以软件设计过程为主
   题的会议、研究软件设计方法论的工作小组,以及为了丰厚市场利益扬言能描述
   软件设计逻辑方法的课程。这些人想达到什么样的目的?)


如果我们已确定了一个理想过程但不能精确地遵循它,我们仍然能够写文档,产生的文档就像我们已经遵循了该理想过程。文档的读者会从 **遵循对设计的理性解释中** 受益。这就是我们所说的“佯装一个理性设计过程”。

下面,我们列举做此佯装的一些理由:

1. 设计师们需要指导。当承担一个大项目时,我们容易被任务的艰巨性压倒。不能确定先做什么。对理想过程的充分理解将帮助我们知道如何前进。

2. 比起依权宜之计前进,如果我们试着去遵循某一过程,那么我们会更接近理想过程,更接近理性设计。例如,即使我们无法知道设计一个理想系统所需的所有事实,在开始编码前去寻找这些事实的努力会帮助我们更好设计,更少返工。

3. 当一个组织承担很多软件项目时,有一个标准流程会有很多优势。它会使好的设计审查,以及人员、想法和软件从一个项目到另外一个项目之间的转移变得更容易。如果我们要去指定一个标准过程,它应该是理性的。

4. 如果我们已经对一个理想过程取得一致意见,那么衡量项目进展就会变得容易很多。我们能够比较项目的成果与理想过程所要求的成果。我们能识别落后(或领先)的地方。



(Page 6)


5. 外部人员对项目进展定期审查对良好的管理是必不可少的。 如果项目正试图遵循理想过程,审查将会更容易。

IV. 开发过程描述应该告诉我们什么?
``````````````````````````````````````````````````````````````````````````

我们相信过程描述的最有用形式是在工作产品方面。对于过程的每一个阶段,我们描述:

  - 下一步应该做什么;

  - 工作产品必须满足什么标准;

  - 应该由什么样的人来做这项工作;

  - 他们工作中应使用哪些信息;

无法用工作产品描述的任何过程管理只能由会读心的人来做。 只有知道需要哪
些工作产品,以及它们必须满足的标准,我们才能审查项目并衡量进度。


V. 理性设计过程是什么?
``````````````````````````````````````````````````````````````````````````

本节我们描述我们所遵循的理性的、理想化的软件设计过程。 每个步骤
都伴随着与该步骤相关的工作产品的详细描述。

以下过程的描述既不包括测试也不包括审查。 这并不意味着我们忽视了其中的
任何一个。 在本文我们正描述的是一个理想的过程;测试和审查属于实际的过程,
而不是理想的过程。 在应用本文所描述的过程时,我们包括了对每一个工作产品广泛而系统的的审查
以及对生成的可执行代码的测试。


(Page 7)


(在实施本文描述的过程时,我们对每一个工作产品进行了广泛而系统的审查,
并对生成的可执行代码进行了测试。)


A.  建立并记录需求
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



如果我们要成为理性设计师, 我们必须一开始就知道必须做什么才能成功。  我们将它记录在被称为 **需求文档** 的工作产品中。在设计开始之前完成本文档,使我们能够根据摆在我们面前的所有需求进行设计。


1. 为什么我们需要需求文档?

   
- 降低设计程序时意外地去做需求决定的可能性。
  
- 避免重复与不一致。 没有这个文档,许多文档回答的问题会在整个开发过程中被设计者、程序员、评审者反复问及。这个代价是昂贵的,并且常常会导致不一致的答案。

- 程序员通常不熟悉应用领域。有一个关于外部可见行为的完整参考,他们就不必决定什么是对用户最好的。

- 是较好估计构建系统所需工作量与资金的必要文档(但非充分文档)。
  
- 是对冲人员流动花销的有价值的保险。当有人离开项目时,我们所获得的关于需求的知识不会丢失。
  
- 为制定测试计划提供良好的基础。没有它,我们不知道测试什么。
  
- 在系统长时间投入使用后,需求文档可以用于为将来的变更定义约束条件。


(Page 8)


- 它可以用于平息争论; 我们不再需要成为或咨询应用专家。

确定详细的需求可能是这个过程中最困难的部分,因为通常没有有条理的信息来源。理想情况下,需求文档应由未来用户代表来写。实际上,它可能将由软件设计者来写,然后由用户代表们同意。

2. 什么内容到需求文档中去?

在理想化的设计过程中,需求文档的内容定义是简单的: 它应该包含为编写正确软件你需要知道的所有事情,仅此而已。当然,如果现有信息准确且有条理,我们可以引用现有的信息。理想的需求文档的通用规则有:

- 每个陈述应对所有可接受的产品有效,不应依赖实现决定。

- 文档应该是完整的,从某种意义上说,如果产品满足文档中的每个陈述,则产品应该是可以接受的。

- 如开发必须开始前信息尚不具备,则指出不完整的地方,而非简单地省略。

- 需求文档应该是按照参考文件的结构来组织,而不是对系统的介绍性叙述,因为参考文件是最有用的形式。虽然制作这样一份文档要付出相当多的努力,并且它比介绍性文件更难读,但是从长远来看,它节省了人力,因为在这个阶段获得的信息是以便于在整个项目中参考的形式记录下来的。



(Page 9)


通过使用关注点分离( **Separation of Concerns** )得到以下部分,我们获得需求文档的完整性:

- 软件运行机器的规范。机器不必是硬件 -- 对于一些系统,这一节可能仅仅指向一个语言参考手册;

- 软件与外界通信必须使用的接口的规范;

- 对于每个输出,所有时刻软件可检测系统状态的输出值说明;

- 对于每个输出,软件要多频繁或多快速来重新计算它;

- 对于每个输出,它要有多精确。

- 如果要求系统易于更改,则需求必须包括可能发生更改的地方的定义。你无法设计里面每样东西都同样容易改变的系统,并且,程序员不应该去决定哪些东西最有可能改变。

- 需求还必须讨论由于意外事件,系统无法满足全部需求时应该做什么。大多数需求文档忽略这些情况;它们讨论当一切都完美工作时将发生什么,却把发生部分故障时应该做什么留给程序员去决定。

我们希望明确,除非定义了每个需求,否则无法编写出正确的软件,并且,一旦你成功地指定了每个需求,你就已经完全指定了系统的需求。




(Page 10)


为确保文档的一致性与完整性, 文档组织结构背后必须有一个简单的数学模型。  我们的模型是由我们在实时系统上的工作所启发的。 但因为所有系统都是实时系统, 它是完全通用的。

我们假设,对于实时控制系统,理想的产品不是纯数字计算机,而是混合计算机,它由控制模拟计算机的数字计算机组成。 模拟计算机将输入的连续值转换为连续输出。 当离散事件发生时,数字计算机使由模拟计算机计算的函数发生离散变化。 实际的系统是该混合系统的数字近似。  与其他工程领域一样,写软件规范时, 我们首先描述“理想”系统, 然后指定允许的误差。 在需求文档中,比起输入,我们更看重输出。 如果输出值是正确的,即使我们不读取输入,也没人会介意。 因此,在过程第一阶段,关键是 **识别所有输出** 。 我们的需求文档, 其核心是由表格形式表示的数学函数集合。 每个函数将单个输出的值指定为与应用程序相关的外部状态变量的函数。 文献 [9] 中给出了以这种方式生成的完整文档例子, [8]中对该例子有讨论。

.. _B:

B. 设计并记录模块结构
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


除非产品很小,小到能由单个程序员来做,否则我们必须考虑如何将工作分解为几个工作任务,即模块。 此阶段应生成的文档为 **模块指南 (Module Guide)** 。 模块指南定义各模块的职责, 把我们的设计决策 (design decisions) 封装在各模块。 一个模块可以由多个子模块组成,也可以被视为单个工作任务。


(Page 11)


我们需要模块指南去避免重复,避免分歧,实现 **关注点分离 (Separation of Concerns)**,并且最重要的是,当对软件不熟悉的维护人员有一个问题报告时,去帮助他找出必须处理的模块。再一次,我们看到记录设计决定的文档与在维护阶段使用的文档是同一个文档。

如果我们勤勉地将信息隐藏 (information hiding) 或关注点分离 (separation of concerns) 应用于大型系统,那么最终肯定会有大量模块。只是简单列出模块名而没有其它结构的指南,只会对熟悉系统的人有帮助。 我们的模块指南有一个树形结构,它将系统划分成少量几个模块,并以相同的方式细分每个模块,直到所有模块都非常小为止。 这种文档的完整例子, 见[3]。 对这一办法以及其好处的讨论,见[15,6]。

C. 设计并记录模块接口
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

高效快速的软件生产需要程序员能够独立工作。模块指南定义了模块职责,但没有给出足够的信息去允许独立实现模块。每个模块必须规定精确的接口。为每个模块写一个 **模块接口规范 (Module Interface Specification)** ;规范必须正式,并提供每个模块的黑箱图片。规范由资深设计师撰写,并由潜在的接口实现者与会用到这些接口的程序员一起评审。 一个模块的接口规范只需包含足够信息使其他模块的程序员能够用该模块的功能即可,而不需要其他信息。 这也是模块实现者需要的信息。我们产生的文档被两者(其他模块的程序员、本模块实现者)使用。


(Page 12)


虽然每个这样的文档都有一个人负责, 但是这些文档实际上是在模块实现者、模块使用者以及其他对设计感兴趣的人(例如评审员)协商过程中产生的。这些规范文件的主要内容有:

-  可被其他模块的程序调用的程序列表,(叫做访问程序 (access program));

-  访问程序的参数;
   
-  访问程序之间的相互影响;
   
-  时间约束与精度约束 (必要时);
   
-  意外事件(禁止发生的事件)的定义。
   
许多方面,该模块规约类似需求文件。 但是,模块规约所使用的符号与结构更适合在过程的这个阶段我们所关注的软件对软件的接口。

已发表的例子和解释包括 [11],[2],[1],[5]。


D. 设计并记录模块内部结构
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

一旦指定了模块接口, 除了评审外,它的实现可以作为独立任务去执行。 但是,开始编码之前, 我们想在模块设计文档中记录主要的设计决策。 该文档允许编码前对设计的高效评审, 并向未来的维护程序员解释代码背后的意图。

在某些情况下, 模块被简单地分成子模块并且子模块的设计文档是另一个模块指南,在这种情况下,那个模块的设计过程在上文B步骤处继续。


(Page 13)


在其它情况下,我们从描述内部数据结构开始;在有些情况下,这些数据结构由子模块实现(并隐藏)。 对每个访问程序,我们包括一个函数 [10] 或者 LD-关系 [14], 用于描述它对数据结构的影响。对于模块返回给调用者的每个值,我们提供另外一个数学函数,这个数学函数叫做抽象函数,它将数据结构的值映射进返回值。对于每个意外事件,我们描述如何检查它。最后,我们提供一个"验证",论证拥有这些属性的程序将会满足模块规约。

我们继续分解与设计子模块,直到每个工作任务足够小,小到即便负责这个模块的程序员离开了项目,我们也可以承担放弃它并重新开始的代价。

如果我们无法用易读的高级语言编程,例如,如果没有编译器可用,我们就把伪代码作为文档的一部分。我们发现让最终编码者以外的人来写伪代码,并让两个程序员负责保持程序的这两个版本一致是有用的 [7]。

E. 设计并且记录使用层次
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


一旦我们知道所有的模块与它们的访问程序,就能设计使用层次(uses hierarchy) [13] 了。使用层次可以用一个二进制矩阵方便表示,当且仅当程序A的正确性依赖于系统中存在正确的程序B时,位置(A,B)上的条目为真。**使用层次** 定义了子集组成的集合, 这个集合可以通过删除整个程序而无需重写任何程序得到。使用层次对于分阶段交付,故障弱化(fail-soft)系统,与程序族开发是重要的 [12]。

(蓝珲注:本节提到的程序,容易引起混淆,其实就是指模块。二进制矩阵其实描述了模块之间的依赖关系。例如,有1,2,3,4四个模块,3依赖1, 1依赖2, 2依赖4,则{4}, {2,4}, {3,1}. {1,2,4}, {1,3,4},{1,2,3,4} 这些子集都可以各自组成独立程序,因为每个子集都包含了全部依赖,不必依赖与子集外面的东西。)


(Page 14)


F. 编写程序
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


完成所有这些设计和文档之后,我们就已经准备好编写实际的可执行代码了。我们发现编写过程快速而流畅。我们认为,代码注释不应该包含文档中已经写有的冗余内容。这是不必要的,这会使维护系统更加昂贵,同时增加了代码与文档不一致的可能性。
(因为会出现代码注释改了,而忘了更新对应位置的文档。或者文档改了,而忘了更新对应位置的代码注释。-- 译者注)


VI.文档在此过程中的角色是什么?
```````````````````````````````````````````````````````````````````````````````

A. 当前的文档有什么问题? 为什么它难用? 为什么没有人读它?

应该清楚的是,文档在我们描述的设计过程中扮演着重要角色。大多数程序员视文档为事后的,只是因为有些官僚要求它才做的麻烦事。我们认为,在发布之前未被使用过的文档总会是低质量的文档。

大多数这种文档不完整且不准确,但这些并不是主要问题。如果是,只需添加或更正信息就可以改正它们。事实上,是潜在的结构问题导致了不完整与错误,且难以修复:

- 糟糕的组织。今天大多数文档可以被描述为“意识流”和“执行流”。意识流写作是想到哪里就写到哪里。执行流写作是按照系统运行时事情发生的顺序来描述系统。



..
   Page 15 
   ----------------------------------------------------------------------------

   (陈俊蕾 翻)

   文档中多余的注解是没必要的,且会使系统维护变得更加昂贵。 除此之外,还
   会增加代码与原文档不符的可能性。

   VI. 文件在此过程中充当什么样的角色?
   ``````````````````````````````````````````````````````````````````````````````

   现在的文档到底哪儿有问题? 为何难用? 为何没人读?

   显然,设计过程中文档扮演着一个重要的角色。是祸躲不过,许多程序员认为文
   档是“事后考虑”之物,只是官僚的要求而作。我们认为那些在发表前从未被使用
   的文档都是很差的文档。

   大多数文档都不完整、不精确,但这不是主要问题。可以通过添加或修改信息进
   行改正。 事实上是引起文档不完整与不正确的内在结构组织问题不容易修复。


   - 很差的文档组织结构。 现今的文档,可分为 **意识流** 与 **执行流** 。
     意识流写作把即兴想法写在作者正在写的地方。执行流写作按照系统运行时事
     件发生的顺序去写。 这两种文档风格的问题是:除作者本人,其他人不容易
     找到他们想要的信息。 因此难以判定某信息是否丢失,难以修改错误信息。
     软件发生变化时,找不到所有文档对应的位置去修改。


(Page 15)


这两种文档风格的问题都在于,除了作者之外,其他人找不到他们想要的信息。
因此,确定事实缺失或纠正事实错误并非易事。当软件更改时,要找到文档中应该更改的所有部分并非易事。
这些文档维护起来会非常昂贵,而且在大多数情况下将得不到维护。

— 枯燥的行文。
我们发现很多要说的话可以用一条编程语言语句、一个公式或一个图表来说。
我们发现某些事实在许多不同部分被重述。
这增加了文档及维护它的成本,并导致无法集中注意力阅读以及未被发现的错误。

— 令人困惑和不一致的术语。任何复杂的系统都需要发明和定义新的术语。
如果没有它,文档将会太长。
然而,软件文档的编写者经常不能为他们使用的术语提供准确的定义。
因此,这些术语不能被一致使用。
仔细阅读可以发现,许多术语被用于同一概念,许多相似但有区别的概念被同一术语描述。

—不完整性。在项目接近完成时编写的文档是由长期使用该系统的人编写的,以至于他们认为重要决策是理所当然的。
他们记录他们认为会忘记的小细节。
不幸的是,结果是一份对熟悉系统的人有用的,但对新手却是无法理解的文档。
在大型软件项目中,总会有新手。

  
(Page 16)


B. 如何避免这些问题?

在理想设计过程中的文档满足开发人员需求,也满足后来的维护程序员的需求。上述每个文件都记录了设计决策,并在其余的设计中被用作参考文件。然而,它们也提供了维护者需要的信息。因为这些文件在整个软件构建过程中被用作参考手册,它们将是成熟的,并且在以后的工作中随时可用。 它们将总是最新的。 我们设计过程中的文档不是事后的想法;它被视为项目的主要产品之一。 可以做一些检查来增加其完整性和一致性。

这种产生文档的方法的主要优点之一是可以减轻 **人月神话效应 (The Mythical Man Month effect)** [4]。当新程序员加入项目时, 他们不需要依赖老员工来获得信息。他们将有一套最新的且理性的文件集可用。

我们通过花大力气去设计每个文件的结构来避免“意识流 (stream of consciousness)”与“执行流 (stream of execution)”。我们通过规定文件必须回答的问题来定义文件;我们将这一纪律贯彻至文件的所有小节。我们试图为每一个必须要包括的事实找一个位置,并且确保这样的位置只有一个。 只有在确定了文件结构之后,我们才开始写。如果我们要写一个特定类型的许多文件,我们就为这些文件编写并且发布一个标准结构 [5]。 我们所有的文件都是按照与指导软件设计相同的原则来设计的,即关注点分离。系统的每个方面都只在一节中描述,且该节不描述其他内容。当我们的文件被审查时,我们会审查它们对文档规则的遵守情况以及准确性。





(Page 17)


由此产生的文档虽然阅读起来并轻松容易,但也不无聊。
我们用表格、公式和形式符号来增加信息密度。
我们的结构规则避免了信息重复。
结果是这样的文档,它必须被专心地阅读, 但是会给读者详细准确的信息。

为了避免传统文档遍布着令人困惑的与前后矛盾的术语的情况, 我们采用一种特殊括号与类型字典系统。
每个必须定义的术语都被嵌在一对反映术语类型的括号符号中。
对每个这样的类型,我们都有一个仅包含对该类型定义的字典。
虽然一开始读者会觉得!+terms+!、 %terms%、 #terms#等的存在烦人, 但是我们文档的老用户会发现这些括号暗含的类型信息使得文档更容易阅读。
类型字典的使用减少了我们为同一个概念定义两个术语, 或者为同一个术语给出两个含义的可能。
特殊的括号符号便于对已引入但未定义或已定义但从未使用的术语进行机械检查。

.. 上面描述了我们希望遵循的理想流程以及在此流程中生成的文档。我们通过生成
   文档来伪造这个过程,如果我们以理想的方式做事,就会生成这些文档。我们试
   图按我们所描述的顺序制作这些文件。如果我们不能得到一条信息,我们就会注
   意到在文档的一部分中,信息应该去哪里并继续设计,就好像该信息会按照预期
   发生变化一样。如果发现错误,我们将更改它们,并在随后的文档中进行相应的
   更正。我们将文档作为设计的媒介,在所有级别的设计决策都被批准纳入文档之
   前,不会考虑任何设计决策。无论我们在途中遇到多少困难,最终的文档都将更
   容易理解和适应。我们没有展示事情实际发生的方式,我们展示的是我们希望事
   情发生的方式和事情的方式。 (见下文方子安处)


(Page 18)


VII. 现在, 我们如何佯装理想的过程?
``````````````````````````````````````````````````````````````````````````````     

上文描述了我们希望遵循的理想过程,以及在这过程中会产生的文档。 我们通过产生(假如我们用理想的方式做事会产生的)文档来佯装这个过程。 我们试图用之前描述过的顺序去产生文档。 如果我们无法得到某部分信息,我们在文档相应位置注明,然后着手设计(犹如期望这部分信息将会改变)。
如果我们发现错误,就会更改错误,并在后续文件中做相应的更改。 我们将文档作为设计的媒介,并且,在将设计决定加入文档获得所有层面的批准之前,我们不认为已经做出了任何设计决定。不管中途我们如何磕磕绊绊,最终的文档都会更容易理解并且准确。
我们不展示事情的实际发生过程,我们展示我们希望事情发生的过程,以及事情本身。

即使是数学,许多人认为最理性的学科,也遵循上述过程。数学家们勤勉地改进他们的证明,通常提出一个与他们最初发现的证明很不一样的证明。第一个证明通常是痛苦的发现过程的结果。当数学家们致力于证明时,理解加深,证明简化。最终,某一位数学家找到了一个更简单的证明,使定理的真理性更明显。更简单的证明出版了,因为读者对定理的真理性感兴趣,而非发现的过程。


我们相信类似的推理同样适用于软件。阅读软件文档的人们想要理解这个程序,而不是再现程序的发现过程。我们提供他们需要的合理化的文档。

我们的文档在一个重要方面与理想化的文档不同。 我们制定一项政策,记录我们考虑过与拒绝过的所有备选方案,包括在文档的早期版本中记录的决定。  对于每个备选方案,我们解释为什么它被考虑和为什么最后被拒绝。 几个月,几周,甚至几个小时后,当我们奇怪我们当初为什么那样做时,我们可以回去并找到原因。二十年后,维护者将有很多同样的问题,并将从我们的文档中找到他的答案。



(Page 19)

我们几年前为演示理想过程所写的软件需求文档,可作为这个过程取得成功的一个例证 [9]。通常,人们会假设需求文件是在编码前产生的,之后就不会再被使用了。 然而,事实证明并非如此。满足该需求文档的软件的原始版本,现在仍在修订。软件每次更改后,负责测试的机构大量使用这个文档来选择他们要做的测试。当需要新的改变时,需求文档被用于描述什么必须改变、什么不能改变。软件投入使用后,这个过程中产生的第一份文档一直被用了很多年。明确的信息是,如果精心制作文档,它将在很长时间都有用。反过来说,如果文档将被广泛使用,那么就值得把它制作好。

成为一个理性的设计师很难,而且我们可能永远做不到。在我们尝试遵循这个过程当中,我们常发现在一些地方继承了一个没有明确理由的设计决策。  一个例子是我们要用的等式中的常数。当我们询问常量的推导时, 我们发现,要么推导不存在,要么推导无效。当进一步追问时,我们被告知做这个决策是“因为它有效 ( **because it works** )”。在这种情况下,设计师可以立一个研究项目去找出为什么它起作用, 或简单地“不要纠结继续吧 ( **Get On With It**) ”。为我们的工作付费的人已经把 **GOWI** 作为对许多此类问题的标准回答,并且我们不认为真正的工作会有所不同。然而, 每当我们把“因为它有效”作为决策理由时,我们将坦诚记录这个理由,而非误导以后的软件维护者去认为我们那么做有什么深刻的、哲学的原因。


(Page 20)


**致谢**

美国海军研究实验室(NRL)的斯图尔特·福克(Stuart Faulk)与约翰·肖尔(John Shore)为这篇文章提供了深思熟虑的审稿意见。

该研究由美国海军(U.S. Navy)和加拿大国家科学与工程研究委员会(NSERC)资助。