天天看點

程式員應該多寫“壞”代碼如何識别那些壞建議(Bad Advices)經驗法則 (Rules of Thumb)多寫一點“壞”代碼

多寫些壞代碼,然後學會更快地識别它們。

告誡程式員們 “不要這樣做”的文章比比皆是,隔三差五網上就會冒出一篇。例如,不要使用繼承,永遠不要寫單例,scrum項目管理已經過時,等等。但是我們真的應該摒棄一切嗎?“if 語句”真的有那麼糟糕嗎?我們如何判斷哪些建議值得聽取?

世界上不存在一種完美的程式設計語言,也沒有所謂的正确編碼方式。然而,各種指南和已知的陷阱早已鋪天蓋地。如今在網上浏覽 “程式設計建議” 是件很可怕的事情,因為每個人都在告訴你 “千萬不要這樣做或那樣做” 。如此發展下去,很快我們就會沒有指令可用了。一切都會是bug的來源。

思考一下這個比喻:你不能把一輛卡車從木橋上開下去,然後指望它能完好無損地到達另一端。但這并不意味着木橋已經徹底被淘汰了,也不意味着你應該停止使用卡車。

程式設計語言給我們提供了工具。我們需要知道何時、為何、以及如何使用這些工具。不要因為錘子砸到了手指就把錘子扔掉——應該借此機會提高“瞄準”能力。

Don't throw your hammer away, improve your aim.

如何識别那些壞建議(Bad Advices)

好的建議有三個要素:資訊本身、适用場景以及不适場景。糟糕的建議往往缺乏第二點和第三點,而常常自诩為百分百有效。

一個常見的觀點是 “不惜一切代價都要避免使用繼承”。如果沒有明确适用場景或不适用的場景,你大概會盲目地遵循這一點,因而失去OO程式設計最基本的工具之一。相反,考慮一下這個說法:“繼承本是一個不錯的工具,但過深地繼承層級往往對代碼維護弊大于利”。從這種說法可以看出,問題是在于繼承的深度。這種解釋要精确得多,直接洞察問題本質,并提示我們相對淺的繼承層級是完全可行的,不必過分擔心。

另外需要注意的是語言表達。很多作者都被訓練為 “大膽地寫、絕不姑息”。這種論調在網際網路的部落格平台上尤其大行其道。但問題是,為了顯得大膽而刻意誇張表達是有害的。作者常常會忘記加上:此建議并不通用 。為了避免聽起來沒有說服力和感染力,作者常有意無視那些建議的不适場景。

好的建議應該像“友情提示”一樣的溫暖,而不是如“嚴厲警告”一般的恐吓。 你可以回憶一下這些年來獲得的真誠幫助,有哪次對方是憤怒地向你伸出了援手?

經驗法則 (Rules of Thumb)

特别是在編碼方面,有兩條經驗法則供大家參考:

一、語言的建立和維護成本很高,但如果一個特性總是被添加到新生的語言中,說明該特性仍然不可或缺。

這就是為什麼全局作用域仍然很關鍵,繼承和if語句也是如此。任何主張完全避免它們的文章,都忽略了這些特性的一個重要方面。

強制類型就是一個很好的例子。Python和JavaScript等語言創造的這類有些感性的無類型的世界吸引了很多開發者投入它們的懷抱,TA們随後便後悔用這種非結構化的語言寫出了上萬行代碼。而在Java和C#的傳統世界裡,這些都不存在 。當然,這并不意味着Java和C#就是天堂。

這也難怪TypeScript會變得很受歡迎。強類型概念已經在那些所謂的類型松散型的語言中逐漸回歸了——你輸入差不多的資訊就夠了,隻不過剩下的,如類型聲明/注解等工作,則由編譯器自動填充。這個想法非常成功,以至于它分别通過var和auto關鍵字進入了C#和C++世界。現在連Python都有了類型注解的功能。

二、現代程式設計語言在設計階段已經規避了很多的曾經讓人糟心的東西

這就是為什麼我們再也看不到宏、goto語句或顯式記憶體管理了。當年Java的垃圾回收機制有相當多的批評,但現在GC特性已經超越了JVM,幾乎成為了所有的現代語言的标配之一。

最近被移除的功能是空指針異常。現代語言如Kotlin和Swift在設計上會強制執行null檢查。C# 8也在走類似的路線。實作異步任務不論使用原生線程還是異步回調都會遇到類似的麻煩。好在現在,我們已經可以用更友善的async/await控制結構來更簡潔地編寫異步任務。

