Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

《编程卓越之道(卷 3):软件工程化》—— Randall Hyde #76

Open
thzt opened this issue Feb 6, 2023 · 0 comments
Open

Comments

@thzt
Copy link
Owner

thzt commented Feb 6, 2023

编程卓越之道

我刚做软件开发工作时,特别关注 “编程” 这件事,以为最重要的工作就是写代码,对于写代码之外的事情,比如文档、测试、修复缺陷等,都比较排斥,觉得它们都是额外开销,浪费时间。
但随着参与的项目越来越复杂,尤其是做了软件研发经理之后,认知发生了很大变化,我意识到,编程只是软件开发众多环节中的一环,仅仅完成它,距离顺利交付完整的软件系统,还有十万八千里的距离。
此时,我也发现,团队里的多数程序员和当初的我一样,过于关注 “编程” 环节,对编码之外的事情,诸如 设计、文档、测试、软件开发方法论 等,不是一知半解,就是主动忽略,这严重影响了个人生产力和团队生产力

前言

卓越的代码,是按照一套可以指导程序员在编码时做出决策的规则,所编写出来的软件。
在和其他程序员一起编写卓越的代码时,时刻要注意编写相关文档,使得其他人也可以阅读、理解和维护软件。
我认为这套规则是软件开发的黄金法则,是软件工程的关键核心。

具体一点,卓越的代码是,

  • 运行速度快,可以有效的利用 CPU、系统资源和内存的代码
  • 文档良好,易于阅读、维护和扩展的代码
  • 遵循一套统一编程风格的代码
  • 经过明确的设计,遵循已建立的软件工程约定的代码
  • 经过良好的测试,运行稳定的代码
  • 效率高、成本低代码

程序员的分类

  • 业余程序员:业余程序员指自学成才、只有很少编程经验的人。业余程序员编写的代码的问题在于,他们通常只为自己或者朋友编写代码,因此,这些代码通常不符合现代软件工程的项目标准。但是业余程序员可以通过少量的培训教育,来提高他们的水平。
  • 程序员
    • 实习生:通常是指做兼职工作的学生,他们会被分配一些所谓的烦琐工作,比如,运行一套固定的测试程序,或者为软件编写文档。
    • 初级程序员:刚毕业的学生通常会填补初级程序员的岗位,通常,他们会从事测试或者维护的工作。他们很少有机会从事新项目的开发;相反,他们的大部分编程时间都花在重新编写已有的代码逻辑,或者处理遗留代码上。
    • 编码人员:较资深的程序员会将一个大项目的一些子模块分配给这些编码人员,以帮助自己更快的完成项目。
    • 一级程序员和二级程序员:系统分析师通常会向一级程序员和二级程序员提供他们的一个大概设想,而一级程序员和二级程序员能够帮助补充所缺少的细节部分,并且开发出符合系统分析师所期望的应用程序。
    • 系统分析师:系统分析师会研究某个问题,并且确定实现某种解决方案的最佳方法。通常,系统分析师会选择要使用的主要算法,以及组建最终开发应用程序的团队。
    • 系统架构师:系统架构师会根据系统分析师设计的多个组件,选择如何让它们在一个更大的系统中协同工作。通常,系统架构师会指定软件开发流程、硬件,以及其他与软件无关的事项,作为整个解决方案的一部分。
    • 终极程序员:终极程序员是所有这些细分职责的组合。也就是说,终极程序员能够研究一个问题,设计一种解决方案,用某种编程语言实现该解决方案,并且测试最终的结果。
  • 软件工程师:软件工程更关心的是如何在预算范围内,按时开发完成应用程序,而不是尽可能用最好的方式来编写代码。
  • 卓越的程序员:对工作真正的热爱,持续不断的接受教育和培训,以及拥有在解决问题时跳出思维定式的能力。
    • 热爱你的工作,做你热爱的工作
    • 优先接受教育和培训
    • 跳出思维定式

软件工程师的工作,是在面临众多相互冲突的需求的情况下,通过在系统设计中做出适当的妥协来创造出更好的产品。
在此过程中,软件工程师必须对需求的优先级进行排序,并根据项目的约束条件来选择解决问题的最佳方案。

软件开发的比喻

软件是被开发或者设计出来的,它不是传统意义上被制造出来的。
大多数软件都是定制化的,而不是由已有的(标准)组件组装而成的。

