-
Notifications
You must be signed in to change notification settings - Fork 17
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
TDD 二回炉 #176
Labels
Comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
好久没有提交了,寂寞啊。
出差项目一周,收获颇丰。辨识出接下来需要学习的几种能力:
其中技术栈是用以完成功能的,敏捷实践是帮你更好地干活的。老实说,web 开发中那种过分斤斤计较于这个字段空不空,那个字段的 code 是否等于这个字段的 code,这些,我觉得都没什么意思,对大脑和精力都是昂贵的损耗。软件这么开发就没意思了,也不好意思收那么多钱了。
而软件项目、技术栈、程序语言等虽然千差万别,但是其中有很多一样的东西。我借由混乱看到了规律。在这些具体的技术栈、具体的领域问题背后,有着一些更抽象的相似性。那就是模式。是藉由抽象和思考得到的模型。
这次项目,我得以认识到 TDD 团体性的一面,以及其他不好明说的一些问题。我看到自己的傲慢,需要时刻引以为戒,但有人又鼓励这种傲慢,让我觉得不知所措。
以前我发表过一些关于 TDD 的言论,大概的核心论点是:TDD 是一个工具,一个用来帮你无限制提升出代码效率的思维和设计工具。它的核心是快,快的方式是通过帮你更好地 明确需求、减少浪费。同时,它也只是工具,不是其他的什么东西;既是表达的问题,那么没有想法,也就无从表达;既是设计的工具,那么不懂设计,也就设计不出什么来。使用它,前置技能要求是 任务分解、重构 和 设计。
这是我第一阶段的观点。TDD 是完全个人的工具,对于团队一直是抱持爱用不用的态度,并且因着在辩论中自我隔离而得到满足。纯净的、个人的 TDD,是非常美好的。它的快,也是美好的。无论是否仅作为个人的工具,它的一些思维方法通用,节省了一些公用环节的成本:
任务列表
这让你既不多做,也不少做,避免了你做无谓的浪费。最重要的是,它逼着你要去思考,做卡前就要想清楚所有的需求、所有的细节(无法穷尽、一直练习以趋于熟练),让你的抽象思考和细节实现能力都得到极大的锻炼。细节实现,在 TDD 红绿循环的过程得到锻炼;抽象思考,在细节实现的基础上,抽取共性,形成模式和高阶的抽象,以支持日后在本领域和其他领域的快速全面的思考。
实现过程的回归成本
本质是人脑容量有限,要求线性思考、分而治之。举个简单例子,一个5个 task 的需求,做完了第一个,很快很爽 so easy;撸第二个,没问题,就加一层判断;再做第三个,勉勉强强,代码开始看不太明白了;再做第四个,已经忘记了前面实现得怎么样了,战战兢兢地做完,打开浏览器或者什么的,一个个再大概手测下;再做第五个,有点儿复杂了,查资料,试用了各种不熟悉的库和语法之后,经过无数调试,终于实现了需求, 打开浏览器再手测一个,发现第四个task 怎么挂了,于是返回去看代码,一看原来是某个 code filter 错了,改完以后浏览器一测,发现第四个 task 还是挂的,同时第三个 task 也挂了,于是打开源代码开始调……这日常对程序员来讲似乎已经不再是痛点,而是日常工作的一部分,是常态。用测试来表达,初看是慢的,但这不一定是个正确的感性感觉,它最大的贡献,是通过单元测试自动化了任务列表的回归验证,使得成功极低的自动化测试成为了任务是否完成的 single source of truth。这点思维要转变过来。
重构的成本
没测试,不重构。因为你不知道你对了没有,没有测试你只能手测,那这画面简直太美不能看。没有测试,除了以上的回归成本(如果你觉得还能忍),最大的问题还是决定了重构不可能高频率发生。这样,大家都会倾向于 继续往上堆代码 并且 尽量不要改动任何别人的逻辑。经常可以看到为了在别人的代码中添加一些逻辑时,为了不搞挂别人以前的东西,加一个 if-else,抄一份原来的逻辑,再在新抄的代码里面加自己的东西。这样一个 if-else 马上就会变成两个,三个……很快一块代码就会有五层判断、十个关注点,变得难以再添加测试。恭喜你,你遇到了软件开发中最常见的一个坑。
修 bug 的成本
需求都实现了,重构也做了,信心满满地挪卡,是不是感觉一切都如此美好?不好意思,QA 测出了个 bug,是个做卡前大家都没想到的场景,行那么改吧。此时,如果没有测试,你怎么知道你有没有改挂以前的?那肯定是手测对不对。为了快速修好 bug,尽量不破坏原来的功能,你一定会选择在 bug 发生的路径上加一个 if-else 是不是?「只有这个条件满足,并且 xxx === null 的时候,我才做这个事情,这样既修了 bug,又不会影响以前的代码。」这样的台词,是不是似曾相似?这么战战兢兢地修完 bug,你还敢重构吗?还想再全回归一遍?又想加班?于是你只能到页面上,凭着你对 AC 的印象,草草地点点点几下,大致确认没问题,然后又把卡挪回测试,祈求 QA 不要再找出什么奇妙的 bug 来了。老子不想加班啊!!!
纵观以上四点,TDD 分别提升了你的思维效率、做卡速度、代码质量、对变化的响应能力,你还是觉得不写测试更自然就是快么?「具体情况具体分析」,最后肯定就是没分析不了了之;「等后面有时间了一定要补上」,肯定就是不会加。这些都是开发黑话了嘛。
这是我以前的观点。然而我所见到的现实总是截然相反。相反中,我发现这样的看法是有点个人热情和理想主义在里面的,因为 TDD 本身也是需要练习的,它对人员的 TDD 熟练程度和学习热情做了预设。现实中所见到的截然相反,很大程度与这两个预设是有关的。它多少有些高高在上,即我所说的傲慢。因此,与纯净的、个人的 TDD 相比,现实的、团队中的 TDD 还需要面对两个非常重要的问题:
人员能力
能力在这里偏指 TDD 、重构、设计、建模等能力,是一个中性词。我既觉得自己也缺乏训练,同时也有些消极无益的傲慢。
毫无疑问,TDD 本身是需要练习的,而且需要大量的练习。「你不觉得好是因为你不会」,这是无奈的傲慢。但需要认清楚一件事,对成员的能力和热情不应有预设(多么消极)。至少在团队推动 TDD 时,不能去说是人员能力不行,因为这个假定本身就不成立。人员没有经过刻意练习,本来就不是训练有素的。
这种能力上的客观现状,会在进度压力下暴露出来。如果进度紧张, TDD 技能的不熟练,确实从客观上就是写测试比不写要慢的。上面列举的四点好处,有一个前提,就是思维要转变。在客观进度的压力下,哪里有时间转变思维?强行推行,势必要遇到 思维、习惯、工作方式等方面的阻碍和反弹。如果已经到了这个时候,我认为应以让团队成员感觉舒服、自然的方式来撸代码,不要强力去压迫,否则会有反弹。
那么自然,能力不足遇到进度压力的隐患,最好从一开始就防范于未然。即便已经发生,渡过难关后也要进行 retro,痛定思痛,下次改进。那么问题来了,人员能力这个事情,在不应假定人员客观水平和热情的前提下,如何来解决呢?
我的回答是,团队中必须至少有一个人,他该知道怎么 TDD、怎么重构、怎么设计,具体到大部分的细节,怎么是好的 TDD/重构/设计,然后由 TA 来在日常工作中 手把手为团队示范如何落地,并 实际展示出这些实践所带来的效率提升,让团队也感受到这种好处,形成良好的技术氛围互相学习成长,以此提升团队能力。TA 必须是一个技术的实践者,热情者。
另外,最好有个人来负责制定规则,比如风格检查、覆盖率检查等,并在团队中推行下去。
综上,一个项目技术实施上,要想做好,不想给自己和后人挖坑,需要 TDD。而 TDD 在一个团队中的发展,在不对人员做假定的预设下,至少需要两个角色:一个是技术的熟练践行者,一个是推动者。两者可以是同一个人 Tech Lead,也可以不是。
大熊🐻说我暗指……然而并没有,我就是这么一步步看的。
如何在现有复杂裸奔代码上 TDD
这个问题很重要,然而如何做我并没想好,还在实践摸索。
之所以很重要,是因为这种情况必然会发生,而且必然要面对。即便你想自己练好 TDD,那也不能保证一定能给你一个非常纯净、关注点分离、架构良好的项目或代码库让你 T 个爽啊;那这样的项目代码库一定是很少的——至少在我们的人员能力梯队成长起来以前,如果不能对人员做假设,那也就不能对 TL 这个角色做假设,那项目就有可能会没有测试裸奔几个月,成长为一个方法里 n 多关注点杂糅、没有测试覆盖的「传统遗产」型代码。还记得我们上次和阿蒙蒙讨论过这个话题,最后结论是,能不能 TDD 看接手的代码库质量好不好。那难道,遇到这样的代码,我们也就注定只能让上堆、继续不加测试地裸奔了吗?
我看到的现状是,一个项目,无论如何 TL 们似乎都是尽职尽心地把架构选好、框架搭好,这样至少有个好处,就是框架分层、架构明显。分层明显,就已经是分离了关注点,对这些部分我们有望能快速上手进行测试驱动开发。然而,一般的重灾区是那些 关注点不分离、过于复杂 的代码/函数/类里。
综上,如何在现有复杂裸奔代码上进行 TDD 这个问题域,我根据我的经验,转换成下列的痛点。这些痛点能有切实可落地的方案,TDD 就可在项目任意节点的中间起步,从无到有,逐渐朝更好的方向演进,而不需要求一个绝对纯净的代码库。这也是一些同学及我的困惑点,TDD 练习看似是会了,但一进行复杂的项目却总感觉有力使不上,举步维艰:
痛点。还别说,真是有痛点的,打勾勾的就是已解决,没打的继续摸索:
done
功能)import
了直接拿来用,这跟 Java 中的最差实践基本是一样的。怎么办呢?方法有二:测试框架需要支持全局 mock,比如现在在用的 Jest 就提供了相应支持,就跟注入进去的一样;添加一层 container,但为测试入侵产品代码,并且我也没搞过,是拍脑袋拍出来的,排除;state.productCategory.solutions
,另一个单元拿到的数据却是data.solutions
。而中间的这个数据转换,还是需要你对这个项目的经验、对数据结构的了解,像刚来的新人根本不能很快上手,写完 UT,还得 debug 一下把整个流程走通就此。
The text was updated successfully, but these errors were encountered: