软件随想录

dc4g 6年前

软件随想 录

文/程序人生(微信号:programmer_life)。

(一)

软件领域有个叫格林斯潘的哥们,估计大家都不怎么熟悉,但下面这句话写过代码可能没几个不知道:

Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp.

任何C或 Fortran 程序复杂到一定程度之后,都会包含一个临时开发的、不合规范的、充满程序错误的、运行速度很慢的、只有一半功能的 Common Lisp 实现。

这便是所谓的「格林斯潘第十定律」(不用找了,没有前九个定律)。这老兄一辈子也没特别 NB 的作品,但却有这么一段注定要在程序员鄙视链上流芳千古的定律。

作为一个C程序员,在数次领教了这句话的威力后,我终于在去年末杀入 Lisp 阵营,首先拿了 racket 开刀,学得如痴如醉,随后又禁不住诱惑,跳入 clojure 这个 golden club,接受 Rich Hickey 和 David Nolen 等牛的醍醐灌顶。虽然杀进来前有个 evil 的私心:想让自己站在鄙视链的顶端傲倪四方;杀进来后却是战战兢兢,汗不敢出,学到的东西越多,自己越是把自己鄙视得一无是处。

学习一门对你而言「离经叛道」的语言相当于为自己开辟了一个全新的天地,让你走出达克效应(D-K effect)。那感觉,就像C程序员第一次使用 python 的 repl,第一次看见 list,dict 优美地想要哭。当然,语言有各自的适用场景,高下并不能以是否有 repl 论断,而在于你能从中得到多少你本不知道的智慧。一个 python 程序员,学习C代码,弄明白了 preprocessor,compiling,linking,loading,在 disassemble 的过程中如庖丁解牛般「看」到了系统的脉络,也会幸福地哭。

这便是 学习对自己而言是离经叛道的语言 的好处。python 程序员学C,学 erlang,学 clojure,学 haskell,都属离经叛道;学 ruby 却不是。这哥俩需要 paradigm shift 的地方着实不多,连 Cython 和 MRI 的 GIL(Global Interpreter Lock)都亲如一家人。学任何东西,paradigm shift 非常重要,有点像我们常说的「破而后立,败而后成」的意思。它是让人不断成长的一个关键。

(二)

C 和汇编有如太祖长拳。无名小厮耍起来也就是小朋友乱斗的效果,在萧锋手上,却是招招致命。语法本身极其简单,关键词手脚并用都能数得出来,写个 hello world 更是两分钟就能搞定,但只有你对系统融会贯通,练好各种内功心法,才能发挥其巨大威力。

PHP/javascript 是吸星大法。练起来不难,没内力的入门很快,网上到处是现成的模块,据为己有后立刻等级提升。不过其致命的缺陷导致你只能在准一流游走,用不好关键时刻还会反噬。

Python/Ruby 是太极剑,变化多端,小到一个卑微的脚本,大到高逼格的机器学习,都能轻松对付。可是 performance 和解释器实现上的先天不足(Guido/Matz 其实挺冤:我给你们个电钻,你们非要用它来钻钢板,性能不好,怪我咯)是其破绽,导致遇到计算密集/IO 密集型的问题,处理起来很是伤肾。

Erlang/Elixir 像是降龙十八掌,大开大阖,刚劲有力。可是入门不易,思想深邃,会的人不多,只能靠自己苦苦钻研。actor model,supervision tree,messaging passing,pattern matching,光理解透了,便是半载光阴,练出名堂,那出手便是大师风范。

clojure 好似独孤九剑,「风雷是一变,山泽是一变,水火是一变」,变化多端,核心是以不变应万变。需求纵使千变万化,提纲携领,找到破绽,然后以 macro 和 polymorphic 化之。代码即数据,数据即代码,以轻御重,化烦(object)去简(function),退则滴水不漏,进则攻无不克。

Haskell 像是乾坤大挪移,没有深厚的内力修为很难参透。lazy computation/monad 干的就是牵引挪移这样匪夷所思的事情。一个程序,不过是从输入到输出中间经历的一系列 transformation,你是一招一式传递数据,还是传递运算,斗转星移?回答了这个问题,haskell 也就算是入了门。

