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

《实例化需求》—— Gojko Adzic #73

Open
thzt opened this issue Sep 20, 2022 · 0 comments
Open

《实例化需求》—— Gojko Adzic #73

thzt opened this issue Sep 20, 2022 · 0 comments

Comments

@thzt
Copy link
Owner

thzt commented Sep 20, 2022

实例化需求

实例化需求说明的原则和实践主要影响软件交付团队中的人员沟通,以及他们如何同使用者和项目干系人进行协作。我确信许多工具供应商会试图卖给你一套技术方案。如果有工具可以立即消除遇到的问题,许多经理会乐于为此买单。不幸的是,他们遇到的主要是人的问题,而不是技术问题。

比尔·盖茨说过:“在企业中应用任何一项技术时,首要的法则是,在有效率的系统中导入自动化,将使效率倍增。第二条法则是,在缺乏效率的系统中导入自动化,会使效率更低下。”很多团队在使用实例化需求说明的时候失败了,他们使用自动化工具反而导致他们的过程更加低效。我不想把注意力放在特定的工具上,相反,我想侧重分析团队努力实现这些想法的真实原因。一旦你们能正确地沟通和协作,你们就可以选择适合的工具去使用。

在2010年的伦敦领域驱动开发交流大会① 上,Eric Evans跟别人争论,说敏捷作为一个术语已经失去了一切意义,因为现在什么都可以称为敏捷。很不幸的是,他是正确的。尽管有大量的著作讲如何正确地实施极限编程、Scrum以及其他不那么流行的敏捷过程,但我见过太多太多的团队,试图去实现冠以敏捷一词的过程,但那些过程又显而易见地违背了敏捷的精神

主要优点

想要有效地构建正确的产品,软件开发实践必须满足以下几点。

  • 保证所有项目干系人和交付团队的成员都对需要交付哪些东西有一致的理解
  • 准确的需求说明,这样交付团队才能避免由模棱两可和功能缺失造成的无谓返工
  • 有用来衡量某项工作是否已经完成的客观标准
  • 具有引导软件功能或团队结构变更的文档

实例化需求

  • 更有效地实施变更。他们拥有活文档——系统功能的可靠信息来源——让他们得以分析潜在变更的影响,同时可以有效地分享知识。
  • 更高的产品质量。他们清晰地定义了预期,使得验证过程很有效率。
  • 更少的返工。他们在需求说明上协作得更好,并确保所有团队成员对预期达成共识。
  • 同一项目不同角色的活动协调得更好。改善协作形成定期的交付流程。

当时遗留系统中的一部分功能只有少数几个人了解,而现在情况已经好很多了,那是因为团队拥有一套针对那部分功能的、不停增长的测试套件(活文档),它描述了该遗留系统的功能。当专家休假时,还可以从活文档系统中寻找问题的答案。对其他开发人员来说,可以更清晰地了解软件中某部分的功能。并且还是测试过的。

在团队认可一个故事之前,与产品负责人一起审核测试场景可以使工作小组(产品负责人、开发人员和测试人员)澄清模棱两可或缺失的需求。有时,会议结果甚至会把故事给撤销了,例如,测试场景会揭露出系统隐藏的复杂性或相互矛盾的需求。有一次,进行这样的讨论之后,大家做出的决定是几乎重新设计整个功能。

通过举行这些会议,我们发现自己的生产力和效率都提高了,因为减少了浪费,而且模糊和需求缺失的程度降到了最低。同时还让团队对预期达成了共识。
大多数团队显著地减少或完全消除了由于误解需求或忽视客户的期望而造成的返工。

关键过程模式

  • 从目标中获取范围

成功的团队不会盲目地接受软件需求,将其作为未知问题的解决方案,相反,他们会从目标中获取范围。他们以客户的业务目标为起始,然后通过协作界定可以实现目标的范围。团队与商业用户一起工作确定解决方案。商业用户专注于传达所需功能希望达到的目的,以及他们期望由此带来的价值,这样有助于所有人了解所需的功能。然后团队提议一个解决方案,这要比商业用户自己想出来的方案更实惠、更快,并且更容易交付或维护。

  • 协作制定需求说明

在设计需求阶段,如果开发人员和测试人员都没有参与,那么我们就必须单独将这些需求传达给他们。在实践中,这很容易造成一些误解,在需求传递的过程中会丟失很多细节。结果是,商业用户不得不在软件交付后进行验证,而如果验证失败,那么团队必须回去修改软件。这些都是不必要的返工。

成功的团队不会依赖于某个人独自去收集正确的需求,而是会与商业用户一起协作制定解决方案。

