软件开发中的陷阱

本文是翻译,版权归原作者所有



为了优化某个情况却发现事情变得更糟了,你曾遇到过吗?你曾遵循传统的「最佳实践」却发现它未能尽如人意?貌似你尝试得越多,事情反而变得越糟糕,是这样吧?

Dana Meadows 写了一本开创性的、关于系统思维【注1】的作品,《Systems Thinking — A Primer》,她认为大量的系统结构倾向于导致「有问题的行为」。这种典型的「陷阱」,由于未能认识到它们而导致不可预期的问题。这种陷阱在生活的方方面面都极为普遍,软件开发行业也不乏其例。

本文描述了 Dana Meadows 提出的 8 种陷阱,根据我自己在软件开发的经历给出了相应例子。

下面的几种陷阱,你中招了多少?又该如何抚慰伤口?

策略抵制

策略抵制(policy resistance)或许称作「固定某个错误」更为合适。在同一系统的不同部分的需求之间没有经过协调时,这种原型就会出现。

策略抵制(policy resistance)

举个非法毒品供应的例子。用户需要足量的供应,而法律强制少量供应,交易商想让供应多到足以满足需求、而少量供应足以维持了高价格。在这个系统中,任何干预将导致其它部分付出双倍努力才能更好地满足他们自己的需求。然而改变不了多少。

这是一个例子,平衡反馈回路(feedback loops)会对彼此起着反作用,阻止了有意义的改变。

现在让我们回到工作场所(如果你是毒品交易商,我下面的话就不用说了),考虑一下技术债务和与之相关的各方目标。开发人员愿意保持较低的技术债务,如果你是开发人员,理由是再明显不过了。管理人员也愿意减少技术债务,但是经常迫于压力而不得不快速地为利益相关人交付东西。这或许需要一种妥协,技术债务的减少和满足利益相关人的紧急需求。利益相关人很少关心技术债务(假定质量是已知的),更愿意看到他们的需求被满足,越快越好。

减少技术债务的期望遇到了增加吞吐量的要求;增加吞吐量的期望遇到了减少技术债务。因此,很少能改变什么。这种陷阱导致了开发人员和利益相关人之间的习得性失助【注2】,在你搞清楚这种处境之前,你发现自己所处的组织,到处弥漫着一种心态,那就是「为什么要烦恼?」。

如果利益各方意识到反馈回路和引发的技术债务之间的延迟,那么这个具体的例子就容易修复了。解决技术债务事实上会导致更高的吞吐量,但是这种收益只有在技术债务被足够减少时才会显现。如果你的组织始终以紧急重要的模式运行,这种延迟就不会符合心意。

简单地说,策略抵制是一个系统内多方之间需求冲突的结果。或许我能建议的终极解决方案,可以简单地归结为满足各方需求

公地悲剧

公地悲剧【注3】是一种系统的原型,描述了公共资源使用的增加,最终导致其耗尽和最终破坏。重要的是,资源的个体消费者即使知道这样做会导致减少,仍然会增加他们的消费。

公地悲剧

通常被描述为公地悲剧的场景是过度捕鱼【注4】。每个渔夫都想抓住更多的鱼,即使这样做将最终导致鱼类数量减少和渔夫生计的破坏。公地悲剧的特点是,个人的短期利益对于其他人利益或公共资源的长期需求没有给予足够重视。

一个经典的子优化(sub-optimisation)。

在软件行业,集体代码所有权可以被视作「公地」,因为不注重长期需求而容易遭到消耗,比如鱼的存量、牧场。如果开发人员倾向于交付「价值」,而以可维护性和可理解性为代价,那么这个问题就特别普遍了。如果是这种情况,每次代码修改将大大减少其总体质量,最终导致软件的不可维护。

软件开发中的其它公地悲剧有:

这看起来有些自私,不是吗?然而也不是。对于动机,无论内在还是外在,无论故意还是偶然,都比较有力量。公地悲剧的每个参与人,或许完全没有注意到他们自己在「公地」上的努力,常常是因为这种努力只有在悔之晚矣的时刻才能看到。

你的动机是什么?

没有?好好想想。

你的真正动机是什么?

滑向低效

一个系统不仅抵制积极的改变,而且持续滑向更糟糕的效率,这种陷阱被称之为滑向低效。

滑向低效

这种陷阱把人类思维的可怜倾向变成了信奉坏消息而非好消息。当一个系统所感受到的状态糟于实际状态时,滑向低效就发生了。随着时间的推移,目标逐步被侵蚀,导致螺旋式下降。

这个陷阱经常出现在遗留代码,不管你有没有维护它的期望,随着时间的推移,代码质量会变得越来越低。遗留代码所感受到的状态(负责遗留项目的开发人员的心境)要比真实状态糟糕。因此对于质量维护的目标,将持续低于客观现实(假定有这么一种衡量代码质量的客观方法)。当一个人的目标总是低于现实时,对目标的侵蚀最有可能的就是产出了,从而导致越来越糟糕的代码。

