寫(xiě)出整潔的 JavaScript 代碼

前言

每個(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,346評(píng)論 25 708
  • 在日常的開(kāi)發(fā)過(guò)程中,如果大家都遵循統(tǒng)一的代碼規(guī)范,我們就可以避免許多無(wú)緣無(wú)故的Bug,提高程序的準(zhǔn)確性、連續(xù)性、可...
    xqqlv閱讀 1,273評(píng)論 0 1
  • 第5章 引用類(lèi)型(返回首頁(yè)) 本章內(nèi)容 使用對(duì)象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類(lèi)型 使用基本類(lèi)型...
    大學(xué)一百閱讀 3,691評(píng)論 0 4
  • ES6允許使用“箭頭”(=>)定義函數(shù)。 上面的箭頭函數(shù)等同于: 如果箭頭函數(shù)不需要參數(shù)或需要多個(gè)參數(shù),就使用一個(gè)...
    小冕閱讀 218評(píng)論 0 0
  • 我因?yàn)槭滞吹脑?,周末未能出門(mén),窩在宿舍里看電影。 小伙伴推薦我看《原諒他77次》。 我用...
    oh塵埃閱讀 487評(píng)論 0 1

友情鏈接更多精彩內(nèi)容