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是人和另外一堆代碼的界面。記得讀大學的時候,學校里有個工業心理學實驗室,主任是現在阿里的王堅,他們的主要方向就是各種人機界面設計問題。