你所須要知道的代碼整潔之道

程序是寫給人讀的,只是偶爾讓計算機執行一下。 —— Donald Ervin Knuthgit

每次 review 過往寫的代碼,總有一種不忍直視的感受。想提升編碼能力,故閱讀了一些相關書籍及博文,並有所感悟,今將一些讀書筆記及我的心得感悟梳理出來。拋轉引玉,但願這磚能拋得起來。程序員

大綱

  • 壞味道的代碼
  • 圈複雜度
  • 重構
  • 代碼整潔之道
  • 編碼原則 & 設計模式
  • 總結
  • 參考

壞味道的代碼

開始閱讀以前,你們能夠快速思考一下,你們腦海裏的好代碼和壞代碼都是怎麼樣的「形象」呢?github

若是看到這一段代碼,如何評價呢?算法

if (a && d || b && c && !d || (!a || !b) && c) {
  // ...
} else {
  // ...
}
複製代碼

上面這段代碼,儘管是特地爲舉例而寫的,要是真實遇到這種代碼,想必你們都「一言難盡」吧。你們多多少少都有一些壞味道的代碼的「印象」,壞味道的代碼總有一些共性:編程

  • Duplicated Code(重複代碼)
  • Long Method(過長函數)
  • Large Class (過大的類)
  • Long Parameter List(過長參數列)
  • Temporary Field(使人迷惑的暫時字段)
  • Shotgun Surgery(霰彈式修改):一種變化引起多個類相應修改
  • ......

那壞味道的代碼是怎樣造成的呢?設計模式

  • 上一個寫這段代碼的程序員經驗、水平不足,或寫代碼時不夠用心;
  • 產品經理提出的奇葩需求致使寫了不少hack代碼;
  • 某一個模塊業務太複雜,需求變動的次數太多,經手的程序員太多。
  • ......

對壞味道的代碼有一個大概的瞭解後,或許讀者心中有一個疑問:代碼的好壞有沒有一些量化的標準去評判呢?答案是確定的。bash

接下來,經過了解圈複雜度去衡量咱們寫的代碼。然而當代碼的壞味道已經「瀰漫」處處都是了,這時咱們應該瞭解一下重構。代碼到了咱們手裏,不能繼續「發散」壞味道,這時應該瞭解如何編寫 clean code。此外,咱們還應該掌握一些編碼原則及設計模式,這樣才能作到有的放矢。markdown

圈複雜度

圈複雜度(Cyclomatic complexity,簡寫CC)也稱爲條件複雜度,是一種代碼複雜度的衡量標準。由托馬斯·J·麥凱布(Thomas J. McCabe, Sr.)於1976年提出,用來表示程序的複雜度。架構

圈複雜度能夠用來衡量一個模塊斷定結構的複雜程度,數量上表現爲獨立現行路徑條數,也可理解爲覆蓋全部的可能狀況最少使用的測試用例數。app

斷定方法

圈複雜度能夠經過程序控制流圖計算,公式爲:

V(G) = e + 2 - n

  • e : 控制流圖中邊的數量
  • n : 控制流圖中節點的數量

有一個簡單的計算方法:圈複雜度實際上就是等於斷定節點的數量再加上1。

注:if elseswitch casefor循環三元運算符||&&等,都屬於一個斷定節點。

衡量標準

代碼複雜度低,代碼不必定好,但代碼複雜度高,代碼必定很差。

圈複雜度 代碼情況 可測性 維護成本
1 - 10 清晰、結構化
10 - 20 複雜
20 - 30 很是複雜
>30 不可讀 不可測 很是高

圈複雜度檢測方法

ESLint 規則

ESLint 提供了檢測代碼圈複雜度的 rules。開啓 rules 中的 complexity 規則,並將圈複雜度大於 0 的代碼的 rule severity 設置爲 warn 或 error 。

rules: {
    complexity: [
        'warn',
        { max: 0 }
    ]
}
複製代碼

CLIEngine

藉助 ESLint 的 CLIEngine ,在本地使用自定義的 ESLint 規則掃描代碼,並獲取掃描結果輸出。

下降代碼的圈複雜度

