被诅咒的与被祝福的

身陷南洋囹圄,心系故园风物,无以聊赖之中,撰文权作慰藉。

从Neal Ford的一段话讲开去

前段时间看了Neal Ford的一个演讲,原视频

其中一段讲到了传统工程与软件工程的区别。

於我心甚有戚戚焉。

译如下:

软件工程和传统工程是截然不同的。

在传统工程的世界中,当你进入施工阶段时,你基本上是在给原子塑形,原子结构是很难重构的。一旦你将它们塑造成特定的形状,它们往往会保持这种形状,很难将它们变成其他形状。除非你熔化它们再回炉重造。因此,制造物理物品是一个非常昂贵的过程。这就意味着,我们会希望在施工开始之前正确做出设计。

但在软件世界中,我们不是在铸造原子。我们在比特(bit)的世界里,这些比特(bit)都非常具有可塑性。对比特(bit)进行更改非常容易。我们来比较一下,传统工程中有施工,软件世界中与之对应的是什么呢?那就是是编译和部署。编译和部署实际上是我们所设计的东西在现实世界中的表征,电子沿导电线路移动,对现实世界施加影响力。这就表明,对于我们软件世界而言,我们的设计蓝图就是我们的源代码。

在传统工程业中,最昂贵的部分是施工阶段。在桥梁和其他种类的物理制造中存在许多预测性数学,这样做的原因并不是为了获得更安全的结构,虽说它在客观上确实会达成这一点。初衷其实是为了节约成本。造完了一座桥梁,然后让重物碾压过去,观察桥梁是否会崩塌,从而来确认桥梁质量达标与否,这样做,万一垮塌了,这成本就实在是太昂贵了。所以你需要可预测性,因为制造这些东西太贵了。

但在软件世界中,因为施工过程对应的是编译和部署,所以改变的成本非常低,可以随时进行。我们甚至不必为此操心,每当我们停止打字时,我们的计算机内都会涌现出一小群精灵把我们设计的东西给制造出来,以便我们能够以一种逼真的方式对其进行测试。

所以Reeves先生说,“鉴于软件设计相对容易并且基本上可以免费构建,一个不足为奇的启示是,软件设计往往非常庞大和复杂。”

我相信软件是现在人类思维创造出来的最复杂的东西,有几个原因导致它们如此复杂,其中一个原因是物理世界中存在许多有用的约束,而软件的世界中这样的约束太少。


蓝图设计对应源码编写,建造施工对应编译部署,这着实有趣。

我们使用建筑这个隐喻来关照软件的时候,通常都是把架构设计对照到蓝图上去,而把源码编写对照到建造施工上去。

而Neal Ford不是这样使用建筑隐喻的。

Markdown

可以看出来,Neal Ford这个隐喻在落地程度上更进一层。

牛老爵爷的诅咒

Neal Ford这段话的内容非常丰富。

除了上述隐喻之外,还有桥梁不能像软件一样测试,软件如此复杂的原因是我们缺乏真实世界中的物理限制。

诚哉斯言,与土木工程师比起来,我们软件工程师确实是具有非常奢侈的环境。这个代码怎样?跑跑测试就知道了。

能想象土木工程师说这种话吗?

老板娘: 那栋楼设计搞的怎么样了?有没有仔细的检查过?

工程师: 我觉得没问题,直接施工建造出来看看就好了。

老板娘: 好,我相信你,既然你这么有信心,那就来吧。

Markdown

Markdown

工程师: 😳 😳 😳

老板娘: 请问您信心的来源是啥?有依据吗?你这么乱搞,我得陪多少钱?

工程师: 这是个小工程,有点粗心了。另一个,那个大桥的项目,我一定用心!

Markdown

老板娘: 合龙这部分,你是不是忘了设计喜鹊了?你打算让牛郎织女自己组成一个平行四边形吗?

工程师: 卒

老板娘: 破产

一旦发现失误,拆掉重造太贵,土木工程师没法像我们软件工程师一样动不动就跑测试,动不动就debug,只能事先把设计做好。

不过,也正是由于牛顿老爵爷设下了诅咒,土木工程师被逼练就了严谨的作风,扎实的推算能力。

(此处用牛老爵爷代表包括麦克斯韦在内的诸多先贤)

蛮荒西部 牛顿管不了 图灵没来得及立法

牛爷的诅咒持久而坚固,不过,还是有它管不到的法外之地。

