2021年6月27日星期日

如何有理有据地说服其他程序员

TL;DR 当两个相对聪明和胜任的程序员对同一个问题持有截然不同的观点时,往往问题出现在大家对问题的定义不一样。争辩观点对错的往往并不能达成一致,这时候必须先确认大家在讨论的是同一个问题。如果没办法对齐(align)问题,尤其是当双方的优先级(priority)不一致时,争辩问题的解决方案是不可能有结果的。

人际矛盾

人际矛盾是行为面试(behavior interview)中常见的考点,作为面试官我自己经常问相关的问题。例如说,我在 Facebook 时就经常问以下两个问题:

  1. 有没有试过你和你的同事各持不同的观点,而且双方都坚持认为自己是对的,最后你成功说服了对方?
  2. 把上面的问题反过来,有没有试过最后你被对方说服了?

对于不同级别的程序员,我们对人际矛盾有不同的期望值。初级程序员,就算不能把矛盾解决,至少不能把矛盾激化。高级程序员,需要有能力把矛盾化解掉,从而推动自己的项目前进。更资深的程序员,需要有能力梳理组织当中错中复杂的利益和矛盾关系,优化组织提高效率。

在这篇文章里,我们的关注点是如何化解人际矛盾,这是很多初级程序员需要学会的技能,也是很多高级程序员需要持续提高的技能。在 career coaching 过程中,我时不时也会遇到客户提出这方面的问题,我给出的方案都是类似的,因此我觉得用一篇文章来解释会更好。

两个聪明人解同一问题……

我还在 Facebook 时,一个非常资深(总监级别)的华人程序员在一次吃饭时分享了一个观点:如果两个聪明人尝试解决同一个问题,但他们各自得出了不一样的答案,往往并不是其中一个人错了,而是他们拥有的信息不一样,各自根据自己拥有的信息进行推理就会得到不一样的答案。这时候双方必须先互通有无,把自己拥有的信息都摆到台面上来,然后再一起利用眼前的信息解决问题。

这一个观点是我的理论基础。我见过很多人因为意识不到这一点而陷入无效的争辩,甚至激化双方情绪。尤其是强调逻辑思维能力的程序员,很容易推理出这样一个结论:因为一个问题必然有且仅有一个正确的答案,又因为我和你的答案不一样,所以我们两个当中至少有一个人是错的。因为我很确信我自己是正确的,所以你必然是错的。因为你是错的,所以你比我笨。

这个推理过程是正确的,但前提是两个人在解决的是同一个问题。否则就如同考试后对答案发现两个人的答案不一样,争辩半天谁对谁错后才发现这场考试分 A 卷和 B 卷,大家做的根本不是同一道题。很多聪明人都犯过这样的错误,也就是跟别人争辩为什么自己的解法是对的,而忘记了检查大家是不是在做同一道题。

先定义问题再讨论解法

对于很多名校毕业或者大厂工作的程序员来说,其实自己和身边的人都已经足够聪明了。如果发现自己在跟别人争辩观点争辩不出来结果时,最好就先审视一下双方是不是在讨论同一个问题,如果不是很确定的话就先把问题定义清楚。当然,复杂的问题不可能方方面面都定义得很清楚,还是会涉及很多假设。如果双方对约束和优先级做了不同的假设,得出不同的结论是很正常的。所以我会建议大家先明确约束和优先级都有哪些。

对约束进行不同的假设

两个人从广州出发去北京,A 说往左走,B 说往右走。这时候看起来「从广州去北京」是同一个问题定义,但可能 A 想着的是去机场搭飞机,B 想着的是去火车站坐火车,所以走了不同的方向。如果两个人对于交通工具没什么看法,那澄清一下自己假设使用的交通工具矛盾就化解了。「我以为我们去搭飞机呢,原来你想着的是坐火车。我其实没什么所谓,你想要搭什么我们就一起走吧。」

但如果有更深层次的原因导致他们做出这样的假设呢?例如说,B 没有钱搭飞机,他只能坐火车。这就是一个约束了,加上了这个约束问题定义就不再是「从广州去北京」,而是「用有限的钱从广州去北京」。如果 A 不知道 B 有这个约束,那 A 和 B 在解决的就不是同一个问题。接着 A 和 B 可以就飞机好还是火车好大战五百回合,但矛盾仍然是解决不了。要化解这个矛盾,就必须要让 A 知道 B 有一个钱的约束,之后问题可能多种解法。A 可以说「那我陪你一起坐火车吧」,也可以说「我多出点钱请你搭飞机吧」。但如果 B 觉得没钱搭飞机是一条必须隐藏起来的信息,那这个矛盾就非常难解决了。(你需要一个情商非常高的 A,在获得这一条信息后按照这个约束来解题,同时又不暴露自己掌握了这一条信息。)


