作者 | 廖雪青
前言
文章以真实项目实践总结为基础,选取了结对编程中的典型场景,叙述了在敏捷交付项目中,开发者在实践结对编程时经常遇到的挑战。场景中会找到你或你身边同事的影子吗?
结对编程的十个场景
角色假设
A:Senior DEV,结对编程实践者
B:Junior DEV,刚接触结对编程
场景一:结对前的约定
A:好,那任务列表(check list)我们梳理清楚达成一致了,现在要开始编码工作了。你们之前是怎么结对编程的?
B:嗯~怎么结对编程具体指什么?
A:好吧,那不然我们一个人写45分钟,然后休息10分钟,再交换?
B:好啊,可以的。
开始结对之前,做一些约定是较好的开始。协商结对编程过程中如何交换角色,多久休息一次等。例一:A写 45分钟,休息15分钟,然后B写45分钟,休息15分钟;例二:TDD A写测试,B写实现,A重构,B写测试,A写实现,编码40分钟左右休息10分钟。
场景二:知识传递
A:休息时间结束,那我们开始?
B:好,该我编码了。那我们接下来要构造接口的返回值了
A:对的,我们可以新建一个文件,导出一个方法,在该方法中构建接口返回值
B:嗯,那我们现在去找一个地方放新建的文件
A:可以在 XX 目录下建这个文件...B找到对应的目录并打开...
B:嗯。咦,这个目录已经有很多文件了,我看一下其他文件都是干什么的。
A:嗯,其实这个库有自己的模式,不然我们去看一下库的文件结构说明文档?
B:好呀,那说明文档在哪呢?
A:README文件中有入口,可以从那里点进去。
B:哦。...查看结构说明文档...
B:所以我们在给上游发请求前如果要构建请求参数的话,可以在文件夹下面建立XXRequestParams.js文件用来构建参数,从上游接口拿到返回值后,如果要对值做一些转换的话,可以在文件夹下面建XXTransformer.js文件构建接口返回值。是吗?
A:对的,这样的话可以做到把部分功能放到单独的文件中,避免文件过大。还有一个好处是更好写测试。
及时发现结对编程的伙伴(peer)某方面上下文的欠缺并耐心传达,帮助新人快速获得项目知识。 当发现peer某方面的不足时,我们要意识到发展他人的机会来了。在上下文传递或知识点讲解的过程中,我们可能也会有新的发现。
场景三:Senior直接上手解决问题
A:我们需要给 state 中增加一个 XX字段
B:对,我们现在要去state文件。这个文件,好像不是加到这个state文件呀!这个文件呢?不对不对,好像也不是。
A:我们要构建的页面属于XX模块,得把state字段加在XX目录下的state文件中。
B:嗯嗯,有道理。那这个文件,嗯~好像也不对!
A:不然我来写吧。
...A拿过键盘,啃哧啃哧的编码...
B:要遍历数组了,用 for each 方法吧!
A:嗯~有其他遍历数组的方法吗?
B:还有 for…of、map 等方法,那我改成 map 吧!
A:其实这块用 reducer 方法可能更合适
B:reducer 方法!reducer方法该怎么写呀?
A:那我来写吧。
...A拿过键盘,啃哧啃哧的编码...
要给Junior足够的实践机会和成长时间。任何成长都是需要时间和实践的,每个人学通一个技术或领域知识所需花费的时间也不尽相同,我们要关注peer取得的进步,而不是聚焦于peer还没有掌握的部分,也不要因为peer的速度较慢就自己上手,剥夺他人实践和学习的机会。
敢于暴露自己的弱点。 敢于承认自己在某方面的能力比较欠缺,寻找机会有针对性地进行提升,不要害怕暴露缺点而不敢发言。展示弱点并不会让我们矮于他人,相反还会让大家觉得你比较容易接近。
团队要创建一个安全包容的环境。 团队有好的氛围,成员才不会担心说错,有更大的信心自由地发表自己的观点。
场景四:一个人的独角戏
A:任务列表的这一项太大了,不然我们把它拆小一些?
B:......
A:这块逻辑很复杂,还不好写测试,不然我们抽一个纯函数去处理这段逻辑吧?
B:......
A:这块代码这样写好像不太符合项目的模式,我们去找个已有例子参考一下?
B:......
全程无回应(我是谁?我在哪?我在做什么?)。
是peer没听到吗?是我声音太小了吗?是peer太专注没心思搭理我吗?
及时回应。沟通需要至少两个人的参与才能正常进行,就像讨论需要有来有往才是有效的。如果A讲了话却没收到来自B的反馈,有三种可能情况:1. B没听到。2.B听到了但是没有回答。3.B听到并回答了,但是回答的方式没有被A理解到。对A来说,若不确定B究竟听到没,可以加大音量重复所讲的内容。如果还是没有回答的话,可以问A是否有听到。为了让结对编程更顺畅,建议我们听到他人说话后,及时给出回应,以示意我们听到了。这个回应可以是语言、动作甚至眼神,所以如果我们正在思考,不能被打断或不方便给出回应时,可以选择动作等非语言的形式给出示意。
及时反馈。如果我们讲话后没收到对方的回应,要及时反馈。如果该现象发生了多次,我们可以建立一次反馈会议,跟peer针对该问题进行正式的沟通,分析原因,明确及时回应的必要,商讨对应的解决方法。
场景五:你怎么都不会
A:我们用 reducer 方法来对数组进行处理吧!
B:这数组遍历的 reducer 该怎么写呀?
A:你这都不会!
...A讲解 reducer 的使用方法...balabala… 一顿解释...
B:测试要mock外部依赖了
...B开始编码,尝试了两种mock方法,都没有达到预期效果...
A:这块结合实际情况,要这样mock
...A讲解该处mock外部依赖的方法...balabala…
B:对的,这样写测试结果符合预期。
A:你怎么这也不会!这很简单呀。
注意沟通技巧、用词和语气。我们在沟通的过程中,一定要以平和的心态,不带偏见的、尽可能客观地表达我们的意见。有些用词对你来说可能不带任何含义,但是在他人来说就不一定了,所以斟酌用词也是必要的,尽量使用常见的中性词。当然我们也没必要对每一个用词语气都反复斟酌,否则会过犹不及影响正常的沟通,毕竟都是项目的成员,说话的人和听话的人的感受都值得被关注、被考虑。重要的是在发生不愉快的时候,我们都要有去解决的意识,能及时沟通讲清前因后果及双方的理解,解除不愉快。
正确处理peer的能力低于预期的情况。peer 的打字速度好慢呀,都不用快捷键,方法定位也不会,这么菜吗?针对这些情况,要明确:闻道有先后,术业有专攻。如果 peer 某方面表现很不好的时候,不要随意批判轻视对方,要知道 peer 在这方面比较 junior,但是在其他方面,我们也可能会是初学者,所以我们要换位考虑对方的感受,同时还要意识到发展他人的机会来了。针对具体问题也要具体分析,通过沟通等手段寻找问题根因。是因为 peer 刚接触该技术栈呢?还是 peer 最近压力比较大,没休息好,所以有时会反应比较慢呢?亦或是文件搜索这块正好是 peer 的短板呢?不管是客观原因还是主观原因,都要结合实际情况,多方沟通探讨可行的解决方法并进行追踪。
场景六:建议被忽略
针对一个问题
A:...balabala…(讲完自己的思路)
B:...balabala…(提出一种新思路)
A:...coding…(按自己的思路开始编码)
B:...%#@$%&
...…B很懵,貌似自己的方案更合适吧?怎么就开始编码了呢?...
关注并重视自己和peer的感受。结对编程实践下,一天的大部分时间我们都在和peer进行协作,如果心有隔阂,那么结对工作很难顺利进行下去,所以在内心感到不适的时候,我们要重视自己的感受,及时反馈给对方并进行沟通,针对具体情况协商解决。
针对不同解决方案要沟通达成一致再编码。如果结对的两个人针对同一个问题分别提出了几个方案,这时要针对这些方案进行沟通,确保双方都理解了这些方案,并对方案的优缺点、工作量等有一定的理解,双方都赞同采用某方案后,再开始编码,避免实现到一半再出现分歧。这个过程中,可以对每个方案都从头到尾梳理一下,能提前识别一些存在问题的方案并过滤掉。如果针对要采用的方案无法达成一致,可以考虑暂时搁置争论,寻找支持论点的证据后,再开始新一轮的相互说服,也可以考虑引入第三方,TL或在该领域经验丰富的组员都是较好的选择。
及时反馈。反馈文化是我们所提倡的,可以建立多维度的反馈会议,比如peer的某种行为让我感到不适,如果不想打断工作进度且可以接受暂时不适时,可以选择一天工作快结束的时候再进行反馈交流,总结一天的工作,识别一些做得好或值得深挖、保持的地方。
场景七:按自己的节奏走
...A啃哧啃哧编码...无讲解思路...
B:你在不同的文件之间跳来跳去的,是在干嘛?
A:我们要修改测试环境的配置。
...A继续啃哧啃哧的编码...无讲解思路...
B:这又是在干嘛?我们不是要先去查看一下XX方法,然后看看能不能拿到我们想要的结果吗?
A:嗯,对。
...A继续啃哧啃哧的编码...无思路讲解......
B很懵,不知道A的思路是什么,也不知道A是按怎样的顺序处理问题...
共同贡献,团队拥有。结对编程实践下,开发软件功能是两个人的事情,Senior 不要大包大揽独自完成,只按照自己的节奏,而在没有讲解思路的情况下忽略了peer是否能跟上。Junior 也要为卡负责,不懂就问,进度太快就提出来,不要独自承受压力。
高频率的沟通。结对编程实践下,做卡是两个人的事情,这需要高频率的沟通以确保结对的两个人都清楚理解该卡的上下文、解决思路以及当前正在做的事情是什么。在讲解实现思路的时候,要细致到每一步(参考美食主播讲解菜谱的过程),毕竟peer的思路可能完全不同。
场景八:表现出被针对
项目组每周交换一次结对伙伴,刚交换结对伙伴后,A新加入该功能的开发。
...B给A同步正在实现的功能的上下文...
A:停,打断一下,这个方法为什么要先过滤再遍历数组呢?
B:因为...
...B反应激烈,声音拔高...
...A感觉peer竖起了盾牌...
当有新成员加入卡时,同步卡的上下文。需要同步卡的范围(scope),当前进度,是否有阻塞(blocker)等。在同步的过程中,讲解者能再次梳理卡的内容,有可能发掘之前忽略的小细节,接听者通过提问等也有可能提出不合理的地方,为卡的质量增砖添瓦。
结对编程时,大家的建议和争议都是对事不对人的。在结对编程的过程中,意见并不总是一致的。当发生争议时,作为被挑战的一方,应该客观地倾听别人的意见。如果之前没有充分考虑,要勇于承认并接受别人的意见,而不是认为别人在攻击自己,急于辩解并不断反驳。即使别人提出的建议很有道理,也不要因维护自己的虚荣心而拒绝采纳。可能团队成员缺乏足够的上下文信息,而挑战只是发出需要同步上下文的信号。要相信别人提出建议是为了让我们的代码更加可靠或者需要更多的上下文信息来理解我们正在做的事情。作为挑战的一方,我们需要注意用词和语气。我们的目的是提出想法,保证代码质量,而不是通过发表建议来展示自己的能力和不凡。我们应该保持客观的态度,使用平和的语气。如果感到对方比较排斥,要及时澄清情况。
场景九:因为私人事情离开
...A正在思考着解决方案,边思考边讲述着自己的思路...
...B突然离开...
...A猜测B是因为私人事情离开,感受很不好...
团队信任很重要。首先,我们不能主观评价peer离开的原因,也不能评价事件的重要性。其次,我们需要具备基本的职业素养,尽可能专注于结对编程,不在未沟通的情况下无故离开,除非情况紧急。
在每天开始结对编程前,要检查当天的日程并同步可结对的时间段。提前同步可结对时间可以帮助规划卡的工作,例如在两个人都能结对的时候确定解决方案,或先实现一些复杂逻辑。这样,在一个人离开后,另一个人可以无障碍地继续编码,避免遗留下来的人无法确定解决方案或无从下手处理复杂逻辑。
如果需要在结对编程过程中离开,尽量使事情透明。结对编程需要两个人频繁互动,因此考虑到peer的感受可以建立良好的关系,有利于结对编程的顺利进行。如果我们正在专注于结对编程,因为突发情况需要离开,尽量告知peer,方便的话讲清离开的原因及预计返回时间,这是对他人的一种尊重。
相信 peer 是专注于结对编程的。在专注于结对编程的同时,我们也要相信 peer 也是专注于结对编程的。如果peer在没有通知的情况下离开,我们应该相信是因为情况紧急而没有来得及通知,而不是因为工作态度有问题。
离开的人回来后,留下的人应该主动同步这期间的代码改动等。代码改动等同步可以让peer了解卡的最新状态,方便在下一步的编码中提出有效的建议。如果留下的人没有主动同步,离开的人也要主动询问改动内容,以便及时将注意力集中到当前的任务上。
场景十:peer 分心做其他事
...A在讲解一个解决方案的思路,讲着讲着发现B在看手机...瞬间没有讲解的欲望了,那自己继续编码吧...
...过了一会…
B:你刚才说什么?
...A偷偷叹了一口气,把刚才讲的内容重复了一遍...
A:我们这是不是应该提取个方法呢?...
等了20s左右无回应,转头一看,B在浏览网页...
...过了一会…
B:现在到哪了?
A:......
在进行结对编程时,应尽量避免分心。不要进行类似阅读邮件或使用手机等的活动,要专注于结对编程。如果出现特殊情况需要随时查看邮件来确认某件事情,就应提前沟通,让这个事情透明化,而不是等到出现矛盾时才采取事后措施。
在规划结对编程时间时,要预留一些时间。确保每个人都有足够的时间阅读自己的电子邮件。
总结展望
结对编程是一项需要团队合作的实践,也是一项复合技能,应用场景十分广泛,不能一一列举。我们应该在结对编程中不断磨练这项技能,提高结对编程的体验和价值。同时,在遇到困难时,也要敢于迎难而上,寻找解决方案。
最重要的是,我们应该认识到结对编程是一项团队实践,遇到的任何问题都可以从团队中寻求帮助。