2002年的冬天,我在北京聯通出差。剛參加工作,有點小緊張,給電腦接電源,插三孔插座的時候錯位60度,噗哧一下冒起了火花。沒有釀成事故,但是這件事情一直沒能忘記。其實國標的三孔操作在設計規範上已經考慮了這種錯誤,三個插孔的開口方向是不一樣的。問題出在插座上面,當時用的是萬能接線板,除了國標,歐標和美標也能插進去,因此國標插頭即便錯開60度,用點力氣也能插進去。此後到其他地區出差,特別留意他們的插座,發現最安全的是歐標,三相插孔的設計兩陰一陽,根本沒有錯位的可能。
歐標插座還有其他優點,比如最多調整90度就可以對準;插頭作為整體嵌入插座,不用擔心金屬部分暴露漏電;圓柱狀金屬頭加球面前端設計,省力且牢固。這些暫且按下不表,今天想談的是程式設計語言中的安全措施。
程式語言的插座–型別系統
最近用python開發過幾個小工具,頗為趁手。原因之一是python支援duck typing。可以把任意型別的物件賦值給變數,執行時系統在訪問到這個物件的時候才會判斷其型別是否正確。換句話說,實現了類級別的執行時多型(對比一下,C++/JAVA通過介面和虛擬函式支援函式級的執行時多型,而模板支援類和函式級別的編譯時多型)。
首先,不要求提前定義介面。可以邊思考邊寫程式碼,不用在介面部分和實現部分之間來回切換。
其次,不要求統一的物件介面。實現多型的時候不需要物件都是從統一介面上擴充套件出來的,物件可以擁有隻在某些特定分支下會被呼叫的介面,其他物件,如果不會命中這些分支,就可以不用定義這些介面。如果習慣了c++/java,這種風格就是腦殘,但是對快速開發真的很有效。想象一下,在靜態語言的OOD中,如果原來設計的統一介面滿足不了需求,通常意味著概念抽象出了問題。樂觀情況下,要麼擴充套件原有介面方法的內涵,並在呼叫者和被呼叫者之間重新分佈職責;要麼把無法涵蓋的部分抽取出來,增加一個介面方法。
凡事必有兩面,首先是正確性不易驗證。型別錯誤只有程式執行時才能檢測出來,通電以後才能發現抽頭插錯了,用靜態語言,同樣的錯誤最晚編譯時就能檢查出來。
其次是理解程式碼費力。閱讀靜態語言,看看資料結構、函式原型(C語言),或者型別/介面定義(C++/JAVA),對程式結構能瞭解個八九不離十。動態語言,非得把物件間的呼叫程式碼讀完,才能把握程式的設計結構。
JAVA是相反風格的代表,典型的“歐標插座”。不支援動態型別,更不支援記憶體這種“萬能插座”。做一件事情,JAVA只給你一種方法。
語言設計和程式設計要考慮兩個認知因素,一是方便寫,一是方便讀。動態型別方便寫,靜態型別方便讀。動態語言編寫結構複雜,確定性要求高的程式還是面臨挑戰。語言沒有內建“歐標插座”,必然要求語言的使用者自律,尤其是團隊協作開發時必須遵循一致的設計策略。
提升安全的語言特性
程式設計語言一直在往裡面新增安全特性。比如ANSI C新增對函式原型的支援(最古老的C語言只關心函式名字,不檢查返回值和引數型別,C的連結器現在還保留這樣的設計),const和static修飾符;C++連結器的隱式函式型別檢查,引用型別,類成員的可見性修飾;java對指標的棄用,package,等等。恰當使用這些特性,可以把錯誤捕獲在編碼階段。
不止是軟體設計
但凡人會犯糊塗的地方,都是“歐標插座”的用武之地。只能單向開啟的消防門,電腦機箱裡的各種接線纜接頭,優秀軟體的人機介面等等。程式設計語言和API,本質上是一種抽象的人機介面。程式語言是人和計算機之間的介面,API是人和另外一堆程式碼的介面。記得讀大學的時候,學校裡有個工業心理學實驗室,主任是現在阿里的王堅,他們的主要方向就是各種人機介面設計問題。