引力,电磁力,强力,弱力,你们能耐程序员何?程序员就是要任性地玩弄bits。

祖师爷死得早,一口苹果啃完,留待我等程序员飘零纵欲至今。

福兮?祸兮?

Robert C. Martin:还是要设立并遵守基本法

无独有偶。最近看了《架构整洁之道》这本书。作者Robert C. Martin讲述了编程范式的发展历史,字里行间表达了与Neal Ford颇为神似的一些观点。

摘抄如下:

结构化编程是第一个普遍被采用的编程范式(但是却不是第一个被提出的),由Edsger Wybe Dijkstra于1968年最先提出。与此同时,Dijkstra还论证了使用goto这样的无限制跳转语句将会损害程序的整体结构。接下来的章节我们还会说到,也是这位Dijkstra最先主张用我们现在熟知的if/then/else语句和do/while/until语句来代替跳转语句的。我们可以将结构化编程范式归结为一句话:结构化编程对程序控制权的直接转移进行了限制和规范。

面向对象编程对程序控制权的间接转移进行了限制和规范。

函数式编程对程序中的赋值进行了限制和规范。

它们都从某一方面限制和规范了程序员的能力。没有一个范式是增加新能力的。也就是说,每个编程范式的目的都是设置限制。这些范式主要是为了告诉我们不能做什么,而不是可以做什么。

这三个编程范式分别限制了goto语句、函数指针和赋值语句的使用。那么除此之外,还有什么可以去除的吗

这些编程范式的历史知识与软件架构有关系吗?当然有,而且关系相当密切。譬如说,多态是我们跨越架构边界的手段,函数式编程是我们规范和限制数据存放位置与访问权限的手段,结构化编程则是各模块的算法实现基础。这和软件架构的三大关注重点不谋而合:功能性、组件独立性以及数据管理。


原文的意思非常明白,Structured programming,OO,FP都是在约束我们作为程序员的行为,给我们立下基本法,免得我们瞎搞。

Neal Ford: 自然界的物理定律是个好东西啊,可以限制土木工程师,让他们无法乱搞。我们软件世界里缺的就是这个。

Robert C. Martin: 老弟所言甚是。缺少自然的馈赠,我们就搞人为的规约。诸多编程范式都已经尝试过做这件事了。天不立法人立法。

张信哲表示同意

张信哲:你俩说的对,自由是个好东西,但是不能《过火》,免得过犹不及。

Markdown

工程师之荣光 演绎 归纳

人类99%的机率都在使用归纳法,只有1%的机率使用演绎法因为演绎法需要消耗认知能量,所以默认使用归纳法。

土木工程师要仰仗演绎推理,这是理工科之荣光。

此乃吾辈偏执之骄傲。

怎奈沦落为软件工程师,我们要重度依赖归纳才能勉强维持生活这样子。

代码有问题?跑一跑tests,改一改。还不行?再改改,再跑跑。仍然不行?那我可要debug了!!!

反复利用run tests和debug的方式来观察现象,收集现象,总结现象,反推原理。

这是软件工程师在脑力不断失败的绝望中不断降低自己身段的慷慨悲歌。

福兮?祸兮?

测试要先写 别着急跑 debug不到万不得已最好别用

为了保护我们宝贵的智商与荣光,在此作出如下建议。

测试写完,然后去写实现代码的时候要尽力去争取一遍过。

在试图用run test去验证刚刚写完的实现代码之前,别着急,可以先回头把代码捋一遍,用肉脑跑一遍先。

不到万不得已,别用debug。因为一旦你开始debug,就是认输了,认怂了。承认无法通过肉脑来演绎推理明白了。

不要过于骄纵,不要过量服用软件世界给我们的自由,多给自己一些人为设置的限制,向土木工程师看齐。

没想到吧?前面写的神神叨叨,最后来到了如此接地气的建议上。

别误会 不是那个意思

不是宣扬big upfront design。

不是反对short feedback loop。

不是反对跑测试,只是建议别着急跑,不要太过依赖跑测试,优先用肉脑,然后才跑测试。

不是否认debug的价值,只是希望大家尽力放慢做出要去debug的决定的速度,让子弹飞一会,给肉脑机会,让它跑一会。

不是否认归纳的价值,只是建议演绎与归纳不可偏废,不要过度依赖归纳而冷落演绎。


谨以此文与诸君共勉。For the serious hardcore programmers。