为什么遗留代码所感受到的状态要比客观现实差呢?嗯,就从「遗留代码」的名字开始,它已经将其定性为负面了。维护遗留代码质量的目标,更倾向于辗转于「够用即可」,随着时间的推移,遗留会变成更多的遗留,对状态的感受也渐行渐远,「够用即可」的认识和相关的维护努力也会如此。

当我们根据系统的当前状态而非最佳状态、或客观的、务实的理想来预测时,这个陷阱就会出现。

你在软件工程的哪个地方看到过滑向低效的情况发生?

Scrum【注5】中的冲刺预测会成为侵蚀目标的牺牲品。未能满足你的冲刺目标?下一次少预测一点儿。仍然不能满足你的目标?为什么不再少预测一点点呢?听着很耳熟吧?

如果团队陷入兑现承诺的负罪感,或当最后期限到来时会有惩罚措施出现时,这种趋势就尤为普遍了。单纯地更加努力工作是非常少有的解决方案,如果你想通过威胁来满足需求,那么希望你有好运气。

升级

系统陷阱的军备竞赛。如果你打我一拳,我就狠狠地打你一拳,我最好撑住自己、甚至给你更狠的反应。这种陷阱是你总比别人强一些。

升级

有人可能认为,这种陷阱刚好与「滑向低效」相反,前者针对某个竞争系统所感受到的状态,持续升级某种系统状态。它可以是积极和消极的,取决于动机或可感受到的系统目标。

设想一下,系统目标是为了更有效地满足客户需求而非竞赛。这种情况下的军备竞赛将是良性的。然而,如果「满足客户需求」意味着将成本降低到竞赛之下,那么在其它需求(比如质量、收益等)起作用之前,这种加强的反馈回路只能运行了。总之,胜人一筹会很快导致负面影响,因为指数级的改变从来不会继续。

这种陷阱经常见于软件工程,尤其是当组织内部把竞争错误地当做动机的时候。

你曾经用你编写的单元测试的数量来衡量其他人吗?bug 被修复的数量、完成的故事、完成的代码审查、或但愿没有你编写代码的行数?你的团队曾经根据你的速度标准和另一个团队竞争吗?那些「KPI」曾经被广为宣传,只是为了「激励持续改善」或(更多的是)点名批评?当我们所有人被这样一种方式衡量时,会怎样呢?

这些衡量将要改善,就这么简单!

它们将要不断改善,直到你做了大量单元测试、大量 bug 修复、大量代码审查、以及多得不可思议的代码行数。恭喜你。

当你完成监测这些测量时,抽出一分钟看看自己是否得到了收获。记住,打算出门前,把灯关了。

由于我或多或少地夸大了的例子,当升级和另一个系统陷阱「寻求错误目标」结合时,将是危害最大的。甚至当目标相当有价值时,升级会很快搞坏你的系统。

富者愈富

富者愈富【注6】、贫者愈贫。这是对富者愈富的概括:在这个系统原型里,机会只属于那些已经富有的人。这是一个增强反馈回路的例子,富有孕育着富有。

富者愈富

这就是你说的陷阱?确定这就是世界运行的方式?这不是等同于达尔文进化论吗?这不是精英制度吗?精英制度不好吗?

不,不是这样简单,绝不。

你的组织自豪地把精英制度作为一种文化价值吗?这意味着成功与特权、财富没有关系,而与「宣扬的价值」有关。这对你有什么影响呢?

是什么在激励着员工,或更重要地,是什么在激励着员工貌似取得成功?如果当前过程中最重要的因素是表现出成功,那么它对你失败的意愿有着怎样的影响?它会怎样影响你创新的能力(作为个人或组织)?

斯坦福大学心理学教授 Carol Dweck 出了一本优秀作品《MindSet》,她描述了增长心态(Growth Mindset)和固定心态(Fixed Mindset)的区别。有着固定心态的个人,倾向于信奉基本素质是固定特质,比如智商或才能。然而,他们倾向于把时间用在尽量表现智商或才能上,而非主动开发这些特质。另一方面,有着增长心态的个人,更信奉大脑和才能只是起点,可以通过专注和艰苦工作来持续开发。增长心态产生了对学习的热爱、对失败的恢复能力,这对于取得伟大成就是不可或缺的。

你对精英制度培养的目标是哪种心态?

Linda Rising 认为,增长心态等同于「敏捷心态」(agile mindset),乐于失败、适应并坚持持续改进,和固定心态不兼容。然而,我们目前的文化和组织,传统上倾向于看重成功的出现,而非让你到达成功的、肮脏、杂乱、易于失败的道路。

这种敏捷的路线是拥抱失败,因此当心你们对「富者愈富」的对待方式。