工程师们感兴趣的是如何用低成本、高收益的方法来解决实际问题,其中包括设计成本和生产成本。
使用一种系统化的、有理可寻的、可量化的方法来开发、运行和维护软件,即工程化在软件方面的应用。

为了写出卓越的代码,你必须首先理解是什么让代码变得卓越的。
你需要努力学习如何写出卓越的代码,然后再努力去做到这一点。
一个优秀的软件开发人员,会从刚才讨论的各种特性中,取其精华去其糟粕。

软件工程主要会涉及经济和项目管理方面,有趣的是,那些真正负责管理项目、维护时间进度表、选择软件方法论等的人,往往并不是软件工程师,他们被称为项目经理、项目负责人,以及其他暗示权威地位的头衔。
同样,我们称为软件工程师的人,实际上并不是做软件工程的 —— 他们只是编写由那些真的软件工程师(项目经理和项目负责人)所规定的代码。
也许,这就是大家对 “软件工程” 这个词理解如此混淆的原因。

软件工程,是一门研究如何开发和管理大型软件系统的学问。

  • 小型项目:一个普通程序员能够在合理的时间内(< 2 年)完成的项目(50~100 KLOC)
  • 中型项目:一个人无法在合理的时间内完成,但是一个由 2 ~ 5 个程序员组成的小团队可以完成的项目(50 ~ 1000 KLOC)
  • 大型项目:需要一个较大的程序员团队(> 5 个成员)来完成(500~1000 KLOC)

小型项目管理起来很简单,因为小型项目不需要程序员之间的交流,也不需要程序员与外部环境之间的交互,所以生产力几乎完全取决于程序员个人的能力。
中型项目会带来新的挑战。由于多个程序员在项目中一起工作,沟通可能会成为一个问题,但是又因为团队足够小,所以这种开销还是可管理的。不过,因为团队之间的互动需要额外的资源支持,所以会增加编写每一行代码的成本。
大型项目需要一个大型的程序员团队。团队成员之间的沟通和其他开销通常会消耗掉每个工程师 50% 的生产力。因此,对于大型项目而言,有效的项目管理至关重要。

软件工程涉及成功管理需要大型程序员团队完成的项目的各种方法、实践和策略。
遗憾的是,个人或者小型团队的良好实践经验无法被扩展到大型团队中,而大型项目的方法、实践和策略也无法被应用到中小型项目中。
在大型项目中应用良好的实践经验,通常会给中小型项目带来不合理的开销,从而降低那些小团队的生产力。

如果一个流程可以被完全定义,你对它的所有事情都很清楚,这个流程就是可被设计的,重复运行该流程可以产生可预测的结果,那么这样的流程就被称为已定义流程,并且可以被自动化
如果你对一个流程中的所有事情都不完全清楚,即当你输入一堆东西时,只知道大概会发生什么,并且不确定如何测量和控制结果,那么这样的流程就被称为经验性流程
软件开发不是一个已定义流程,而是一个经验性流程。软件开发不能被完全自动化,并且通常将工程领域的经验应用到软件开发中是很困难的。
部分问题在于,实际的工程实现在很大程度上,依赖于对现有设计的重用。尽管在计算机编程中也存在大量的重用,但是它比其他工程领域需要更多的定制化

软件匠艺,是指一个程序员在编程大师的指导下,训练和实践各种技能,立志于终身学习并成为最优秀的软件开发人员。
遗憾的是,很少有软件工匠有时间或者能力,从头开始培训一个学徒。他们忙于现实世界的项目开发,无暇顾及教给实习生他们所需要知道的一切。

经验并不会像人们想象的那样重要。
计算机科学领域变化如此之快,以至于一个有 10 年编程经验的人可能会错过这 10 年中所有伟大的研究成果,而这些成果正是新的程序员会接触到的。

你能编写出卓越的代码,并不是因为遵循了一系列规则。你必须自己下定决心,努力保证写出的代码真的很棒。
遗憾的是,书本只能教给你这些规则和方法,但是创造力和智慧需要你自己去培养

生产力

在 20 世纪 60 年代末,大家已经非常清楚,培养再多的程序员也不会缓解软件危机。唯一的解决方式是提高程序员的生产力 —— 也就是说,让现有的程序员能够编写更多的代码 —— 这就是软件工程领域的起源。
我们可以将生产力定义为:在一定时间内,或者在一定成本下完成的单位任务的数量。
这个定义的问题在于如何定义一个单位任务

