軟件隨想錄
文/程序人生(微信號: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 也是打工的角色);即便創業,也是一個理論上來說容易被替換的角色。