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

.. 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 
----------------------------------------------------------------------------

(应舸 翻)

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

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

.. _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));

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

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


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

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

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


Page 13 
-------------------------------------------------------------------------------

(袁世家 翻)

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

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

如果无法用易读的高级语言编程,例如,如果没有可用的编译器,就把伪代码作为文档的一部分。 写伪代码的不是最终程序员, 并让他与最终程序员负责确保两种代码一致。 这么做很有用 [7]。

E. 设计并且记录使用层级 (uses hierarchy)
````````````````````````````````````````````````````````````````````````````````         
一旦知道所有的模块与它们的被访程序, 就可以设计使用层级。 为记录方便, **用一个真值矩阵表示使用层级** ,当且仅当程序A的正确性取决于程序B的正确性, 矩阵的(A,B)位置为真。 使用层级定义了一组子集组成的集合, 这个集合可以由删除整个程序并且不需要重写任何程序获得。 使用层级对于分阶段交付, 软失败(fail-soft)系统,和开发程序家族很有用。

(蓝珲的理解: 文中提到的程序, 容易引起混淆, 其实就是模块。 真值矩阵其实描述了模块之间的依赖关系。 例如, 有1,2,3,4四个模块, 3依赖1, 1依赖2, 2依赖4。 {4}, {2,4}, {1, 2, 4}, {3,1,2,4} 这些子集都可以组成独立程序。)


Page 14 
------------------------------------------------------------------------------

(陈肖飞 翻)

F. 编写程序
````````````````````````````````````````````````````````````````````````````````

在设计和文档编制完成后,我们就可以编写代码了。我们发现这件事进展的迅速
而顺利。我们认为,代码注释不应该包含文档中已经有的内容。否则,系统维护
会更加昂贵,同时增加了代码与文档不一致的可能性。(因为会出现注释改了,
而忘了更新对应位置的文档。或者文档改了,而忘了更新对应位置的注释。)


VI.文档在这一过程中的作用是什么?
```````````````````````````````````````````````````````````````````````````````

A. 当前的文档有什么问题? 为什么它不便使用? 为什么它晦涩难懂?

很明显,文档在我们描述的设计过程中扮演着重要角色。大多数程序员认为文档
是一种必要的累赘,是在事后才做的,只是因为有些官僚需要文档。但我们认为,
在发布之前都没有使用过的文档一定是糟糕的文档。

多数文档不完整不准确,但这并不是主要的问题。如果是的话,只要简单地添加
或者纠正信息就可以纠正它们。事实上,有一些根本的文档组织结构问题才是导
致不完整和不准确的原因,而且这难以修复:

- 糟糕的组织。今天的大多数文档可以被描述为“意识流”和“执行流”。意识流写
  作将信息放在作者写作的时候突然想到的那个点上。执行流描述了系统在运行
  时发生的事情的顺序。



Page 15 
----------------------------------------------------------------------------

(陈俊蕾 翻)

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

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

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

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

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


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


Page 15 
----------------------------------------------------------------------------

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

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

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

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

  
Page 16 
------------------------------------------------------------------------------

(王海榕 翻)

如何避免这些问题?

理想设计过程中, 文档既满足了开发人员需求, 也满足了维护人员的需求。上
面提到的每个文件都记录了设计决策,并在剩余的设计中作为参考文件使用。
这些文件也提供了软件维护人员所需要的信息。由于这些文件在整个软件开发过
程中被用作参考手册,因此它们将是成熟的,并且已经准备好在以后的工作中被
用到。 这些文件总是及时更新的。 在我们设计过程中, 文档不是事后才想到
的产品; 而是被看作是项目的主要产品之一。 一些检查可用来增加其完整性和
一致性。

这种产生文档的方法,其主要优点是可以减轻 **神话的人月效应 (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)**) ”。 给我们的工作付钱的人把 **GOWI** 作为对许多此类问题的标准回答。 我们并不认为真正的工作会有所不同。 然而, 每当我们把“因为它们起作用”作为决策理由时, 我们就诚实地把这个理由写下来, 以免误导将来的软件维护者去花心思想在我们的决策后面有什么深刻的理由。


Page 20 
----------------------------------------------------------------------------------

(蓝珲 翻)

**致谢**

NRL的Stuart Faulk与John Shore为这篇文章提供了经过深思熟虑后的评论。

美国海军与加拿大NSERC对本研究提供了资助。