软件创建
开发计算机软件是一项非常复杂的工作。在过去的十五年中,研究者们已经明确了这项工作的主要方面,包括以下几个关键阶段:
问题定义
- 目的:明确软件需要解决的问题。
- 方法:与利益相关者进行沟通,确定软件需求的初步想法。
需求分析
- 目的:深入理解并定义软件的功能和约束条件。
- 方法:收集详细的用户需求,编写需求文档。
实现计划
- 目的:制定实现软件所需的详细步骤和时间表。
- 方法:项目管理和资源分配。
总体设计
- 目的:定义软件的架构和高层次结构。
- 方法:创建设计文档,明确系统的组成部分和它们之间的交互。
详细设计
- 目的:具体化每个组件的设计。
- 方法:详细说明具体模块的功能和接口。
创建即实现
- 目的:将设计转化为实际代码。
- 方法:编程和代码实现。
系统集成
- 目的:将不同模块或组件整合为一个完整的系统。
- 方法:集成测试,确保各部分协同工作。
单元测试
- 目的:验证单个模块或组件的功能。
- 方法:编写和执行针对单一模块的测试用例。
系统测试
- 目的:确保整个系统满足设计和需求规格。
- 方法:进行全面的测试,包括功能测试、性能测试等。
校正性的维护
目的:修复软件运行中出现现的错误和问题。
方法:定期更新软件,解决已发现的缺陷和性能问题。
功能强化
- 目的:根据用户反馈和市场变化,增加新的特性或改进现有功能。
- 方法:持续迭代开发,增强软件的功能和性能。
隐喻编程
隐喻在软件开发中起着关键的启发性作用,它通过将软件开发过程与我们熟悉的事物相比较,帮助我们更深入地理解软件开发的本质。以下是几个重要的隐喻及其在软件开发中的应用。
计算机科学的语言隐喻
- 描述:计算机科学的语言丰富多彩,包括病毒、蠕虫、臭虫、炸弹等。
- 意义:这些形象的隐喻描述了特定的软件现象,有助于我们理解和描述软件中的复杂问题。
播种与培植庄稼的隐喻
- 描述:将创建软件比作播种或培植庄稼。
- 意义:强调通过小步前进的方法(增量设计、构造、测试)来减少错误,提高软件质量。
沉积与增量的隐喻
- 描述:将软件开发比作从水中沉积物中积累出代码。
- 意义:强调在系统中逐渐添加新功能的重要性,即增量设计和测试。
建造房屋的隐喻
- 描述:将软件创建比作建造房屋的过程。
- 意义:展示了软件开发的各个阶段,从问题定义到优化,与建房子的步骤相类似。
利用现成资源的隐喻
- 描述:在建房时,使用现成的洗衣机、电冰箱等,而不是自己制造。
- 意义:鼓励在软件开发中利用现有的资源,如库、框架和高级语言特性。
定制化建造的隐喻
- 描述:如果要建造一流的别墅,可能会选择定制家具和装饰。
- 意义:对于高级别的软件开发,可能需要创建定制化的组件和功能,以实现特定的需求。
智能工具箱的隐喻
- 描述:软件开发实践像一个装满各种工具的工具箱。
- 意义:强调没有万能的工具,为每个任务选择合适的工具是成为优秀程序员的关键。
结论
- 隐喻的重要性:隐喻帮助我们通过比较熟悉的事物来理解软件开发的复杂性。
- 选择合适的隐喻:不同的隐喻适用于不同的情境。选择恰当的隐喻可以帮助更好地解决问题和理解软件开发的特性。
- 工具箱理念:每位程序员都有自己的技术和方法集合。在不同的开发阶段选择合适的技术和方法是至关重要的。
软件创建的先决条件
先决条件的重要性
软件开发过程中的准备工作对于最终产品的质量至关重要。以下是几个关键点:
- 项目启动前的准备:像建筑工人在修建房屋前做准备一样,优秀的程序员在项目开始前会进行充分的准备。这包括确保所有必要的资源已准备就绪,并在计划的每个阶段都注重高质量。
- 抵制立即编码的冲动:许多程序员倾向于立即开始编码,但应先考虑准备阶段可能出现的问题。准备工作的经验是必不可少的,特别是在大型项目中。
- 教育周围的人:程序员的职责之一是教育管理人员和同事理解技术项目的开发过程,确保他们理解先决条件的重要性。
- 项目规划的重要性:在大型项目开始前进行彻底的规划至关重要。这与建造房屋前的规划工作相似。
- 避免不必要的修改:研究表明,最好是一次完成项目,因为不必要的修改成本很高。错误应尽早在软件开发过程中被发现并修正。
问题定义先决条件
在需求分析之前,应该清晰地定义问题。良好的问题定义应遵循以下准则:
- 专注于问题本身:问题定义应仅描述要解决的问题,而不涉及解决方案。例如,“我们无法跟上指令系统”是一个问题,而“我们需要优化数据入口系统以便跟上指令系统”则偏向解决方案。
- 从用户视角出发:问题定义应使用用户的语言,避免使用计算机技术术语。这有助于确保最佳的解决方案可能并不局限于技术实现。
需求分析的先决条件
需求分析是确定系统功能的关键步骤,应注意以下要点:
- 明确需求的重要性:清晰的需求有助于由用户而非程序员决定系统功能。这可以防止程序员在编程过程中对用户需求进行猜测。
- 需求分析的质量:如果需求分析不佳,应停止当前工作并返回需求分析阶段。
- 管理变更请求:对于在需求分析阶段之外提出的新想法,应进行成本和进度估计,然后再决定是否采纳。
- 应对不稳定需求:如果需求特别奇怪或频繁变化,考虑放弃项目可能是一个明智的选择。
结构设计先决条件
结构设计阶段需要考虑以下关键因素:
- 模块功能的明确性:每个模块应只完成一项任务,并应尽可能降低模块间的相互了解程度。
- 变动应对策略:结构设计应清晰描述系统应对变动的策略,包括对旧软件的重用和代码购买。
- 关键数据结构:明确使用的主要文件、表和数据结构,及其替代方案。
- 依赖特定算法的设计:如果设计依赖于特定算法,应详细描述或指出该算法。
- 内存管理:预估正常和极端情况下所需的内存。
- 错误处理:错误处理是软件开发中的关键部分,应在设计中明确处理策略。
- 坚固性和容错性:设计应指明所期望的系统坚固性和容错性类型。
- 性能目标:如果考虑性能,应在设计中明确性能目标,包括速度和内存使用。
- 设计一致性:确保结构设计的每次变动都与总体设计概念一致。
选择编程语言先决条件
选择编程语言时,应考虑以下因素:
- 语言对思维的影响:编程语言会影响程序员的思维方式。确保所选语言适合项目的需求和团队的技术背景。
编程约定
在开始编码之前,明确的编程约定是至关重要的。这些约定应该详细到在编程过程中不需改动,确保代码的一致性和可维护性。
- 编程风格:包括命名规范、代码布局、注释风格等。
- 代码标准:定义函数、类的结构,变量的使用规则等。
- 错误处理:统一的错误报告和处理机制。
- 性能要求:对于性能的基本要求和目标。
- 安全性:安全编程准则,防止常见的安全漏洞。
应花在先决条件的时间
软件项目的先决条件阶段需要投入适当的时间和资源,一般建议分配项目总时间的20%至30%。
- 项目规模与复杂度:大型或复杂项目可能需要更多的时间进行准备。
- 团队经验:经验丰富的团队可能在某些阶段更快地完成准备工作。
- 风险评估:高风险项目需要更细致的先决条件分析。
编码前的技术评审
在正式开始编码之前进行技术评审,以确保设计的合理性和可行性。
- 设计评审:评估设计方案是否符合需求。
- 代码审查:早期发现潜在的代码问题和不一致之处。
- 性能分析:确保设计能满足性能要求。
团队协作准备
确保团队成员间的有效沟通和协作。
- 角色分配:明确每个团队成员的职责和任务。
- 沟通机制:建立有效的沟通渠道和会议安排。
- 文档共享:确保所有相关文档易于团队成员访问。
技术栈和工具选择
选择合适的技术栈和工具是项目成功的关键。
- 编程语言:根据项目需求和团队技能选择合适的编程语言。
- 开发框架:选择支持项目需求的开发框架和库。
- 开发和测试工具:选用高效的IDE、版本控制系统和测试工具。
建立子程序的步骤
建立子程序是软件开发中的一个重要过程,需要遵循一系列明确的步骤以确保代码的质量和可维护性。
描述子程序的操作
- 使用自然语言:以清晰的自然语言描述子程序的每一步操作,确保其目的和功能明确。
命名子程序
- 清晰且具体:子程序的命名应清楚、具体,避免引起误解。
- 反映功能:名称应反映子程序的功能和作用。
考虑效率
- 性能优化:在保证功能和清晰性的基础上,考虑子程序的运行效率。
- 资源管理:注意资源使用和管理,避免不必要的资源浪费。
算法和数据结构
- 合适的选择:根据子程序的功能选择合适的算法和数据结构。
- 效率与可维护性:平衡算法的效率和代码的可维护性。
从抽象到具体
- 注释先行:编写子程序时,先从抽象的注释开始,明确子程序的目的和功能。
- 清晰的目标:如果在这一步遇到困难,可能需要对子程序的角色和功能进行深入思考。
逐步细化数据
数据设计:仔细考虑子程序将处理的数据类型和结构。
逐步实现:从大致框架开始,逐步细化实现细节。
使用程序设计语言 (PDL)
- 易懂的语言:使用清晰易懂的自然语言编写PDL,避免过度依赖特定编程语言的语法。
- 注重目的:PDL应关注于描述子程序要做什么,而非具体实现方式。
- 从PDL到代码:PDL可以直接转化为代码的注释,确保注释的准确性和实用性。
持续检查和改进
- 代码检查:在开发过程的每一步中检查子程序,确保质量。
- 同事审查:鼓励团队成员互相审查代码,及早发现并纠正错误。
- 降低成本:通过早期发现和修正错误,降低后期修复成本。
高质量子程序的特点
生成子程序的原因
创建子程序的理由总结
- 降低复杂性:使代码更易于理解和维护。
- 避免重复代码:促进代码重用,减少冗余。
- 限制改动影响:简化维护和更新。
- 隐含顺序和控制:明确代码的执行流程。
- 改进性能:通过优化关键子程序来提升整体性能。
- 进行集中控制:统一管理相关操作。
- 隐含数据结构和指针操作:隐藏复杂的数据处理。
- 隐含全局变量:减少全局变量的直接使用。
- 促进代码重用:提高代码的可重用性。
- 计划开发软件族:为未来的扩展做准备。
- 改善可读性和可移植性:使代码更易于阅读和适应不同环境。
- 分隔复杂操作:将复杂的操作拆解成更小的单元。
- 独立非标准语言函数的使用:减少对特定语言特性的依赖。
- 简化复杂的布尔测试:使逻辑判断更清晰。
子程序命名
- 清晰描述:名称应明确反映子程序的功能。
- 适当的长度:长度应足以描述功能,但不过于冗长。
- 避免模糊用语:选择明确无歧义的词汇。
强内聚性
内聚性类型
- 功能内聚性:子程序执行单一且明确的功能。
- 顺序内聚性:操作需要按特定顺序执行。
- 通讯内聚性:操作使用相同的数据。
- 临时内聚性:操作基于相同的时间发生。
松耦合性
耦合性的类型
- 简单数据耦合:优选,通过参数表传递非结构化数据。
- 数据结构耦合:通过参数表传递结构化数据。
- 控制耦合:一个子程序通过参数控制另一个子程序的行为。
- 全局数据耦合:子程序通过全局变量进行通信。
- 不合理耦合:直接修改另一个子程序的内部数据或代码(应避免)。
子程序长度
- 理想长度:一般建议子程序长度为一到两页代码(大约66到132行)。
防错性编程
- 使用断言:验证程序状态,确保数据的正确性。
- 垃圾输入处理:确保对不合理的输入给予合适的响应。
- 异常处理:设计异常处理机制,应对意外情况。
- 预计可能的改动:考虑未来可能的变化和扩展。
- 去除调试代码:发布时移除专用于调试的代码部分。
- 保留错误提示信息:对于错误和异常提供清晰的反馈。
- 检查函数返回值:验证其他函数的返回值,确保数据的正确性。
子程序参数
参数设计和使用
- 匹配实际与形式参数:确保调用时传递的参数与子程序定义匹配。
- 参数顺序:输入、修改、输出参数的顺序应一致。
- 类似子程序的参数顺序一致性:相似功能的子程序应有相同的参数顺序。
- 使用所有参数:不使用的参数应从子程序接口中移除。
- 状态和错误变量放在最后:使子程序的主要功能更明显。
- 参数非工作变量:避免将参数用作内部的工作变量。
- 特殊情况的说明:明确参数的特殊用途和限制。
- 限制参数个数:减少参数数量,避免过度复杂。
- 规范参数命名:清晰、一致的命名提高代码可读性。
- 传递必要部分的结构化变量:仅传递子程序所需的数据结构部分。
模块化设计
模块的内聚性
- 原则:模块应提供一组相互联系的服务,保持高内聚。
- 意义:内聚性强的模块能更清晰地定义功能,简化理解和维护。
模块的耦合性
- 原则:设计模块时应保持与其他模块的耦合度低。
- 实践:模块应被设计成可提供完整功能,确保与其他部分的清晰交互。
封装和信息隐蔽
- 定义:将模块的信息细节隐蔽起来,也称为“封装”。
- 优点:
- 便于修改。
- 澄清代码编写意图。
- 提高可靠性。
- 简化调试。
- 统一数据存取模式。
高级结构设计
设计流程
- 子系统划分。
- 模块化设计。
- 程序的细节设计。
- 子程序内部和设计。
设计方法
- 自顶向下分解。
- 自底向上合成。
设计原则
- 抽象:创建与问题同等抽象层次的编程抽象,避免过度细节化。
- 封装:在老旧语言中是自愿的,而在如Ada等现代语言中则是强制性的。
- 继承:在面向对象编程中,继承性简化了编程,通过通用和专用子程序处理对象间的共同与不同特性。
面向对象设计步骤
- 识别对象及其属性。
- 确定对象的功能。
- 确定对象间的相互作用。
- 确定对象的可见性和接口。
往返设计
- 概念:设计是一个迭代过程,可能需要多次往返以深化理解。
- 好处:在设计过程中进行迭代可以带来更大的好处。
问题解决方法
- 理解问题。
- 设计方案。
- 执行解决方案并检查每一步。
- 回顾并检查答案。
数据名称
变量命名
- 原则:变量名称应准确描述其代表的实体。
- 建议:使用自然语言描述变量代表的实体,名称应体现问题而非解决方法。
名称长度
- 标准:选择合适长度的变量名,确保清晰易懂。
- 例外:在特定情况下,如临时变量,短名称(如
i
)可能更合适。
命名风格
- 限定词使用:如
totals
,averages
,maximums
等,限定词放在变量名末尾。 - 反义词使用:恰当使用反义词以增强变量意义的清晰度。
特殊类型的变量命名
- 状态变量:避免使用模糊的名字如
flag
,应使用更具描述性的名称。 - 临时变量:通常用作保存中间计算结果,应避免无意义的命名。
逻辑和枚举变量
- 逻辑变量:使用肯定的名称,易于理解。
- 枚举变量:使用统一的前缀或后缀以表明同一类型。
变量
作用域和初始化
- 作用域:变量在程序中的可见范围。
- 初始化:应在使用前恰当地初始化变量。
持久性和全局变量
- 持久性:变量的使用寿命,避免硬编码,确保功能单一性。
- 全局变量:最初设为局部变量,必要时转为全局变量,但应优先考虑使用存取子程序。
控制
循环和条件控制
- 边界错误:明确循环结束点和条件语句中的等号情况,避免边界错误。
- 简化循环:通过减少循环次数、明确进出口等方式简化循环。
嵌套和子程序
- 简化嵌套:重新编写测试条件,将深层嵌套代码转为子程序或使用
case
语句替代if
嵌套。
文档
文档类型
- 综合资料:供开发者使用的非正式记录。
- 详细设计文档:描述模块层或程序层的决策。
代码注释
- 注释类别:包括代码的重复、解释、标记、总结和意图描述。
- 注释原则:避免无关注释,注释应清晰表达代码意图。
注释实践
- 行注释:避免过度使用,注释应与代码紧密相关。
- 结束行注释:应集中在“为什么”而非“如何”上。
项目大小的影响
交流和方法
- 交流规范化:对大型项目而言,规范化文档和交流方式非常重要。
- 方法应用:小型项目可能采用偶然和本能的方法,而大型项目则需要精确和计划性的方法。
项目规模影响
- 代码行数和参与人数:项目大小不仅由代码行数和参与人数决定,还包括软件的质量和复杂性。
- 生产效率:受人员素质、编程语言、方法、产品复杂性等多种因素影响。
单元测试
测试原则
- 测试覆盖:选择能有效发现错误的测试用例,包括结构测试和数据流测试。
- 边界条件:重点关注边界条件和最小最大值。