唯快不破的 TDD

发布于 2024-10-09 12:50:20 字数 7714 浏览 35 评论 0

本文将辨述关于 TDD 的观点与论战,给出我认为的作为编码任务管理的 TDD 如何为个人开发提升效率,为读者减少分辨、纠结于各类 TDD 论战上的时间浪费,尽快投入实践;最后,我会给出一个 TDD 的工作流,以及作为个人效率工具的 TDD 实践如何落地,从而达到及时反馈,不断进步的目的。

我眼中的 TDD

TDD 究竟是什么?究竟有没有用?使用了 TDD 是否真的能自然驱动出设计来?TDD 是否能应对频繁变化的需求呢?为什么要先写显然不能通过的测试用例?各路大神论战不休,我究竟听谁的?如何形成自己对 TDD 的看法?

在这其中,TDD 有没有用,是最首当其冲的问题。其次,无论是否有过 TDD 实践,相信很多人也对 TDD 能否驱动出设计来这个问题有所思考与观点。第二个问题是很有意思的,答案也出人意料地简单。不过,先让我们来看看第一个问题,即 TDD 究竟有没有用。要回答这个问题,我们就必须界定一下,什么是 TDD。

TDD 究竟是什么

我认为, TDD 是一种用于提升个人开发效率的方法论。它通过避免编写无效代码、缩短反馈周期等方式减少浪费,在你已有需求、有了设计的情况下,帮助你快速、高效、高质量地完成既定编码任务 。它的理念总结起来,有三点:

  • 快速反馈
  • 减少浪费(花在编写无效代码上的浪费、各种纠结的浪费)
  • 天下武功,唯快不破

总而言之,TDD 是一种方法论,就好比 GTD 是时间管理的方法论,PKM 是个人知识管理的方法论一样,TDD 是一种提高个人开发效率的方法论。简单来说,它帮你更快地完成代码,不仅如此,它还能促使你更高效、更高质量地完成。

因此,在讨论 TDD 时,我们所提的问题,都应该从 TDD 是否真的能有效提高效率的角度来回答。当你在问 TDD 究竟有没有用时,其实你是在问,TDD 是否真的能有效提高开发效率?相比其他需求/任务管理的方式,TDD 优秀在哪里?很关心这个问题的同学,请跳到 TDD 工作流与实践方式 一节,我会告诉你,TDD 提高了哪些方面的效率,以及我们怎样来实践。在此之前,对于 TDD 仍有某种存在而挥之不去的疑惑的同学,请接着往下看,我会进一步定义 TDD 的边界,即哪些问题,你不能向 TDD 寻求。

TDD 不是什么

我所见关于 TDD 的边界讨论中,最常见的三个东西便是: 需求设计代码质量

  • TDD 不能帮你设计 - 如果你本来不会设计、没做设计
  • TDD 不能帮你明确需求 - 如果你本来需求就不确定
  • TDD 不能帮你提高代码质量 - 如果你本来就不知道怎么写整洁代码

你说的我都懂,但我就想知道 TDD 究竟能不能自然地驱动出设计来呢?

如果这里的“设计”做“特定适合于当前问题域的设计”解,那么回答是,不能。

我对 TDD 的定义中,讲到它是一种当你“有了设计”之后发挥作用的技术。也就是说,设计不仅是 TDD 之前需要做的工作,而且是需要设计知识来发挥作用的领域,它是在 TDD 的能力边界之外的。熊节 3 年前已经在 虚拟座谈会 上谈到,要使用 TDD 的前提就是你要:

  1. 会做设计
  2. 做设计

尽管,TDD 的实践手段确实会使得驱动出来的代码具备一定的特征,比如输入输出明确、依赖更加独立等,但我认为这种特征并不是定向的,更不能认为其就是特定于当前问题域的恰当设计。想通了这个边界,事情就很自然了:如果你本来就不知道如何写出整洁代码,那么用了 TDD 显然也不能写出整洁代码(而是应该去看 重构 或者 编写可读代码的艺术 );如果你本来就不知道如何设计,那么用了 TDD 显然也不能“驱动”出设计(而是应该去看 设计模式 )。

TDD 论战回顾

历史上发生过几次比较大的 TDD 论战,从可以看到的时间顺序大概是:

  1. 2011 年 2 月 12 日,CoolShell 博主陈皓首先提出, TDD 并不是看上去的那么美 ,提出了关于测试范围、TDD 与设计方面的反对意见
  2. 2011 年 2 月 23 日,infoq 主编张凯峰随即组织了一场虚拟座谈会 TDD 有多美? 继续论战,这里熊节提到了 TDD 并非毫无设计,而是相反
  3. 同日,博客园一位博主 Todd Wei 撰文 TDD 到底美不美 ,力挺 CoolShell,并提出 TDD 无法应对需求频繁变动场景的诘难
  4. Martin Fowler, Kent Beck, DHH 三位大神的 Is TDD Dead 讨论

