为什么敏捷实施,或是任何一点的过程改进都步履维艰?即使是十几人的团队中,也会出现“写自动化测试”──“不写自动化测试”──“写自动化测试”──“不写自动化测试”这种循环往复的过程?
除了人们常常总结的“敏捷实施模式”,或是“敏捷失败经验分享”这样的具体话题之外,是不是还有一些存在于思维模式中的更加根本性的因素,阻碍了我们对系统全景的认知,从而导致改革先行者的黯然退场?
本文将通过两个案例来讲述如何使用系统思考,从全局掌握我们所处的复杂环境,做到既见树木,又见森林。
案例 1:舍本逐末
有一个测试团队的负责人找到我们说,“我觉得现在的自动化测试问题很大,执行时间长,也不稳定,有的时候是测试写错了,也要花很长时间修。我打算组织一批人,重新设计一下测试代码的架构,把常用的底层功能封装成设计良好的 API。”
我的同事说,“好啊,你们打算怎么做呢?”
他说,“我也还没想好,所以想过来商量一下。我希望这个东西做成以后,能够让不会写程序的 QA 们都能用它来写自动化测试脚本了,他们现在就是这样,又要做测试,又要学着写程序,我觉得太辛苦了。能让他们不用学编程就能写测试脚本就好了。”
“呃……要不我们先看一下现在的代码,了解一下都有什么问题,然后再讨论?”我们内心有点小小的纠结。
“好吧,我来给你们开通访问权限,找人给你们讲代码”。他很爽快的答应了。
打开代码之后我们就忍不住风中凌乱了,满屏都是重复的代码片段,让人一阵阵的眩晕。两天之内,我们仅仅用了“提取方法”这一个重构手法,就删掉了 1200 行代码。期间还发现,不知道谁在调试的时候把一处代码从等待 3 秒改成了等待 10 秒忘了改回来,于是其他人再复制粘贴的时候,就全变成了等待 10 秒。
于是事情就明朗化了。
依赖于一小拨人重新设计代码结构,提炼 API,确实会在短期内使问题得到缓解。但使用这些 API 的人依然是那些不懂得如何编程的 QA,他们依然会使用复制粘贴来解决问题。再好的架构、再优秀的设计,最终还是会淹没在大量重复的代码中,犹如黄金深埋浮沙之下。而且如果问题表象得到暂时解决,人们就会缺少动力从根本上提升 QA 的编码能力,随着设计一点一点腐化,就又需要精兵强将充当救火队员。如此不断反复,直到有一天又回到重新设计乃至重写的老路上来。
这是个典型的“舍本逐末”的模型。
分析
在上面的场景中,对于“测试代码质量低劣”这个问题有两种解决方案,一种是精兵强将解决,另一种是测试人员自己解决,每一种方案都会削弱代码质量不断下滑的趋势,从而让系统处于一种平衡状态。如下图所示:
图中的“S”表示同向连接,即箭头起点所示变量的增长会导致另一方变量的增长;“O”表示反向连接,即箭头起点变量的增长会导致另一方的减少。天平表示调节环路,该回路会趋于平衡稳定。
但是,由精兵强将出马可以让问题症状迅速得到缓解,提升测试人员的编码能力则需要长期的辅导训练,不可能一蹴而就,所以图中下方的调节环路实际上是有时间滞延存在的:
与此同时,由精兵强将解决问题会减少测试人员锻炼的机会,从而削弱测试人员的编码能力,进一步使人们不倾向于让测试人员自己解决问题,又转过头来增强了对于精兵强将的依赖。所以还要在图中增加另外一条回路:
滚雪球表示增强环路,该回路会使得回路上的所有节点持续增强
这幅图的全貌便构成了“舍本逐末”的模型。彼得·圣吉在《第五项修炼》中对此解释到:
上面的环路代表快速见效的症状解,它迅速解决问题症状,但只是暂时的。下面的环路包含了时间延滞,它代表较根本的解决方案,但其效果要较长的时间才会显现出来。然而它可能是惟一持久见效的方式。有时候舍本逐末的结构中,会多出一个由症状解所带来的副作用所形成的增强环路。发生这样的情形时,副作用常使问题更难以解决。
人们或者是因为没有找到问题的根源,或者是因为时间延滞的存在,倾向于采取一种简单易行又可以立竿见影的方案,这便是症状解了。但是症状消除以后,问题就不再令人重视,从而丧失了从根本上解决问题的能力。而问题依然深藏,等到它有一天再度浮上水面时,症状就会更重,更难解决。
这是一条不断衰减的增强环路。在回路上每走一步,情况就会更恶劣一分。
面对“舍本逐末”,通常的解决方案有两条:
- 必须要认识到症状解只是短期止痛的手段,切不能形成依赖;
- 在症状得到缓解之后,要继续加强对根本解的重视。
但见招拆招总是相对容易一些的,更关键的地方是,如何才能识别出“舍本逐末”这样的模型?当我们采取某些理所当然的对策却得到了不合理的结果,有什么办法可以帮助我们分析问题根源,找到解决方案?
要认清问题的本质,就必须要认识到,我们所处的是各个因素之间紧密连接相互影响的复杂系统,当前采取的行动,会从多方面对这个系统产生影响,而这些影响之间或推波助澜,或相互牵制,从而导致相同的行动在长期和短期来看具有不同的结果。
比如,当代码中发现 bug 的时候,很多人的常见反应都是“调试──定位──解决”这样的思路。从眼前来看,发现 bug 立即修复是可以最快见效的手段,但却丧失了将测试进行完善的机会,相当于是安全网上明明出现了漏洞却听之任之。等到以后因为需求变更等原因影响了这块代码的时候,就再也无法通过执行测试来得到快速而完整的反馈了。
丹尼斯·舍伍德在《系统思考》中说,
如果你希望了解一个系统,并进而能够预测它的行为,那么,就非常有必要将系统作为一个整体来研究。将系统各部分割裂开来研究,很可能破坏系统内部的连接,从而破坏系统本身。
如果你希望影响或控制系统的行为,你必须将系统作为一个整体来采取行动。在某些地方采取行动并希望其他地方不受影响的想法注定要失败──这也就是连接的意义所在。
为了帮助人们更好的从整体上研究复杂系统的行为,丹尼斯在书中提供了一套完整的工具──系统循环图。
系统循环图中共有三个基本要素:
- 增强环路。在增强环路中共有偶数个 O 型连接,增强环路上的各个节点会呈现指数增长或指数衰退。
- 调节环路。在调节环路上共有奇数个 O 型连接,调节环路上的各个节点会趋于平衡状态。它会消化掉外界的影响力,使改变难以发生。
- 时间滞延。时间滞延是一个不容忽视的影响力,由于滞延的存在,人们常常会发现某个行为在短期内没有产生预想的结果,从而加大投入力度,当行为的后果出现在眼前时,却已经矫枉过正。
下面我将通过另一个真实的案例来讲述“系统循环图”的应用。
案例 2:历史不断重演
一天中午,我忽然听到有人说,“我们又开始讨论要不要放弃自动化功能测试了。”
“咦?你为什么要说又呢?”我忍不住问道。
“因为我们半年前已经讨论过一次,当时得出的结论是不再写自动化测试了。后来不记得是什么原因了,又用 Cucumber 来写,最近发现每次上线还是要做大量的手工测试,这些自动化测试又要浪费很多时间来修,所以我们准备讨论一下到底还要不要继续写。”
当天下午,我参加了这个团队的讨论,终于弄清楚了事情的来龙去脉:
大概是一年前,为了减少手工测试的成本,团队决定一步步把上线时需要手工回归的测试用例转换成自动化,同时决定每个 story 做完以后都要加入自动化测试。研究了几天 webdriver 以后,就开始了自动化测试的尝试。
但麻烦很快就出现了。第一,开发人员用 Java 代码写的测试,QA 不好理解,也不是很清楚哪些场景被测试覆盖到,哪些没有,所以无法信赖测试结果;第二,测试跟开发共用一套数据库,数据总是受到污染,因此造成测试失败,浪费大量定位和修复的时间。而数据库是由国外的客户控制的,催促了很长时间也没能给测试分离出一套专用的数据库来。
测试红啊红,修啊修,后来一算时间,在自动化测试上投入了 120 多人天,却依然得不到一套稳定执行的测试用例,不但没办法保证交付质量,还让每个人心力交瘁。于是毅然决然的停掉了。
过了两三个月,客户终于准备好了一套专门用来跑功能测试的数据库,开发团队也对行为驱动开发有了一定的认识。于是又开始写自动化测试,这次用了 Cucumber,QA 写场景,Dev 写实现。
写了大概有 100 多个场景的测试,又有人开始质疑这一切:第一,整套系统实在是太庞大复杂了,写到现在为止,连 1/4 都没覆盖到,所以上线还是手工回归。我们到底要花多大的精力才能把所有的场景都自动化起来,这些投入是不是值得?第二,测试环境还是不稳定,比如本地数据跟 CI 用的数据不一致,比如 Tomcat 里面部署的应用常常启动不起来,等等。每个问题都要消耗大量的人力。这些浪费能不能平衡自动化测试到最后能够带来的收益?
但团队中还有其他人对自动化测试持有乐观态度,认为问题总是可以解决的,只要坚持不懈就能够看到长期的回报。于是就有了这次讨论。
分析
绘制系统循环图的第二条法则是:“从有趣的地方开始”。在这个案例的场景下,开发团队最关心的是该不该写自动化测试,它对交付能力会带来什么影响,于是我们选择“自动化测试的数量”作为起始点。
接下来是第三条法则──“询问‘它将驱动什么’,以及‘它的驱动力是什么’”
我们首先可以想到的是,自动化测试的数量增加,会缩短发现 Bug 的周期,但是这个作用是需要测试数量积累到一定程度才会发挥出来的。它同时还会增加测试的开发和维护成本,增加测试执行时间,缩短手工测试周期。见下图:
手工测试周期的缩短会带来交付周期的缩短,提升产品收益,从而使人们更倾向于编写自动化测试。于是在图中就出现了一个增强环路:
而测试的开发维护成本增加,会导致开发进度放缓,从而削弱收益,于是在图的下方出现了调节环路,这条调节环路的存在,会阻碍人们在自动化测试上的投入。
与此类推,我们同样在图中可以发现其他的增强环路与调节环路:
而在这四条回路之外,还会有其他因素对这个结构造成影响:
画出系统循环图以后,就可以结合团队的状况进行整体分析:
首先回到质疑的声音上来,有人说,“整套系统实在是太庞大复杂了,写到现在为止,连 1/4 都没覆盖到,所以上线还是手工回归”,这里反映出的正是从“自动化测试数量”到“手工测试时间”这条线上的时间滞延的效果。在前文中提到过,时间滞延在反馈环路中会造成矫枉过正,这里是它的第二个作用──给人带来挫败感。它会导致某个行为在短期内看不到任何效果,当滞延的时间过长时,会令人失望乃至放弃努力。消除时间滞延可以对系统起到卓有成效的改善。在这个案例中,我们可以通过推动手动测试用例向自动化测试的转化来缩短滞延。
然而,当时间滞延的作用被削弱以后,还有另外的问题等着去解决。下面再来看看这支团队从“写自动化测试”到“不写自动化测试”的变化过程中发生了什么。
在刚开始写自动化测试的时候,团队主要的感受是 QA 少了手工测试的时间,质量多了保障,所以增强环路发挥了作用,每个 story 完成以后,开发人员都会为所对应的场景写几个测试。但当测试数量增加到一定程度,调节环路的反馈力量开始占据主导地位──测试时间变长、维护成本增加。而且测试数量越多,带来的问题就越大,最后便有了第一次的选择:放弃自动化测试。
这正是《第五项修炼》中描述的“成长上限”模型:
增强环路导致成长。成长总会碰到各种限制与瓶颈,然而大多数的成长之所以停止,却不是因为达到了真正的极限。这是由于,增强环路固然产生快速的成长。却常在不知不觉中,触动一个抑制成长的调节环路开始运作,而使成长减缓、停顿,甚或下滑。
……
当改善的速度慢下来,你会更加努力地去改善。但渐渐的,你愈是用力推动你所熟悉的做法,调节环路的反作用愈是强烈,使你的努力愈是徒劳无功。到了最后,最常有的反应是放弃他们原来的目标。
所以,当我们观察到有增强环路与调节环路遭遇的情况出现时,更为有效的解决方案应该到调节环路上寻找。在上面的系统循环图中,“测试环境稳定性”、“开发人员技能”可以限制“测试的开发维护成本”,“硬件数量与质量”可以限制“测试运行时间”,我们可以通过控制这些在调节环路之外起作用的因素,削弱调节环路的影响,让增强环路的成长继续开始。
小结
结构决定行为。增强环路总是产生指数级的上升或衰减;调节环路总是倾向于使整个环路趋于平衡状态;各种环路的相互影响,就会产生“舍本逐末”、“成长上限”或是“饮鸩止渴”等基本模型。
然而不幸的是,参与系统的各个部分,常常见树不见林,只能针对眼中见到的局部信息,做出局部的最佳决策。但每个人的局部最佳决策,并不能构成整个系统的全局最佳决策。因为系统中有反馈、有延迟,因和果在时空上并不紧紧相连,显而易见的解往往无效。
在纷乱芜杂的因果关系中,使用系统思考可以帮助我们理清问题的脉络,认识系统全貌,从而进一步寻找有效的杠杆解──比如案例 2 中的限制调节环路;而不是固囿于局部优化,把环状的因果关系割裂成线段,看不到当前采取的行动会在未来反作用于自身,因此导致各种系统问题的出现。
系统循环图是一个很强大的工具,但一个人的视野总是有限的,用头脑风暴的方式往往可以得到更全面的认知。案例 2 的图形就是小组讨论的结果。只是这种方式需要引导者时时注意控制讨论的边界,不要偏离重心。
本文通过两个实际案例对系统思考的基本概念进行了描述,并讲解了如何使用系统循环图对一个复杂系统进行分析,对这方面知识感兴趣的读者可以通过《系统思考》和《第五项修炼》这两本书进行深入研究,学会用这套工具来指导持续改进的步伐。
感谢张凯峰对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。
评论