寫代碼的指導思想:如何寫出易測、清晰、健壯的牢固代碼

代碼只是形式,邏輯和思考纔是神韻。html

背景

寫出 BUG 不算糟糕,給人埋坑,讓別人寫出 BUG ,耗時耗力才更使人討厭。要想不寫出 BUG, 不埋坑,須要用心寫出 「易測、清晰、健壯」 的牢固的代碼。95% 的代碼,能作到這一點,就能夠保證幾乎無問題了;3%的代碼能作到「可複用、可擴展」,善莫大焉!程序員

本文結合以前的經歷和案例,探討如何寫出「易測、清晰、健壯」的代碼。

編程

代碼三性

代碼的基本標準是:易測、清晰、健壯。

安全

易測

易測,是說代碼容易測試。寫代碼的第一思想是:不要對代碼過於自信併發

有人說,一行代碼能出什麼問題? 可耗資巨費的火箭由於一行代碼發射失敗,金融行業由於一行代碼使數億美圓蒸發,都是已發生過的案例。我我的也遭受過一次小小的懲罰,可參見:「訂單搜索分頁失效的教訓:怠惰必受懲罰」框架

有人說,異常有什麼好測的 ? 甚至連是否能編譯經過都不檢查。結果就出現了這個案例:「遺留問題,排雷會炸,不排也會炸!」。走到了異常分支,你才知道痛!異步

所以,寫代碼宜慎之又慎,一行尚且會潛藏問題,況且百行?函數式編程

那麼,如何寫出容易測試的代碼呢?其基本思想是:將可測邏輯與主流程、外部依賴分離。可測邏輯,主要指含有了 if, if-else, while, for 等條件、循環的邏輯。與主流程、外部依賴分離,就是集中兵力解決最核心部分。放在主流程裏,就致使易測邏輯受其餘部分影響,連帶考慮的東西太多,耗費腦力,且測試很容易不完全(不容易覆蓋到全部情形);與外部依賴放在一塊兒,就要 mock 外部依賴,寫一些沒必要要的測試代碼。函數

「改善代碼可測性的若干技巧」 這篇文章講述了一些用於編寫易測代碼的基本技巧:「代碼語義化」、「分離獨立邏輯」、「分離實例狀態」、「表達與執行分離」、「分離純函數」、「面向接口編程」; 「使用Groovy+Spock輕鬆寫出更簡潔的單測」 這篇文章則將如何使用好的框架去容易地編寫單測。

工具

清晰

清晰,是說把代碼寫明白,一看就懂,無需猜想。

要把代碼寫清晰,就要保證「語義與細節分離」,即將「作什麼」(dowhat) 與 「怎麼作」(howtodo)分離開。在主流程裏,堅持只敘述 dowhat ,只在函數或方法裏寫清楚 howtodo 。

要達成良好清晰的語義,須要望文知義的命名。這又涉及到一個基本思想:單一事實職責。相信 80% 的開發者都據說過這個基本思想,但真正能始終貫徹的人很少。單一事實職責,就是每一個方法,每一個類只作份內的事情。你把本身當成一個將軍,給每一個方法、每一個類去分配職責,可以把這個職責分配清楚,纔有作將軍的才能。

從「單一事實職責」以及其餘軟件思想中,可進一步提煉出一個核心原則:「關注點分離」。關注點分離原則,是確保寫出清晰代碼的最重要的根本原則。後文再詳述。

把代碼寫清晰,也要落實到註釋裏。有的註釋:「有個場景須要下單後立刻查看訂單詳情」,就寫的不明白:哪一個場景?當人員變更時,後面的人就要花很大的溝通成本去找到這個場景。

健壯

健壯,主要指代碼應對錯誤的能力。 健壯,可分爲技術健壯性和業務健壯性。

技術健壯,就是從技術層面來考察錯誤。技術健壯是健壯性的基本考量。好比一次請求調用,若是超時怎麼處理,若是依賴服務報錯怎麼處理 ? 若是資源不存在怎麼處理 ?對象的一次方法調用,若是對象爲空怎麼處理?若是方法未能執行成功拋異常怎麼處理? 要保證技術健壯性,須要採用「防護式編程」。