当然这不全是敏捷。富者愈富导致「彼得原理」【注7】,根据人们之前角色的绩效而被晋升到不能胜任的新角色。如果有人把精英制度等同于达尔文进化论,请提醒他们这里有个另外的名字,那就是「社会达尔文主义」【注8】,它被某些阿道夫·希特勒用于评判 20 世纪上半叶期间所有不愉快的事情。

将负担转嫁给干预者

将负担转嫁给干预者和上瘾有很多共通之处。这种情况下的干预者是该问题的解决方案,如果解决方案为了维护本身而暗中破坏了系统功能,那么这种陷阱就出现了。举个酗酒的例子,真正解决戒掉酒瘾的方法就是再喝一次。当第二天早上痛苦再次降临时,只有喝更多的酒才能减轻痛苦。对于这个例子,酒就是解决方案,一个真正糟糕的解决方案。

将负担转嫁给干预者

当我们审视上瘾时,回忆偏倚(recall bias)让我们想到了酒和毒品,但是同样系统化的模型就在我们周围,包括专注软件开发的组织。我没有暗示说,工作驱使你喝酒

考虑修复 bug 的例子。你经常有个选择:针对该故障打补丁、或找到该 bug 的潜在原因。快速的故障消除,经常是选择了第一种方案,而留下了未解决的潜在原因。简单地给 bug 打补丁,会导致代码更加难以维护,相应地会产生更多的 bug,需要更多的补丁才能修复。这种螺旋式下降最终导致了不可维护软件的悲剧:软件成了针对软件本身的、不明智解决方案的牺牲品。

一旦你注意到了这种模式,就能看到它出现在各个地方。下面是软件驱动组织里的、一些将负担转嫁给干预者的例子。

你对什么上瘾?切记,第一步是承认你存在问题。

打破规则

速度陷阱真的降低了速度,或它们鼓励加速、随之而来的是在摄像头前面的急减速?部分预算真地扮演了消费的上限,或它们成了过度消费的目标?

打破规则

「打破规则」描述了一种现象,规则让某个系统处于扭曲的状态,在缺乏规则时变得毫无意义。这通常可归结为遵循规则条文而非规则精神。规则破坏者看起来好像在遵守规则,实际上在违反着规则。

让我们考虑一种组织规则,它要求某个冗长的签退过程被评估的时间,比完成的天数 x 要长。这将导致所有的努力都要屈从于这个签退过程?这将导致很多工作量都被评估为 x-1 天?

开发团队被设定了一个绩效指标,在今年完成他们对 80% 冲刺的承诺。这个结果将导致更好的专注并增加对结果的关注吗?为了达到该指标,团队将故意(或下意识地)对他们的工作评估过高吗?

如果「敏捷调查」表示着所有的用户故事必须以「作为一个<角色>/我想要<活动>/以便于<商业价值>」格式来撰写,又会怎样?这真的可以让人们专注于用户需求吗?你真的能够以「做为一台 LDAP 服务器」开头,写一堆用户故事?

你正在破坏着哪些规则?你的哪些规则正在被破坏?

寻求错误的目标

当你发现愚蠢的事情发生时,说明你遇到了破坏规则的问题,因为这是围绕规则的一种方式。当你发现愚蠢的事情发生时,说明你遇到了错误目标的问题,因为「这关乎规则」。 ------Dana Meadows

迈达斯【注9】寻求了错误目标,他想成为巨富,但是设定了糟糕的目标,最终死去了。金子,但是死了。

寻求错误的目标

当一个人朝着目标的过程通过间接测量而非目标本身时,「寻求错误目标」的陷阱常常会发生。对于迈达斯的例子,他对财富的追求被定义成了创造金子的能力。最后,他把碰到的所有东西都变成了金子,这不一定让你富有。对幸福的追求是另一个目标,易受这种陷阱的影响。比如,某人或许通过财富的间接测量来追求幸福,然而追求财富让你变得富有,却常常不能让你幸福。

当间接测量成为目标本身时,这种陷阱就出现了。我们经常在软件开发中看到过,「价值」被机智地从客观角度测量,因此我们求助于间接目标。当缺乏价值的客观测量时,我们或许选择测量速度、或「被交付的故事点」。这是非常不明智的,因为速度与价值完全无关,因为它容易当成儿戏,尤其在团队对于其增长负有责任时。鼓励速度增长,实际上会导致客户可感知的价值减少,因为团队把努力都聚焦在了创造价值的表现上、而非真正地创造价值。

软件开发中,寻求错误目标的其它例子有:

就像传统神话故事中的三个愿望一样,系统有一个可怕的倾向,那就是准确地产出、或仅产出你要求他们产出的。要当心你让它们所产出的。

总结

根据我自己做为软件工程师的经历,写了这么长一篇文章,真地帮我理清了一些负面的系统原型。我很有兴趣听到你面对这些陷阱时的体会、以及你是如何克服它们的。你愿意分享你的体会吗?

Part II 接着本文,并给出了一些答案。


译文:软件开发中的陷阱 》| 腊八粥