讨论是有益的,但是过多纠结在各方观点中无法决断、无法形成自己的观点,则是最大的浪费。我通读了这些论战,就其中观点与同事交流过,最后形成了自己的看法,希望这一小节,能让读者进一步明白我眼中的 TDD 是/不是什么,放下纠结,挽起袖子就是干。仝健老师说, 纠结才是最大的浪费 。我觉得很对。

TDD 并不是看上去的那么美 一文中,作者关于 TDD 本身的论点几乎全是错的,这恰好完美验证了他在 这里 所提到这篇文章的真正论点:最重要是人的问题。正因为 TDD 并没有看上去那么简单,需要很多动手实践和思考,所以才不是适用于所有人。毕竟,人是软件项目中最复杂的因素,也是倾向于抵抗力最小路径的,凭什么要求别人去学会一种如此复杂、需要耐心和方法坚持的方法论实践法?

这个观点我其实是同意的,不过它谈到的是从组织角度讲 TDD 推行所存在的问题。推行,这是另一个问题域,好比你会写代码和会教别人写代码是完全不同的事情一样。本文不会涉及 TDD 如何在组织推行的问题,也不会涉及 TDD 的流程价值,而只谈,作为个人开发效率提升工具的 TDD。如果你觉得,你想要更高效更高质量地写代码,这篇文章可能有帮助。

然后,熊节在 TDD 有多美? 则提出,TDD 作为一种设计方法,本来就要求你会设计做设计。首先厘清了 TDD 与设计的关系问题:TDD 促进你思考、表达你已有的设计,但不能替代它。如果你本来就不会设计,那你该先学会设计;如果你会但你不做,那谁也帮不了你,顶多就是促使你思考一下。

那么同理,代码质量与设计也是一样的道理:TDD 的工作方式(重构环节)促使你写出更整洁的代码,但如果你不会、不做,那也别赖 TDD,换谁都没办法。

接着是 Todd Wei 的这篇文章: TDD 到底美不美 ,它提出了一个更难回答的问题,即 **TDD 与需求的关系 **问题:TDD 适用于需求非常固定的场景,然而不适用于需求经常变动的场景,毕竟需求变了就要改测试,测试扔了不就是所有成果都白费了么。

这个问题我也纠结了很久,于是我发了一封邮件请教了一下仝健和小波等老师。测试作为需求的等价表述,需求变,测试肯定要变,这个不管你是 TDD、先写测试后写测试都一样——除非你不写测试。那么上面的诘难其实是,需求变了怎么办? 改呗 。那需求反正肯定是要变的,这是软件工程界的共识,不管你用什么开发方法都一样。那我能做的,就是改起来也快。如何做到呢?那可不就是 TDD 的问题域了嘛。

“我做得比你快,我改得也比你快。”改起来越快,成本就越低,扔起来就越不心疼,你做实现的限制就越小。这正是我们提倡已久的理念: 天下武功,唯快不破

TDD 工作流与实践方式

终于进入工作流与实践法了,本节讲述:怎样更快。

任务分解 Tasking

任务分解是 TDD 工作流的第一步,也是重中之重。它的目标,是对已有需求做一个完全穷尽、独立的分解,产出一个任务列表。这个列表不需要是粒度很细的测试用例(也不推荐是),只需要从逻辑上完全覆盖业务上的需求即可。以 FizzBuzzWhizz 这个问题为例,结合 JUnit 5@Nested 注解,我甚至可以直接把我的 tasking 转化成代码,达到 todolist as code 的效果,从而避免了维护一个额外的任务列表的重复。