协作制定需求说明使我们能够充分利用整个团队的知识和经验。它还创造了需求的集体所有制,让每个人能更多地参与到交付过程中。

  • 提炼需求说明

详细描述如何实现某个功能,而不去描述需要什么功能,这是一种浪费行为。过多的细节会让实例更难沟通和理解。

他们应明确软件该做什么,而不是软件该如何工作。

  • 自动化验证时不修改需求说明

人工运行这些检查会产生不必要的延误,并且反馈也会较慢。
快速反馈是短迭代或流程模式软件开发中一个必不可少的元素,所以我们要把验证系统的流程变得廉价而且高效。一个显而易见的解决方案就是自动化。

  • 频繁验证

为了有效地支持软件系统,我们需要知道它在做什么以及为什么那么做。在许多情况下,唯一的办法是深入研究程序代码或者找到能帮我们做到这点的人。代码往往是我们唯一能真正信任的东西,而大多数编写好的文档在项目交付前就已经过时了。程序员是知识的权威和信息的瓶颈。

商业目标是项目或项目里程碑的潜在原因。它是帮助商业项目干系人(无论是内部的还是外部的)决定投资软件开发的指导性愿景。商业组织应当能清晰地看到这些目标如何赚取、节省或者保护财富。

可度量的目标让我们可以确定项目成败、跟踪进度和更好地排列优先级。

活文档

目前,我们一般认为实例化需求说明的过程和工件有两种流行的模型:以验收测试为中心的模型和以系统行为规范为主导的模型。

以验收测试为中心的模型(通常称为验收测试驱动开发,ATDD或者A-TDD)侧重于自动化测试,并把它作为实例化需求说明过程的一部分。这个模型的主要优点是开发目标更加明确,并且可以防止功能退化。

以系统行为规范为主导的模型(通常称为行为驱动开发或者BDD)侧重于制定系统行为的场景。它的主要工作是通过协作和需求澄清,在项目干系人和交付团队之间建立起共识。该模型认为,通过自动化测试预防功能退化也很重要。

开始改变

检查需求的完整性和一致性的最好方式,就是针对它们设计黑盒测试。

对一个遭受‘测试瓶颈’之苦并且不断对抗开发变更的测试人员来说,通过自动化测试来提供有价值的反馈(甚至是在修复缺陷之前)是非常、非常、非常具有吸引力的。这是一个需要努力实现的激励性愿景。

项目的成功需要得到管理层的支持,尤其是当已经有一个系统的时候,因为要使之达到运行良好、稳定的程度,必定需要花费相当多的时间。你必须参与所有的迭代,查看哪里尚未稳定、哪里出现奇怪的响应,修正它们,并不断重复。经过大约一年的时间,你才会得到一个非常宝贵的系统。反之,如果不那么做,或者你认为手工进行用户界面测试能很快修正缺陷,那么你最终得到的系统将会是一个难以维护并且价值不大的东西。

如果管理层不予支持,反而施加压力,那么大家就会退回到做事的老路上去,并开始保护他们自己的位置,而不是进行协作。

不要让测试自动化成为最终的目标
团队只关注测试自动化时,就不会更好地协作

不要太关注工具

团队过分关注于工具,而不是去关注高层次的协作过程变更。最终,他们浪费了很多时间和精力,建立的一套技术性测试方法让业务人员和测试人员根本没法使用。

从目标中获取范围

  • 构建正确的范围
    • 理解“为什么”和“谁
    • 理解价值从何而来
    • 了解商业用户预期的输出是什么
    • 让开发人员提供用户故事的“我想要”部分
  • “在没有高层次控制权的情况下,协作确定范围
    • 询问“为什么这些东西有用?
    • 询问替代方案
    • 不要只顾最低层次的需求
    • 确保团队交付完整的功能

设计师没有直接去实现那些需求,而是去寻求对问题更深入的理解。一旦有了更深的理解,他们就可以找出真正的目标,并从那些目标中形成他们的设计,而不是从建议的解决方案或有关功能的随意期望中获取设计。这是成功产品设计的本质,在飞机研发中如此,在软件设计中也同样重要。

与我一起共事过的商业用户和客户,大多喜欢把需求描述成解决方案;他们很少会去讨论想要达到的目标,或者亟待解决的问题具有什么特殊性质。我见过太多的团队有一种危险的误解,他们认为客户总是正确的,客户要求的东西总是一成不变的。这导致很多团队盲目地接受客户建议的解决方案,然后竭尽全力去实现。成功的团队不会那么做。

成功的团队不会把定义范围的责任推到其他人身上,他们会积极协作,同商业用户一起,确定良好的范围,以期达到他们的目标。

要求商业用户提供范围,实际上是依赖没有软件设计经验的人来提供高层次的解决方案。

