JavaScript 程式碼簡潔之道

renke發表於2021-09-09

JavaScript 程式碼簡潔之道

測試程式碼質量的唯一方式:別人看你程式碼時說 f * k 的次數。

程式碼質量與其整潔度成正比。乾淨的程式碼,既在質量上較為可靠,也為後期維護、升級奠定了良好基礎。

本文並不是程式碼風格指南,而是關於程式碼的可讀性複用性擴充套件性探討。

我們將從幾個方面展開討論:

  1. 變數
  2. 函式
  3. 物件和資料結構
  4. SOLID
  5. 測試
  6. 非同步
  7. 錯誤處理
  8. 程式碼風格
  9. 註釋

變數

用有意義且常用的單詞命名變數

Bad:

Good:

↑回到頂部

保持統一

可能同一個專案對於獲取使用者資訊,會有三個不一樣的命名。應該保持統一,如果你不知道該如何取名,可以去 codelf 搜尋,看別人是怎麼取名的。

Bad:

Good:

↑回到頂部

每個常量都該命名

可以用 buddy.js 或者 ESLint 檢測程式碼中未命名的常量。

Bad:

Good:

↑回到頂部

可描述

通過一個變數生成了一個新變數,也需要為這個新變數命名,也就是說每個變數當你看到他第一眼你就知道他是幹什麼的。

Bad:

Good:

↑回到頂部

直接了當

Bad:

Good:

↑回到頂部

避免無意義的字首

如果建立了一個物件 car,就沒有必要把它的顏色命名為 carColor。

Bad:

Good:

↑回到頂部

使用預設值

Bad:

Good:

↑回到頂部

函式

引數越少越好

如果引數超過兩個,使用 ES2015/ES6 的解構語法,不用考慮引數的順序。

Bad:

Good:

↑回到頂部

只做一件事情

這是一條在軟體工程領域流傳久遠的規則。嚴格遵守這條規則會讓你的程式碼可讀性更好,也更容易重構。如果違反這個規則,那麼程式碼會很難被測試或者重用。

Bad:

Good:

↑回到頂部

顧名思義

看函式名就應該知道它是幹啥的。

Bad:

Good:

↑回到頂部

只需要一層抽象層

如果函式巢狀過多會導致很難複用以及測試。

Bad:

Good:

↑回到頂部

刪除重複程式碼

很多時候雖然是同一個功能,但由於一兩個不同點,讓你不得不寫兩個幾乎相同的函式。

要想優化重複程式碼需要有較強的抽象能力,錯誤的抽象還不如重複程式碼。所以在抽象過程中必須要遵循 SOLID 原則(SOLID 是什麼?稍後會詳細介紹)。

Bad:

Good:

↑回到頂部

物件設定預設屬性

Bad:

Good:

↑回到頂部

不要傳 flag 引數

通過 flag 的 true 或 false,來判斷執行邏輯,違反了一個函式幹一件事的原則。

Bad:

Good:

↑回到頂部

避免副作用(第一部分)

函式接收一個值返回一個新值,除此之外的行為我們都稱之為副作用,比如修改全域性變數、對檔案進行 IO 操作等。

當函式確實需要副作用時,比如對檔案進行 IO 操作時,請不要用多個函式/類進行檔案操作,有且僅用一個函式/類來處理。也就是說副作用需要在唯一的地方處理。

副作用的三大天坑:隨意修改可變資料型別、隨意分享沒有資料結構的狀態、沒有在統一地方處理副作用。

Bad:

Good:

↑回到頂部

避免副作用(第二部分)

在 JavaScript 中,基本型別通過賦值傳遞,物件和陣列通過引用傳遞。以引用傳遞為例:

假如我們寫一個購物車,通過 addItemToCart() 方法新增商品到購物車,修改 購物車陣列。此時呼叫 purchase() 方法購買,由於引用傳遞,獲取的 購物車陣列 正好是最新的資料。

看起來沒問題對不對?

如果當使用者點選購買時,網路出現故障, purchase() 方法一直在重複呼叫,與此同時使用者又新增了新的商品,這時網路又恢復了。那麼 purchase() 方法獲取到 購物車陣列 就是錯誤的。

為了避免這種問題,我們需要在每次新增商品時,克隆 購物車陣列 並返回新的陣列。

Bad:

Good:

↑回到頂部

不要寫全域性方法

在 JavaScript 中,永遠不要汙染全域性,會在生產環境中產生難以預料的 bug。舉個例子,比如你在 Array.prototype 上新增一個 diff 方法來判斷兩個陣列的不同。而你同事也打算做類似的事情,不過他的 diff 方法是用來判斷兩個陣列首位元素的不同。很明顯你們方法會產生衝突,遇到這類問題我們可以用 ES2015/ES6 的語法來對 Array 進行擴充套件。

Bad:

Good:

↑回到頂部

比起命令式我更喜歡函數語言程式設計

函式式變程式設計可以讓程式碼的邏輯更清晰更優雅,方便測試。

Bad:

Good:

↑回到頂部

封裝條件語句

Bad:

Good:

↑回到頂部

儘量別用“非”條件句

Bad:

Good:

↑回到頂部

避免使用條件語句

Q:不用條件語句寫程式碼是不可能的。

A:絕大多數場景可以用多型替代。

Q:用多型可行,但為什麼就不能用條件語句了呢?

A:為了讓程式碼更簡潔易讀,如果你的函式中出現了條件判斷,那麼說明你的函式不止幹了一件事情,違反了函式單一原則。

