Code review是个数学问题:从二向箔说起
写代码有两件最重要的事情,一是仰望星空,二是脚踏实地。在搞code review之前,我们先看一张星空的图,梵高的星空:
看到了这张星空,不知道读过《三体》的同学是不是联想到了二向箔。
即使是没有看过《三体》的同学,对于降维攻击这个词应该也不陌生。二向箔是将三维空间变成二维空间的武器。
我们引用一段原文:
“两个二维体的结构在星光下清晰地显现出来:在二维化的太空艇,可以看到二维展开后的三维构造,可以分辨出座舱和聚变发动机等部分,还有座舱中那个卷曲的人体。在另一个二维化的人体上,可以清楚地分辨出骨骼和脉络,也可以认出身体的各个部位。在二维化的过程中,三维物体上的每个点都按照精确的几何规则投射到二维平面上,以至于这个二维体成为原三维太空艇和三维人体的两张最完整最精确的图纸,其所有的内部结构都在平面上排列出来,没有任何隐藏,但其映射规程与工程制图完全不同,从视觉上很难凭想象复原原来的三维形状。”
关键的一句来了:
“与工程图纸最大的不同是,二维展开是在各个尺度层面上进行的,曾经隐藏在三维构型中的所有结构和细节都在二维平面排列出来,于是也呈现了从四维空间看三维世界时的无限细节。”
这简直就是在讲code review的方法论嘛。code review的最终愿景,就应该是将所有可能出错的代码都找出来,或者说能证明代码是正确的。
这可能跟很多搞code review的同学的思路是不同的,有的同学认为,最强大的code review系统,应该是能够精确发现未来能够被发现有bug的代码,没有发现的不算。这样最节省力量,也符合很多同学对牛人的美好愿望。
这本质上是一种归纳法,通过为每个已经发现的问题写规则来预防未来出现的问题。这当然要搞。但是,当想对规则做一些拓展和泛化的时候就会发现,实现这些规则的难度一点也不低,甚至发现用现有的算力也仍然做不到。
如果一个问题是有限的,那么通过分治法可以将问题拆解到原子状态。但是如果一个问题是分形的,那么无论如何拆分得到的都是同样的问题。
那么面对这样复杂的一个问题,我们该如何办?我的答案是从数学中想办法。
我们不讲原理,讲两个故事吧。
十维空间的故事
无限的世界跟我们的直觉经常是不一致的,有很多我们直觉上的无限其实实际上是有限的。
比如在地球上的一条线,直觉上是可以无限延长,一直到想象中的无限的中,但是实际上最长不过也就是地球球面上的大圆。
再比如昨天在路上听到一个小学生给同学讲,他要给某物体放到零下几十万度的环境里去。
我们在写代码里经常是可以创造多维世界的。比如版本号,大版本号1.0,2.0,3.0可以形成一条主线。1.0可以独立发展为1.1,1.2,1.3等,每个小版本又可以独立发展成不兼容的线。其实使用正实数来描述是不对的,明明应该是用[1,0,0], [2,1,0], [3,2,0, patch1]这样的向量来描述才对。
我们举个例子,假如给所有已经发布的版本都打上一个同样的patch1,那么如果线性增长就容易乱套了。比如有版本1.0.0, 1.0.1, 1.1.0,比如1.0.0升到1.0.2, 1.0.1升到1.0.3,1.1.0升到1.1.1这样的方案就容易乱。现有的线性方案就不管1.0.0了,直接给1.0.1升1.0.2这样。
但是其实如果是长期维护的话,故事往往没有这么简单。比如Linux内核3.10.*已经没有官方维护了,官方的线性版本没有了,于是就不可避免地产生分叉,也就是从一维变成n维了。
在机器学习中,成百上千维的数据更是家常便饭了。
但是,维度这么多,是不是像想象中的零下几十万度一样是个错觉?
在超弦理论中,世界是十维的,到达十维之后,就只剩下一个包罗万象的点,所有的可能连线都包含在其中。
对于我们生活的四维空间,虽然没有广义相对论加持,但是也还算是有足够经验。
从四维到五维是一个艰难的突破,不过我们有《星际穿越》做教材,这就是讲五维世界的。
在四维空间里,我们只能经过过去走向未来。而如果我们从五维上看,就可以回到过去,预知未来。
回到过去了之后,你想起了曾经错过的女生,想要改变历史。当历史改变的那一刻,你已经进入了六维空间,因为时间线分叉了。
既然都分叉了,那干脆把所有的可能性都试一下?两条线可以确定一个平面,而从一点发出无数条直线,这是一个球。
我们取球内的任意两点做连线,这就是一条七维空间的线。这样的两条线相交就成为一个新的面,即八维空间。两条线不可能只在一个平面上,变成一个三维的体,就是九维空间。
九维空间中的所有的点任意相连,最终就形成十维空间。不过现有理论认为十维空间包容一切可能的点,再也不能构成十一维的空间了。
请注意,上面我不是讲科学理论,是讲个故事。就是说,我们不想投机的办法,直接用最笨的办法,面对的问题可能并不是无限的,而是可能有边界的。
程序代码的逻辑可能要更复杂一点,比如有死循环走不出来的。但是,写代码的目的并不是要把code review系统搞死,而是尽可能借用它的力量来节省脑力体力。
战胜一切市场的人的故事
讲完无限可能是有限的故事,我们再讲一个数学家的故事。
常言说,买的没有卖的精。在做生意的人中,可能没有比开赌场的更精明了,因为赌博本身就是个玩数学的游戏。
历史上研究赌博的数学家不计其数,比如概率论学科就是这么起源的。但是直到索普出手之前,所有数学家的结论都是玩家不可能击败庄家。
索普这个人牛就牛在,不但数学理论扎实,也懂得使用最先进的算力。1959年,索普用当时很先进的IBM大型计算机,推演了最有利于玩家的游戏21点的所有可能情况下的概率分布。经过推算发现,在有利于玩家的情况下,可以比庄家的胜率高5%。
但是也不能推着IBM大型机去赌场啊,于是索普研究特征工程,总结出前面出现的小牌越多,剩下的牌对于玩家越有利的规律。索普亲自到赌场去实践,赌场甚至派上出老千的发牌手仍然无计于事。最后只好把索普拉黑。索普甚至出了一本书来指导玩家,差点被赌场的大佬干掉。
索普还是端智能的前辈,为了计算更复杂的轮盘赌中的小球的轨迹,他和信息论之父香农一起制造了一个放在鞋里的IoT计算设备,证明有效。只不过因为当时的硬件设备稳定性还不行所以没有推广开来。
不过再这么玩下去,索普被赌场大佬干掉是早晚的事了,于是他又转战股票市场。他没学过证券分析更不懂经济学,也没有内幕信息,于是又把投资变成了一个数学问题,他的办法是对冲。他开创的方法被称为量化投资,至今仍是证券市场的主流流派之一。
索普这次算法没开源,于是有三位学者搞了个开源的实现,最终健在的两位获得了诺贝尔经济学奖,他们推出的模型就是BSM模型,可能很多同学都学习过。
小结
讲了这么多,我想说的是:
- code review本质上要做的事情是将代码运行的所有可能都铺开。这可以认为是code review的第一性原理。
- 状态全展开的复杂度很可能与归纳法做完善的等价的,它可能是有边界的。
- 从索普的故事可知,就算第2条做不到,我们只要能打败目前市场上其它的code review系统就有生意做。