就像添加第二个烤箱,不会让烤蛋糕变得更快一样。

随着项目变得越来越复杂,程序员的生产力会随之降低,因为更复杂的项目需要更深入(更长时间)的思考来理解正在发生的事情。
此外,随着项目复杂度的增加,软件工程师有可能将缺陷引入系统中,并且在系统早期引入的缺陷可能直到后期才会被发现,此时纠正这些缺陷的成本要高得多。

复杂性有两种形式,下面两种对复杂性的定义,

  • 概念复杂性(难以理解):有复杂的、错综关联的或者相互交织在一起的部分,从而导致系统难以理解
    • 复杂的构想比简单的构想,需要更多的思考(因此也需要更多的时间)
    • 复杂的构想,会更有可能包含后续需要修正的缺陷,从而降低相应的生产力
  • 范围复杂性(人类大脑难以消化太多信息):由许多相互关联的部分组成
    • 当项目达到一定规模时,项目中的程序员可能完全不知道项目的其他部分正在发生什么,并且可能会重复开发系统中已经存在的代码
    • 当面对系统的某一部分时,一个小团队的工程师可能会测试他们各自的部分,但是他们看不到与系统其他部分的交互

通过良好的软件工程实践,有可能会降低这种复杂性。
但是通常结果是相同的,随着系统变得愈加复杂,人们必须花费更多的时间来考虑它们,并且缺陷会急剧增加,最终结果就是降低了生产力。

生产力是一种可以被度量尝试预测的项目属性。
当一个项目完成时,如果团队准确记录了项目开发期间完成的任务,那么就很容易确定这个团队(及其成员)的生产力。
尽管过去项目的成功或失败不能保证未来项目的成功或失败,但是过去的生产力是预测软件团队未来生产力的最佳指标
如果你想要改进软件开发过程,那么就需要同时跟踪那些有效的技术和无效的技术,这样才知道在未来的项目中应该做什么(或者不应该做什么)。
为了跟踪这些信息,程序员及其支持人员必须记录所有的软件开发活动。

大多数度量指标都失效了,因为他们测量的是错误的东西
它们测量的是程序员编写的代码量,而不是程序员对整个项目的总体贡献(即生产力)。

虽然目前确实有一些度量指标,但是没有一个是完美的 —— 甚至没有一个非常好的指标。
然而,一个糟糕的度量指标,总比没有指标要好,因此软件工程师仍在继续使用它们,直到出现更好的度量指标。

软件工程生产力研究最重要的一个结果是,提高生产力的最好办法不是通过发明一些方法,让程序员在单位时间内编写两倍的代码,而是减少在调试、测试、记录文档和重写代码,以及给新程序员讲解代码(一旦第一个版本已经存在)上所浪费的时间
为了减少这些浪费,改进程序员在项目中使用的流程,要比培训他们在单位时间内编写两倍的代码容易得多。
软件工程总是在关注这个问题,并试图通过减少所有程序员花费的时间来提高生产力。

虽然生产力对于管理层来说是发放奖金、加薪或口头表扬的重要因素,但是跟踪生产力的真正目的是为了预测未来项目的开发时间。
过去的结果并不能保证未来的生产力,所以你还需要知道如何估计项目进度。

人们在估计小型项目的进度时最容易犯的一个最大错误是,他们会把子任务的时间加到进度表中,而忘记了会议、电话、电子邮件和其他管理任务的时间。
他们还容易忘记增加测试时间,以及发现和修复缺陷(和重新测试)的时间。

中型项目和大型项目会存在小型项目中不存在的问题。

管理层(和某些工程师)经常会认为,当进度开始延后时,程序员总是可以多投入 “几个小时” 来赶上进度。
结果是,进度往往比他们期望的会更加延后(因为他们忽略了工程师大量加班所带来的负面影响)。

在项目进度安排中一个常见的问题是,管理层认为可以通过向项目增加程序员人数来提前发布软件。
然而,正如前面所提到的,这并不一定是正确的。你不能通过在一个项目中增加或者减少工程师人数,就期望项目进度能产生相应的变化。

对子项目的估计,还得考虑是否愿意接受进度预估的结果。

为了能在最后期限之前完成任务,工程师通常每周会投入更多的时间来缩短(实际的)交付日期。
当这种情况发生时,就说该项目处于 “危机模式”。

