我敢打賭,你確定有過(或者在你的職業生涯中,某個時刻看到過)。這樣的代碼,一般存在於一些遺留的系統中,而且一般是很舊的。當你須要閱讀這樣的代碼的時候,你可能會感受不太好。面試
這段代碼的問題在於,它不只太冗長,而更重要的是,它隱藏了業務邏輯(這短代碼還有其餘問題,咱們將在後面講到)。在企業應用程序中,咱們編寫代碼來解決實際的業務問題。所以。咱們不該該在修改代碼的時候產生新的問題。請注意,當咱們編寫"系統代碼" 或者以高性能爲目標的 Library 時,或者咱們解決的問題在技術上太過複雜時,能夠適度的犧牲可讀性。但即便如此,咱們也應該當心翼翼的避免編寫隱藏邏輯的代碼邏輯。算法
Robert C.Martin 在它的書《Clean Code : A Handbook of Agile Software Craftsmanship》 中提到過,"閱讀(代碼)和寫做的時間比例,遠遠超過10 :1"。在一些遺留系統中,我發現本身花費大部分時間試圖理解如何閱讀代碼,而不是能直接去閱讀代碼自己的邏輯。測試和調試這樣的系統也是很是棘手的。在大多數狀況下,有一些特殊的、不尋常的方式去處理邏輯,而這將徹底不一樣於你以前所理解的一切。數據庫
代碼不是例外。代碼不該該隱藏,用於解決問題的業務邏輯或算法。相反,它應該明確指出這些關鍵的業務邏輯或算法。代碼中使用的方法的名稱,方法的長度,甚至代碼的格式應該看起來像問題已被處理的謹慎而專業。編程
那接下來看看,你對這短代碼有什麼感受?設計模式
這段代碼,看着像是戰後的戰場,傷痕累累。我想每一個會閱讀並修改這段代碼的開發者,都討厭這樣的代碼,並視圖從這個地獄中逃脫出去,而這將使得狀況變的更糟糕。不一樣的編碼風格和糟糕的命名方式,清晰地代表,這段代碼不止讓一個開發人員在這個地獄中輪迴。這聽起來像 破窗理論,不是嗎?理解這段代碼的功能並不容易(不只由於你看代碼時會眼暈)。這段代碼返回數組的總和減去元素的數量。讓咱們以更方便的方式來作到這一點。
數組
如今,咱們可使用 Java 8 的流式編程方式,使咱們的代碼變的更加簡潔和可讀。緩存
Clean Code 不是爲了讓咱們的代碼看起來漂亮,而是爲了讓咱們的代碼更易於維護。當代碼模糊不清時候,咱們的大部分時間都將花在閱讀上。安全
所以,開發者的生產力下降了。模糊的代碼的後果是,維護過它的開發人員一般會讓它變得更糟,就像咱們前面看到的那樣。這樣作的緣由並非由於他們沒法清理並重構這段代碼,而是因爲時間的限制,一般是時間不夠。數據結構
當咱們編寫模糊的代碼時,因爲系統的體系結構/設計隱藏在代碼中,因此很難估計修復 Bug 或實現新功能須要多長時間。所以,爲了完成工做,咱們最終以打補丁的方式,修復了問題或增長了功能,而這將增長新的技術債務。多線程
另外一方面,簡潔的代碼顯示了做者的想法,因此即便在代碼中存在一個錯誤,也很容易找到並修復它。簡潔的代碼能夠幫助咱們長遠地加快編程速度。
對這些模糊的代碼,若是想要解決它,可能須要花費幾個月(或更多)的時間來重構並清理它。可是公司一般不會接受發展將被暫停的代價,來讓開發者重構代碼,這樣的機會很是的渺茫,除非已經到了業務沒法繼續推進下去。
因此,咱們還能作些什麼?
正如 Robert C.Martin 說說,童子軍規則(The Boy Scout Rule)背後的思想至關的簡單:讓代碼比你看到它的時候更乾淨! 每當你接觸舊代碼的時候,你應該正確的清理和適當的重構它。不該該以打補丁的方式只改動你必需要改動的地方,這將使代碼更難理解。
這個規則更多的是在說開發者應該擁有的心態,經過使系統更易於維護,從而讓他們的工做更加輕鬆容易。
我必須誠實的認可,在大多數狀況下,處理遺留系統並不容易,特別是當沒有測試資源或者自動化測試代碼再也不維護的時候。可是在這篇文章中,我想關注一些我認爲有用的通常性建議,來描述如何編寫更多具備表達性的代碼。
開發人員應該在編寫代碼以前清晰的認識到你正在作什麼?咱們是使用代碼在解決問題,代碼只是媒介,而不是實際的解決方案。
所以,當咱們編寫代碼時,咱們必須格外當心,以即可以讓咱們編寫的代碼,清晰的代表咱們須要解決問題的解決方案。代碼應該解決問題,而不是帶來新的問題。
你有沒有被要求作 代碼審查(Code Review),當咱們意識到代碼中存在錯誤,惟一的解決辦法是從頭再次寫一遍?我看到許多開發人員一旦獲得開發任務,就開始在 IDE 中輸入內容。他們認爲,若是他們這樣作,他們看起來就像在工做。大多數狀況下,這被證實是錯誤的方法,由於沒有通過思考,就開始編寫代碼可能會致使錯誤向錯誤的方向發展。固然,一些很是有經驗的開發人員能夠立刻開始編寫代碼並朝正確的方向發展,可是大多數開發人員在實際編碼以前須要仔細想一想。
這個例子中的的代碼,並無什麼很差的。對吧?可是實際上,這裏使用了 策略模式 ,代表這端代碼須要有必定的靈活性。而在這裏例子中,咱們只實現了一個策略,沒有更多的實現,而這裏使用策略模式,可能會誤導讀者。一個策略模式是須要編寫更多的代碼的,因此讀者天然可能會想到,讓前一個開發者使用策略模式的緣由是什麼呢?YAGNI 原則表示,"你不會須要它",可是這裏卻作了更多沒必要要的事情。預測將來咱們將須要什麼,是很難預測的。在預測將來的需求上,有時候經驗是會有幫助的,可是大多數狀況下,保持簡單是比較安全的。
設計模式幫助咱們以一種通用的優雅方式來解決特定的問題。可是若是這樣的問題並不存在(例如前面的例子中,並不須要策略模式),以後代碼的閱讀者將會被誤導,並認爲這樣作是有必要的,是爲了解決實際問題。須要特別說明一下,我並無反對任何設計模式,我也很是喜歡用它們,問題是有時候人們會去套用設計模式來解決問題,只是由於他們知道這個設計模式。
咱們的工具集中有不少工具,咱們應該有能力分清楚,什麼時候是使用它們最恰當的時候。僅僅是由於框架或者庫被大多數開發者使用,是沒有意義的。咱們必須知道它們能解決什麼問題,會存在什麼問題,並以一種不隱藏業務邏輯的方式來使用它們。
現在,許多編程語言都是支持流的。例如 Java、Kotlin、JavaScript 等來幫助咱們編寫表達式代碼。流已經用 "if" 語句取代了大段的循環。數據流幫助咱們以一種聲明式的方式,更具備說明性的操做來進行數據轉換。若是你想找到一個集合中全部小於某個值的元素,循環迭代集合將沒有意義,只須要經過過濾器操做數據流就能夠了。
Map、Filter 和 Reduce ,幾乎每個支持流的語言都支持它。因此,每一個人均可以理解你寫的東西,就像每一個人都能理解一個循環或者一個 if 語句同樣。
有這樣的清晰處理邏輯的方式是強大的。首先,你沒必要測試這個功能,由於它們必定是穩定的。而你有沒有注意到第一個例子中的問題?當使用函數式編程的方式,它將變的更加簡單。函數式編程在這篇文章中,有不少好處,可是我重點介紹它來如何幫助代碼提升可讀性。
第一個例子中,基於流的解決方案以下:
簡單而乾淨。很容易就理解它在作什麼。如今,再來看看下面的例子:
你是否指望當你調用這個方法額時候,方法的第二個參數將會被改變?這個方法是否按照所想的去作?方法名稱是否合適?你真的獲得預期的結果了嗎?
那麼,如今呢?
在這個例子中,返回值是一個新的列表,沒有參數會受到影響。咱們只是讀取參數併產生一個新的結果。理解這個方法如今作什麼以及如何使用它將變的更容易。這種方法能夠很容易的與其它方法組合。
通常而言,組合是流和函數式編程的最重要的好處之一。組合能使咱們可以在更高級別上進行數據的轉換、過濾等操做,並編寫更具說明性和表達性的代碼,而不是舊的命令式風格。咱們寫的代碼表達了咱們想要作的而不是如何完成!這是代碼可讀性的重大改進。
把一個大問題分解成多個子問題,解決每個子問題,而後組合這些解決方案,這將爲解決初始問題提供瞭解決方案。
請注意,Java 8 中的 toList()
會返回一個可變的列表,而在函數式編程中,咱們一般使用不可變數據結構。不過,咱們生成一個新的數據集合和將參數是爲制度的,會有利於咱們改進代碼的可讀性。
編寫表達式代碼不是一件容易的事情。有句 Albert Einstein 說過的名言,"若是你不能簡單的解釋它,你並非真正的理解它"。因此,當我看到抽象層混合的邏輯代碼時,例如與 DAO 交互的 UI 類,能直接與數據庫交互,又或者低層次的細節在不該該被暴露的地方暴露了。咱們都知道單一職責原則的 SOLID 原則,可是關於這個問題一直受人詬病,由於有些時候很難作職責的劃分,這部分是它有爭議的關鍵。在代碼中使用註釋來解釋代碼,並非一個解決方案,咱們將在後面的文章中看到。我相信有人寫的越簡單、越具表達性的代碼,他或者她對這個問題的理解就越清晰。
當對象的狀態發生變化,而咱們沒有注意到它的時候,這真的會讓人困惑。使用返回值能夠構造一半的對象也是很危險的,特別是當咱們處理具備多個線程的程序時。共享這些對象真的很難作到正確。另外一方面,不可變對象是線程安全的,也是緩存的最佳選擇,由於它們的狀態不會改變。
可是爲何人們選擇可變對象呢?我相信最有可能的緣由是他們認爲他們會得到更好的結果,由於所使用的內存會更少,由於這些更改已經完成了。並且,讓一個對象的狀態在其整個生命週期中發生變化是很天然的。這是咱們在 OOP 中學到的。這些年來,咱們一直在寫程序,其中大部分的對象都是可變的。
現在,一個系統的內存數量比幾十年前大了幾個數量級。咱們面臨的真正問題是可擴展性。處理器的速度再也不像過去幾年那樣告訴的提升了,可是如今咱們有了幾十個內核的盒子。因此,對於咱們的規模來講,咱們須要利用如今的狀況。因爲咱們的程序須要可以在多個內核上運行,因此咱們須要以一種安全的方式編寫它們。經過使用可變對象,咱們必須處理鎖定以確保其狀態的一致性。併發並非一個小問題要解決。另外一方面,因爲它們的性質,不可變對象在多線程和處理器之間共享是固有安全的。並且,不須要同步的事實爲建立具備低延遲和高吞吐量的系統提供了機會。所以,不變性是實現可擴展性的更安全的選擇。
除了可擴展性的好處,不變性使咱們的代碼更清潔。在上一節的第一個示例中,做爲參數傳遞的集合在方法調用以後發生了更改。若是收藏是不可改變的,那麼這是被禁止的。所以,不變性會促使咱們走向更好的解決方案。另外,因爲狀態不可變,閱讀者沒必要記住他心中的狀態變化。閱讀者只須要將一個名稱與一個值關聯起來,而不記得變量的最新值。
程序必須是爲人們閱讀而寫的,它只是恰巧讓機器執行了。
— Harold Abelson
這篇文章更多的是關於編寫更具可讀性和表達性的代碼的通常建議。在未來的文章中,咱們將討論生產代碼和測試代碼中的氣味。咱們也將看到咱們如何才能經過查看咱們的測試來在咱們的生產代碼中找到可能的設計問題。
敬請關注!
原文地址:
以爲文章不錯的喜歡的小夥伴能夠關注加轉發,歡迎你們前來探討交流。