標籤:

《代碼之髓》讀後感

《代碼之髓》讀後感

近來從南京回學校圖書館閉關,在計算機科學語言類書櫥上發現了一本好書名為《代碼之髓》。巴掌大小,是乃日本人西尾太和所著。比起機械工業翻譯精準、證明嚴謹的《深入理解計算機系統》來,書實在算不上高明,不過其引述邏輯層層遞進、講述問題精簡但深邃。如果你也對計算機語言對底層寄存器的具體操作以及其構成的邏輯原理抱以深深的疑惑的話,此書一定不可以錯過。

一直以來竊以為,著手於某一種具體的語言以及語法是永遠無法在編程上有本質上提升的,編程語言究其本質還是控制計算器寄存器運行邏輯,而奇怪的悖論是:往往學習計算機最初的開始又恰恰是深入學習了解一門語言,而本書恰恰能幫助你,從你所掌握的已有語言中跳出,橫向比較各類語言的語法構成,縱向說明幾種語言的發展歷史。

關於語言的討論:

所有語言之間的差異本質上就是語法的差異,而語法它決定了怎樣的代碼對應怎樣的語法樹,對於相同的語法樹(實現相同功能)而言,無非是語法的爬蟲沿著怎樣的路徑爬完樹的閉環邏輯的差異。

寄存器的運轉邏輯就像一顆長滿各種功能果子的果樹,每一隻語言的小蟲沿著他自洽的邏輯盡量遍歷每一個果實,實現每一個功能,這其中或到達某顆果實的距離較近,到達某顆果實的距離較遠,取決於語言創造的目的,所以你會看到C/C++語言傾向於蟲子爬行的速度,python注重代碼的可讀性就像是蟲子的顏值,但其執行效率卻很慢。PHP語言距離編寫web服務的果實很近,但距離文字處理的果實卻很遠。

所以,關注每一門語言創造的目的和擅長解決的問題,而不是語言本身。

關於結構化程序設計:

這個問題源自對不同語言之間結構不同的思考:即為什麼會出現if/while/for結構?foreach又是緣何演化而來?其實在if語言誕生以前,彙編語言沒有if同樣也能實現條件判斷功能:

_main: movl $123,-8(%rbp) #if語句前 movl -8(%rbp),%eax cmpl $456,%eax jne LBB1_2 #if語句中LBB1_2: #if語句後

對應C語言:

int main(){ int x=123; /*if語句前*/ if(x==456){ /*if語句中*/ } /*if 語句後*/}

同樣,if……else也能在彙編語言中實現。彙編中jump對應於C語言中goto 語句作為跳轉,而goto語句恰恰是C語言中的禁忌,不僅僅會是代碼的邏輯混亂,可讀性也會變得很差。

隨後誕生的while便是出於這樣一個目的:讓反覆執行的if語句更加簡潔。For語句為的是讓數值漸增的while語句更加漂亮,而foreach基於一個更加常用性的目的:根據處理的對象來控制循環操作。總之,這方面的一切的進展都是為了使語言表述更為精簡。

關於函數:

函數的產生在於它便於理解以及重複使用,具體而言是指可以用一段可供理解的字元串映射到某個值的內存位置,而嵌套式的數據邏輯產生了遞歸。程序員在使用函數、變數的過程中又不可避免地帶來了名字命名衝突,順著這個思路走下去的話其解決方法有二:一、取更長的變數名。二、使用作用域。

程序,總是越寫越大,人,總是趨於懶惰、尋求一勞永逸的解決辦法,到這裡,順理成章的帶來了兩種可能:動態作用域以及靜態作用域。函數依照對照表的優先順序搜尋變數的參數。時至今日,很多語言都選擇使用靜態作用域,拿python作為例子,對照表(作用域)分為三個層次,範圍由大到小依次為內置、全局、局部。而動態作用域在如今新語言的衝擊下python引入了global作為聲明,也算是銜接歷史遺留的產物,一步步走到了今天。

關於異常處理:

正所謂人非聖賢,孰能無過。本科期間學習C語言過度關注於實現函數內在邏輯,幾乎完全忽略了在工程上應用廣泛的錯誤處理機制。C其本身也是有錯誤處理機制——1、通過返回值傳達出錯誤信息。2、通過goto指令跳轉集中進行處理。

為什麼隨後的程序例如python會引進try/expect/finally語句專門處理測試代碼?上述方法一來容易在程序員的邏輯中遺漏,二來導致本身可讀性不強的C語言變得更難讀懂。最後,還是goto語句,一個設計出來本身就被列為禁忌的指令。

然而出錯後就應該立即拋出異常么?語言的設計何時拋出異常?發生錯誤立即停止操作並報告,這一錯誤優先(fail first)的設計能讓學習開發階段的程序員立刻注意到這固然幫助很大,可是軟體的目的不一樣,有時簡單地停止操作就顯得不太妥當。而各種豐富語言的背後恰恰都給出了它們自己的取捨。

關於名字和作用域:

通俗易見的賦值操作背後是關於命名的由來:用簡單易於人理解的方式表示計算機存儲數據的位置。早期的計算機中,名字的對應關係好比一塊公司共有的一塊大黑板,大家都能在上面寫上幾個對應關係。而這就帶來一個很重要的問題便是命名衝突。那麼如何避免衝突呢?

取更長的變數名:(略)

使用作用域:

動態作用域:

sub shori{ $old_i = $i $i = 0 $i = $old_i}

一種簡單的解決辦法便是把變數原來的值事先保存在函數的入口處,再在出口處寫回變數中。這就好比在公司的內部還添加了一塊部門的小黑板,只有在部門黑板上找不到對應關係時,才去查找公司的那一塊大黑板。

靜態作用域:

從動態走向靜態背後的理由大概是部門的私密性進一步提高,如果把每個函數看作一個部門的話,那麼他們都會有自己的小黑板(本地對照表),公司大黑板(全局對照表),合伙人內部小賬本(內置對照表)。當函數在本地對照表中找不到映射關係式,就按照局部、全局、內置的順序依次查看,而不再向動態作用域關注部門間級別問題,一言不合便上報公司(笑~)

關於類型與容器:

劃分各個類型的數據即是讓計算機給予底層的01信號給出不同的解釋,至於容器,即是在數據結構中討論的各種存儲、刪除、插入的時間複雜度問題了。沒有萬能的數據容器,只有針對問題合適的選擇。

關於繼承:

類的基本作用是分類,繼承機制即是將這種分類進一步細化,而由於同一類別事物具有共同的屬性,細化後這些屬性還是會被繼承下去。繼承在什麼時候實現?往往是從以下三個方面出發:

一、一般話語專門化:即從類進一步細分類。二、共享部分的提取:從多個類中抽取出共同部分作為父類。三、差異實現:子類是父類在功能上的追加,但這種情況下子類通常都不再作為父類的一種了。最後是關於多重繼承的機制,其發展歷史也是十分有趣,有些語言中乾脆把多重繼承給禁止掉了,有部分語言按照順序進行探索(深度優先/C3線性化順序),部分語言採用細分混入式處理。


推薦閱讀:

證明論學習筆記5:賦值系統
如何在 Windows 系統中高效使用 Alt 鍵?
美國留學申請之CS專業
心血來潮,試試專欄。
人類對人工智慧的嚮往和幻象由來已久,那麼,這次有什麼不同?——Yann LeCun上海紐約大學講座及座談精華

TAG:計算機科學 |