危机模式可以在短时间内产生效果,使工作在最后期限之前被(迅速的)完成,但是总的来说,危机模式并不总是有效的,并且会导致较低的生产力。
因为大多数人需要照顾工作以外的事情,需要时间休息、减压,需要让他们的大脑花时间来思考已知的所有问题。
当你疲劳工作时,会导致犯错,而错误通常需要更多的时间来纠正。
从长远来看,放弃使用危机模式,坚持每周工作 40 小时会更有效率。

使用危机模式的最佳方法是,在整个项目中添加里程碑,从而产生一系列 “小危机”,而不是在最后生成一个大危机。
每个月多花一天或者几天的时间,比在项目快结束时连续干几周要好得多。

如果运作得当,危机模式也可以帮助你在特定的期限内完成任务。
最好的解决办法是制订更好的进度计划,完全避免使用危机模式。

你可以跟踪在项目期间将时间都花费在了何处,并且很容易的看到直接花费在项目上的时间,以及花费在其他活动上的时间。
如果你的其他活动时间超过总时间的 10%,那么你应当重新考虑日常活动。
你应当试着减少或者整合这些活动,来降低它们对你的工作效率的影响。

如果不是最后期限迫在眉睫,人们往往会放慢工作节奏,当最后期限临近时,他们又会进入 “超级模式”,这是人类的天性。
如果没有目标,那么人们就很难高效的完成工作。
如果没有最后期限,那么人们就很难有动力及时去实现这些目标。

因此,为了提高你的工作效率,一定要有明确的目标和子目标,并将其附加到里程碑上。
从项目管理的观点来看,里程碑是项目中的一个标记点,它代表了工作的进展程度
一个好的管理者总是会在项目进度中设定目标里程碑

能否提高工作效率,取决于你的态度
虽然别人可以帮助你更好的管理时间,或者在你陷入困境时帮助你,但是最重要的是你必须主动改善自己
你需要时刻注意自己的节奏,不断努力提高自己的表现。
通过跟踪自己的目标、努力和进步,你会知道什么时候需要 “让自己振作起来”,通过努力的工作来提高工作效率。

缺乏动力可能是提高工作效率的最大障碍之一。
当然,你做的每一个任务并不都是有趣的,当一个项目让你感到 “缺乏动力” 时,你需要有足够的自我激励
试着创造一些理由,让这份工作更有吸引力。

有时候,无论你多么有动力,你都会对自己的工作感到无聊,也很难集中注意力,你的工作效率会大幅下降。
如果你不能进入状态,无法将注意力集中在任务上,那么就休息一下,做一些其他事情。
不要以无聊为借口,在一个又一个任务之间来回奔波,却又完成不了多少工作。

你应该尽力尝试处理所有分配给你的任务。
虽然这不会提高你的工作效率,但是如果你不断的向其他工程师寻求帮助,则可能会降低他们的生产力。

成为一个卓越程序员的一个方面是,认识到自己陷入困境,需要帮助才能前进。
当你被困住的时候,最好的方法是,设置一个定时闹钟 —— 在被困在这个问题上几分钟、几小时甚至几天之后,寻求帮助。
如果你知道该向谁寻求帮助,那么久直接寻求帮助。如果你不确定,那么就和你的经理谈谈。

软件开发模型

软件开发生命周期(SDLC)

  • 产品概念
  • 需求开发与分析
  • 设计
  • 编码(实现)
  • 测试
  • 部署
  • 维护
  • 退休

软件开发模型描述了软件开发生命周期的所有阶段,在软件项目中是如何组合的。
不同的模型适用于不同的环境。

为什么开发人员不选择一种流行的模型,并将其应用于所有项目呢?
原因是对个人或者小团队效果良好的实践标准,不能很好的被扩展到大型团队中。
同样的,在大型项目中效果良好的技术,也很少能被应用到小型项目中。

常见的软件开发模型

  • 非正式模型
  • 瀑布模型
  • V 模型
  • 迭代模型
  • 螺旋模型
  • 快速应用程序开发模型
  • 增量模型

软件开发模型会描述要做什么,但是在如何做的问题上存在相当大的空白。
软件方法论:由一组原则,以及一组定义了软件开发风格的思想、概念、方法、技术和工具组成的一个系统。
即,在开发软件时,你可以使用各种开发风格