我在管理某个 iOS 的团队时就遇到过由于对约束的假设不一致带来的矛盾。我们在做一个新的 app,一边是两位很资深的程序员,他们拥有丰富的创业经验,他们用创业的风格迅速的写了一版原型;另一边是几个大厂培养出来的高级程序员,他们接过这个原型,把它打磨成最终能发布的产品。这种一边是特种兵另外一边是正规军的配合,很快就出现了矛盾。其中有一个矛盾我印象深刻,因为这个矛盾明显就是由约束不一致造成的。

正规军觉得特种兵写的原型代码是渣渣,接手后就开始大规模重构,按照 iOS 的思路把东西都封装进不同的 ViewController 里面。特种兵觉得正规军在浪费时间重构,正规军觉得这样做才符合大厂规范。这个矛盾我深挖到底,才发现源头来自于哪里。在特种兵打头阵写原型时,他们负责解决的是最难的技术问题,因为这些问题交下去给别人写可能就解决得不够好。其中一个技术挑战是,这个 app 在云端有三万张照片时,客户端要能够流畅地滚动和浏览照片。这个挑战在原型中是解决了的,但重构的过程中为了进行封装,一个原本 O(n) 的算法就被迫重写为 O(n^2),于是客户端在有三万张照片时滚动就不再流畅。

这里的隐藏约束就是「有三万张照片时要能流畅滚动」,这是当初特种兵带头立项时定下来的需求之一。但这没有很好地沟通给所有人,所以当正规军接手后就不知道有这个需求。在没有这个约束时,重构一下能提升接下来的工作效率那当然应该重构,于是就重构到不再符合这条约束了。这个矛盾的本质跟前面的例子一样,「显示云端照片」和「显示三万张云端照片且能流畅滚动」不是同一个问题。

对优先级进行不同的假设

A 和 B 又要从广州去北京了,这时候 B 已经有钱搭飞机了,但 A 反而想要坐火车了。A 尽管有钱搭飞机,但他最近想要存钱投资,觉得坐火车省钱更好。B 则想要快点到北京,多一点时间在北京玩。这时候看起来问题都还是「从广州去北京」,但两个人的优先级是不一样的。A 要解决的问题是「用尽量少的钱去北京」,B 要解决的问题是「用尽量少的时间去北京」,这自然会导致不一样的解法。

不同优先级带来的矛盾更难解决,因为有时候就算双方都把自己的优先级摆上台面了,但因为双方都只在乎自己的优先级不在乎对方的优先级,问题定义无法达成一致,最终问题不可能被好好解决。如果 A 坚持要省钱,B 坚持要省时间,那就算双方把问题定义清楚了,还是不能一起解决。最终的方案可能是,A 提前出发坐火车,最后跟 B 同一时间达到,这样 A 省了钱同时 B 省了时间,但两个人就不能同行了。


这种矛盾在那些前后端明确分工的公司还挺常见的:后端的优先级是自己少干活,最好就是 API 数据结构和数据库直接存在映射关系,复杂的场景就让前端调用多个不同的 API 然后自行组织数据维护状态吧。前端的优先级也是自己少干活,最好前端要做的每一件事情都只需要一个对应 API 调用就能解决,如果多个 API 调用之间需要维持状态那就应该由后端来维持。这种优先级的矛盾,如果双方都隐藏起来不说,再怎么争辩什么是「更好的系统设计」都没有用。如果双方愿意把自己的优先级说清楚,明白到对方的苦处,找个折衷方案,各自承担一部分的负担,那才能把矛盾化解掉。

总结

最后总结一下,当你发现你跟别人争辩不出结果时,尤其是你觉得自己是对的但对方好像也有道理时,你最好检查一下你们是不是在解同一道题。往往你会发现你们在解的不是同一道题,这时候你们就应该先讨论问题的定义,把问题定义清楚往往很快能找到一个双方都同意的结论。当然有时候因为双方优先级不一致,无法接受同一个问题的定义,那就是另外一类问题了。有空我可以再写写如何应对优先级不一致造成的矛盾,大家可以订阅/关注我,这次就先写到这里。