(三)

Professor Randy Pausch 讲过一个故事。他小时候打橄榄球,教练在让大伙做对抗训练的时候却并不把球给他们。有个孩子不爽:教练大人,我们这是在打橄榄球呢还是在打橄榄球呢?教练让孩子们停下来,问:

「一场比赛有多少球员参赛?」

「22 人」

「有多少人手里拿着球?」

「1 人」

「我就是教你们剩下 21 人的打法」

Randy 在回顾这个故事时说:fundementals,fundementals,fundementals。酷炫的东西就像冰山浮起的部分,我们只是看不见那更为关键的底部。

所以学一门语言,语法只是那飞来飞去的橄榄球。你接得住球,扔得远,并不代表你会无球跑动, 防守时巧妙卡位,进攻时神出鬼没。学一门语言没有领会其基本思想,也只能流于表面。

(四)

我们写代码写久了,有些东西总是绕不过去:流入系统的请求(Request)首先是要被授权(authorize)和鉴定 (authenticate)的,然后要被验证(validate)的,接下来是要被路由(route)的,然后是就是各种各样的变换 (transform),如有必要,记录(persist)需要保存的中间结果,最后输出(Response)。

所以,格林斯潘说的其实不完全对,对于大部分人而言,写一个软件,就像在写一个临时开发的、不合规范的、充满程序错误的、运行速度很慢的、只有 一小部分功能的编译器。我们只是使用未经良好设计的,原始而粗糙的手段,用拼凑出来的类,函数,if-else 攒了一个只能用在特定场景的编译器而已。

或者数据库。其实数据库也是编译器,编译器也是数据库。看你怎么理解。

(五)

现在似乎已经不是 lex/yacc 或 bison/flex 的时代了。我亲眼看见一个同事在费力地用 perl 一行行解析某个系统的数据文件,却压根没想到写个 BNF。BNF 对他来说,不是一种选择。

数据库也渐渐没有 store procedure,trigger 什么事情了。生在 web 下,长在创业潮的新一代已经把这些劳什子定性为 vendor lockin 的脏东西,轻易不碰。我自己也有很多年没写过 trigger 了。最近对付一个没有 hook 接口的第三方的老 java 系统,为了追踪某个表下的特定的列的更新,好让我的代码能够不修改这系统(我也没能力改一个复杂的 EJB 系统),我又重抄旧业,耍起了 trigger 和 temp table。同事看到,说:哈?这玩意怎么用在 ORM 里?

rich hickey 谈到 tradeoff 时说,你得先至少有两个 solution,才谈得上 tradeoff。然而,大部分时候我们找到一个 solution 都不容易,何谈两个三个,可不幸的是,几乎每个人写代码的人在做 design 的时候都会把 tradeoff 挂在嘴边。

(六)

我们在选择技术,完成工作的时候,忘记了软件其实是在为商业目标而打工。一切不以实际商业目标而优化的代码都是在耍流氓。作为程序员,我们很容易进入到 programmer-centric 的境界:

  • 这特么不是 bug,用户用错了

  • 提这需求的客户太 2B 了

  • 要的功能已经实现了,没人用不是我的错

商业上看中的是 cost/benefit,ROI,time to market,profit;程序员看中的是测试通过,代码提交,没事少改需求。

我朋友在的一家创业公司,研发状态混乱无比,代码没有 review,没有 UT,没有 CI,开发人员自己测吧测吧就 push production,也不写 log,系统局部瘫了都要用户发现才知道。但人家业务做得好。软件烂,欠了一屁股技术债,总是能通过招入更好的人进来慢慢弥补的;业务烂,软件再 NB,CI pipeline 轻舞飞扬,又如何?

我最近研究的一个产品 instavest,UI 简陋地连我都想帮他们改改 —— 同样是用 bootstrap,我觉得 UI 水平烂如我这程度,都能胜过他们。然并卵。

所以程序员别抱怨自己不受重视,没有话语权。business vision 才是核心。你不锻炼 business vision,找不到产品能被人使用,客户愿意购买的点,只能是打工的角色(做到 CTO 也是打工的角色);即便创业,也是一个理论上来说容易被替换的角色。