常见的软件开发方法论

  • 传统的(预测型)方法论
  • 自适应型方法论
  • 敏捷开发
  • 极限编程
  • Scrum
  • 功能驱动开发

卓越的程序员应该能够适应团队,使用任何的软件开发模型或者方法论。
也就是说,对于不同的项目来说,有些模型比其他模型更合适。
没有一种方法论,是既可向上又可向下扩展的,因此,你需要根据项目的规模选择合适的模型和方法论

对于小型项目,使用无文档的瀑布模型,可能是一个不错的选择。
对于中等规模的项目,选择迭代(敏捷)模型中的一种是最好的。
对于大型项目,使用顺序模型,或者功能驱动开发模型是最容易成功的(尽管代价也比较高)。

在通常情况下,你不会有机会为项目选择开发模型,除非它们是你的个人项目。
关键是你要熟悉各种模型,这样就能在实际使用时游刃有余。

UML 和用例介绍

因为 UML 本质上是联合起来设计的,所以它包含了许多不同的方法来说明同一件事情,从而导致了大量系统性的冗余和不一致。
那么,为什么还要使用 UML 呢?尽管它有缺点,但是它是一种相当完整的、面向对象设计的建模语言,也是事实上的 IEEE 文档标准。
因此,即使你不打算在自己的项目中使用 UML,也需要在阅读其他项目的文档时能够理解它。

  • UML 用例模型
    UML 通过用例来描述一个系统的功能。一个用例大概会对应一个需求。
    设计者会创建一个用例图,从一个外部观察者的角度来指定系统需要做什么,这意味着他们只管系统做什么,而不管如何做
    然后,设计者会创建一个用例故事来描述设计图的细节。

  • 用例图的元素

  • 用例包

  • 用例包含

  • 用例泛化

  • 用例扩展

  • 用例故事

  • 用例场景

UML 活动图

UML 活动图,传统上称为流程图,用来说明系统不同组件之间的工作流程。

  • 开始和结束状态
  • 活动
  • 状态
  • 转移
  • 条件
  • 合并点
  • 事件和触发器
  • 分叉和合并(同步)
  • 调用符号
  • 分区
  • 注释和注解
  • 连接器

UML 类图

类图是在程序中定义数据类型、数据结构和数据操作的基础。
UML 的创建者希望能有一个正式的系统,用来设计面向对象的软件,以取代当时(20 世纪 90 年代)使用的结构化编程。

  • 类图中的可见性
    • 公共的类可见性
    • 私有的类可见性
    • 受保护的类可见性
    • 包级别的类可见性
    • 不支持的可见性类型
  • 类属性
    • 属性可见性
    • 属性派生值
    • 属性名称
    • 属性数据类型
    • 操作数据类型(返回值)
    • 属性多重性
    • 属性初始值
    • 属性字符串
    • 属性语法
  • 类操作
  • UML 的类关系
    • 类的依赖关系
    • 类的关联关系
    • 类的聚合关系
    • 类的组合关系
    • 关系特性
    • 类的继承关系
  • 对象

UML 交互图

交互图用来为系统中不同对象(主体)之间发生的操作建模。
在 UML 中有三种主要类型的交互图:时序图协作(通信)图计时图

  • 时序图
    时序图是按照事情发生的顺序来显示各个主体(参与者和对象)之间的交互的
    活动图用来描述对象上某个操作的细节,而时序图用来将活动图联系在一起,从而显示多个操作发生的顺序。
    从设计的角度来看,时序图比活动图能够提供更多的信息,因为它们说明了系统的总结架构。

  • 生命线

  • 消息类型

  • 消息标签

  • 消息序号

  • 守卫条件

  • 迭代

  • 长延时和时间约束

  • 外部对象

  • 激活条

  • 分支

  • 可选流

  • 对象的创建和销毁

  • 时序片段

  • 协作图
    协作(或者通信)图提供了与时序图相同的信息,但是其形式更加紧凑。
    在协作图中,我们不是在生命线之间绘制箭头的,而是在对象之间直接绘制消息箭头,并在每条消息上添加数字来表示顺序。

