过年了,各种公众号都在玩拜年,玩红包,甚至在玩喜羊羊,连程序君订阅的一些技术类的公号也不能免俗。作为大年三十还在苦逼上班的程序君,自然不会放过这个绝好的机会写点和技术沾边的文章来填补大家空虚的心灵 —— 因为我深深的知道,你们会边看春晚边想这个歌舞是不是冗余代码,那个小品是不是UT没做好,主持人虽然使用了多核,但使用的脚本一看就是php,性能不佳;来年应该让导演上erlang。。。
为了让你们那无时无刻不在运算的大脑继续保持很好的节奏感,这篇文章依旧会和技术有关;但又不会太techie,看一看,想一想,乐一乐就好。
今天瞎侃侃代码的发展史,走心走肾随你。
言归正传。
纵观整个软件工程的发展史,项目代码规模的不断增大导致了人们一直在寻求更好的代码组织方式,使其适应「笨拙」的人脑的理解能力。
最早的代码估计没有项目的概念,只是一个文件,几张A4纸就能将其表述清楚。这时的代码有最原始的控制结构(jmp,goto),整个程序揉在一起,被形象地称作意大利面条(spaghetti)。
意大利面条式的代码撑不到太大的规模,便超出了人脑所能理解的范畴。为了让代码更可读,从goto中衍生出了更好的控制逻辑:分支,循环(或者递归),以及用于管理目的的 [1] 函数,类 [2] 和模块。代码可以以更清晰,更可控地方式被撰写。
感谢文件系统的诞生,原本处在一个平面上的代码被人们以树状的结构进行管理。功能不相干的代码被放入不同的文件,继而放在不同的目录,于是库或者模块的概念产生了。有的程序员开始专门为其他程序员开发库或者模块。代码脱离了小农时代的自给自足,进化到了商品经济时代的社会化分工。
有了社会化分工,代码的规模开始急剧扩张。为了让规模化的代码编译过程更可控,诞生了make,scons,grandle,mix等工具;为了让代码的修改和追踪更可控,cvs,svn,git等工具又前来救驾。
有了分工,便有了依赖,依赖管理工具便就此诞生。pip,ivy,npm等在其各自的领域中,为开发人员减轻想想就头疼的依赖冲突。
软件工程越大,boilerplate代码就越多。各种framework对此专门优化,把boilerplate扼杀在摇篮里。DSL和metaprogramming让代码越来越专注于business logic,其它一切都「放着我来」。
从撰写的角度来讲,随着各种语言的lisp化,以及主流VM上lisp的亲戚 [3] 越来越成熟,代码的静态可维护性已经不成问题。然而,运行中的代码却依然没有太大的改观。大部分软件,尽管从静态的角度来看,模块化和关注点分离已经做到了足够好,代码与代码之间甚至在物理上都被树状的文件系统隔离,可当其编译运行起来成一个进程后,这种隔离消失了,所有的运行的代码又被统统揉在了一个平面中。在这个平面上,某个角落里产生的exception,有可能把整个进程搞挂。所以为了尽可能让某个局部的错误不至于影响全局,大家一致的做法是defensive coding —— 甭管谁的代码引起的问题,反正问题不能出在我这里,try catch也好,if error check也好,总之宁可错杀千人,不可漏掉一个。
defensive coding不能解决所有问题,那么,运行时有什么类似于文件系统的东西把不同逻辑的代码隔离开来,当defensive coding无法奏效,也可以把exception控制在可控的范围?
threading/multiprocessing可以勉强看作是一种手段,尽管它们的初衷是为了concurrency。不少软件利用multiprocessing,使用经典的supervisor/worker模式(如nginx),把更易出错的worker的exception隔离开来,让软件的robust大大提高。
可程序员们还在呼唤更好的解决之道:既然静态的代码可以用树状的层级结构来管理,为什么运行时的代码不能采用同样的方式呢?
大家的目光停留到了erlang,这个诞生于上世纪80年代,静静躺在不为人知的角落里的语言。它有一种奇怪的结构叫process(下面称actor,避免和众所周知的process混淆),还有一种奇怪的思想叫let it crash。
在erlang中,actor则相当于软件的细胞。若干个细胞结合起来,成为软件的组织;若干组织结合起来,成为软件的器官,然后再结合成整套软件。这种软件的组合模式看上去像是这样:
如果某个细胞损伤,那就让这个细胞死去,再克隆出一个新的细胞即可,这就是let it crash的思想。细胞如此,由细胞组成的组织,或者器官,也如此。erlang的actor发生异常,如果自己搞不定,就把自己杀掉,由其supervisor重新构建。
(restart one for one)
(restart one for all)
(restart rest for one)
生物体总有些关键的部位是不能损坏的(如心脏,大脑),一旦损坏,生物体也就完蛋了。这部分要保证足够健壮,不会出问题。在erlang里,这被称为error kernel。软件的撰写者要分辨出软件的哪部分是一定不能出问题的,一旦出问题,软件就得crash。除此之外,软件的任何部分(actor)出问题,无非就是这个部分重启的事情。
(error kernel)
于是,运行时的软件不再是一个各种代码揉在一起的平面,而是一棵层层隔离的树。
erlang开启的先河,被scala吸收了过去,构建在JVM和scala之上的akka将这思想传播到了更深远的地方(不是说akka优于erlang - akka还在拾erlang牙慧的路上 - 只是JVM过去二十年在企业的应用要远远广于beam),并且做得更彻底一些(erlang的actor可以选择是否supervise,akka所有actor都会被parent supervise)。
也许未来十年,这将成为软件的主要组织方式 [4]。因为,「永远在线」的互联网,越来越承受不起宕机 —— 哪怕仅仅是几分钟。未来,也许软件需要达到硬件一样的6sigma,尽管这在目前来讲简直是天方夜谭 [5]。
你觉得呢?
1. Don’t Repeat Yourself可以视作代码管理的一种手段
2. functional language没有类的概念,只有函数和模块
3. JVM上的scala, clojure,BEAM上的elixir
4. 我还没讲这种结构下concurrency,deployment的优势呢
5. 也不尽然,爱立信用erlang写的交换机软件达到了9sigma