重读《人月神话》(14)-整体部分(The Whole and the Parts)
系统整体与其组成部分之间的关系,特别是如何有效地管理和集成这些部分,以构建一个成功的软件系统。系统不是简单部分的总和,一个复杂的软件系统不仅仅是其各个部分的简单叠加,系统的行为和性能受到各个部分之间交互的影响,这些交互往往是非线性的,难以预测的。在设计和开发过程中,应该从整体的角度出发,考虑系统的全局优化,而不是仅仅关注单个部分的最优解。
剔除 bug 的设计
详尽的体系结构设计是预防bug的关键。这包括细致的功能定义、详细的规格说明和规范化的功能描述。Bell实验室的安全监控系统项目负责人V.A.Vyssotsky指出,许多失败案例的根本原因在于产品定义不够精确。因此,确保产品定义的准确性和完整性,是减少bug数量的第一步。
测试规格说明
测试规格说明的实践也至关重要。在编写任何代码之前,规格说明应当由专门的测试小组进行审查,以确保其完整性和明确性。这是因为开发人员可能会倾向于自己解决疑惑,而非主动寻求澄清,这可能导致误解和错误的发生。
自顶向下的设计
自顶向下的设计方法为减少bug提供了一种有效的途径。这种方法首先从高层次的任务定义和解决方案入手,然后逐步细化到更具体的层面。每个细化步骤都可以独立进行,这不仅有助于识别和纠正早期的设计缺陷,还能确保每个阶段的测试都能集中在适当的问题上。通过这种方式,自顶向下的设计帮助保持了设计的清晰度和模块的独立性,从而降低了系统级别的bug风险。
结构化编程
结构化编程技术也是防范bug的重要手段。Dijkstra等人提出的结构化编程理念主张使用循环和条件判断等控制结构来替代无限制的goto跳转,以此来构建逻辑清晰、易于理解和维护的程序。虽然这一方法在实践中可能需要一些灵活的应用,比如合理使用多路分支和异常处理等附加控制结构,但其核心思想——将系统的结构视为控制结构来考虑——无疑是软件开发领域的一大进步。
构件单元调试
程序调试的历史演变充满了反复,但其核心目标始终未变——提高效率与准确性。
本机调试
早期调试受限于简陋的输入输出设备,如纸带和磁带,导致调试过程繁琐且耗时。为此,程序员们精心规划调试流程,包括设定断点、检查内存状态及制定应对策略,这一阶段的调试工作几乎占据项目开发的一半时间。
内存转储
随着技术进步,内存转储成为主流,尽管这种方式在减少计算机使用时间上表现出色,但增加了人工分析的复杂度。内存规模的增长促使快照技术的诞生,它允许选择性地保存程序状态,大大简化了调试流程。
交互式调试
进入交互式调试时代后,得益于多任务操作系统和高级语言的支持,程序员能够实时调试代码,同时保持机器的高利用率。然而,研究指出,首次交互往往比后续交互更能推动问题解决,这表明预先规划的重要性并未因技术进步而减弱。实际上,有效的调试实践建议,每两小时的终端调试应配以相同时间的桌面工作,包括整理日志、分析异常和准备下一阶段的测试计划。这一流程确保了调试工作的连续性和效率。
系统集成调试
系统集成调试是软件开发过程中一个关键且复杂的环节,它不仅考验开发者的技能,还需要一套完备的系统化方法来确保调试过程的有效性和效率。本文将探讨几种有效的系统集成调试方法,帮助开发者克服常见的挑战。
使用经过调试的构件单元
常见误区
-
“合在一起尝试”:这种做法看似能提早暴露系统层面的问题,但实际上会使调试变得更加困难,因为多个bug同时出现,使得问题的定位和解决变得复杂。
-
相互测试:虽然这种方法可以减少构建测试环境的时间,但可能会引入额外的问题,尤其是当被测试的组件本身就有缺陷时。
最佳实践
-
确保构件质量:在集成之前,每个构件单元都应经过严格的测试,以确保其功能正确无误,这样可以显著减少系统级别的bug数量,提高整体调试效率。
-
文档化bug:对于那些不可避免的、已知的bug,应该详细记录下来。这样,在进行系统测试时,可以优先关注新出现的问题,而不是重复处理已知的问题。
搭建充分的测试平台
测试辅助的形式
-
伪构件:为了模拟尚未完成的系统部分,可以使用伪构件来提供必要的接口和测试场景,确保其余部分的测试不会受到影响。
-
微缩文件:通过创建含有典型记录的小文件,可以有效地验证文件格式的正确性。
-
伪文件:在特定条件下,使用伪文件代替真实文件进行测试,有助于简化测试过程。
-
辅助程序:开发专门的辅助工具,例如数据生成器和特殊打印输出程序,以满足特定的测试需求。
控制变更
关键步骤
-
专人管理:指派专人负责构件单元的变更管理和版本控制,确保变更过程的有序进行。
-
系统受控拷贝
-
最终锁定版本:这是用于构件单元测试的稳定版本。
-
测试版本:专为修复缺陷而设计的版本。
-
安全版本:其他开发人员可在此基础上独立开展工作。
-
-
变更记录:所有变更,无论是快速补丁还是正式修改,都应详细记录,确保每个修改都经过了充分的测试并文档化。
一次添加一个构件
好处
-
逐步排查:每次只添加一个构件,有助于逐步识别和解决系统级别的bug。
-
回归测试:在引入新构件后,重新执行之前的测试用例,确保系统的稳定性和一致性。
注意事项
-
避免诱惑:不要因为急于求成或过于乐观而跳过此步骤。系统测试的核心在于假设系统中可能存在大量错误,并通过系统化的方法逐一排除。
阶段(量子)化、定期变更
目的
-
稳定周期:通过定期发布新的系统版本,确保每位用户都能获得一个稳定的生产环境。
-
减少混乱:避免频繁变更带来的混乱,保持开发流程的有序性。
实践建议
-
大而稀疏的阶段:研究表明,相较于小而频繁的迭代,大而稀疏的开发阶段更有利于维持系统的长期稳定。
-
紫色线束技术:在每个版本发布前,先应用快速补丁解决问题;随后,在下一个主要版本中,将所有经过测试和文档化的补丁整合进去。