其他 UML 图

  • 组件图

  • 包图

  • 部署图

  • 合成结构图

  • 状态图

  • 组件图
    UML 使用组件图来封装可重用的组件,比如一些库和框架。

  • 包图
    UML 包是其他 UML 项目(包括其他的包)的一个容器。
    UML 包相当于文件系统中的子目录、C++ 和 C# 编程语言中的命名空间,或者 Java 和 Swift 编程语言中的包。

  • 部署图
    部署图展示了一个系统的物理视图。
    物理对象包括 PC、打印机 和 扫描仪 等外设,以及服务器、插件接口主板和显示器等。

  • 合成结构图

  • 状态图
    UML 状态图(或者状态机)非常类似于活动图,因为它们也展示了通过某个系统的控制流。
    其主要区别在于,状态图仅仅显示了系统可能存在的各种状态,以及系统如何从一个状态转换到另一种状态。

系统文档

系统文档用来说明 系统需求、设计、测试用例 和 测试过程。

系统文档类型

  • 系统需求规范(SyRS)文档:系统级的需求文档,除了软件需求,它还可能包括硬件、业务、过程、手册和其他与软件无关的需求。
  • 软件需求规范(SRS)文档
  • 软件设计描述(SDD)文档:描述了系统如何构造(而 SyRS 和 SRS 文档描述了系统将要做什么)。
  • 软件测试用例(STC)文档:描述了各种用例,用来验证系统是否满足所有的需求,以及除此之外,它是否能够正确的运行。
  • 软件测试过程(STP)文档:描述了如何有效的执行(STC 文档中规定的)软件测试用例,从而验证系统是否可以正确运行的过程。
  • 需求(或反向)可追溯矩阵(RTM)文档:通过 RTM 文档,软件的利益相关方,可以验证软件设计和代码是否实现了需求,以及测试用例和测试过程,是否正确的检查了需求的实现。

系统文档最大的问题就是一致性。当你更改了某个需求时,可能也需要更改 SDD、STC 和 STP 文档中的内容。
因此,最佳实践是使用可追溯性,它允许你轻松的从一个文档追溯到所有其他的系统文档。
如果可以从需求追溯到设计元素、测试用例和测试过程,那么你就可以在修改某个需求时,快速定位并更改这些内容。

反向可追溯性允许你从测试过程追溯到相应的测试用例,以及从测试用例和设计追溯到它们相应的需求。
例如,你可能会遇到需要更改某个测试过程的情况,那么就可以通过反向可追溯性,定位到相应的测试用例和需求,以确保你对测试过程的更改,仍然可以满足这些测试用例和需求。
通过这种方式,反向可追溯性还可以帮助确定是否要更改测试用例或者需求。

建立文档可追溯性的方法

  • 创建一个标识符或者标签,将需求、设计、测试用例或测试过程文档关联起来。
  • 创建一个 RTM(需求/反向可追溯性矩阵),它可以跟踪系统文档中各个内容之间的链接,尽管 RTM 是另一个你必须维护的文档,但是它提供了一种完整且易用的机制来跟踪系统中的所有组件。

确认,是一个过程,表明产品符合最终用户的需求(我们开发的是正确的产品吗)。
验证,是确保你按照项目规范开发了产品(我们是用正确的方式开发的产品吗)。
确认发生在需求的结束阶段整个开发周期过程中,而验证通常发生在软件开发周期的每个阶段末尾,来保证该阶段满足所有的输入需求。

文档成本通常是项目总成本的一个主要组成部分,一是因为有太多的文档,二是因为文档之间是相互依赖的,这使得它们难以更新和维护。
与需求阶段相比,在涉及(架构)阶段纠正错误的成本是其 3 倍,在编码阶段是其 5~10 倍,在系统测试阶段是其 10 倍。

需求阶段(SyRS 和 SRS 开发)是最需要确认行为的阶段。
如果你坚持要求客户理解并同意所有的需求,然后再进入后续阶段,那么要确保没有客户不想要的需求,并且确认解决的是客户所面临的问题;
否则,你就会花好几个月的时间记录、编码和测试一个程序的功能,结果客户说:“这不是我想要的”。

一个良好的确认过程,可以帮助减少出现这种情况的可能性。

  • SyRS(如果有的话)
    • 每个现有需求都很重要吗?这些需求是否描述了客户想要的某些功能?
    • 每个需求都是正确的吗?它们是否准确(没有歧义)的说明了客户想要什么?
    • 是否有遗漏任何需求?
  • SRS
    • SyRS 文档中列出的所有软件需求(如果有的话)是否也在 SRS 文档中列出了?
    • 现有每个需求都很重要吗?这个功能对系统架构师来说重要吗?客户是否同意?
    • 每个需求都是正确的吗?它们是否准确(没有歧义)的说明了软件必须做什么才有效?
    • 是否有遗漏任何需求?

