前言
每個(gè)人寫(xiě)代碼風(fēng)格不一樣,但本文給出了十個(gè)不同正反例說(shuō)明,大家可以多參考,但不一定要遵守。本文由@alivebao授權(quán)分享,較長(zhǎng)多圖,大家可以慢慢看。
正文從這開(kāi)始~
介紹
作者根據(jù)Robert C. Martin《代碼整潔之道》總結(jié)了適用于JavaScript的軟件工程原則《Clean Code JavaScript》。
本文是對(duì)其的翻譯。
不必嚴(yán)格遵守本文的所有原則,有時(shí)少遵守一些效果可能會(huì)更好,具體應(yīng)根據(jù)實(shí)際情況決定。這是根據(jù)《代碼整潔之道》作者多年經(jīng)驗(yàn)整理的代碼優(yōu)化建議,但也僅僅只是一份建議。
軟件工程已經(jīng)發(fā)展了50多年,至今仍在不斷前進(jìn)?,F(xiàn)在,把這些原則當(dāng)作試金石,嘗試將他們作為團(tuán)隊(duì)代碼質(zhì)量考核的標(biāo)準(zhǔn)之一吧。
最后你需要知道的是,這些東西不會(huì)讓你立刻變成一個(gè)優(yōu)秀的工程師,長(zhǎng)期奉行他們也并不意味著你能夠高枕無(wú)憂(yōu)不再犯錯(cuò)。千里之行,始于足下。我們需要時(shí)常和同行們進(jìn)行代碼評(píng)審,不斷優(yōu)化自己的代碼。不要懼怕改善代碼質(zhì)量所需付出的努力,加油。
變量
使用有意義,可讀性好的變量名
反例:
正例:
使用ES6的const定義常量
反例中使用"var"定義的"常量"是可變的。
在聲明一個(gè)常量時(shí),該常量在整個(gè)程序中都應(yīng)該是不可變的。
反例:
正例:
對(duì)功能類(lèi)似的變量名采用統(tǒng)一的命名風(fēng)格
反例:
正例:
使用易于檢索名稱(chēng)
我們需要閱讀的代碼遠(yuǎn)比自己寫(xiě)的要多,使代碼擁有良好的可讀性且易于檢索非常重要。閱讀變量名晦澀難懂的代碼對(duì)讀者來(lái)說(shuō)是一種相當(dāng)糟糕的體驗(yàn)。 讓你的變量名易于檢索。
反例:
正例:
使用說(shuō)明變量(即有意義的變量名)
反例:
正例:
不要繞太多的彎子
顯式優(yōu)于隱式。
反例:
正例:
避免重復(fù)的描述
當(dāng)類(lèi)/對(duì)象名已經(jīng)有意義時(shí),對(duì)其變量進(jìn)行命名不需要再次重復(fù)。
反例:
正例:
避免無(wú)意義的條件判斷
反例:
正例:
函數(shù)
函數(shù)參數(shù) (理想情況下應(yīng)不超過(guò)2個(gè))
限制函數(shù)參數(shù)數(shù)量很有必要,這么做使得在測(cè)試函數(shù)時(shí)更加輕松。過(guò)多的參數(shù)將導(dǎo)致難以采用有效的測(cè)試用例對(duì)函數(shù)的各個(gè)參數(shù)進(jìn)行測(cè)試。
應(yīng)避免三個(gè)以上參數(shù)的函數(shù)。通常情況下,參數(shù)超過(guò)兩個(gè)意味著函數(shù)功能過(guò)于復(fù)雜,這時(shí)需要重新優(yōu)化你的函數(shù)。當(dāng)確實(shí)需要多個(gè)參數(shù)時(shí),大多情況下可以考慮這些參數(shù)封裝成一個(gè)對(duì)象。
JS定義對(duì)象非常方便,當(dāng)需要多個(gè)參數(shù)時(shí),可以使用一個(gè)對(duì)象進(jìn)行替代。
反例:
正例:
函數(shù)功能的單一性
這是軟件功能中最重要的原則之一。
功能不單一的函數(shù)將導(dǎo)致難以重構(gòu)、測(cè)試和理解。功能單一的函數(shù)易于重構(gòu),并使代碼更加干凈。
反例:
正例:
函數(shù)名應(yīng)明確表明其功能
反例:
正例:
函數(shù)應(yīng)該只做一層抽象
當(dāng)函數(shù)的需要的抽象多余一層時(shí)通常意味著函數(shù)功能過(guò)于復(fù)雜,需將其進(jìn)行分解以提高其可重用性和可測(cè)試性。
反例:
正例:
移除重復(fù)的代碼
永遠(yuǎn)、永遠(yuǎn)、永遠(yuǎn)不要在任何循環(huán)下有重復(fù)的代碼。
這種做法毫無(wú)意義且潛在危險(xiǎn)極大。重復(fù)的代碼意味著邏輯變化時(shí)需要對(duì)不止一處進(jìn)行修改。JS弱類(lèi)型的特點(diǎn)使得函數(shù)擁有更強(qiáng)的普適性。好好利用這一優(yōu)點(diǎn)吧。
反例:
正例:
采用默認(rèn)參數(shù)精簡(jiǎn)代碼
反例:
正例:
使用Object.assign設(shè)置默認(rèn)對(duì)象
反例:
正例:
不要使用標(biāo)記(Flag)作為函數(shù)參數(shù)
這通常意味著函數(shù)的功能的單一性已經(jīng)被破壞。此時(shí)應(yīng)考慮對(duì)函數(shù)進(jìn)行再次劃分。
反例:
正例:
避免副作用
當(dāng)函數(shù)產(chǎn)生了除了“接受一個(gè)值并返回一個(gè)結(jié)果”之外的行為時(shí),稱(chēng)該函數(shù)產(chǎn)生了副作用。比如寫(xiě)文件、修改全局變量或?qū)⒛愕腻X(qián)全轉(zhuǎn)給了一個(gè)陌生人等。
程序在某些情況下確實(shí)需要副作用這一行為,如先前例子中的寫(xiě)文件。這時(shí)應(yīng)該將這些功能集中在一起,不要用多個(gè)函數(shù)/類(lèi)修改某個(gè)文件。用且只用一個(gè)service完成這一需求。
反例:
正例:
不要寫(xiě)全局函數(shù)
在JS中污染全局是一個(gè)非常不好的實(shí)踐,這么做可能和其他庫(kù)起沖突,且調(diào)用你的API的用戶(hù)在實(shí)際環(huán)境中得到一個(gè)exception前對(duì)這一情況是一無(wú)所知的。
想象以下例子:如果你想擴(kuò)展JS中的Array,為其添加一個(gè)diff函數(shù)顯示兩個(gè)數(shù)組間的差異,此時(shí)應(yīng)如何去做?你可以將diff寫(xiě)入Array.prototype,但這么做會(huì)和其他有類(lèi)似需求的庫(kù)造成沖突。如果另一個(gè)庫(kù)對(duì)diff的需求為比較一個(gè)數(shù)組中收尾元素間的差異呢?
使用ES6中的class對(duì)全局的Array做簡(jiǎn)單的擴(kuò)展顯然是一個(gè)更棒的選擇。
反例:
正例:
采用函數(shù)式編程
函數(shù)式的編程具有更干凈且便于測(cè)試的特點(diǎn)。盡可能的使用這種風(fēng)格吧。
反例:
正例:
封裝判斷條件
反例:
正例:
避免“否定情況”的判斷
反例:
正例:
避免條件判斷
這看起來(lái)似乎不太可能。
大多人聽(tīng)到這的第一反應(yīng)是:“怎么可能不用if完成其他功能呢?”許多情況下通過(guò)使用多態(tài)(polymorphism)可以達(dá)到同樣的目的。
第二個(gè)問(wèn)題在于采用這種方式的原因是什么。答案是我們之前提到過(guò)的:保持函數(shù)功能的單一性。
反例:
正例:
避免類(lèi)型判斷(part 1)
JS是弱類(lèi)型語(yǔ)言,這意味著函數(shù)可接受任意類(lèi)型的參數(shù)。
有時(shí)這會(huì)對(duì)你帶來(lái)麻煩,你會(huì)對(duì)參數(shù)做一些類(lèi)型判斷。有許多方法可以避免這些情況。
反例:
正例:
避免類(lèi)型判斷(part 2)
如果需處理的數(shù)據(jù)為字符串,整型,數(shù)組等類(lèi)型,無(wú)法使用多態(tài)并仍有必要對(duì)其進(jìn)行類(lèi)型檢測(cè)時(shí),可以考慮使用TypeScript。
反例:
正例:
避免過(guò)度優(yōu)化
現(xiàn)代的瀏覽器在運(yùn)行時(shí)會(huì)對(duì)代碼自動(dòng)進(jìn)行優(yōu)化。有時(shí)人為對(duì)代碼進(jìn)行優(yōu)化可能是在浪費(fèi)時(shí)間。
這里可以找到許多真正需要優(yōu)化的地方
反例:
正例:
刪除無(wú)效的代碼
不再被調(diào)用的代碼應(yīng)及時(shí)刪除。
反例:
正例:
對(duì)象和數(shù)據(jù)結(jié)構(gòu)
使用getters和setters
JS沒(méi)有接口或類(lèi)型,因此實(shí)現(xiàn)這一模式是很困難的,因?yàn)槲覀儾](méi)有類(lèi)似public和private的關(guān)鍵詞。
然而,使用getters和setters獲取對(duì)象的數(shù)據(jù)遠(yuǎn)比直接使用點(diǎn)操作符具有優(yōu)勢(shì)。為什么呢?
當(dāng)需要對(duì)獲取的對(duì)象屬性執(zhí)行額外操作時(shí)。
執(zhí)行set時(shí)可以增加規(guī)則對(duì)要變量的合法性進(jìn)行判斷。
封裝了內(nèi)部邏輯。
在存取時(shí)可以方便的增加日志和錯(cuò)誤處理。
繼承該類(lèi)時(shí)可以重載默認(rèn)行為。
從服務(wù)器獲取數(shù)據(jù)時(shí)可以進(jìn)行懶加載。
反例:
正例:
讓對(duì)象擁有私有成員
可以通過(guò)閉包完成
反例:
正例:
類(lèi)
單一職責(zé)原則 (SRP)
如《代碼整潔之道》一書(shū)中所述,“修改一個(gè)類(lèi)的理由不應(yīng)該超過(guò)一個(gè)”。
將多個(gè)功能塞進(jìn)一個(gè)類(lèi)的想法很誘人,但這將導(dǎo)致你的類(lèi)無(wú)法達(dá)到概念上的內(nèi)聚,并經(jīng)常不得不進(jìn)行修改。
最小化對(duì)一個(gè)類(lèi)需要修改的次數(shù)是非常有必要的。如果一個(gè)類(lèi)具有太多太雜的功能,當(dāng)你對(duì)其中一小部分進(jìn)行修改時(shí),將很難想象到這一修夠?qū)Υa庫(kù)中依賴(lài)該類(lèi)的其他模塊會(huì)帶來(lái)什么樣的影響。
反例:
正例:
開(kāi)/閉原則 (OCP)
“代碼實(shí)體(類(lèi),模塊,函數(shù)等)應(yīng)該易于擴(kuò)展,難于修改?!?/p>
這一原則指的是我們應(yīng)允許用戶(hù)方便的擴(kuò)展我們代碼模塊的功能,而不需要打開(kāi)js文件源碼手動(dòng)對(duì)其進(jìn)行修改。
反例:
正例:
利斯科夫替代原則 (LSP)
“子類(lèi)對(duì)象應(yīng)該能夠替換其超類(lèi)對(duì)象被使用”。
也就是說(shuō),如果有一個(gè)父類(lèi)和一個(gè)子類(lèi),當(dāng)采用子類(lèi)替換父類(lèi)時(shí)不應(yīng)該產(chǎn)生錯(cuò)誤的結(jié)果。
反例:
正例:
接口隔離原則 (ISP)
“客戶(hù)端不應(yīng)該依賴(lài)它不需要的接口;一個(gè)類(lèi)對(duì)另一個(gè)類(lèi)的依賴(lài)應(yīng)該建立在最小的接口上?!?/p>
在JS中,當(dāng)一個(gè)類(lèi)需要許多參數(shù)設(shè)置才能生成一個(gè)對(duì)象時(shí),或許大多時(shí)候不需要設(shè)置這么多的參數(shù)。此時(shí)減少對(duì)配置參數(shù)數(shù)量的需求是有益的。
反例:
正例:
依賴(lài)反轉(zhuǎn)原則 (DIP)
該原則有兩個(gè)核心點(diǎn):
1. 高層模塊不應(yīng)該依賴(lài)于低層模塊。他們都應(yīng)該依賴(lài)于抽象接口。 2. 抽象接口應(yīng)該脫離具體實(shí)現(xiàn),具體實(shí)現(xiàn)應(yīng)該依賴(lài)于抽象接口。
反例:
正例:
使用ES6的classes而不是ES5的Function
典型的ES5的類(lèi)(function)在繼承、構(gòu)造和方法定義方面可讀性較差。
當(dāng)需要繼承時(shí),優(yōu)先選用classes。
但是,當(dāng)在需要更大更復(fù)雜的對(duì)象時(shí),最好優(yōu)先選擇更小的function而非classes。
反例:
正例:
使用方法鏈
這里我們的理解與《代碼整潔之道》的建議有些不同。
有爭(zhēng)論說(shuō)方法鏈不夠干凈且違反了德米特法則,也許這是對(duì)的,但這種方法在JS及許多庫(kù)(如JQuery)中顯得非常實(shí)用。
因此,我認(rèn)為在JS中使用方法鏈?zhǔn)欠浅:线m的。在class的函數(shù)中返回this,能夠方便的將類(lèi)需要執(zhí)行的多個(gè)方法鏈接起來(lái)。
反例:
正例:
優(yōu)先使用組合模式而非繼承
在著名的設(shè)計(jì)模式一書(shū)中提到,應(yīng)多使用組合模式而非繼承。
這么做有許多優(yōu)點(diǎn),在想要使用繼承前,多想想能否通過(guò)組合模式滿(mǎn)足需求吧。
那么,在什么時(shí)候繼承具有更大的優(yōu)勢(shì)呢?這取決于你的具體需求,但大多情況下,可以遵守以下三點(diǎn):
繼承關(guān)系表現(xiàn)為"是一個(gè)"而非"有一個(gè)"(如動(dòng)物->人 和 用戶(hù)->用戶(hù)細(xì)節(jié))
可以復(fù)用基類(lèi)的代碼("Human"可以看成是"All animal"的一種)
希望當(dāng)基類(lèi)改變時(shí)所有派生類(lèi)都受到影響(如修改"all animals"移動(dòng)時(shí)的卡路里消耗量)
反例:
正例:
測(cè)試
一些好的覆蓋工具.
一些好的JS測(cè)試框架
單一的測(cè)試每個(gè)概念
反例:
正例:
并發(fā)
用Promises替代回調(diào)
回調(diào)不夠整潔并會(huì)造成大量的嵌套。ES6內(nèi)嵌了Promises,使用它吧。
反例:
正例:
Async/Await是較Promises更好的選擇
Promises是較回調(diào)而言更好的一種選擇,但ES7中的async和await更勝過(guò)Promises。
在能使用ES7特性的情況下可以盡量使用他們替代Promises。
反例:
正例:
錯(cuò)誤處理
錯(cuò)誤拋出是個(gè)好東西!這使得你能夠成功定位運(yùn)行狀態(tài)中的程序產(chǎn)生錯(cuò)誤的位置。
別忘了捕獲錯(cuò)誤
對(duì)捕獲的錯(cuò)誤不做任何處理是沒(méi)有意義的。
代碼中try/catch的意味著你認(rèn)為這里可能出現(xiàn)一些錯(cuò)誤,你應(yīng)該對(duì)這些可能的錯(cuò)誤存在相應(yīng)的處理方案。
反例:
正例:
不要忽略被拒絕的promises
理由同try/catch.
反例:
正例:
格式化
格式化是一件主觀的事。如同這里的許多規(guī)則一樣,這里并沒(méi)有一定/立刻需要遵守的規(guī)則??梢栽谶@里完成格式的自動(dòng)化。
大小寫(xiě)一致
JS是弱類(lèi)型語(yǔ)言,合理的采用大小寫(xiě)可以告訴你關(guān)于變量/函數(shù)等的許多消息。
這些規(guī)則是主觀定義的,團(tuán)隊(duì)可以根據(jù)喜歡進(jìn)行選擇。重點(diǎn)在于無(wú)論選擇何種風(fēng)格,都需要注意保持一致性。
反例:
正例:
調(diào)用函數(shù)的函數(shù)和被調(diào)函數(shù)應(yīng)放在較近的位置
當(dāng)函數(shù)間存在相互調(diào)用的情況時(shí),應(yīng)將兩者置于較近的位置。
理想情況下,應(yīng)將調(diào)用其他函數(shù)的函數(shù)寫(xiě)在被調(diào)用函數(shù)的上方。
反例:
正例:
注釋
只對(duì)存在一定業(yè)務(wù)邏輯復(fù)制性的代碼進(jìn)行注釋
注釋并不是必須的,好的代碼是能夠讓人一目了然,不用過(guò)多無(wú)謂的注釋。
反例:
正例:
不要在代碼庫(kù)中遺留被注釋掉的代碼
版本控制的存在是有原因的。讓舊代碼存在于你的history里吧。
反例:
正例:
不需要版本更新類(lèi)型注釋
記住,我們可以使用版本控制。廢代碼、被注釋的代碼及用注釋記錄代碼中的版本更新說(shuō)明都是沒(méi)有必要的。
需要時(shí)可以使用git log獲取歷史版本。
反例:
正例:
避免位置標(biāo)記
這些東西通常只能代碼麻煩,采用適當(dāng)?shù)目s進(jìn)就可以了。
反例:
正例:
避免在源文件中寫(xiě)入法律評(píng)論
將你的LICENSE文件置于源碼目錄樹(shù)的根目錄。
反例:
正例:
關(guān)于本文
譯者:@alivebao
原文:http://mp.weixin.qq.com/s?__biz=MjM5MTA1MjAxMQ==&mid=2651225243&idx=1&sn=b1597c58afabe2c99e87609f9193e3c7&chksm=bd49a51f8a3e2c090be18d2fd4a283f7d320feb6f07ddd324ddcf6a709b9bc9e8a9a91677836&scene=21#wechat_redirect
譯文:https://github.com/alivebao/clean-code-js