结对编程时思路不同,怎么办? - GDCR 2019

四轮紧张刺激的结对编程过后,2019 年全球编程静修日上海站的活动顺利落下了帷幕。在回顾总结时,大家都对这次活动给出了很高的评价:

为什么编程静修会有这么神奇的魔力呢?就让我们通过这次活动中的三个讨论来一探究竟吧!

  1. 怎么在结对编程时快速达成共识?(本文)
  2. 单元测试和集成测试有什么区别?
  3. 怎么写出没有 if 语句的程序?

参加这次编程静修的小伙伴多是第一次结对编程。因此在第一轮的结对中,很多小组就遇到了思路不一致、讨论不出结果、进度缓慢的问题:结对的双方因为背景的不同,对相同的问题有不同的思路。在讨论的时候,双方都觉得自己的思路更优,希望能按照自己的思路编写代码。

触发这个问题的点可大可小,小到一个变量的命名,大到算法设计上的不同,几乎每一行代码都可以有不同的思路。因此,在结对编程这样多人同时写一份代码的实践中,这个问题会频繁地出现。如果不能很好地解决,结对的体验就会变差,开发效率也会变低。如何快速的达成共识,便成了大家第一轮回顾的重点。

结对编程只是让思路冲突尽早地暴露出来

讨论过后,我们认识到这个问题的出现并不是一件坏事。现在的软件项目很少是单兵作战,都是团队开发,团队成员思路的不同在所难免。那么,何时解决成员思路上的冲突,就成了一个至关重要的问题。

有的团队缺少类似的讨论,每个人只负责自己的项目、功能。只有在发现 bug 了之后才讨论有没有更好的解决方案。但这时候的讨论往往已经太迟了,毕竟木已成舟,我们只能在已实现的方案上修修补补。

有的团队有代码审查机制,在代码审查时讨论不同的命名、不同的思路,找寻更优的实现方案。但这时候很难再对算法、架构等大方向做出调整,因为这些调整的成本甚至要高于重新实现的成本。

因此,结对编程其实是将思路上的冲突尽早地暴露了出来,将讨论的时机放在了写代码的当下,而不是等到代码审查时、代码部署后。

更重要的一点是,不同的思路都对应着不同的解决方案。而我们项目中的每一个问题,都有着无数种可能的解决方案。一个项目的成功需要我们尝试各种不同的方案,从中找到最优的一个解。

尝试、对比不同方案的办法有很多。写代码实现、做 A/B Testing 是一种准确率高,但是费时费力的方法;头脑风暴、计划、讨论则是一种准确率略低,但是可以节省时间、减少人力浪费的方法。

如果讨论的结果是方案 A 比方案 B 更好。那么我们成功通过讨论,对比了两个方案的可行性,从中选择了更优的一个。我们不用再实现一遍方案 B,节省了时间。

因此,结对编程的过程,就是通过「讨论」这样成本较低的方式,来对比不同方案的优劣,从中选取最优的方案的过程。

但是,如果讨论的结果是方案 A 和 B 各有优劣,暂时对比不出结果,该怎么办呢?

如何在各有优劣的方案之间取舍?

当我们对比不出两个方案的优劣时,原因往往是需求还不够明确。两个备选方案代表了两个方向,方案 A 可能有更高的性能,方案 B 可能有更强的扩展性。但现在的需求还不够明确,我们还不确定项目需要的是高性能还是可扩展性。

在这种情况下,我们可以通过对比两个方案之间互相切换的难度,选择切换成本更低的那个方案。如果「从 A 切换到 B」比「从 B 切换到 A」更容易,那么就先实现方案 A。

当需求明确之后,如果我们需要的是高性能,那么说明我们之前「赌」对了,不用对代码做出改动;如果我们需要的是可扩展性,那么我们再切换到方案 B,这时至少切换的成本是比较低的;

这样一来,在两个方案之间的取舍就变得简单了,可以快速做出决策。

但是,这样的取舍只是给了我们一个达成共识的思路,并没有降低方案切换的成本:如果要从方案 A 切换到方案 B 的话,我们还是需要修改用到方案 A 的代码,修改成本依然很高。这时候该怎么办呢?

如何降低方案之间切换的成本?

为了减少方案之间切换的成本,我们可以将方案选择的决定结果封装在一个抽象(变量、函数、类、模块)背后。

不知道该选择 HTTP 库 A 还是 HTTP 库 B?用一个类将 HTTP 相关的方法包装起来,在类的内部调用库 A 的方法。需要使用库 B 时,只需要修改这个类即可。

不知道该用算法 A 还是算法 B?用一个函数将算法包装起来,在函数内实现算法 A。需要切换到算法 B 时,只需要修改这个函数即可。

不知道该用函数 A 还是函数 B?用一个变量将函数调用包装起来,在赋值时决定变量的值。需要切换到函数 B 时,只需要修改这个变量的定义即可。

这样一来,我们在切换方案的时候只要保证这个抽象暴露出来的 API 不变,只改变抽象内部的代码,从而将方案切换的成本降到了最低。

总结

结对编程的过程中会有许许多多的沟通,这些沟通的机会迫使我们尽早解决思路上的冲突,通过对比不同的解决方案,尽可能找到更优的解。如果方案之间各有优劣,难以抉择,我们可以通过对比两个方案之间互相切换的难度来做出决定。最后,我们还能通过封装一个新的抽象,来降低方案之间切换的成本。

这些,就是我们在编程静修的第一轮中学到的经验。在下一篇文章中,我们将分享编程静修第二轮(测试驱动开发)的讨论,敬请期待!