验证应该在软件开发过程的每个阶段之后进行。
尤其是在完成 SRS 文档之后,应该有一个与每个系统文档相关的验证步骤

  • SDD
    • 设计组件是否完全覆盖了 SRS 文档中所有的需求?
    • 在需求(多)和软件设计元素(一)之间是否存在多对一(或者一对一)的关系?虽虽然一个设计元素可能会满足多个需求,但是不应该用多个设计元素来满足一个需求。
    • 一个软件的设计元素,是否为实现指定的需求提供了准确的设计?
  • STC
    • 需求和测试用例之间是否存在一对多(或者一对一)的关系?(也就是说,一个需求可以有个多个关联的测试用例,但是不应该有多个需求共享同一个测试用例)
    • 测试用例是否准确的测试了相关需求?
    • 所有与需求相关的测试用例,是否完全测试了该需求的正确实现?
  • STP
    • STC 测试用例与 STP 的测试过程之间是否存在多对一关系?也就是说,一个测试过程是否实现了一个或多个测试用例,而每个测试用例仅由一个测试过程来处理?
    • 测试过程是否准确的实现了其所有相关的测试用例?

需求文档

需求说明软件必须做什么才能满足客户的需求,具体来说,包括:

  • 系统必须执行哪些功能(功能性需求)
  • 系统必须多好的执行它们(非功能性需求)
  • 软件运行必需的资源或设计参数(即各种约束,也是非功能性需求)

好需求的特点:正确的、一致的、可行的、必需的、有优先级的、完整的、明确的、与实现无关、可验证的、原子的、唯一的、可修改的、可追溯的、积极描述

积极描述:一个需求应该说明什么必须是真实的,而不是什么是必须不能发生的
大多数消极描述的需求是不可能被验证的。
例如:

  • 消极需求:泳池监控器不得在气温低于零度的情况下工作
  • 积极需求:当温度降至零度以下时,泳池监控器将自动关闭

范围描述了软件产品的名称,解释了该产品可以做什么,如果有必要,则也可以说明它不能做什么
不要担心这违背了 “积极描述” 原则,因为这是对范围的描述,而不是对需求的描述。

软件设计描述文档

软件设计描述(SDD)文档为软件的设计提供了底层的实现细节。
虽然它不一定要深入到实际代码的层次,但是确实为软件提供了算法、数据结构和底层流程控制的实现。

设计关注点,是系统设计中利益相关方所感兴趣的任何东西。
利益相关方,是对系统设计有发言权的任何人。

设计视图(可能)提供了系统设计的多个视角,来帮助向利益相关方、设计人员和程序员阐明,设计是如何满足一个设计观点相关需求的。

软件测试文档

软件开发测试级别

  • 组件测试(单元测试):用来处理底层代码中的子程序、函数和模块
  • 组件集成测试(集成测试):将独立的单元组合在一起,以形成更大的系统部分,尽管不一定是整个系统
  • 系统测试(系统集成测试):是集成测试的最终形式 —— 将所有的程序单元集成在一起,形成了完整的系统。
  • 验收测试(包括工厂验收和现场验收):是开发完成之后的一个阶段,它指的是客户如何确定系统是否是可接受的。

软件测试计划是描述与测试流程相关的范围、组织和活动的文档。
它是对如何进行测试、测试所需的资源、测试时间进度表、测试必备工具和测试目标的一个管理性概况。

遗憾的是,不可能为每个测试用例都创建自动化测试
无论测试是正式的、非正式的还是自动化的,拥有一个可重复的测试过程都至关重要。

回归测试尤其需要一个可重复的测试过程,因为当你对代码进行了更改之后,它需要检查是否有功能被破坏或者受到影响。
你需要某些测试用例来确保可重复测试。

请记住,创建自动化测试过程可能会很昂贵,并且你必须验证所生成的代码,以确保它正确执行了所有测试。
从长远来看,自动化测试过程往往是经济高效的,因为在所有项目(除了最小项目)中,你会在开发过程中多次重新运行测试过程。

@thzt thzt changed the title 《编程卓越之道 - 卷 3:软件工程化》—— Randall Hyde 《编程卓越之道(卷 3):软件工程化》—— Randall Hyde Feb 15, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant