如何寫好程式碼?
寫了多年的程式碼,始終覺得如何寫出乾淨優雅的程式碼並不是一件容易的事情。按10000小時刻意訓練的定理,假設每天8小時,一個月20天,一年12個月,大概也需要5年左右的時間成為大師。其實我們每天的工作中真正用於寫程式碼的時間不可能有8個小時,並且很多時候是在完成任務,在業務壓力很大的時候,可能想要達到的目標是如何儘快的使得功能work起來,程式碼是否乾淨優雅非常可能沒有能放在第一優先順序上,而是怎麼快怎麼來。
在這樣的情況下是非常容易欠下技術債的,時間長了,這樣的程式碼基本上無法維護,只能推倒重來,這個成本是非常高的。欠債要還,只是遲早的問題,並且等到要還的時候還要賠上額外的不菲的利息。還債的有可能是自己,也有可能是後來的繼任者,但都是團隊在還債。所以從團隊的角度來看,寫好程式碼是一件非常有必要的事情。如何寫出乾淨優雅的程式碼是個很困難的課題,我沒有找到萬能的solution,更多的是一些trade off,可以稍微討論一下。
程式碼是寫給人看的還是寫給機器看的?
在大部分的情況下我會認為程式碼是寫給人看的。雖然程式碼最後的執行者是機器,但是實際上程式碼更多的時候是給人看的。我們來看看一段程式碼的生命週期:開發 --> 單元測試 --> Code Review --> 功能測試 --> 效能測試 --> 上線 --> 運維、Bug修復 --> 測試上線 --> 退休下線。開發到上線的時間也許是幾周或者幾個月,但是線上運維、bug修復的週期可以是幾年。
在這幾年的時間裡面,幾乎不可能還是原來的作者在維護了。繼任者如何能理解之前的程式碼邏輯是極其關鍵的,如果不能維護,只能自己重新做一套。所以在專案中我們經常能見到的情況就是,看到了前任的程式碼,都覺得這是什麼垃圾,寫的亂七八糟,還是我自己重寫一遍吧。就算是在開發的過程中,需要別人來Code Review,如果他們都看不懂這個程式碼,怎麼來做Review呢。還有你也不希望在休假的時候,因為其他人看不懂你的程式碼,只好打電話求助你。這個我印象極其深刻,記得我在工作不久的時候,一次回到了老家休假中,突然同事打電話來了,出現了一個問題,問我該如何解決,當時電話還要收漫遊費的,非常貴,但是我還不得不支援直到耗光我的電話費。
所以程式碼主要還是寫給人看的,是我們的交流的途徑。那些非常好的開源的專案雖然有文件,但是更多的我們其實還是看他的原始碼,如果開源專案裡面的程式碼寫的很難讀,這個專案也基本上不會火。因為程式碼是我們開發人員交流的基本途徑,甚至可能口頭討論不清楚的事情,我們可以透過程式碼來說清楚。程式碼的可讀性我覺得是第一位的。各個公司估計都有自己的程式碼規範,遵循相關的規範保持程式碼風格的統一是第一步(推薦谷歌程式碼規範[1]和微軟程式碼規範[2])。規範裡一般都包括瞭如何進行變數、類、函式的命名,函式要儘量短並且保持原子性,不要做多件事情,類的基本設計的原則等等。另外一個建議是可以多參考學習一下開源專案中的程式碼。
KISS (Keep it simple and stupid)
一般大腦工作記憶的容量就是5-9個,如果事情過多或者過於複雜,對於大部分人來說是無法直接理解和處理的。通常我們需要一些輔助手段來處理複雜的問題,比如做筆記、畫圖,有點類似於在記憶體不夠用的情況下我們借用了外存。
學CS的同學都知道,外存的訪問速度肯定不如記憶體訪問速度。另外一般來說在邏輯複雜的情況下出錯的可能要遠大於在簡單的情況下,在複雜的情況下,程式碼的分支可能有很多,我們是否能夠對每種情況都考慮到位,這些都有困難。為了使得程式碼更加可靠,並且容易理解,最好的辦法還是保持程式碼的簡單,在處理一個問題的時候儘量使用簡單的邏輯,不要有過多的變數。
但是現實的問題並不會總是那麼簡單,那麼如何來處理複雜的問題呢?與其借用外存,我更加傾向於對複雜的問題進行分層抽象。網路的通訊是一個非常複雜的事情,中間使用的裝置可以有無數種(手機,各種IOT裝置,桌上型電腦,laptop,路由器,交換機…), OSI協議對各層做了抽象,每一層需要處理的情況就都大大地簡化了。透過對複雜問題的分解、抽象,那麼我們在每個層次上要解決處理的問題就簡化了。其實也類似於演算法中的divide-and-conquer, 複雜的問題,要先拆解掉變成小的問題,從而來簡化解決的方法。
KISS還有另外一層含義,“如無必要,勿增實體” (奧卡姆剃刀原理)。CS中有一句 “All problems in computer science can be solved by another level of indirection”, 為了系統的擴充套件性,支援將來的一些可能存在的變化,我們經常會引入一層間接層,或者增加中間的interface。在做這些決定的時候,我們要多考慮一下是否真的有必要。增加額外的一層給我們的好處就是易於擴充套件,但是同時也增加了複雜度,使得系統變得更加不可理解。對於程式碼來說,很可能是我這裡呼叫了一個API,不知道實際的觸發在哪裡,對於理解和除錯都可能增加困難。
KISS本身就是一個trade off,要把複雜的問題透過抽象和分拆來簡單化,但是是否需要為了保留變化做更多的indirection的抽象,這些都是需要仔細考慮的。
DRY (Don’t repeat yourself)
為了快速地實現一個功能,知道之前有類似的,把程式碼copy過來修改一下就用,可能是最快的方法。但是copy程式碼經常是很多問題和bug的根源。有一類問題就是copy過來的程式碼包含了一些其他的邏輯,可能並不是這部分需要的,所以可能有冗餘甚至一些額外的風險。
另外一類問題就是在維護的時候,我們其實不知道修復了一個地方之後,還有多少其他的地方還需要修復。在我過去的專案中就出現過這樣的問題,有個問題明明之前做了修復,過幾天另外一個客戶又提了類似的問題出現的另外的路徑上。相同的邏輯要儘量只出現在一個地方,這樣有問題的時候也就可以一次性地修復。這也是一種抽象,對於相同的邏輯,抽象到一個類或者一個函式中去,這樣也有利於程式碼的可讀性。
是否要寫註釋
個人的觀點是大部分的程式碼儘量不要註釋。程式碼本身就是一種交流語言,並且一般來說程式語言比我們日常使用的口語更加的精確。在保持程式碼邏輯簡單的情況下,使用良好的命名規範,程式碼本身就很清晰並且可能讀起來就已經是一篇良好的文章。特別是OO的語言的話,本身object(名詞)加operation(一般用動詞)就已經可以說明是在做什麼了。重複一下把這個操作的名詞放入註釋並不會增加程式碼的可讀性。並且在後續的維護中,會出現修改了程式碼,卻並不修改註釋的情況出現。在我做的很多Code Review中我都看到過這樣的情況。儘量把程式碼寫的可以理解,而不是透過註釋來理解。
當然我並不是反對所有的註釋,在公開的API上是需要註釋的,應該列出API的前置和後置條件,解釋該如何使用這個API,這樣也可以用於自動產品API的文件。在一些特殊最佳化邏輯和負責演算法的地方加上這些邏輯和演算法的解釋還是非常有必要的。
一次做對,不要相信以後會Refactoring
通常來說在程式碼中寫上TODO,等著以後再來refactoring或者改進,基本上就不會再有以後了。我們可以去我們的程式碼庫裡面搜尋一下TODO,看看有多少,並且有多少是多少年前的,我相信這個結果會讓你很驚訝(歡迎大家留言分享你查詢之後的結果)。
儘量一次就做對,不要相信以後還會回來把程式碼refactoring好。人都是有惰性的,一旦完成了當前的事情,move on之後再回來處理這些機率就非常小了,除非下次真的需要修改這些程式碼。如果說不會再回來,那麼這個TODO也沒有什麼意義。如果真的需要,就不要留下這個問題。我見過有的人留下了一個TODO,throw了一個not implemented的exception,然後幾天之後其他同學把這個程式碼帶上線了,直接掛掉的情況。儘量不要TODO, 一次做好。
是否要寫單元測試?
個人的觀點是必須,除非你只是做prototype或者快速迭代扔掉的程式碼。
Unit tests are typically automated tests written and run by software developers to ensure that a section of an application (known as the “unit”) meets its design and behaves as intended. In procedural programming, a unit could be an entire module, but it is more commonly an individual function or procedure. In object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method.
From Wikipedia
單元測試是為了保證我們寫出的程式碼確實是我們想要表達的邏輯。當我們的程式碼被整合到大專案中的時候,之後的整合測試、功能測試甚至e2e的測試,都不可能覆蓋到每一行的程式碼了。如果單元測試做的不夠,其實就是在程式碼裡面留下一些自己都不知道的黑洞,哪天呼叫方改了一些東西,走到了一個不常用的分支可能就掛掉了。我之前帶的專案中就出現過類似的情況,程式碼已經上線幾年了,有一次稍微改了一下呼叫方的引數,覺得是個小改動,但是上線就掛了,就是因為遇到了之前根本沒有人測試過的分支。單元測試就是要保證我們自己寫的程式碼是按照我們希望的邏輯實現的,需要儘量的做到比較高的覆蓋,確保我們自己的程式碼裡面沒有留下什麼黑洞。關於測試,我想單獨開一篇討論,所以就先簡單聊到這裡。
要寫好程式碼確實是已經非常不容易的事情,需要考慮正確性、可讀性、魯棒性、可測試性、可以擴充套件性、可以移植性、效能。前面討論的只是個人覺得比較重要的入門的一些點,想要寫好程式碼需要經過刻意地考慮和練習才能真正達到目標!
最後
歡迎各位技術同路人加入阿里云云監控(CloudMonitor)團隊,我們專注於解決雲上服務和資源的可觀測性問題,並和雲上的運維工具進行整合,致力於為企業、開發者提供一站式的智慧監控運維服務,內推直達郵箱:guodong.chen@alibaba-inc.com
相關連結
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31550522/viewspace-2709133/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 如何寫好程式碼
- 如何寫好前端業務程式碼?前端
- 如何寫好 5000 行的 SQL 程式碼SQL
- 如何寫好5000行的SQL程式碼SQL
- 感悟篇:如何寫好函式式程式碼函式
- 如何真正寫好程式碼註釋 — 現代 JavaScript 教程JavaScript
- 好程式設計師不寫程式碼程式設計師
- 架構師日記-如何寫的一手好程式碼架構
- python加法程式碼如何寫Python
- 我寫的 Python 程式碼,同事都說好Python
- Java程式碼寫好後怎麼執行?Java
- 阿里如何用 AI 寫程式碼?阿里AI
- 程式設計師如何寫出好程式碼?程式設計師
- 碼農如何寫好一封郵件/1
- 碼農如何寫好一封郵件/0
- 如何寫好專欄?
- 好程式設計師寫出來的程式碼,就叫好程式碼嗎?你錯了!程式設計師
- 如何寫出更好的 React 程式碼?React
- 如何寫出優雅的程式碼?
- 如何編寫高效的Android程式碼Android
- 如何編寫簡潔的程式碼?
- github copilot如何幫助寫程式碼Github
- 如何寫出漂亮的 JavaScript 程式碼JavaScript
- PbootCms模板中如何寫 PHP 程式碼?bootPHP
- python如何換行編寫程式碼Python
- 如何寫出整潔的程式碼
- 如何提高Java程式碼質量-優雅的寫程式碼Java
- JS 互動程式碼這樣寫可能好維護點JS
- 不會寫程式碼的播音生不是個好運營?
- 【譯】如何寫出更好的 React 程式碼React
- [譯] 如何寫出更好的 React 程式碼?React
- 如何使用 Sphinx 給 Python 程式碼寫文件Python
- [譯] 如何寫出漂亮的 JavaScript 程式碼JavaScript
- 如何學習用Java編寫程式碼?Java
- 如何用 SpringBoot 優雅的寫程式碼Spring Boot
- 如何寫出更優質的程式碼
- 「奇淫技巧」如何寫最少的程式碼
- 如何寫一段死鎖程式碼