簡介: 爲了趕進度功能實現怎麼快怎麼來,只好欠下一堆技術債?業務邏輯複雜,如何處理比較好?類似的功能邏輯要不要copy修改一下複用?代碼真的不須要寫註釋嗎?好的代碼無論對於我的仍是團隊來講,都是很是關鍵重要的,然而要寫好代碼倒是一件很是不容易的事情,須要長期的經驗積累和學習。本文做者分享了6個關於寫好代碼的入門的比較重要的點,但願對同窗們有所幫助。git
寫了多年的代碼,始終以爲如何寫出乾淨優雅的代碼並非一件容易的事情。按10000小時刻意訓練的定理,假設天天8小時,一個月20天,一年12個月,大概也須要5年左右的時間成爲大師。其實咱們天天的工做中真正用於寫代碼的時間不可能有8個小時,而且不少時候是在完成任務,在業務壓力很大的時候,可能想要達到的目標是如何儘快的使得功能work起來,代碼是否乾淨優雅很是可能沒有能放在第一優先級上,而是怎麼快怎麼來。github
在這樣的狀況下是很是容易欠下技術債的,時間長了,這樣的代碼基本上沒法維護,只能推倒重來,這個成本是很是高的。欠債要還,只是早晚的問題,而且等到要還的時候還要賠上額外的不菲的利息。還債的有多是本身,也有多是後來的繼任者,但都是團隊在還債。因此從團隊的角度來看,寫好代碼是一件很是有必要的事情。如何寫出乾淨優雅的代碼是個很困難的課題,我沒有找到萬能的solution,更多的是一些trade off,能夠稍微討論一下。算法
在大部分的狀況下我會認爲代碼是寫給人看的。雖然代碼最後的執行者是機器,可是實際上代碼更多的時候是給人看的。咱們來看看一段代碼的生命週期:開發 --> 單元測試 --> Code Review --> 功能測試 --> 性能測試 --> 上線 --> 運維、Bug修復 --> 測試上線 --> 退休下線。開發到上線的時間也許是幾周或者幾個月,可是線上運維、bug修復的週期能夠是幾年。編程
在這幾年的時間裏面,幾乎不可能仍是原來的做者在維護了。繼任者如何能理解以前的代碼邏輯是極其關鍵的,若是不能維護,只能本身從新作一套。因此在項目中咱們常常能見到的狀況就是,看到了前任的代碼,都以爲這是什麼垃圾,寫的亂七八糟,仍是我本身重寫一遍吧。就算是在開發的過程當中,須要別人來Code Review,若是他們都看不懂這個代碼,怎麼來作Review呢。還有你也不但願在休假的時候,由於其餘人看不懂你的代碼,只好打電話求助你。這個我印象極其深入,記得我在工做不久的時候,一次回到了老家休假中,忽然同事打電話來了,出現了一個問題,問我該如何解決,當時電話還要收漫遊費的,很是貴,可是我還不得不支持直到耗光個人電話費。網絡
因此代碼主要仍是寫給人看的,是咱們的交流的途徑。那些很是好的開源的項目雖然有文檔,可是更多的咱們其實仍是看他的源碼,若是開源項目裏面的代碼寫的很難讀,這個項目也基本上不會火。由於代碼是咱們開發人員交流的基本途徑,甚至可能口頭討論不清楚的事情,咱們能夠經過代碼來講清楚。代碼的可讀性我以爲是第一位的。各個公司估計都有本身的代碼規範,遵循相關的規範保持代碼風格的統一是第一步(推薦谷歌代碼規範[1]和微軟代碼規範[2])。規範裏通常都包括瞭如何進行變量、類、函數的命名,函數要儘可能短而且保持原子性,不要作多件事情,類的基本設計的原則等等。另一個建議是能夠多參考學習一下開源項目中的代碼。app
通常大腦工做記憶的容量就是5-9個,若是事情過多或者過於複雜,對於大部分人來講是沒法直接理解和處理的。一般咱們須要一些輔助手段來處理複雜的問題,好比作筆記、畫圖,有點相似於在內存不夠用的狀況下咱們借用了外存。運維
學CS的同窗都知道,外存的訪問速度確定不如內存訪問速度。另一般來講在邏輯複雜的狀況下出錯的可能要遠大於在簡單的狀況下,在複雜的狀況下,代碼的分支可能有不少,咱們是否可以對每種狀況都考慮到位,這些都有困難。爲了使得代碼更加可靠,而且容易理解,最好的辦法仍是保持代碼的簡單,在處理一個問題的時候儘可能使用簡單的邏輯,不要有過多的變量。編程語言
可是現實的問題並不會老是那麼簡單,那麼如何來處理複雜的問題呢?與其借用外存,我更加傾向於對複雜的問題進行分層抽象。網絡的通訊是一個很是複雜的事情,中間使用的設備能夠有無數種(手機,各類IOT設備,臺式機,laptop,路由器,交換機...), OSI協議對各層作了抽象,每一層須要處理的狀況就都大大地簡化了。經過對複雜問題的分解、抽象,那麼咱們在每一個層次上要解決處理的問題就簡化了。其實也相似於算法中的divide-and-conquer, 複雜的問題,要先拆解掉變成小的問題,從而來簡化解決的方法。ide
KISS還有另一層含義,「如無必要,勿增實體」 (奧卡姆剃刀原理)。CS中有一句 "All problems in computer science can be solved by another level of indirection", 爲了系統的擴展性,支持未來的一些可能存在的變化,咱們常常會引入一層間接層,或者增長中間的interface。在作這些決定的時候,咱們要多考慮一下是否真的有必要。增長額外的一層給咱們的好處就是易於擴展,可是同時也增長了複雜度,使得系統變得更加不可理解。對於代碼來講,極可能是我這裏調用了一個API,不知道實際的觸發在哪裏,對於理解和調試均可能增長困難。函數
KISS自己就是一個trade off,要把複雜的問題經過抽象和分拆來簡單化,可是是否須要爲了保留變化作更多的indirection的抽象,這些都是須要仔細考慮的。
爲了快速地實現一個功能,知道以前有相似的,把代碼copy過來修改一下就用,多是最快的方法。可是copy代碼常常是不少問題和bug的根源。有一類問題就是copy過來的代碼包含了一些其餘的邏輯,可能並非這部分須要的,因此可能有冗餘甚至一些額外的風險。
另一類問題就是在維護的時候,咱們其實不知道修復了一個地方以後,還有多少其餘的地方還須要修復。在我過去的項目中就出現過這樣的問題,有個問題明明以前作了修復,過幾天另一個客戶又提了相似的問題出現的另外的路徑上。相同的邏輯要儘可能只出如今一個地方,這樣有問題的時候也就能夠一次性地修復。這也是一種抽象,對於相同的邏輯,抽象到一個類或者一個函數中去,這樣也有利於代碼的可讀性。
我的的觀點是大部分的代碼儘可能不要註釋。代碼自己就是一種交流語言,而且通常來講編程語言比咱們平常使用的口語更加的精確。在保持代碼邏輯簡單的狀況下,使用良好的命名規範,代碼自己就很清晰而且可能讀起來就已是一篇良好的文章。特別是OO的語言的話,自己object(名詞)加operation(通常用動詞)就已經能夠說明是在作什麼了。重複一下把這個操做的名詞放入註釋並不會增長代碼的可讀性。而且在後續的維護中,會出現修改了代碼,卻並不修改註釋的狀況出現。在我作的不少Code Review中我都看到過這樣的狀況。儘可能把代碼寫的能夠理解,而不是經過註釋來理解。
固然我並非反對全部的註釋,在公開的API上是須要註釋的,應該列出API的前置和後置條件,解釋該如何使用這個API,這樣也能夠用於自動產品API的文檔。在一些特殊優化邏輯和負責算法的地方加上這些邏輯和算法的解釋仍是很是有必要的。
一般來講在代碼中寫上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
相關連接
[1]https://google.github.io/styleguide/
[2]https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/