業務健壯,就是從業務層面來考察錯誤和變化。好比同城配送異常檢測功能,是同城配送是否成功的檢測。正常場景下,「同城送訂單自動呼叫失敗」,作個標識,是正常流程處理。但是,業務上還容許,同城配送失敗後能夠發貨完成,這個就是業務的「異常場景」。若是不考慮這個場景,那麼訂單實際上配送成功了,但依然顯示配送異常。再考慮一個栗子。零售網店訂單派送倉庫發貨,正常場景下,一個訂單隻會有一個派送記錄;可是,網店訂單還能夠改派,這時候,一個訂單會有多個派送記錄,必須過濾掉那些無效的派送記錄。要保證業務健壯性,須要有「閉環與全局「思想。不只僅只想到眼前這個業務,還要考慮這個業務與其餘業務聯合的情形,以及該業務 5%-10% 的很是規路徑。

不少線上問題 和 BUG,都是健壯性不佳所致使; 若是在關鍵路徑上健壯性不佳,還可能致使重大故障。

不報問題,勿覺得安全無憂;不是沒有,只是沒發現,或沒報上來,或沒計較。等要計較時,麻煩就來了。由於這麻煩有可能正好插在你很是忙的時候,更容易使人心煩意亂。

要避免往後的麻煩,就要完全乾淨地作好健壯性。有時會看到一段代碼,捕獲了異常,卻什麼都沒作,或者簡單打了個日誌 log.error("failed") 。等於什麼都沒作。可見這作事態度毛毛糙糙。 三年寫這樣的代碼,三年沒長進。 學了技術,作事態度依然毛糙,是無法令其擔當大任的。

錯誤日誌,要作規範細緻,須要儘可能可以一眼就能判定問題所在。可參見: 「如何使錯誤日誌更加方便排查問題」

指導思想

凡事均可以分爲基本原理和實戰經驗兩部分。 基本原理,是幫助人認識一些基本狀況,給予一些基本指導和方法;實戰經驗,則是遇到具體狀況時,應用和擴展基本原理來解決問題。

要寫出「易測、清晰、健壯」的代碼,是有一些基本指導思想的;在基本指導思想之下,有一些法則及技巧輔助。

關注點分離

這是我認爲的最核心的指導思想,能夠推演出不少其餘的軟件設計和開發思想。

我深認爲,軟件中蘊藏着不可勝數的大大小小的關注點。軟件開發和設計的本質,就是將關注點分離、組織、鏈接。 可以將不一樣的關注點分離開,再合理有序地組織起來,呈如今代碼裏,就離寫出清晰代碼不遠了。

關注點,能夠分爲技術關注點和業務關注點。通俗地理解,一個「關注點」,可視之爲一個「事實」;但關注點的含義,比事實要普遍得多。

技術關注點,側重於解決某一類技術問題。好比線程池、鏈接池、事務、冪等、切面、異步、MVC等。 「代碼抽象與分層」 列舉了代碼裏不少細小的抽象和關注點。

業務關注點,側重於描述某個業務點。好比訂單已發貨、訂單全額退款、獲取訂單已退金額,等都是業務關注點。業務關注點可大可小,小至一個業務常量,大至一個下單的基本流程。

可以將技術關注點和業務關注點抽離出來,對實現可複用、可擴展、可定製大有裨益。

從「關注點分離」思想,能夠推導出不少重要的軟件開發和設計思想。

  • 從關注點分離思想,能夠推導出「語義與細節」分離思想。由於語義與細節分別是一類關注點:語義是宏觀性的,細節是微觀性的;
  • 從關注點分離思想,能夠推導出「單一事實」(SRP)和「開閉原則」(OCP)。由於關注點分離出來,就更容易地維護、複用和擴展;
  • 從關注點分離思想,能夠推導出 「基於接口設計與編程」思想,由於接口是關注點的抽象,而實現是關注點的具體實施;基於接口設計與編程的示例可見:「基於接口編程:使用收集器模式使數據獲取流程更加清晰可配置」「基於接口設計與編程」
  • 從關注點分離思想,能夠推導出「封裝和多態」思想,由於封裝本質上就是關注點的重組,而多態則是「將相異關注點進行分離,而後將相同關注點和相異關注點進行組合」;
  • 從關注點分離思想,能夠推導出「組件化」思想,由於組件實質是多層次關注點的組合。

關注點分離,是近乎於「道」的統攝全局的根本思想


make-it-right-then-good

好代碼並不是一蹴而就的。若是有人能一次性寫出易測、清晰、健壯的代碼,那這人很了不起。

和大多數開發者同樣,我也急於完成業務邏輯。可是,實現業務邏輯只是起點,並非結束。從起點到結束,還須要持續的修改和完善。

