[人月神话_6] 另外一面 | 一页流程图 | 没有银弹
另外一面(The other face)
计算机程序是人类向机器传递信息的一种方式,为了确保意图能够被无言的机器准确理解,程序采用了严格的语法和精确的定义。(这就需要 我们有严密的逻辑思维)
- 然而,除了作为机器执行指令的载体之外,程序还承载着另一层意义——它向用户讲述一个“故事”。
- 即便是为个人使用而编写的程序,这种沟通也是必不可少的,因为随着时间的流逝,即使是原作者也可能逐渐忘记程序的具体细节。
(所以 测试和 文档编写,在软件工程中和开发一样重要)
对于面向公众的应用程序而言,由于用户与开发者在时间和空间上的距离,文档的重要性尤为突出。
- 良好的文档不仅帮助用户理解软件的功能,还能延长软件的使用寿命,并克服开发者在面对紧迫的时间表时可能产生的惰性。
- 我们都有过这样的经历:面对那些文档简陋的程序,心中不免会埋怨那些远在他方、未曾谋面的作者。
在为什么巴比伦塔会失败?(Why Did the Tower of Babel Fail?)一章中,提到项目工作手册的重要性。
- 在本章“另外一面”(The Other Face)中作者指出,虽然编写代码是软件开发的核心活动,但创建高质量的文档同样至关重要。
- 文档不仅对于用户理解和使用软件很重要,而且对于维护和未来的开发者来说也是必不可少的资源。
与其继续空谈文档的重要性和特性,不如直接展示如何才能创作出优秀的文档。
- 接下来的部分将侧重于具体的操作指南,而不是理论上的讨论。
- 我们将探讨实际步骤和技巧,帮助开发者们写出既实用又吸引人的文档,从而提高软件产品的整体质量和用户体验。
需要什么样的文档
为了满足不同用户的需求,文档应当提供不同程度的详细信息。
对于偶尔使用的用户、依赖程序执行关键任务的用户以及需要根据具体需求调整程序的高级用户,文档都应具备相应的深度和广度。
以下是创建全面且有用文档的关键要素:
1.目标与功能
- 主要功能:明确说明软件的核心作用。
- 开发背景:阐述开发该程序的原因及其解决的问题。
2. 运行环境
- 硬件配置:列出所需的处理器、内存等最低要求。
- 操作系统:指定支持的操作系统版本。
- 其他软件依赖:任何必需的库或框架。
3. 功能范围
- 输入限制:定义可接受的数据类型及范围。
- 输出展示:说明合法的显示结果及其格式。
4. 实现细节
- 算法描述:精确解释程序逻辑和所用算法。
- 技术实现:提供对核心组件的简要说明。
5. 输入输出规范
- 格式定义:给出所有输入/输出的具体格式标准。
- 示例数据:提供实例以帮助理解格式要求。
6. 操作指南
- 基本操作:介绍如何启动程序及基础使用方法。
- 异常处理:说明遇到错误时的应对措施。
7. 用户选项
- 可用设置:列举程序提供的各种选项。
- 选择指导:为用户提供选择适合其需求选项的建议。
8. 性能指标
- 预期时间:基于特定条件下的运行时间预估。
- 性能优化:提出可能的性能瓶颈及改进方向。
9. 精度与验证
- 精度水平:设定结果的精确度目标。
- 测试方法:详述如何验证计算结果的准确性。
测试用例
- 常规测试:覆盖日常使用场景的案例。
- 边界值测试:检验极端情况下的表现。
- 非法输入测试:确保软件能够妥善处理不合规的数据。
修改指南
- 架构图解:提供系统结构或流程图。
- 算法详解:完整描述算法逻辑。
- 文件管理:解释项目中各文件的作用。
- 数据流分析:概述数据处理流程。
- 设计考量:讨论原设计时考虑的修改点及未来扩展性。
- 缺陷记录:记录已知问题及其解决方案。
通过上述内容,不仅能够给予最终用户清晰的操作指引,还能为开发者维护和升级软件提供有力支持。
编写这类文档时,力求简洁明了的同时保证信息的全面性和准确性至关重要。
流程图
流程图在程序文档中经常被过度推崇,但实际上,并非所有程序都需要它们,且很少有程序需要超过一页纸的详细流程图。
- 流程图的主要作用是展示程序的逻辑结构和判断流向
- 但当流程图过于复杂,不得不分页并使用编号出口和连接符时,其整体清晰度就会大打折扣。
( 所以画流程图时,要做到 清晰凝练)
一页纸流程图的价值
一页纸内的流程图能够优雅地展现程序的基本结构和步骤,同时易于绘制。
- 它对于初学者理解算法思维很有帮助,但对于经验丰富的开发者来说,这种详细的流程图往往显得过时且繁琐。
- 随着高级编程语言的发展,很多原本需要通过流程图来表达的细节已经可以直接通过代码实现
因此,流程图中的许多元素变得多余。
流程图的实际应用
- 教育工具:对于初学者,流程图有助于培养逻辑思考能力。
- 设计辅助:在项目初期,简要的流程图可以帮助团队成员快速理解程序架构。
- 沟通媒介:在跨部门或与非技术人员交流时,流程图可以作为直观的说明工具。
实践中,经验丰富的程序员通常不会在编写代码前绘制详尽的流程图。
- 很多时候,这些图表是在开发完成后为了满足文档要求而补做的。
- 一些组织甚至使用自动化工具从现有代码生成流程图,这实际上反映了对流程图实际用途的一种重新评估。
自文档化(self-documenting)的程序
数据处理的基本原则告诉我们,将信息分散在多个文件中并保持同步是一项极其繁琐且容易出错的任务。
- 更合理的方法是让每个数据项包含所有必要的信息,并通过指定的键值进行区分,然后整合到单一文件中。
- 然而,在编写程序文档时,我们却常常违反这一原则,试图维护机器可读的代码和一系列描述性文本及流程图。
- 这种做法导致了文档质量低下以及更新不及时的问题。
(1) //QLTH JOB ...
(2) QLTSRT7: PROCEDURE (V):
(3) /**********************************************************************
/*A SORT SUBROUTINE FOR 2500 6-BYTE FIELDS, PASSED AS THE VECTOR V. A */
/*SEPARATELY COMPILED, NOT-MAIN PROCEDURE, WHICH MUST USE AUTOMATIC CORE*/
/*ALLOCATION. */
/* */
/*THE SORT ALGORITHM FOLLOWS BROOKS AND IVERSON, AUTOMATIC DATA PROCESSING,
/*PROGRAM 7.23, P. 350. THAT ALGORITHM IS REVISED AS FOLLOWS: */
/*STEPS 2-12 ARE SIMPLIFIED FOR N=2. */
/*STEP 18 IS EXPANDED TO HANDLE EXPLICIT INDEXING OF THE OUTPUT VECTOR. */
/*THE WHOLE FIELD IS USED AS THE SORT KEY. */
/*MINUS INFINITY IS REPRESENTED BY ZEROS. */
/*PLUS INFINITY IS REPRESENTED BY ONES. */
/*THE STATEMENT NUMBERS IN PROG. 7.23 ARE REFLECTED IN THE STATEMENT */
/*LABELS OF THIS PROGRAM. */
/*AN IF-THEN-ELSE CONSTRUCTION REQUIRES REPETITION OF A FEW LINES. */
/* */
/*TO CHANGE THE DIMENSION OF THE VECTOR TO BE SORTED, ALWAYS CHANGE THE */
/*INITIALIZATION OF T. IF THE SIZE EXCEEDS 4096, CHANGE THE SIZE OF T, TOO.*/
/*A MORE GENERAL VERSION WOULD PARAMETERIZE THE DIMENSION OF V. */
/* */
/*THE PASSED INPUT VECTOR IS REPLACED BY THE REORDERED OUTPUT VECTOR. */
/**********************************************************************/PROCEDURE (V);
/* A SORT SUBROUTINE FOR 2500 6-0 T FIELDS */
DECLARE CH, IVDEE1;
DIMENSION T(0:8190), V(*);
T(0:8190) INITIALIZED WITH INFINITY; DO L = 0 TO 4095 T(L) = NEHF;
END; DO L = 4095 TO 8190 T(L) = PINT;
END; K1 = 1;
DO WHILE (T(K1) != INFINITY) J = SCAN_BRANCHES_FROM_NODE(K1); IF (T(J) <= T(J+1)) THEN REPLACE_WITH_SMALLER_BRANCH; ELSE HANDLE_HIGHER_LEVEL_NODE; END IF; K1 = NEXT_INDEX;
END DO; OUTPUT_VECTOR = T(STORAGE_INDEX);
为了解决上述问题,建议采用“自文档化”的方法,即将文档直接整合进源代码中。
- 这种方法不仅促进了文档的即时更新,还确保了用户能够方便地获取所需资料。
- 随着高级语言的发展,将文档与代码合并成为了一种更加自然的选择。
自文档化作为一种文档编制的新方法,其核心思想在于减少不必要的文档负担,同时提高文档的质量和可用性。
- 通过充分利用现代编程语言特性,结合良好的编码习惯,自文档化能够在大多数项目中得到广泛应用。
- 这不仅是技术进步的体现,也是以人为本设计理念的具体实践。
- 最终,它有助于创建更加高效、易维护的软件系统。
没有银弹-软件工程中的根本和次要问题
软件开发的核心挑战在于构建一个由复杂的抽象概念组成的结构
这要求开发者不仅能够准确捕捉和表达这些抽象的逻辑实体,而且还需要在现实的物理和技术限制内将它们转换成计算机可以执行的指令。
软件项目中的“人狼”现象与银弹的追寻
在作者国度的民间传说中,最令人胆寒的妖怪莫过于人狼——这些生物能在瞬间从熟悉无害的模样转变为令人畏惧的怪物。
- 软件开发领域同样存在着类似的“人狼”特性,尤其是在非技术背景的管理者眼中:一个看似简单、明了的项目,却可能悄然转变成一个进度滞后、预算超支且布满缺陷的庞然大物。
- 面对这样的挑战,业界内外都响起了寻找“银弹”的呼声——一种能够像计算机硬件成本降低一样,彻底解决软件成本问题的神奇解决方案。
没有银弹
尽管计算机科学和技术在过去几十年有了巨大的进步,但并没有找到一种能够迅速、大幅度提高软件开发生产力的解决方案——即所谓的“银弹”。
- 作者认为,虽然技术的进步可以解决一些附属性问题,但对于本质性问题,我们还没有找到有效的应对办法。
- 因此,在可见的未来,我们不太可能发现一个能显著缩短开发时间或大幅减少成本的“银弹”。
是否一定那么困难呢?
- 软件开发的挑战,并非源于其自身发展的缓慢,而是被计算机硬件快速进步所衬托出的相对滞后。
- 在过去的三十年间,计算机硬件的性价比提高了六个数量级,这样的飞跃在人类历史上任何其他产业中都是前所未有的。
- 硬件从手工装配向流水线生产的转变,带来了成本降低和性能提升的双重红利,而软件却未能复制这一成功模式。
- 软件开发面临的困难可以分为两类:内在的(即固有的复杂性)和次要的(当前生产过程中遇到的问题)。本节将聚焦于前者——那些根植于软件本质中的障碍。
复杂度
软件系统的复杂度是其最显著的特征之一。
- 它不仅体现在规模上,更在于各个组成部分之间的相互作用。
- 每个软件元素都是独一无二的,几乎不存在完全相同的代码片段。
- 与物理世界中的实体不同,软件系统通过添加新的、不同的元素来扩展,而非简单重复已有组件。
- 这些新增加的元素以非线性的方式交互,导致整个系统的复杂度呈几何级数增长。
数学和物理学可以通过简化模型来研究现象,但当复杂度本身成为问题的核心时,这种方法便不再适用。
一致性
与物理学家寻求自然界的统一法则不同,软件工程师面对的是由人为设计的接口和惯例构成的复杂环境。
- 这些接口随着时间演变,给软件的一致性带来了巨大挑战。
- 为了确保兼容性,开发者必须遵守多种多样的标准和协议,而这进一步增加了系统的复杂性。
任何试图简化这些复杂特性的尝试都会受到限制,因为它们本质上是为了维持与其他系统的协调一致。
可变性
软件的生命历程充满了变化的需求。
- 成功的软件产品往往需要不断地适应用户需求的变化和技术环境的演进。
- 相比于工业制造品,软件更容易修改,这既是它的优势也是负担。
- 用户对新功能的渴望以及底层硬件平台的更新换代,都迫使软件持续进行调整。
即使是最基本的功能也可能因为外部因素的影响而发生改变,使得软件始终处于动态之中。
不可见性
最后,软件的无形性构成了另一个重大挑战。
- 不同于建筑图纸或机械制图,软件缺乏直观的表现形式。
- 尽管我们尝试用图表来描绘软件结构,但这些图形往往是多维度且高度抽象的,难以捕捉到所有关键信息。
- 控制流、数据流、依赖关系等多种要素交织在一起,形成了一个复杂的网络。
这种不可见性不仅阻碍了个人的设计思考,也限制了团队成员之间的有效沟通。
软件开发中的三次重大突破及其局限
回顾软件领域最具影响力的三次进步——高级语言、分时系统和统一编程环境,我们可以看到它们如何显著改善了软件构建的效率与质量。
然而,这些改进主要针对的是非本质性的挑战,而非软件固有的复杂性。
1. 高级语言:抽象层次的飞跃
高级语言无疑是软件开发史上的一次革命。它
- 通过提供更高层次的抽象,极大地提升了程序员的工作效率、代码的可靠性和简洁性。
- 具体来说,高级语言使开发者能够专注于业务逻辑,而无需关心底层硬件细节,如寄存器操作或位级指令。
- 这种抽象不仅减少了程序中不必要的复杂度,还提高了代码的理解性和维护性。
但是,高级语言并非适用于所有场景。对于那些不常用到复杂语言特性的用户,反而可能增加学习曲线和思维负担。此外,尽管高级语言简化了许多任务,但并没有根本改变软件的本质复杂性,比如处理大型系统的内在难度。
2. 分时系统:即时反馈的力量
分时系统的引入显著缩短了程序开发周期中的等待时间,使得开发者可以在更短的时间内完成编译、调试和测试等环节。
- 这不仅提高了工作效率,更重要的是保持了开发者的思路连贯性
- 避免因长时间中断而导致的记忆丢失和重新适应成本。
- 不过,分时系统的提升也有其上限。
- 当响应时间接近人类感知的极限(约100毫秒)时,进一步缩短时间所带来的边际效益将变得微乎其微。
因此,分时系统虽然解决了批处理模式下的低效问题,但它并不能触及软件开发的核心难题。
3. 统一编程环境:工具集成的革命
集成开发环境(IDE),如Unix和Interlisp,通过提供统一的文件格式、管道机制以及丰富的库支持,大大简化了不同工具之间的互操作性。
- 这种环境允许开发者轻松地创建、调用和组合各种工具,从而促进了生产力的极大提升。
- 统一编程环境的成功之处在于它消除了共同使用程序时的技术障碍,激发了更多创新工具的诞生。
然而,这些改进仍然属于优化层面,未能直接解决软件开发中的根本性挑战,如复杂系统的管理和不可见性的克服。
银弹的希望
- 现有的所谓“银弹”技术更多的是针对软件开发过程中的一些非核心障碍提供了不同程度的帮助,而未能从根本上解决软件固有的复杂性问题。
- 未来的软件开发仍需探索新的方法和技术,以应对那些深层次的挑战。