唯快不破的 TDD

发布于 2024-10-09 12:50:20 字数 7714 浏览 50 评论 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技术交流群

发布评论

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

关于作者

缱倦旧时光

暂无简介

文章
评论
25 人气
更多

推荐作者

梦途

文章 0 评论 0

蓝眼睛不忧郁

文章 0 评论 0

134fengkuang

文章 0 评论 0

yang18

文章 0 评论 0

属性

文章 0 评论 0

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