欢迎来到软件开发(AS Level 9618)的世界!
你好!这一章极其重要,因为它超越了单纯的编程,深入探讨了创建软件的整个过程——从最初的构思,一直到最终成品程序及后续的维护。你可以将其理解为构建任何成功应用程序或系统的专业“路线图”。
掌握**软件开发生命周期(SDLC)**、规范的设计技巧以及全面的测试方法,将使你成为一名更加出色的计算机科学家。别担心术语太深奥,我们将通过清晰的例子为你拆解每一个步骤!
12.1 程序开发生命周期(PDLC)
开发生命周期的目的是什么?
**程序开发生命周期(PDLC)**是一个结构化的框架,描述了构建和维护软件所涉及的所有阶段。
我们为什么需要它?
PDLC 确保项目能够:
- 高效管理(保持在预算和进度之内)。
- 满足用户需求(解决正确的问题)。
- 经过全面测试并具备清晰的文档。
- 能够在未来进行维护和升级。
程序开发生命周期的各个阶段
尽管存在不同的模型,但 PDLC 通常包含五个核心阶段。记住它们的一个好方法是按顺序记忆:**A**nalysis(分析)、**D**esign(设计)、**C**oding(编码)、**T**esting(测试)、**M**aintenance(维护),简称 ADCTM。
1. 分析(程序应该做什么?)
这是开发者确定确切需求以及用户实际需要的阶段。它涉及收集信息并定义问题的范围。
2. 设计(程序如何实现?)
规划解决方案的结构。这包括创建算法、设计数据结构,并决定程序的各个部分如何组合在一起(通常使用结构图等工具)。
3. 编码(编写指令)
使用选定的编程语言,将设计方案(算法/伪代码)转换为实际的计算机代码。
4. 测试(程序运行是否正确?)
系统地检查代码以发现并纠正错误(缺陷)。这确保程序满足分析阶段定义的需求。
5. 维护(保持程序运行)
软件部署后进行的活动,以确保它能够持续运行并满足不断变化的需求。
不同的开发生命周期模型
生命周期的选择完全取决于程序的需求、规模以及客户的灵活程度。
1. 瀑布模型 (Waterfall Model)
这是一种经典的顺序化方法。必须完全完成一个阶段,才能进入下一个阶段。
类比:想象在刷墙。你必须完成准备工作(分析)后才能刷第一遍漆(编码),且必须刷完第一遍后才能刷第二遍(测试)。
- 优点:易于管理,每个阶段都有明确的文档,适合需求明确的小型项目。
- 缺点:非常僵化,缺乏灵活性。如果在后期(如测试阶段)发现错误,修复的成本非常高,难度大,因为必须回溯到设计或分析阶段。
2. 迭代模型 (Iterative Model)
项目被分解为较小的子项目或“迭代”。每个迭代都经历自己的分析、设计、编码和测试小周期,从而产出系统的功能模块。
类比:与其一次性造出一辆完整的汽车,不如先造出一辆自行车(迭代1),然后加装引擎变成助力车(迭代2),最后装上外壳和车身变成汽车(迭代3)。
- 优点:可以尽早获得用户反馈,风险管理更容易,且能适应不断变化的需求。
- 缺点:需要强大的管理能力以防止“范围蔓延”(即功能不断无节制地增加),如果初始设计不佳,最终系统架构可能不稳定。
3. 快速应用开发 (RAD)
RAD 极度侧重于快速创建原型,并在整个过程中进行密集的协作。它通常用于时间紧迫且需求不断演变的场景。
- 优点:开发速度快,用户参与度高,部署快。
- 缺点:不太适合极其复杂的系统;文档和测试有时会被仓促处理或不完整;严重依赖熟练的开发人员和配合度高的用户。
快速复习:PDLC 模型
如果项目有固定的、明确的需求,选择**瀑布模型**。
如果项目需要频繁反馈且需求可能会变,选择**迭代模型**。
如果速度和用户参与是最高优先级,选择**RAD**。
12.2 程序设计
设计阶段对于确保代码逻辑清晰、易于阅读且易于维护至关重要。我们使用特定的工具来记录这种结构。
结构图 (Structure Charts) 与模块分解
**分解 (Decomposition)** 是将大型复杂问题拆解为更小、更易于管理的问题的过程。在编程中,这些子问题成为**模块**(过程或函数)。
**结构图**是一种自顶向下的图表,用于记录这种分解过程。
- 它展示了系统的整体结构。
- 每个方框代表一个模块(过程/函数)。
- 它展示了模块之间的连接方式。
- 关键点在于,它展示了模块间传递的**参数**(数据)。
结构图的组成部分:
绘制结构图时,必须体现参数的传递:
- 向下流动的数据(从调用模块传递给被调用模块)
- 向上流动的数据(从被调用模块返回给调用模块的结果)
例如:名为 ProcessOrder 的模块可能会调用名为 CalculateTax 的子模块。结构图会显示“价格”向下传递(输入参数),而“税额”向上返回(返回参数)。
状态转换图 (State-Transition Diagrams)
**状态转换图**用于记录算法,特别是当处理在任何给定时间只能处于特定条件(或“状态”)的系统时,例如简单机器或用户界面元素。
- 每个节点(圆圈)代表系统的一种可能**状态**(例如:开、关、等待)。
- 每个箭头代表导致系统从一种状态转换到另一种状态的**转换**(或事件)(例如:按下按钮、计时器到期)。
例如:自动售货机可能具有“空闲”、“已投币”和“出货中”等状态。投币的行为会导致系统从“空闲”转换为“已投币”。
核心要点:设计工具
结构图显示了**层次结构**和**数据流**(参数)。
状态转换图显示了算法如何随时间在不同**状态**和触发变化的**事件**之间运行。
12.3 程序测试与维护
发现并避免缺陷(错误)
测试至关重要。如果程序有缺陷,可能会导致崩溃、产生错误结果或引发安全漏洞。
你需要定位并识别的三种主要错误类型是:
1. 语法错误 (Syntax Errors)
这是编程语言在语法或规则上的错误(例如:拼写错误的关键字、忘记加括号、或在使用变量前未声明)。
如何发现:它们通常在程序运行前就会被编译器或解释器捕获。这是最容易修正的错误。
2. 逻辑错误 (Logic Errors)
程序运行而不会崩溃,但产生的结果不正确,因为潜在的算法是错误的(例如:本应相减却用了加法,或者循环比预期提前一次结束)。
如何发现:这是最难发现的,通常需要仔细进行**排查 (Dry Run)** 或 **走查 (Walkthrough)** 分析,并使用已知的测试数据进行系统测试。
3. 运行时错误 (Run-time Errors)
这些错误仅在程序执行时发生,导致程序崩溃或卡死(例如:除以零、访问数组边界之外的元素、或内存溢出)。
如何发现:调试器 (Debugger) 可以帮助定位崩溃发生的具体代码行。
测试策略与数据
良好的开发过程需要清晰的**测试策略**(整体方法是什么?)和详细的**测试计划**(将执行哪些具体测试,预期结果是什么?)。
选择合适的测试数据
为了确保健壮性,测试数据必须覆盖所有可能性:
- 正常数据 (Normal Data):有效、合理且在预期范围内的输入数据。(例如:如果要求输入 1 到 10,则使用 5)。
- 极端/边界数据 (Extreme / Boundary Data):正好位于可接受范围极限的数据。(例如:如果要求输入 1 到 10,则使用 1 和 10)。
- 异常数据 (Abnormal Data):无效、无意义或超出预期范围的数据。这用于测试程序如何处理错误。(例如:如果要求输入 1 到 10,则使用 0、11 或输入字母)。
测试方法
1. 桌面检查 (Desk Checking) 与 走查 (Walkthroughs)
- 排查 (Dry Run):程序员使用一组测试数据手动执行代码,并在跟踪表中记录变量的变化值。这有助于尽早发现逻辑错误。
- 走查 (Walkthrough):一组程序员共同逐行评审代码,以检查逻辑错误并确保符合编程标准。
2. 黑盒测试 (Black-Box) 与 白盒测试 (White-Box)
- 黑盒测试:仅根据输入和输出来测试程序,而不了解内部代码如何运作。(关注功能和需求)。
- 白盒测试:测试内部结构、逻辑和代码路径。测试人员必须了解源代码。(用于确保代码的每一行或每一条路径至少被执行一次)。
3. 集成测试 (Integration) 与 验收测试 (Acceptance)
- 集成测试:检查各个独立模块或组件组合在一起时是否能正常工作。(通常涉及使用称为**桩模块 (Stubs)** 的临时占位模块)。
- Alpha 测试:在发布给公众之前,由内部人员(如程序员自身或内部质量保证团队)进行的测试。
- Beta 测试:由有限数量的外部真实用户进行的测试,他们会反馈发现的任何 Bug 或问题。
- 验收测试:由客户或最终用户执行的正式测试,旨在确认系统在正式接收软件之前满足所有合同要求。
程序维护
维护是 PDLC 中最长且通常成本最高的阶段。它发生在软件发布之后。维护分为三种类型:
1. 改正性维护 (Corrective Maintenance)
修复系统上线后发现的 Bug 或错误(例如:修复导致打印报告时崩溃的逻辑错误)。
2. 适应性维护 (Adaptive Maintenance)
修改系统以应对操作环境或技术的变化(例如:更新代码以兼容新版本的操作系统,或者因政府新政策而更改税务计算规则)。
3. 完善性维护 (Perfective Maintenance)
提高程序的效率、性能或用户界面,通常是添加用户请求的非必要功能或简化现有代码。(这让一个能用的程序变得更好)。
你知道吗?
据估计,维护成本在软件整个生命周期的总成本中占比高达 70%!这就是为什么从一开始就进行稳健的设计和测试如此重要。