2002年的冬天,我在北京聯(lián)通出差。剛參加工作,有點(diǎn)小緊張,給電腦接電源,插三孔插座的時(shí)候錯(cuò)位60度,噗哧一下冒起了火花。沒有釀成事故,但是這件事情一直沒能忘記。其實(shí)國標(biāo)的三孔操作在設(shè)計(jì)規(guī)范上已經(jīng)考慮了這種錯(cuò)誤,三個(gè)插孔的開口方向是不一樣的。問題出在插座上面,當(dāng)時(shí)用的是萬能接線板,除了國標(biāo),歐標(biāo)和美標(biāo)也能插進(jìn)去,因此國標(biāo)插頭即便錯(cuò)開60度,用點(diǎn)力氣也能插進(jìn)去。此后到其他地區(qū)出差,特別留意他們的插座,發(fā)現(xiàn)最安全的是歐標(biāo),三相插孔的設(shè)計(jì)兩陰一陽,根本沒有錯(cuò)位的可能。

歐標(biāo)插座還有其他優(yōu)點(diǎn),比如最多調(diào)整90度就可以對準(zhǔn);插頭作為整體嵌入插座,不用擔(dān)心金屬部分暴露漏電;圓柱狀金屬頭加球面前端設(shè)計(jì),省力且牢固。這些暫且按下不表,今天想談的是程序設(shè)計(jì)語言中的安全措施。
程序語言的插座--類型系統(tǒng)
最近用python開發(fā)過幾個(gè)小工具,頗為趁手。原因之一是python支持duck typing??梢园讶我忸愋偷膶ο筚x值給變量,運(yùn)行時(shí)系統(tǒng)在訪問到這個(gè)對象的時(shí)候才會(huì)判斷其類型是否正確。換句話說,實(shí)現(xiàn)了類級(jí)別的運(yùn)行時(shí)多態(tài)(對比一下,C++/JAVA通過接口和虛函數(shù)支持函數(shù)級(jí)的運(yùn)行時(shí)多態(tài),而模板支持類和函數(shù)級(jí)別的編譯時(shí)多態(tài))。
首先,不要求提前定義接口。可以邊思考邊寫代碼,不用在接口部分和實(shí)現(xiàn)部分之間來回切換。
其次,不要求統(tǒng)一的對象界面。實(shí)現(xiàn)多態(tài)的時(shí)候不需要對象都是從統(tǒng)一接口上擴(kuò)展出來的,對象可以擁有只在某些特定分支下會(huì)被調(diào)用的接口,其他對象,如果不會(huì)命中這些分支,就可以不用定義這些接口。如果習(xí)慣了c++/java,這種風(fēng)格就是腦殘,但是對快速開發(fā)真的很有效。想象一下,在靜態(tài)語言的OOD中,如果原來設(shè)計(jì)的統(tǒng)一接口滿足不了需求,通常意味著概念抽象出了問題。樂觀情況下,要么擴(kuò)展原有接口方法的內(nèi)涵,并在調(diào)用者和被調(diào)用者之間重新分布職責(zé);要么把無法涵蓋的部分抽取出來,增加一個(gè)接口方法。
凡事必有兩面,首先是正確性不易驗(yàn)證。類型錯(cuò)誤只有程序運(yùn)行時(shí)才能檢測出來,通電以后才能發(fā)現(xiàn)抽頭插錯(cuò)了,用靜態(tài)語言,同樣的錯(cuò)誤最晚編譯時(shí)就能檢查出來。
其次是代碼結(jié)構(gòu)不易理解。閱讀靜態(tài)語言,看看數(shù)據(jù)結(jié)構(gòu)、函數(shù)原型(C語言),或者類型/接口定義(C++/JAVA),對程序結(jié)構(gòu)能了解個(gè)八九不離十。動(dòng)態(tài)語言,非得把對象間的調(diào)用代碼讀完,才能把握程序的設(shè)計(jì)結(jié)構(gòu)。
JAVA是相反風(fēng)格的代表,典型的“歐標(biāo)插座”。不支持動(dòng)態(tài)類型,更不支持內(nèi)存這種“萬能插座”。做一件事情,JAVA只給你一種方法。
語言設(shè)計(jì)和程序設(shè)計(jì)要考慮兩個(gè)認(rèn)知因素,一是方便寫,一是方便讀。動(dòng)態(tài)類型方便寫,靜態(tài)類型方便讀。動(dòng)態(tài)語言編寫結(jié)構(gòu)復(fù)雜,確定性要求高的程序還是面臨挑戰(zhàn)。語言沒有內(nèi)置“歐標(biāo)插座”,必然要求語言的使用者自律,尤其是團(tuán)隊(duì)協(xié)作開發(fā)時(shí)必須遵循一致的設(shè)計(jì)策略。
提升安全的語言特性
程序設(shè)計(jì)語言一直在往里面添加安全特性。比如ANSI C添加對函數(shù)原型的支持(最古老的C語言只關(guān)心函數(shù)名字,不檢查返回值和參數(shù)類型,C的鏈接器現(xiàn)在還保留這樣的設(shè)計(jì)),const和static修飾符;C++鏈接器的隱式函數(shù)類型檢查,引用類型,類成員的可見性修飾;java對指針的棄用,package,等等。恰當(dāng)使用這些特性,可以把錯(cuò)誤捕獲在編碼階段。
不止是軟件設(shè)計(jì)
但凡人會(huì)犯糊涂的地方,都是“歐標(biāo)插座”的用武之地。只能單向打開的消防門,電腦機(jī)箱里的各種接線纜接頭,優(yōu)秀軟件的人機(jī)界面等等。程序設(shè)計(jì)語言和API,本質(zhì)上是一種抽象的人機(jī)界面。程序語言是人和計(jì)算機(jī)之間的界面,API是人和另外一堆代碼的界面。記得讀大學(xué)的時(shí)候,學(xué)校里有個(gè)工業(yè)心理學(xué)實(shí)驗(yàn)室,主任是現(xiàn)在阿里的王堅(jiān),他們的主要方向就是各種人機(jī)界面設(shè)計(jì)問題。