为什么一个不多不少描述原有需求的 tasking 如此重要呢?它是你减少浪费的基石:凡是不在需求中的任务,我们不会去 task。那么你可能会问,我就是想要设计得尽可能灵活以应对未来可能的需求变化啊。如果你听说过 YAGNI(You Ain't Gonna Need It) 原则,那么你就会明白了:整个 TDD 方法论都是建立在变化一定会发生这个基础上的。那些仅仅是“可能”会发生的事情,我们不会去做,仅凭这个就可以节省大量精力了。那么需求真的变了怎么办?改呗。我们从来没有保证过 TDD 会让应对变化变得容易,但它会让你有底气去做出修改。还是那句话, 天下武功,唯快不破 ,改得越快,成本越低。

在这个例子中,仝健老师说,我的代码中都抽取出了“特殊数”这个概念来,但需求中有强调特殊数需要是可配置的吗?没有的话,这样的需求就是多余,这样写出来的代码就是浪费。我录制了我 TDD 时的视频,那段代码的编写时间,加上我重构快捷键的不熟悉等,一共花去我 14 分钟。想想,平时折腾这种其实根本无效的需求,我们浪费了多少时间。

可验证的用例 Verifiable ACs

一个任务(task),可能可以产生多个测试用例。上一步中,我们有了一个与需求等价的任务列表以后,这一步的任务是把这些任务列表进一步分解成可验证的测试用例:也就是一个个 input->output 明确的测试用例。有了这些测试用例,我们就可以开始 TDD 中经典的红绿循环了。上面两步,是开始写测试前的重要环节,搭起了高层的需求到细节的测试用例之间的桥梁。

红绿循环 Red-Green-Refactor

这是众所周知的 TDD 红绿循环图。拿到上面分解得到的可验证的测试用例后,你就可以开始写代码了。这一步要注意的点,有两点。

  • 最小实现(同样,出于减少浪费的精益思想)
  • 出现了坏味道要重构(TDD 实践中促进代码质量的一步)

什么是坏味道,以何手法改进之,不是 TDD 的问题域,请咨询 重构 一书。

用代码来描述 TDD 的整个工作流,其实就是:

function TDD(requirements, designs) {
  let tasks = tasking(requirements, designs)
  let testCases = tasks.map(task => verifyableACs(task))
  let code = testCases.forEach(redGreenRefactor()).collect()
  
  return code
}

持续修正 PDCA: Plan-Do-Check-Action

这是著名的戴明环,并且这个实践主要取自仝健老师的 编程的精进之法 一文,详细的论述读者可移步。这里会讲,如何把这个训练-反馈的实践用到 TDD 中来。

首先,我们上面的第一二步(任务列表分解 与 可验证的测试用例)显然都是在做 plan;而第三步则是 do 的过程。真正为你的实践起到反馈、修正作用的,是 check 和 action 的环节。

Check:定量化的反馈

check,检查,回顾。回顾什么内容呢?简单来说,就是对你前面 TDD 实践过程质量的检查,仝健老师提到,可以从两个方面来 check:

  • 对比实际完成的 tasks 总量(任务列表)
  • 对比实际完成 tasks 的时间(时间估计)

实际检查下来,如果与你的期望偏差较大,则无非可能是时间估计不准,或是任务列表扩张了。通过进一步的检查,你可能发现大部分的问题出在自己的身上,比如:

  • 任务列表扩张了:有可能是自己任务分解时,对实际步骤就没有想得很清楚,它可能反映出你在或业务或技术或流程上的不熟练;也有可能是你分解的任务看起来穷尽了,但其实并没有穷尽,导致做的时候又发现很多事情需要处理
  • 时间估计不准:可能是自己技术不熟、工作效率还不够高,比如 IDE 快捷键不熟、编辑器不熟、MAC 快捷键不熟、没有总结模板操作、获取信息方式不高效、手速太慢等。任务列表的扩张同样可能导致时间估计不准

如何来获得这些检查时所需要的材料呢?我可以推荐两个方法:

  • 视频录制复盘
  • 定量化的时间分析

Action:持续提高

有了上一步的检查数据,action 就很好做了,其实就是错哪改哪,缺啥补啥,针对特定的问题,制定出改进计划、可验收的标准,并通过刻意练习来提高。比如,发现快捷键不熟悉,那么 action 可以是学习快捷键表,验收标准是完成练习时不需要动用鼠标;如果是手速较慢原因,那么 action 可以是在 typing.io 等专门练习代码手速的网站上练习手速,验收标准是达到 每分钟 45 的 WPM;等等。

总结

本文主要讲了三个方面的内容,循序渐进:

  1. 首先我们一句话介绍了 TDD 是什么。TDD 是一套提升个人开发效率的方法论和实践,其理念在于 减少浪费 。它不是其他的什么东西
  2. 然后,我们简要回顾了 TDD 历史上的论战。这一节我们理清了 TDD 即非需求管理工具,也非代码质量/设计工具,指出相关问题应学习相应领域知识来解决。本节目的是进一步定义 TDD 的边界,避免在形上的论战中浪费时间精力。毕竟, TDD 是一门实践的艺术,而纠结则是最大的浪费
  3. 最后一章从顶向下介绍了 TDD 的工作流和实践流程。其背后理念也是减少浪费, 唯快不破,无效代码一行都不多写

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

缱倦旧时光

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

daid

文章 0 评论 0

我心依旧

文章 0 评论 0

晒暮凉

文章 0 评论 0

微信用户

文章 0 评论 0

DS

文章 0 评论 0

〆凄凉。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文