要理解好的代碼,你需要多多編寫“不好”的代碼
本文轉(zhuǎn)載自公眾號“讀芯術(shù)”(ID:AI_Discovery)。
網(wǎng)絡(luò)上有許多 “切記不要…”的文章:不要使用繼承(inheritance),千萬別用單例模式(singleton),scrum將被淘汰。這些真的都不能用嗎?如果語句真的很差勁,我們又該如何甄別哪些建議可取呢?
沒有完美的編程語言,也沒有最好的編碼方式,有的只是指南(也就是已知的缺陷)。很多編程建議十分夸張,每個人都告訴你不要再做這做那。照這樣發(fā)展,每個指令都是故障之源,我們很快就沒有指令可用了。就如同人們并不指望開卡車過木橋能到達(dá)對岸,但這不意味著木橋沒有承載力,也不是說人們不該開卡車。
編程語言只是工具。使用的時機(jī)、原因以及方法取決于我們自己。不能因噎廢食,我們該改進(jìn)的是使用方式。
怎樣辨別糟糕的建議?
好的建議有三個組成部分。首先,信息本身;第二,何時使用;第三,何時不用。糟糕的建議通常缺少第二和第三部分——通常呈現(xiàn)出永久適用的特征。
一個常見的話題是:“不惜一切代價避免使用繼承機(jī)制”。如果沒有適用和不適用條件,你可能會盲從建議而舍棄面向?qū)ο笙到y(tǒng)(OO系統(tǒng))最關(guān)鍵的工具。如果該建議變成:“繼承機(jī)制是非常好的工具,但深層次機(jī)構(gòu)(deephierarchies)通常有害。”這次問題很顯然出在“深度”上,這個解釋精準(zhǔn)且洞悉了問題——淺層次機(jī)構(gòu)是好的。
另一個要注意的就是語言。很多作者往往“肆意下筆,從不更正”,這樣的基調(diào)在充斥著互聯(lián)網(wǎng)。為了撰寫“肆意”而“肆意”撰寫是極有害的,作者不會說明他的建議并非普遍適用。為了避免語氣折損,他們往往不點明不適用條件。好的建議應(yīng)是友好的,而不是強(qiáng)硬的。每個真誠幫助過你的人都不會是怒氣沖沖的。
圖源:unsplash
經(jīng)驗之談
涉及到編碼,有兩條經(jīng)驗尤其適用:
(1) 創(chuàng)造和維護(hù)語言非常昂貴。如果新語言中不斷加入某種機(jī)制,那說明該機(jī)制十分重要。這就是全局作用域、繼承機(jī)制和如果語句仍然存在的原因。任何一篇聲稱該摒棄它們的文章都忽視了這些機(jī)制的重要性。
舉一個很好的例子:類型系統(tǒng)。Python和JavaScript因為不需要分類而吸引了許多開發(fā)者,之后就會后悔用無結(jié)構(gòu)語言寫了上千行代碼。“落伍的”Java和C#就不存在這個問題(也不是說它們就沒缺點)。
這就難怪TypeScript會出現(xiàn)。類型系統(tǒng)會返回弱類型語言——你輸入一部分,編譯器會補(bǔ)足剩下的。這一想法非常成功,分別通過 var 和 auto關(guān)鍵詞打入了C#和C++的世界,甚至Python現(xiàn)在也引入了類型系統(tǒng)。
(2) 與之相反,第二條經(jīng)驗是:現(xiàn)代語言從設(shè)計上淘汰了混亂的部分。
這就是宏、goto語句和顯式內(nèi)存管理消失了的原因。過去,Java的垃圾回收機(jī)制(GC)飽受抨擊,但幾乎所有現(xiàn)代語言的GC都不再需要Java虛擬機(jī)(JVM)了。
最近移除的是空指針異常。像Kotlin和Swift這樣的現(xiàn)代語言用設(shè)計執(zhí)行null檢查,C#8也是同樣的,Raw threading和async callbacks也存在自己的問題?,F(xiàn)在,我們編寫異步程序時就可以使用便捷的async/await語法。
圖源:unsplash
而這一切都說明:想做一個更好的程序員,需要了解編程語言的歷史。
大多語言由富有工具意識的人創(chuàng)造,由社群指引著發(fā)展。每次加入新要素,整個群體都會專注地探討元素的相關(guān)性和對群體的價值,也包括如何完善元素設(shè)計。改變和移除元素時也是如此,Python 3做了許多引人矚目的突破性改變,都卓有成效。
多編寫不好的代碼
我們使用的是數(shù)十年以來的創(chuàng)新成果和失敗設(shè)計。
只有潛心于“劣質(zhì)的”C/C++代碼,你才能真正地領(lǐng)略到垃圾堆疊的語言之美。這之前你只是想象過去編碼有多痛苦,那些寫過單例模式并深受其擾(有很多問題,比如編寫測試)的人才能真正理解那種痛恨。
教科書上的示例與實際操作千差萬別。前者不過是個提示,后者才會真正改變你的編碼方式。
大多人開始的時候都不會使用Git或Unit Tests來編碼。這些項目往往漏洞很多,也經(jīng)常不能運(yùn)行。沒有Git,你無法得知是否不小心更改了什么。沒有測試,你的代碼會在某次故障后直接不再運(yùn)行。諸如此類的體驗是促使我們每天使用這些工具的原因。
要真正理解如何寫好代碼,首先要會寫不好的代碼。有幾種能促使自己編寫糟糕代碼的方式(或者找到現(xiàn)有片段的缺點),其實歸根結(jié)底一件事:試著用另一種方法編碼,從而得知你的方案有多好或者過去有多糟。
下面是你閑暇時間可以做的事:
- 學(xué)習(xí)一門父系語言: 舉例來說, Kotlin的父系語言是Scala,Swift等語言的誕生是為了解決Objective-C存在的問題,C#取代了Java。學(xué)習(xí)父系語言能為你展示過去沒有但“今天擁有的”,也使人們更加珍惜今天人們唾棄的很多工具。
- 學(xué)習(xí)一門“承繼”語言: C++開發(fā)者應(yīng)該嘗試Rust,Java開發(fā)者試試Go,Python對應(yīng)Julia或Nim,JavaScript對應(yīng)TypeScript或Dart。與學(xué)習(xí)父系語言不同,學(xué)習(xí)承繼語言將揭露現(xiàn)有缺陷及怎樣解決。
- 學(xué)習(xí)LISP:很多人覺得這個語言很怪。LISP沒有變量,它是純函數(shù)式編程語言(比Haskell簡單)。你不需要精通這門語言,只需嘗試寫一些算法,比如斐波那契(Fibonacci)、快速排序算法( quick-sort)或者哈夫曼編碼(Huffman coding)。如果沉下心慢慢做,你會發(fā)現(xiàn)變量在很多情況下并不重要。
- 用純C語言寫一個文本處理器: 為文本文件設(shè)置一個路徑,打開它并刪除所有換行符,在每個句號后(.)加入新?lián)Q行符,然后重組每個單詞,保持第一個和最后一個不變。如果處理后每一行都保持平行就值得表揚(yáng)。它能快速反映出字符串處理的巨變。
- 找出設(shè)計模式:準(zhǔn)備一個設(shè)計模式的清單,并打開你正在做的或做過的項目?;ㄐr間閱讀每種模式,并找到某種模式適用的地方。想象每個模式運(yùn)用在這個項目中將會多簡潔,這是將設(shè)計模式整合到項目中的最佳方法。
圖源:unsplash
這些技巧要么改變你的編碼方式,要么幫助你審視已完成的代碼。無論哪種都會讓你意識到一切都不是想象得那樣光鮮亮麗。
我并不是在講對與錯,也不是講怎樣編碼,而是在邀請各位多多編碼。代碼是一種新的語言,嘗試用兩種方式完成同一件事情,多寫代碼才是碼農(nóng)的終極進(jìn)階之道。