持續小幅重構,精煉代碼「精練代碼:一次Java函數式編程的重構之旅」 展現瞭如何用函數式編程思想和技巧,來持續改善代碼,使得代碼更加精練而可複用。

有人以爲,這樣費事不 ? 代碼只是形式。寫代碼的本質,是表達邏輯,表達思考的結果。 最終的代碼,會折射出思考和表達的質量。 這纔是一個程序員的核心競爭力。

不可變思想

不可變思想,是從函數式編程借鑑過來。意爲「優先使用不可變量」。

在編寫函數和方法的代碼時,忌修改傳參。一旦修改傳參,就會使參數變成隱式的全局變量,在大的業務系統裏,長此以往就很難知道里面究竟有什麼,何時有何時沒有,不知道能依靠和信任什麼。全局變量是那種一旦出錯,能耗上好幾個小時排查讓頭髮瞬間花白的東西,屬於 「no zuo no die」 的神奇代碼物種。

所以,函數和方法若是須要返回值,儘可能使用返回值,而不是修改傳參。這樣作的一個好處是,代碼也更容易測試。

軟件開發的一項挑戰是「軟件行爲的可預測性」。不可變思想,能夠加強軟件行爲的可預測性,減小不少推斷行爲。好比說,使用線程安全的變量和共享不可變變量,孰優孰劣? 前者雖然也能保證併發的安全性,可說到底還須要反覆分析推敲,且規模越大時越難以分析推導;後者就是不可變的,根本無需分析和推導。固然,軟件中不可能一直使用不可變量,所以,原則是:優先使用不可變量

知己知彼

代碼的目標是提供正確有效的服務,而 BUG 的目標則是阻止代碼實現目標或徹底實現目標;從目標意義來看, BUG 就是代碼的敵人;從根源來看,BUG 又與代碼相生相伴。

知己知彼,才能百戰不殆。每一次開發、測試、發佈,實現需求或優化,都是一場戰役。要想不出錯,除了慎之又慎,還須要知彼。知彼指知道代碼中的常見問題以及如何避免。

「代碼問題及對策」 列舉了在程序員職業生涯中可能會遇到的 90% 的錯誤;「故障常見緣由歸類分析及預防和應對措施」 列舉了可能致使故障的各類緣由及預防應對措施。

寫下的每一行代碼,可以評估出可能出現什麼問題,有心避免這些問題,就已經很了不得了。

小處着手

這麼多思想,這麼多法則,這麼多技巧,這麼多問題,從何處着手呢?

從小處着手。 在寫代碼時,始終牢記「關注點分離」思想,堅持編寫單一事實的短小方法,這是基本功;其次,多思考如何寫得更清晰簡潔;再次,適當學點設計思想和模式,讓代碼出有據,具柔性;最後,勤積累,在實戰中積累經驗,及時總結。

匠心態度

寫代碼應持匠心。要用一種精緻的態度去寫代碼,才能寫出優美而牢固的代碼。

忌隨意散漫。隨意散漫,並非說閉着眼睛寫代碼,或者來個葛優躺地寫代碼;而是說,寫代碼只考慮快速去實現邏輯,而不考慮如何更清晰簡潔地表達這個邏輯。這樣,很容易養成一些不良習慣而不自知,長此以往,代碼乃至設計的水平就會停滯不前,僅學會了一點技術來充飢。

在代碼裏隨意寫個魔數,就是一種隨意散漫的習慣。由於魔數,本質上就是一個細小的關注點,也是輕忽不得的。「代碼的味道」 列舉了若干個比較典型的不良習慣及如何糾正;「如何編寫可信賴的代碼」 列舉了許多細小的法則和技巧來幫助編寫可信賴的代碼。

匠心態度,也須要自律和努力寫標準規格的代碼。建築、機械、電子等行業,對於許多零部件,都有各類標準規格的制定,而在實際構建成品時,基本是採用標準規格的部件。反觀軟件行業,基本沒有多少標準規格的東西,總以爲本身能造出更好的輪子,結果大部分都被丟棄了,不少工做成果都沒有獲得很好的繼承和發展。一個業務系統的大部分代碼,放到另外一個業務系統裏,基本不可用。

小結

代碼的基本標準是:易測、清晰、健壯。要作到這點,「關注點分離」思想是根本指導思想,是近乎於「道」的統攝全局的根本思想;輔以多種思想、法則和技巧,熟知各類常見問題,就能寫出牢固的代碼。

道是生髮出法則和技巧的根本層面。遇事猶疑不決時,要回溯到道的層面來解決問題。

相關文章
相關標籤/搜索