构建软件系统最难的部分是精确地定义构建的是什么。问题的表述常常比它的解决方案更重要。

人们告诉你他们自己认为需要什么,通过问他们‘为什么’,你可以找到背后的目标。

要评估一个建议的解决方案,理解为什么需要某些东西以及谁需要它是至关重要的。

理解价值从何而来,除了可以帮助我们设计出更好的解决方案以外,还可以极大地帮助我们排列优先级。

比起只在故事层次上进行讨论,在目标层次上进行讨论能使团队更加高效地处理范围和优先级。讨论目标使他们不必浪费时间在单个故事的估算上。

当很难确定目标的时候,预期系统的输出是比较有用的出发点:研究为什么需要这些输出以及软件如何提供这些结果。一旦你明确了期望的输出,就可以专注于实现这些输出背后的需求。分析为什么需要这些输出结果可以帮助你构想出项目的目标

目标中获取正确范围的一个好方法,就是坚定不移地把提供解决方案的责任交付给开发团队。

我们应该问询高层次的实例,说明某个功能如何产生实际价值,而不是去问询技术上的功能需求说明。这将指引我们找到真正的问题。

提出“为什么需要这些东西”这样的问题,听起来有点质疑的口气,这会让他人产生防御心态,特别是在大型组织里更是如此。反之,询问“为什么这些东西有用”,这种方式以讨论的口吻开始,便不会挑战任何人的权威性。

有时候,大家解释某个功能的价值时,仍然会有困难(即便是要求提供实例时)。更进一步,我会让他们举出一个例子,说明如果系统不提供某个功能,他们会怎么做(临时解决方案)。通常这会帮助他们表达出特定功能的价值。

寻求替代方案可以让功能的提出者再次进行思考,所提议的解决方案是否是最佳方案。同时,也应该与交付团队一起讨论替代方案。

举例说明

每个人都会创造自己的例子,且不说这些例子是否完整,连例子的一致性都无法确保。这就是为什么在软件开发中最终结果往往会背离最初设想的原因。

例子是避免沟通问题的好工具。如果我们自始至终都能够捕获所有的例子,并在分析、开发和测试中一致地使用它们,那么我们就可以避免进入传话游戏中(而导致信息失真)。

举例说明需求所用的时间要比去实现它们少得多。

反馈调查是检验一组人员是否对需求说明达成共识的好方法。在讨论过一个故事之后,如果有人对该故事提出了一个特殊用例,工作坊的主持人必须请参与者写下他们认为系统应该怎样工作。然后比较全组的回答。如果所有人的回答都一致,那么每个人对需求说明的理解就没有出入。如果回答不一致,那么一种有效的作法是按回答的结果分成多个小组,每个小组选出一个人来解释他们的回答。通过讨论可以揭示误解的根源。

如性能、易用性或者反应时间这类特性,往往称为非功能性的,因为它们与单独的功能没有关系。通常来讲,我反对将需求按功能性、非功能性来分类。那些列为非功能性的需求通常说明团队还未明确地找到该功能的项目干系人

到目前为止,我还没有看到过一个非功能性需求无法使用例子来说明的情况。

清晰地指定性能条件和举例说明可以帮助我们建立共识,并可以给开发团队提供清楚的目标实现。
要比现有系统快”不是一个好的性能需求。要告诉人们具体需要多快以及用的是何种方式。

举例说明不可量化的功能,行之有效的一种方式是建立一个参照的例子。

始终使用同一组例子贯穿需求说明、开发以及测试阶段,以确保全体人员对需要交付的内容有同样的理解。比起在实施阶段才发现问题,真实的例子有助于更快地发现不一致的地方和功能分歧。

提炼需求说明

  • 实例要精确可测
  • 脚本不是需求说明
  • 不要使用流程式的描述
  • 需求说明应关注业务功能,而不是软件设计
  • 避免编写与代码紧密耦合的需求说明
  • 不要在需求说明中引入技术难点的临时解决方案
  • 不要陷入到用户界面的细节里
  • 需求说明应该是不言自明的
  • 使用叙述性标题并使用短篇幅阐释目标
  • 展示给别人看并保持沉默
  • 不要过度定义实例
  • 从简单的例子入手,然后逐步展开
  • 需求说明要专注
  • 在需求说明中使用“Given-When-Then”语言
  • 不要在需求说明中明确建立所有依赖
  • 在自动化层中应用缺省值
  • 不要总是依赖缺省值
  • 需求说明应使用领域语言

成功的团队不会直接使用原始的例子,他们会从中提炼需求说明。他们会从关键实例中提取最重要的特性,将其转化成清晰明确的定义,并去掉不相干的细节,使得实现变得完整。验收条件就这样被记录下来并描述清楚,任何人在任何时候都可以拿到最终的需求说明并读懂它。这种带实例的需求说明捕获了让人满意的条件、功能的预期输出以及它的验收测试

