1. 程式人生 > >【轉】程序語言的常見設計錯誤(2) - 試圖容納世界

【轉】程序語言的常見設計錯誤(2) - 試圖容納世界

常見錯誤 rip 不一定 list 哪裏 code pre asc cti

之前的一篇文章裏,我談到了程序語言設計的一個常見錯誤傾向:片面追求短小,它導致了一系列的歷史性的設計錯誤。今天我來談一下另外一種錯誤的傾向,這種傾向也導致了很多錯誤,並且繼續在導致錯誤的產生。

今天我要說的錯誤傾向叫做“試圖容納世界”。這個錯誤導致了 Python,Ruby 和 JavaScript 等“動態語言”裏面的一系列問題。我給 Python 寫過一個靜態分析器,所以我基本上實現了整個 Python 的語義,可以說是對 Python 了解的相當清楚了。在設計這個靜態分析的時候,我發現 Python 的設計讓靜態分析異常的困難,Python 的程序出了問題很難找到錯誤的所在,Python 程序的執行速度比大部分程序語言都要慢,這其實是源自 Python 本身的設計問題。這些設計問題,其實大部分出自同一個設計傾向,也就是“試圖容納世界”。

在 Python 裏面,每個“對象”都有一個“字典”(dictionary)。這個 dict 裏面含有這個對象的 field 到它們的值之間的映射關系,其實就是一個哈希表。一般的語言都要求你事先定義這些名字,並且指定它們的類型。而 Python 不是這樣,在 Python 裏面你可以定義一個人,這個人的 field 包括“名字”,“頭”,“手”,“腳”,……

但是 Python 覺得,程序應該可以隨時創建或者刪除這些 field。所以,你可以給一個特定的人增加一個 field,比如叫做“第三只手”。你也可以刪除它的某個 field,比如“頭”。Python 認為這更加符合這個世界的工作原理,有些人就是可以沒有頭,有些人又多長了一只手。

好吧,這真是太方便了。然後你就遇到這樣的問題,你要給這世界上的每個人戴一頂帽子。當你寫這段代碼的時候,你意識中每個人都有頭,所以你寫了一個函數叫做 putOnHat,它的輸入參數是任意一個人,然後它會給他(她)的頭上戴上帽子。然後你想把這個函數 map 到一個國家的所有人的集合。

然而你沒有想到的是,由於 Python 提供的這種“描述世界的能力”,其它寫代碼的人制造出各種你想都沒想到的怪人。比如,無頭人,或者有三只手,六只眼的人,…… 然後你就發現,無論你的 putOnHat 怎麽寫,總是會出意外。你驚訝的發現居然有人沒有頭!最悲慘的事情是,當你費了幾個月時間和相當多的能源,給好幾億人戴上了帽子之後,才忽然遇到一個無頭人,所以程序當掉了。然而即使你知道程序有 bug,你卻很難找出這些無頭人是從哪裏來的,因為他們來到這個國家的道路相當曲折,繞了好多道彎。為了重現這個 bug,你得等好幾個月,它還不一定會出現…… 這就是所謂 Higgs-Bugson 吧。

怎麽辦呢?所以你想出了一個辦法,把“正常人”單獨放在一個列表裏,其它的怪人另外處理。於是你就希望有一個辦法,讓別人無法把那些怪人放進這個列表裏。你想要的其實就是 Java 裏的“類型”,像這樣:

List<有一個頭和兩只手的正常人> normalPeople;

很可惜,Python 不提供給你這種機制,因為這種機制按照 Python 的“哲學”,不足以容納這個世界的博大精深的萬千變化。讓程序員手工給參數和變量寫上類型,被認為是“過多的勞動”。

這個問題也存在於 JavaScript 和 Ruby。

語言的設計者們都應該明白,程序語言不是用來“構造世界”的,而只是對它進行簡單的模擬。試圖容納世界的傾向,沒帶來很多好處,沒有節省程序員很多精力,卻使得代碼完全沒有規則可言。這就像生活在一個沒有規則,沒有制度,沒有法律的世界,經常發生無法預料的事情,到處跑著沒有頭,三只手,六只眼的怪人。這是無窮無盡的煩惱和時間精力的浪費。

【轉】程序語言的常見設計錯誤(2) - 試圖容納世界