写了多年代码,做了好多的工程,不停地完成项目,但如果你一直仅仅停留在重复这个过程,那么就不会得到真正的成长与提高。你得从这些重复做工程的过程中,抽象提炼出真正解决问题的工程思维,用来指导未来的工程实践。
什么是工程思维?我从自己过往经验中提炼出的理解是:一种具备科学理论支撑,并成体系的系统化思维。做了多年的软件开发工程,碰到和解决了数不清的问题,最终这些问题,我发现稍微抽象一下,可以归为以下两类:
上面的描述可能有点抽象,那具体该怎么理解呢?这里我分别举两个例子:线上有个 Bug,找到了有问题代码片段,需要一个优化实现方案来解决,这就是第一类问题,原因和结果非常明确清晰;线上老是出故障,而且反复总出意外故障,对于这个结果,它的原因是什么,这就很难简单归因了,就属于第二类问题。
对于这两类问题,我想讲讲两种不同的思维框架提供的解法。
第一类问题,现象清晰,归因明确,那么它唯一的难处就是为这个问题找到最优的解决方案。求解最优化问题,就需要科学与理论的支持,也即:科学思维。
先讲一个其他行业的故事:造船工程。很早以前,关于应该造多大的船,人们都是靠感觉摸索的。后来(十九世纪中期)有个英国工程师布鲁内尔(Brunel)意识到船应该尽可能造得大些,于是他设计了当时世界上最大的船。这是一艘挑战当时工业极限的船,该设计甚至还引发了当时社会激烈的辩论。
布鲁内尔的目标是建造一艘足够大的船,大到无需中途停留,直接能从英国开到印度,那么如此远的航程就需要有足够的货物与燃料(那时的燃料主要就是煤)的装载能力。而支撑他设计背后的理论却很简单,船的装载能力是体积决定的,跟船尺寸的立方成正比,而船航行受到的阻力则是和船底的面积成正比。所以,船越大,装载能力越大,但单位载重量的动力消耗却下降了,这就是为什么布鲁内尔要尽可能地造大船。
这就是科学理论给予造船工程的方向指引。吴军老师也曾在一篇文章《计算机科学与工程的区别》里指出:
科学常常指出正确的方向,而工程则是沿着科学指出的方向建设道路;在工程中必须首先使用在科学上最好的方法,然后再作细节的改进。
我做在线客服系统时碰到一个问题和滴滴打车的匹配问题非常类似,打车是人和车的匹配,而咨询客服是人和客服的匹配。抽象来看,这个匹配的算法并不复杂,但因为涉及到非常具体且繁琐的业务规则,实现起来就有特别多业务逻辑,导致性能有问题。这就是软件工程现实中的第一类问题,需要找到优化方案。
对于这类问题的解法,就是先用计算机科学理论来分析其性能的复杂度边界与极限,而咨询分配就是在 N 个客服里进行挑选匹配,每次只匹配一个人,所以理论复杂度极限是 O(N)。只要 N 有限大,那么匹配的性能最坏情况就是清晰的。
理论分析给出了边界,工程实现则是建设道路,这就需要在边界里找到最短路径。在客服匹配问题的工程实现总考虑的方式是:最坏的情况是每次匹配都要遍历 N 次,最好的情况是 1 次,那么实现方案评估就是尽可能让最好的情况发生的概率最大化。假如你的实现方案 90% 的场景概率都发生在最好情况下,10% 的场景发生在最坏情况,那么整体性能表现可能就比最坏情况高至少一到数个量级。实际提高多少,这取决于 N 的大小。
而另一个工程实现考虑的维度是,如果每次匹配中有 M 个高消耗操作,那么进一步的优化方式就是如何减少 M 的个数或降低每次操作的消耗。
这就是用科学思维来指导工程实践,科学理论指出方向,探明边界,工程实践在边界的约束范围内修通道路,达成目标。正如前面故事中,造船理论往大的方向走也有其极限,因为除了能源利用率的经济性外,越大的船对其他建造、施工和运营方面也会带来边际成本的提高,所以也就没法一直往大里造,这就是工程现实的约束。
所以,理论的意义不在于充当蓝图,而在于为工程设计实践提供有约束力的原理;而工程设计则依循一切有约束力的理论,为实践作切实可行的筹划。
简言之,科学理论确定了上限,工程实践画出了路线。
第二类问题,结果明确,但归因很难,那么找到真正的原因就是第一个需要解决的难点。这时,我们就需要用另一种思维方式:系统思维。
回到前面举的例子,线上老是出故障,而且反复出意外故障。如果简单归因,查出故障直接原因,发现是代码写得不严谨,实现有不少漏洞和问题,仔细看就能分析出来,但触发条件罕见不容易测出来,于是提出解决方案是增加代码评审(Code Review)流程来保障上线代码的质量。
关于代码评审就是我从业多年来遇到的一个非常有意思的问题,大家都觉得它有用,也都说好,但很多时候就是执行不下去。因为它不是一个简单问题,而是一个系统问题。万维钢在《线性思维与系统思维》这篇文章里,给出了一些系统问题的典型特征,其中有两条是这样说的:
我呆过的很多公司和团队,都想推行代码评审,最后都无果而终。反而是一些开源项目,还搞得有声有色。还是万维钢的那篇文章里,其对系统的定义:“所谓系统,就是一个由很多部分组成的整体,各个部分互相之间有联系,作为整体又有一个共同的目的。” 简单想想就会发现公司项目所在的 “系统” 和开源项目所在的 “系统” 其构成就完全不同,而且目的也不同。
一个系统中可以有若干个正反馈和若干个负反馈回路,正反馈回路让系统或者增长、或者崩溃,是要偏离平衡,负反馈回路则尽力保持系统的平衡。
对你想要解决的这个问题而言,可能就有一个回路,正在起主导的作用!如果你能发现在系统里起主导作用的回路是什么,你就抓住了系统的主要矛盾,你就找到了问题的关键所在。
曾有行业大牛在前公司有很好的代码评审传统和流程规范要求,自己也坚决支持代码评审。后来去了另一个同行差不多规模的公司,进入到团队后想推行代码评审时,就遭遇了巨大的阻力,不止是 “老人呵呵,一笑了之” 了,还甚至被公开地反对了。显然,对于代码评审这个问题,他的前后两家公司拥有完全不同的正、负反馈回路,以其个体之力,想要去改变已有的反馈回路,其实相当艰难。
我自己也曾在团队做过一些尝试,但一直找不到合适地建立正反馈回路的好方法。引入严格的代码评审流程,其负反馈回路立刻发生作用:更多的工作量,更多的加班等。负反馈,团队立刻就能感知到,而其正反馈回路发生作用带来好处却需要一定的时间。而且另一方面,建立新的回路或者摆脱当前的循环回路,还需要额外的能量来源,也即激励。
在解决系统问题,建设正反馈回路上也有过成功的样本。比如,在公司层面要求工程师产出专利,这对个体来说就是额外的负担,而负担就是负反馈。为了降低负反馈回路的作用,可以让专利和晋升到一定级别关联上,并增加专门培训来降低写作门槛;专利局每通过一份专利,就奖励一笔奖金(几千到上万),甚至没通过都能奖励几百块,这些就是建立正反馈循环回路的激励能量。
另外一个例子是,为了让程序工程师们更有分享的意愿和提升表达能力,就出一个规则,把分享和每年的晋升提报关联起来,本质就是提供了潜在可能的经济激励。经济学原理说:人会对激励做出反应。是的,经济学原理很有效。
软件工程,是研究和应用如何以系统性的、规范化的、可度量的过程化方法去开发和维护软件;而实际上软件开发本身就是一个系统工程,里面存在很多没法简单归因的第二类问题,它们没有通用的解法,只有通用的思维。
一个优秀的工程师应该同时具备科学思维和系统思维,它们是工程思维的两种不同表现形态:系统思维洞察问题本质,科学思维发现最优解法。
学完本章,你也可以去细心观察你周围的环境,看看都有哪些问题,属于哪类问题,以及你能发现它们的本质吗?欢迎你留言分享一二。