Bad:

Good:

↑回到頂部

避免型別檢查(第一部分)

JavaScript 是無型別的,意味著你可以傳任意型別引數,這種自由度很容易讓人困擾,不自覺的就會去檢查型別。仔細想想是你真的需要檢查型別還是你的 API 設計有問題?

Bad:

Good:

↑回到頂部

避免型別檢查(第二部分)

如果你需要做靜態型別檢查,比如字串、整數等,推薦使用 TypeScript,不然你的程式碼會變得又臭又長。

Bad:

Good:

↑回到頂部

不要過度優化

現代瀏覽器已經在底層做了很多優化,過去的很多優化方案都是無效的,會浪費你的時間,想知道現代瀏覽器優化了哪些內容,請點這裡

Bad:

Good:

↑回到頂部

刪除棄用程式碼

很多時候有些程式碼已經沒有用了,但擔心以後會用,捨不得刪。

如果你忘了這件事,這些程式碼就永遠存在那裡了。

放心刪吧,你可以在程式碼庫歷史版本中找他它。

Bad:

Good:

↑回到頂部

物件和資料結構

getset 方法運算元據

這樣做可以帶來很多好處,比如在運算元據時打日誌,方便跟蹤錯誤;在 set 的時候很容易對資料進行校驗…

Bad:

Good:

↑回到頂部

使用私有變數

可以用閉包來建立私有變數

Bad:

Good:

↑回到頂部

使用 class

在 ES2015/ES6 之前,沒有類的語法,只能用建構函式的方式模擬類,可讀性非常差。

Bad:

Good:

↑回到頂部

鏈式呼叫

這種模式相當有用,可以在很多庫中發現它的身影,比如 jQuery、Lodash 等。它讓你的程式碼簡潔優雅。實現起來也非常簡單,在類的方法最後返回 this 可以了。

Bad:

Good:

↑回到頂部

不要濫用繼承

很多時候繼承被濫用,導致可讀性很差,要搞清楚兩個類之間的關係,繼承表達的一個屬於關係,而不是包含關係,比如 Human->Animal vs. User->UserDetails

Bad:

Good:

↑回到頂部

SOLID

SOLID 是幾個單詞首字母組合而來,分別表示 單一功能原則開閉原則里氏替換原則介面隔離原則以及依賴反轉原則

單一功能原則

如果一個類乾的事情太多太雜,會導致後期很難維護。我們應該釐清職責,各司其職減少相互之間依賴。

Bad:

Good:

↑回到頂部

開閉原則

“開”指的就是類、模組、函式都應該具有可擴充套件性,“閉”指的是它們不應該被修改。也就是說你可以新增功能但不能去修改原始碼。

Bad:

Good:

↑回到頂部

里氏替換原則

名字很唬人,其實道理很簡單,就是子類不要去重寫父類的方法。

Bad:

Good:

↑回到頂部

介面隔離原則

JavaScript 幾乎沒有介面的概念,所以這條原則很少被使用。官方定義是“客戶端不應該依賴它不需要的介面”,也就是介面最小化,把介面解耦。

Bad:

Good:

↑回到頂部

依賴反轉原則

說就兩點:

  1. 高層次模組不能依賴低層次模組,它們依賴於抽象介面。
  2. 抽象介面不能依賴具體實現,具體實現依賴抽象介面。

總結下來就兩個字,解耦。

Bad:

Good:

↑回到頂部

測試

隨著專案變得越來越龐大,時間線拉長,有的老程式碼可能半年都沒碰過,如果此時上線,你有信心這部分程式碼能正常工作嗎?測試的覆蓋率和你的信心是成正比的。

PS: 如果你發現你的程式碼很難被測試,那麼你應該優化你的程式碼了。

單一化

Bad:

Good:

↑回到頂部

非同步

不再使用回撥

不會有人願意去看巢狀回撥的程式碼,用 Promises 替代回撥吧。

Bad:

Good:

↑回到頂部

Async/Await 比起 Promises 更簡潔

Bad:

Good:

↑回到頂部

錯誤處理

不要忽略拋異常

Bad:

Good:

↑回到頂部

不要忘了在 Promises 拋異常

Bad:

Good:

↑回到頂部

程式碼風格

程式碼風格是主觀的,爭論哪種好哪種不好是在浪費生命。市面上有很多自動處理程式碼風格的工具,選一個喜歡就行了,我們來討論幾個非自動處理的部分。

常量大寫

Bad:

Good:

↑回到頂部

先宣告後呼叫

就像我們看報紙文章一樣,從上到下看,所以為了方便閱讀把函式宣告寫在函式呼叫前面。

Bad:

Good:

↑回到頂部

註釋

只有業務邏輯需要註釋

程式碼註釋不是越多越好。

Bad:

Good:

↑回到頂部

刪掉註釋的程式碼

git 存在的意義就是儲存你的舊程式碼,所以註釋的程式碼趕緊刪掉吧。

Bad:

Good:

↑回到頂部

不要記日記

記住你有 git!,git log 可以幫你幹這事。

Bad:

Good:

↑回到頂部

註釋不需要高亮

註釋高亮,並不能起到提示的作用,反而會干擾你閱讀程式碼。

Bad:

Good:

↑回到頂部

感謝閱讀!

ref

翻譯自 ryanmcdermott 《clean-code-javascript》,本文對原文進行了一些修改。

相關文章