一段很好的需求说明,加上实例,实际上就是它所描述的功能的验收测试。

需求说明必须是衡量成功与否的客观标准,可以明确地告诉我们何时完成了开发。它必须包含可验证的信息(输入参数与预期输出的组合),可用来对系统进行检查。

脚本解释如何测试某个东西。需求说明解释系统在做什么。

要提防流程式的描述(先做这个,然后做那个……)。除非你是在制定一种真正的处理流程,否则这通常是使用脚本来描述业务规则的信号。这样的脚本会造成许多长期维护的问题。要提防关于系统应该如何工作的描述(怎么做)。我们应当去考虑系统应该做什么(做什么)。

原则上,需求说明不应该牵涉到软件设计。它应该解释业务功能,对软件如何实现不做任何规定。这样做有两个目的:

  • 让开发人员在当下找出最佳的解决方案
  • 让开发人员在未来改善他们的设计

关注于业务功能的需求说明,没有描述实现细节,这使得修改实现更加容易。
与代码紧密耦舍、严密体现软件实现的需求说明会让测试异常脆弱。

要当心需求说明中来自软件实现但并不属于业务领域的名称和概念。比如说数据库标识符(Id)、技术服务名或者对象类名等非直接性的领域概念,以及纯粹出于自动化目的而创造的概念。请重组这些需求说明,避免使用此类概念,这样它们会更易于理解和长期维护。

不要老想着用户界面的细节,考虑用户在站点上的操作过程会更加有用。

为了检查需求说明是否是不言自明的,可以让其他人来察看文档并试着理解它,而你什么都不要说。为了确保需求说明真的能不言自明,可以请其他人来解释他们对需求的理解,看看是否符合你的意图。

一般来说,一个需求说明应该要声明上下文环境,指定一个单一的动作,然后定义好预期的后置条件。可以提醒我们这么做的一个好方法是使用Given-When-Then或者Arrange-Act-Assert。

  • 假定(Given)一个前提
  • 当(When)某个行为发生时
  • 那么(Then)后置条件就会得到满足

自动化验证而不修改需求说明

如果你工作于一个错综复杂的系统,那么理解自己的职责边界所在是很重要的。请沿着这些边界说明需求并自动化测试。

如果可以,尽量避免录制用户界面自动化。录制的脚本除了几乎无法理解外,还难以维护。它降低了创建脚本的成本却会显著增加维护成本。

自动化层应该定义如何进行测试,而需求说明则应该定义要测试什么内容

频繁验证

找出最烦人的问题并将其解决掉,然后不停地重复
不要想一下子就解决问题,更有效的策略是一个迭代一个迭代地做些小的改变。

找到最烦人的问题并将其解决掉,然后别的问题又会变成最烦人的问题,解决之后,目标再次转向另一个最烦人的问题。如此不断重复,最终的系统将会非常稳定,非常有用。

只要可执行需求说明在持续构建(CI)系统中运行,我们就可以从测试的运行历史中看到哪些测试最不稳定。

如果你的应用程序需要部署,并且要运行功能测试,那么保证可重现性的第一步就是确保专用的部署环境。没有专用的环境,很难知道测试失败是由于存在缺陷、由于有人对测试环境做了改变,还是由于系统不稳定所导致的。专用的环境可以排除计划外的改变,并可以降低环境不稳定的风险。

全自动部署可以确保升级只有唯一的标准流程,同时可以确保所有的开发人员拥有和测试环境一样的系统部署。部署必须完全自动化才行。完全不需要或不允许手动干预。(注意我是说可以按需执行的全自动部署,没必要自动触发部署。)如果安装程序需要处理管理员控制台或者半自动化的手工脚本,那么它就不算完全自动化。特别值得一提的是,全自动部署还包括自动化的数据库部署。

等待事件,而非等待固定时长。等待一个事件的发生,而不要等待一段时间的流逝。这会使测试可靠得多,而且不会造成不必要的反馈延迟。

不要在一个大的可执行需求说明中同时测试太多东西。

为当前迭代创建一个测试包
当前迭代的变更,会影响系统中最不稳定和最重要的部分,清晰地划分出当前迭代测试包可以让我们快速地获得有关这部分反馈。

创建已知失败了的回归测试包
当你发现回归测试失败了,并且你不决定马上去进行修复,那么请把相关测试加入到单独的集舍中,这样即使测试失败了也不会影响主要的验证集舍。

自动检查那些被禁用的测试
如果你有禁用的测试,请自动监控它们。

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