綜上所述,我們可以提煉以下幾點建議:

如果你想成為一個更好的編碼者,請了解程式設計語言的曆史。

盡管大多數語言最初都是由具備出色的工具意識(sense of tooling)的個體開發者創造的,但它們日後的發展都是由社群委員會來主導的。每當增加新的功能時,都會進行一系列的工作,專門讨論它們與社群的相關性和價值,并完善其設計。更改和删除功能時也是如此。Python3帶來了許多難以忽視的突破性的變化,但這一切都取得了成功。

多寫一點“壞”代碼

如今,我們所使用的工具都是近幾十年來的成功創新以及失敗設計的産品。

隻有當你潛心研究一些煩人的C/C++代碼時,你才能真正領略到具備垃圾收集特性的語言的魅力。在那之前,你能做的就是想象一下當年的痛苦。對單例設計模式的恨意,隻有那些曾經寫過并面對與之相關的諸多問題(如編寫測試用例)的人才能真正了解。

教材上的案例和現實中的經驗相差甚遠。前者不過是一種提示,真正改變你的編碼方式的則是後者。

我們大多數人在初學之時,都是在沒有Git或Unit Tests的情況下進行編碼。這些項目往往有很多bug,而且經常不能運作。沒有Git,你無法知道自己不小心改了什麼。沒有測試,你的項目可能會罷工幾天,然後周而複始。這種經曆驅使我們每天都使用這些工具。

要想真正了解如何寫出好代碼,你必須先寫出“壞”代碼。

有幾種方法可以強迫自己寫出“壞”代碼,或者在你目前的代碼中發現其醜陋的部分。歸根結底:嘗試用其他方式編碼。這會讓你知道你的解決方案有多好,或者你的解決方案曾經有多愚蠢。這裡用“曾經”是因為當你意識到愚蠢時你會改變它,沒錯吧?

下面就為大家列舉一下業餘時間可以做的事情。

1. 學習一門前任語言(Parent Language): 比如Kotlin就是受到Scala的啟發;除此之外,Swift試圖解決Objective-C的問題;C#取代了Java。學習前任語言可以讓你了解有多少“你現在擁有的東西”是當時沒有的,以及它當時所解決的問題。這教會你更加欣賞很多你可能認為是垃圾的東西。

2. 學習一門後繼語言(Successor Language): 如果你是一個C++開發者,你應該嘗試Rust;Java粉們應該試試Go;Python使用者可以試試Julia或Nim;JavaScript粉們應該嘗試TypeScript或者Dart。與學習前任語言不同,這會讓你知道你現在做的事情有多少是垃圾,以及如何更好地處理。

3. 學習LISP:這對很多人來說有點奇怪。LISP雖然沒有變量,卻是一種通用的程式設計語言,而且還比Haskell容易。你不需要對它精通,但可以試着寫一些算法,比如斐波那契數列、快速排序或赫夫曼編碼。如果你花時間去做,你會意識到很多時候變量是不必要的。

4. 用純C編寫一個文本處理器: 給定一個文本檔案的路徑,打開它,删除所有的換行符,并在每個句号(.)字元後添加新的換行符。然後,保持第一個和最後一個字元不變,對每個字進行重組。如果你能并行處理每一行,就能得到加分。這将快速地向你展示字元串處理是如何急劇發展的。

5.尋找設計模式: 拿一份設計模式的清單,然後打開一些你正在做或已經完成的項目。花點時間閱讀每一種模式,并嘗試找到可以從這種模式中受益的地方。對于你看到的每一項,試着想象一下,如果你使用了它,結果會有多簡潔。當然如果你還能重構它,理應得到加分。這是将設計模式納入你的技能庫的最好方法。

這些技巧本質上都是想讓你用不同的方式來編寫代碼,或者再看看你所做的一切。無論哪種方式,你都會發現,并不是所有的東西都像你曾經想象的那樣光鮮亮麗。

此外,我不是在告誡你何為對錯,也不是在教導如何編碼。相反,我隻是鼓勵你去......編碼。用一種新的語言來編碼,或者嘗試用兩種不同的方式做同一件事。隻有編碼才能讓你成為一個更好的編碼者—— 而不是在一味地在網上搜尋編碼建議。