不少狀況下,下降圈複雜度就能提升代碼的可讀性了。針對圈複雜度,結合例子給出一些改善的建議:

1. 抽象配置

經過抽象配置將複雜的邏輯判斷進行簡化。

before:

// ...
if (type === '掃描') {
    scan(args)
} else if (type === '刪除') {
    delete(args)
} else if (type === '設置') {
    set(args)
} else {
    // ...
}
複製代碼

after:

const ACTION_TYPE = {
    掃描: scan,
    刪除: delete,
    設置: set
}
ACTION_TYPE[type](args)
複製代碼

2. 提煉函數

將代碼中的邏輯進行抽象提煉成單獨的函數,有利於下降代碼複雜度和下降維護成本。尤爲是當一個函數的代碼很長,讀起來很費力的時候,就應該思考可否提煉成多個函數。

before:

function example(val) {
    if (val > MAX_VAL) {
        val = MAX_VAL
    }
    for (let i = 0; i < val; i++) {
        doSomething(i)
    }
    // ...
}
複製代碼

after:

function setMaxVal(val) {
    return val > MAX_VAL ? MAX_VAL : val
}

function getCircleArea(val) {
    for (let i = 0; i < val; i++) {
        doSomething(i)
    }
}
function example(val) {
    return getCircleArea(setMaxVal(val))
}
複製代碼

3. 逆向條件簡化條件判斷

某些複雜的條件判斷可能逆向思考後會變的更簡單,還能減小嵌套。

before:

function checkAuth(user){
    if (user.auth) {
        if (user.name === 'admin') {
            // ...
        } else if (user.name === 'root') {
            // ...
        }
    }
}
複製代碼

after:

function checkAuth(user){
    if (!user.auth) return
    if (user.name === 'admin') {
        // ...
    } else if (user.name === 'root') {
        // ...
     }
}
複製代碼

4. 合併條件簡化條件判斷

將冗餘的條件合併,而後再進行判斷。

before:

if (fruit === 'apple') {
    return true
} else if (fruit === 'cherry') {
    return true
} else if (fruit === 'peach') {
    return true
} else {
    return true
}
複製代碼

after:

const redFruits = ['apple', 'cherry', 'peach']
if (redFruits.includes(fruit) {
    return true
}
複製代碼

5. 提取條件簡化條件判斷

對複雜難懂的條件進行提取並語義化。

before:

if ((age < 20 && gender === '女') || (age > 60 && gender === '男')) {
    // ...
} else {
    // ...
}
複製代碼

after:

function isYoungGirl(age, gender) {
    return (age < 20 && gender === '女'
}
function isOldMan(age, gender) {
    return age > 60 && gender === '男'
}
if (isYoungGirl(age, gender) || isOldMan(age, gender)) {
    // ...
} else {
    // ...
}
複製代碼

後文有簡化條件表達式更全面的總結。

重構

重構一詞有名詞和動詞上的理解。名詞:

對軟件內部結構的一種調整,目的是在不改變軟件可觀察行爲的前提下,提升其可理解性,下降其修改爲本。

動詞:

使用一系列重構手法,在不改變軟件可觀察行爲的前提下,調整其結構。

爲什麼重構

若是遇到如下的狀況,可能就要思考是否須要重構了:

  • 重複的代碼太多
  • 代碼的結構混亂
  • 程序沒有拓展性
  • 對象結構強耦合
  • 部分模塊性能低

爲什麼重構,不外乎如下幾點:

  • 重構改進軟件設計
  • 重構使軟件更容易理解
  • 重構幫助找到 bug
  • 重構提升編程速度

重構的類型:

  • 對現有項目進行代碼級別的重構;
  • 對現有的業務進行軟件架構的升級和系統的升級。

本文討論的內容只涉及第一點,僅限代碼級別的重構。

重構時機

第一次作某件事時只管去作;第二次作相似的事會產生反感,但不管如何仍是能夠去作;第三次再作相似的事,你就應該重構。

  • 添加功能時:當添加新功能時,若是發現某段代碼改起來特別困難,拓展功能特別不靈活,就要重構這部分代碼使添加新特性和功能變得更容易;
  • 修補錯誤時:在你改 bug 或查找定位問題時,發現本身之前寫的代碼或者別人的代碼設計上有缺陷(如擴展性不靈活),或健壯性考慮得不夠周全(如漏掉一些該處理的異常),致使程序頻繁出現問題,那麼此時就是一個比較好的重構時機;
  • 複審代碼時:團隊進行 Code Review 的時候,也是一個進行重構的合適時機。

代碼整潔之道

關鍵思想

  • 代碼應當易於理解;
  • 代碼的寫法應當使別人理解它所需的時間最小化。

代碼風格

關鍵思想:一致的風格比「正確」的風格更重要。

原則:

  • 使用一致的佈局
  • 讓類似的代碼看上去類似
  • 把相關的代碼行分組,造成代碼塊

註釋

註釋的目的是儘可能幫助讀者瞭解得和做者同樣多。所以註釋應當有很高的信息/空間率。

不須要註釋

  • 不要爲了註釋而註釋
  • 不要給很差的名字加註釋(應該把名字起好)
  • 不要爲那些從代碼自己就能快速推斷的事實寫註釋

須要寫註釋

  • 對於爲何代碼寫成這樣而不是那樣的內在理由(指導性批註)
  • 常量背後的故事,爲何是這個值
  • 給讀者意料以外的行爲加上註釋
  • 在文件/類的級別上使用「全局觀」註釋來解釋全部的部分是如何一塊兒工做的
  • 用註釋來總結代碼塊,使讀者不致迷失在細節中
  • 儘可能精確地描述函數的行爲
  • 聲明代碼的高層次意圖,而非明顯的細節
  • 在註釋中使用輸入/輸出例子進行說明
  • 代碼中的缺陷,使用標記
標記 一般的意義
TODO: 還沒處理的事情
FIXME: 已知的沒法運行的代碼
HACK: 對一個問題不得不採用的比價粗糙的解決方案

命名

關鍵思想:把信息裝入名字中。

良好的命名是一種以「低代價」取得代碼高可讀性的途徑。

選擇專業名詞

「把信息裝入名字中」包括要選擇很是專業的詞,而且避免使用「空洞」的詞。

單詞 更多選擇
send deliver, despatch, announce, distribute, route
find search, extract, locate, recover
start launch, create, begin, open
make create, set up, build, generate, compose, add, new

避免像tmp和retval這樣泛泛的名字

  • retval這個名字沒有包含不少信息
  • tmp 只應用於短時間存在且臨時性爲其主要存在因素的變量

用具體的名字代替抽象的名字

在給變量、函數或者其餘元素命名時,要把它描述得更具體而不是更抽象。

爲名字附帶更多信息

若是關於一個變量有什麼重要事情的讀者必須知道,那麼是值得把額外的「詞」添加到名字中的。

名字的長度

  • 在小的做用域裏可使用短的名字
  • 爲做用域大的名字採用更長的名字
  • 丟掉沒用的詞

不會被誤解的名字

  • 用min和max來表示極限
  • 用first和last來表示包含的範圍
  • 用begin和end來表示排除範圍
  • 給布爾值命名:is,has,can,should

語義相反的詞彙要成對出現

add remove
create destory
insert delete
get set
increment decrement
show hide
start stop

其餘命名小建議

  • 計算限定符做爲前綴或後綴(Avg、Sum、Total、Min、Max)
  • 變量名要能準確地表示事物的含義
  • 用動名詞命名函數名
  • 變量名的縮寫,儘可能避免不常見的縮寫

簡化條件表達式

分解條件表達式

有一個複雜的條件(if-then-else)語句,從if、then、else三個段落中分別提煉出獨立函數。根據每一個小塊代碼的用途,爲分解而獲得的新函數命名,並將原函數中對應的代碼改成調用新建函數,從而更清楚地表達本身的意圖。對於條件邏輯,能夠突出條件邏輯,更清楚地代表每一個分支的做用,而且突出每一個分支的緣由。

合併條件表達式

有一系列條件測試,都獲得相同結果。將這些測試合併爲一個條件表達式,並將這個條件表達式提煉成爲一個獨立函數。

  • 肯定這些條件語句都沒有反作用;
  • 使用適當的邏輯操做符,將一系列相關條件表達式合併爲一個;
  • 對合並後的條件表達式實施提煉函數。

合併重複的條件片斷

在條件表達式的每一個分支上有着相同的一段代碼,將這段重複代碼搬移到條件表達式以外。

以衛語句取代嵌套條件表達式

函數中的條件邏輯令人難以看清正常的執行路徑。使用衛語句表現全部特殊狀況。

若是某個條件極其罕見,就應該單獨檢查該條件,並在該條件爲真時馬上從函數中返回。這樣的單獨檢查經常被稱爲「衛語句」(guard clauses)。

經常能夠將條件表達式反轉,從而實以衛語句取代嵌套條件表達式,寫成更加「線性」的代碼來避免深嵌套。

變量與可讀性

變量存在的問題:

  • 變量越多,就越難所有跟蹤它們的動向
  • 變量的做用域越大,就須要跟蹤它的動向越久
  • 變量改變得越頻繁,就越難以跟蹤它的當前值

內聯臨時變量

若是有一個臨時變量,只是被簡單表達式賦值一次,而將全部對該變量的引用動做,替換爲對它賦值的那個表達式自身。

以查詢取代臨時變量

以一個臨時變量保存某一表達式的運算結果,將這個表達式提煉到一個獨立函數中。將這個臨時變量的全部引用點替換爲對新函數的調用。此後,新函數就可被其餘函數使用。

總結變量

接上條,若是該表達式比較複雜,建議經過一個總結變量名來代替一大塊代碼,這個名字會更容易管理和思考。

引入解釋性變量

將複雜表達式(或其中一部分)的結果放進一個臨時變量,以此變量名稱來解釋表達式用途。

在條件邏輯中,引入解釋性變量特別有價值:能夠將每一個條件子句提煉出來,以一個良好命名的臨時變量來解釋對應條件子句的意義。使用這項重構的另外一種狀況是,在較長算法中,能夠運用臨時變量來解釋每一步運算的意義。

好處:

  • 把巨大的表達式拆分紅小段
  • 經過用簡單的名字描述子表達式來讓代碼文檔化
  • 幫助讀者識別代碼中的主要概念

分解臨時變量

程序有某個臨時變量被賦值超過一次,它既不是循環變量,也不是用於收集計算結果。針對每次賦值,創造一個獨立、對應的臨時變量。

臨時變量有各類不一樣用途:

  • 循環變量
  • 結果收集變量(經過整個函數的運算,將構成的某個值收集起來)

若是臨時變量承擔多個責任,它就應該被替換(分解)爲多個臨時變量,每一個變量只承擔一個責任。

以字面常量取代 Magic Number

有一個字面值,帶有特別含義。創造一個常量,根據其意義爲它命名,並將上述的字面數值替換爲這個常量。

減小控制流變量

let done = false;

while (condition && !done) {
    if (...) {
        done = true;
        continue;
    }
}
複製代碼

像done這樣的變量,稱爲「控制流變量」。它們惟一的目的就是控制程序的執行,沒有包含任何程序的數據。控制流變量一般能夠經過更好地運用結構化編程而消除。

while (condition) {
    if (...) {
        break;
    }
}
複製代碼

若是有多個嵌套循環,一個簡單的break不夠用,一般解決方案包括把代碼挪到一個新函數中。

從新組織函數

提煉函數

當一個過長的函數或者一段須要註釋才能讓人理解用途的代碼,能夠將這段代碼放進一個獨立函數中。

  • 函數的粒度小,被複用的機會就很大;
  • 函數的粒度小,覆寫也會更容易些。

一個函數過長才合適?長度不是問題,關鍵在於函數名稱和函數本體之間的語義距離。

將查詢函數和修改函數分離

某個函數既返回對象狀態值,又修改對象狀態。創建兩個不一樣的函數,其中一個負責查詢,另外一個負責修改。

以明確函數取代參數

有一個函數,其中徹底取決於參數值而採起不一樣行爲。針對該參數的每個可能值,創建一個獨立函數。

引入參數對象

某些參數老是很天然地同時出現,以一個對象取代這些參數。

從函數中提早返回

能夠經過立刻處理「特殊狀況」,並從函數中提早返回。

拆分巨大的語句

若是有很難讀的代碼,嘗試把它所作的全部任務列出來。其中一些任務能夠很容易地變成單獨的函數(或類)。其餘的能夠簡單地成爲一個函數中的邏輯「段落」。

其餘建議及思路

減小循環內的嵌套

在循環中,與提前返回相似的技術是continue。與if...return;在函數中所扮演的保護語句同樣,if...continue;語句是循環中的保護語句。(注意:JavaScript 中 forEach 的特殊性)

德·摩根定律

對於一個布爾表達式,有兩種等價寫法:

  • not (a or b or c) <=> (not a) and (not b) and (not c)
  • not (a and b and c) <=> (not a) or (not b) or (not c)

可使用這些法則讓布爾表達式更具備可讀性。例如:

// before
if (!(file_exitsts && !is_protected)) Error("sorry, could not read file.")

// after
if (!file_exists || is_protected) Error("sorry, could not read file.")
複製代碼

使用相關定律能優化開始舉例的那段代碼:

// before
if (a && d || b && c && !d || (!a || !b) && c) {
  // ...
} else {
  // ...
}

// after
if (a && d || c) {
  // ...
} else {
  // ...
}
複製代碼

具體簡化過程及涉及相關定律能夠參考這篇推文:你寫這樣的代碼,不怕同事打你嘛?

從新組織代碼

所謂工程學就是關於把大問題拆分紅小問題再把這些問題的解決方案放回一塊兒。

把這條原則應用於代碼會使代碼更健壯而且更容易讀。

積極地發現並抽取不相關的子邏輯,是指:

  • 看看某個函數或代碼塊,問問本身:這段代碼高層次的目標是什麼?
  • 對於每一行代碼,問一下:它是直接爲了目標而工做嗎?
  • 若是足夠的行數在解決不相關的子問題,抽取代碼到獨立的函數中。

把想法變成代碼

若是你不能把一件事解釋給你祖母聽的話說明你還沒真正理解它。 --阿爾伯特·愛因斯坦

步驟以下:

  1. 像對着一個同事同樣用天然語言描述代碼要作什麼
  2. 注意描述中所用的關鍵詞和短語
  3. 寫出與描述所匹配的代碼

編碼原則 & 設計模式

有必要熟知前人總結的一些經典的編碼原則及涉及模式,以此來改善咱們既有的編碼習慣,所謂「站在巨人肩上編程」。

SOLID原則

SOLID 是面向對象設計(OOD)的五大基本原則的首字母縮寫組合,由俗稱「鮑勃大叔」的Robert C.Martin在《敏捷軟件開發:原則、模式與實踐》一書中提出來。

  • S(Single Responsibility Principle):單一職責原則,簡稱SRP
  • O(Open Close Principle):開放封閉原則,簡稱OCP
  • L(Liskov Substitution Principle):里氏替換原則,簡稱LSP
  • I(Interface SegregationPrinciple):接口隔離原則,簡稱ISP
  • D(Dependence Inversion Principle):依賴倒置原則,簡稱DIP

單一職責原則

A class should have only one reason to change.

一個類應該有且僅有一個緣由引發它的變動。

通俗來說:一個類只負責一項功能或一類類似的功能。固然這個「一」並非絕對的,應該理解爲一個類只負責儘量獨立的一項功能,儘量少的職責。

優勢:

  • 功能單一,職責清晰。
  • 加強可讀性,方便維護。

缺點:

  • 拆分得太詳細,類的數量會急劇增長。
  • 職責的度量沒有統一的標準,須要根據項目實現狀況而定。

這條定律一樣適用於組織函數時的編碼原則。

開放封閉原則

Software entities (classes,modules,functions,etc.)should be openfor extension,but closed for modification.

軟件實體(如類、模塊、函數等)應該對拓展開放,對修改封閉。

在一個軟件產品的生命週期內,不可避免會有一些業務和需求的變化,咱們在設計代碼的時候應該儘量地考慮這些變化。在增長一個功能時,應當儘量地不去改動已有的代碼;當修改一個模塊時不該該影響到其餘模塊。

const makeSound = function( animal ) { 
    animal.sound(); 
};

const Duck = function(){}; 
Duck.prototype.sound = function(){ 
    console.log( '嘎嘎嘎' ); 
};

const Chicken = function(){}; 
Chicken.prototype.sound = function(){ 
    console.log( '咯咯咯' ); 
}; 

makeSound( new Duck() ); // 嘎嘎嘎
makeSound( new Chicken() ); // 咯咯咯
複製代碼

里氏替換原則

Functions that use pointers to base classes must be able to useobjects of derived classes without knowing it.

全部能引用基類的地方必須能透明地使用其子類的對象。

只要父類能出現的地方子類就能出現(就能夠用子類來替換它)。反之,子類能出現的地方父類不必定能出現(子類擁有父類的全部屬性和行爲,但子類拓展了更多的功能)。

接口隔離原則

Clients should not be forced to depend upon interfaces that they don't use.Instead of one fat interface many small interfaces arepreferred based on groups of methods,each one serving onesubmodule.

客戶端不該該依賴它不須要的接口。用多個細粒度的接口來替代由多個方法組成的複雜接口,每個接口服務於一個子模塊。

接口儘可能小,可是要有限度。當發現一個接口過於臃腫時,就要對這個接口進行適當的拆分。可是若是接口太小,則會形成接口數量過多,使設計複雜化。

依賴倒置原則

High level modules should not depend on low level modules; bothshould depend on abstractions.Abstractions should not depend ondetails.Details should depend upon abstractions.

高層模塊不該該依賴低層模塊,兩者都該依賴其抽象。抽象不該該依賴細節,細節應該依賴抽象。

把具備相同特徵或類似功能的類,抽象成接口或抽象類,讓具體的實現類繼承這個抽象類(或實現對應的接口)。抽象類(接口)負責定義統一的方法,實現類負責具體功能的實現。

是否必定要遵循這些設計原則

  • 軟件設計是一個逐步優化的過程
  • 不是必定要遵循這些設計原則

沒有這麼充足的時間遵循這些原則去設計,或遵循這些原則設計的實現成本太大。在受現實條件所限不能遵循五大原則來設計時,咱們還能夠遵循下面這些更爲簡單、實用的原則。

簡單、實用的原則

LoD原則(Law of Demeter)

Each unit should have only limited knowledge about other units: onlyunits "closely"related to the current unit.Only talk to your immediatefriends,don't talk to strangers.

每個邏輯單元應該對其餘邏輯單元有最少的瞭解:也就是說只親近當前的對象。只和直接(親近)的朋友說話,不和陌生人說話。

這一原則又稱爲迪米特法則,簡單地說就是:一個類對本身依賴的類知道的越少越好,這個類只須要和直接的對象進行交互,而不用在意這個對象的內部組成結構。

例如,類A中有類B的對象,類B中有類C的對象,調用方有一個類A的對象a,這時若是要訪問C對象的屬性,不要採用相似下面的寫法:

a.getB().getC().getProperties()
複製代碼

而應該是:

a.getProperties()
複製代碼

KISS原則(Keep It Simple and Stupid)

Keep It Simple and Stupid.

保持簡單和愚蠢。

  • 「簡單」就是要讓你的程序能簡單、快速地被實現;
  • 「愚蠢」是說你的設計要簡單到任何人都能理解,即簡單就是美!

DRY原則(Don't Repeat Yourself)

DRY原則(Don't Repeat Yourself)

不要重複本身。

不要重複你的代碼,即屢次遇到一樣的問題,應該抽象出一個共同的解決方法,不要重複開發一樣的功能。也就是要儘量地提升代碼的複用率。

要遵循DRY原則,實現的方式很是多:

  • 函數級別的封裝:把一些常用的、重複出現的功能封裝成一個通用的函數。
  • 類級別的抽象:把具備類似功能或行爲的類進行抽象,抽象出一個基類,並把這幾個類都有的方法提到基類去實現。
  • 泛型設計:Java 中可以使用泛型,以實現通用功能類對多種數據類型的支持;C++中可使用類模板的方式,或宏定義的方式;Python中可使用裝飾器來消除冗餘的代碼。

DRY原則在單人開發時比較容易遵照和實現,但在團隊開發時不太容易作好,特別是對於大團隊的項目,關鍵仍是團隊內的溝通。

YAGNI原則(You Aren't Gonna Need It)

You aren't gonna need it,don't implement something until it isnecessary.

你不必那麼着急,不要給你的類實現過多的功能,直到你須要它的時候再去實現。

  • 只考慮和設計必需的功能,避免過分設計。
  • 只實現目前須要的功能,在之後須要更多功能時,能夠再進行添加。
  • 如無必要,勿增長複雜性。

Rule Of Three原則

Rule of three 稱爲「三次法則」,指的是當某個功能第三次出現時,再進行抽象化,即事不過三,三則重構。

  • 第一次實現一個功能時,就儘管大膽去作;
  • 第二次作相似的功能設計時會產生反感,可是還得去作;
  • 第三次還要實現相似的功能作一樣的事情時,就應該去審視是否有必要作這些重複勞動了,這個時候就應該重構你的代碼了,即把重複或類似功能的代碼進行抽象,封裝成一個通用的模塊或接口。

CQS原則(Command-Query Separation)

  • 查詢(Query):當一個方法返回一個值來回應一個問題的時候,它就具備查詢的性質;
  • 命令(Command):當一個方法要改變對象的狀態的時候,它就具備命令的性質。

保證方法的行爲嚴格的是命令或者查詢,這樣查詢方法不會改變對象的狀態,沒有反作用;而會改變對象的狀態的方法不可能有返回值。

設計模式

設計模式的開山鼻祖 GoF 在《設計模式:可複用面向對象軟件的基礎》一書中提出的23種經典設計模式被分紅了三類,分別是建立型模式、結構型模式和行爲型模式。

在面向對象軟件設計過程當中針對特定問題的簡潔而優雅的解決方案。

經常使用的設計模式有:策略模式、發佈—訂閱模式、職責鏈模式等。

好比策略模式使用的場景:

策略模式:定義一系列的算法,把它們一個個封裝起來,而且使它們能夠相互替換。

if (account === null || account === '') {
    alert('手機號不能爲空');
    return false;
}
if (pwd === null || pwd === '') {
    alert('密碼不能爲空');
    return false;
}
if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(account)) {
    alert('手機號格式錯誤');
    return false;
}
if(pwd.length<6){
    alert('密碼不能小於六位');
    return false;
}
複製代碼

使用策略模式:

const strategies = {
    isNonEmpty: function (value, errorMsg) {
        if (value === '' || value === null) {
            return errorMsg;
        }
    },
    isMobile: function (value, errorMsg) { // 手機號碼格式
        if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(value)) {
            return errorMsg;
        }
    },
    minLength: function (value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg;
        }
    }
};

const accountIsMobile = strategies.isMobile(account,'手機號格式錯誤');
const pwdMinLength = strategies.minLength(pwd,8,'密碼不能小於8位');
const errorMsg = accountIsMobile || pwdMinLength; 
if (errorMsg) {
    alert(errorMsg);
    return false;
}
複製代碼

又好比,發佈—訂閱模式具備的特色:

  • 時間上的解耦
  • 對象之間的解耦

既能夠用在異步編程中,也能夠幫助咱們完成更鬆耦合的代碼編寫。

若是你們須要瞭解設計模式更多知識,建議另外找資料學習。

總結

宋代禪宗大師青原行思提出參禪的三重境界:

參禪之初,看山是山,看水是水;禪有悟時,看山不是山,看水不是水;禪中徹悟,看山還是山,看水還是水。

同理,編程一樣存在境界:編程的一重境界是照葫蘆畫瓢,二重境界是能夠靈活運用,三重境界則是心中無模式。惟有多實踐,多感悟,方能突破一重又一重的境界。

最後,願你們終將能寫出本身再也不討厭的代碼。

最後

真的是最後了,有空會補上上述的示例代碼,歡迎你們 Star & PR 呀:你所須要知道的代碼整潔之道

參考

  • 《代碼整潔之道》
  • 《編寫可讀代碼的藝術》
  • 《重構-改善既有代碼的設計》
  • 《JavaScript設計模式與開發實踐》
  • 《人人都懂設計模式:從生活中領悟設計模式:Python實現》
相關文章
相關標籤/搜索