程式碼質量第 3 層 - 可讀的程式碼

騰訊雲加社群發表於2022-01-13

點選一鍵訂閱《雲薦大咖》專欄,獲取官方推薦精品內容,學技術不迷路!

可讀的程式碼能極大的提高開發效率。在開發的過程中,有很大一部分時間是在閱讀程式碼。可讀的程式碼,容易理解,也容易改。反之,不可讀性的程式碼,讀起來心情很差,改起來也容易出錯。

下面是一段不可讀讀的程式碼:

const user = ...
const foo = (cb) => ...
const bar = (list, cb) => ...
const other = (list1, list2) => ...

if(user ? user.isAdmin : ((user.permission && user.permission.view) ? user.permission.view === true :  false)) {
  foo((res) => {
    if(res && res.ok && res.list && res.list.length > 0) {
      bar(res.list, (res2) => {
        if(res2 && res2.ok && res2.list && res2.list.length > 0) {
          other(res.list, res2.list)
        }
      })
    }
  })
}

以上程式碼有這些問題:

  • 函式的命名和功能不符。
  • if 條件太複雜,而且巢狀深。
  • 回撥函式巢狀深。

將上面的程式碼改成可讀的程式碼:

const user = ...
const fetchChannel = () => ...
const fetchArticle = (list) => ...
const render = (channelList, articleList) => ...

const hasViewPermission = user.isAdmin || user.permission?.view
if(!hasViewPermission) {
  return
}

const { ok, list: channelList} = await fetchChannel()
if (!(ok && channelList?.length > 0)) {
  return
}

const { ok: fetchArticleOk, articleList } = await fetchArticle(channelList)

if (!(fetchArticleOk && articleList.length > 0)) {
  return
}
render(channelList, articleList)

總結來說,可讀的程式碼主要有如下的特點:

  • 一致的程式碼風格。
  • 合理的命名。
  • 必要的註釋。
  • 沒有大檔案。
  • 沒有巢狀很深的程式碼。

如何寫出可讀程式碼?

寫出可讀程式碼,要滿足上面提到的特點。

一、一致的程式碼風格

一致的程式碼風格指:空格,縮排,命名風格(駝峰,中劃線等)等在整個專案裡是一致的。一致的程式碼風格,看起來很整齊,也能減少理解成本。在專案中,用怎樣的程式碼風格不重要。重要的是,風格要一致。
前端業界比較有名的程式碼風格有:Airbnb JavaScript Style GuideJavaScript Standard Style。不想折騰的,可以使用 JavaScript Standard Style。JavaScript Standard Style 的特點:

無須配置。 史上最便捷的統一程式碼風格的方式,輕鬆擁有。
自動程式碼格式化。 只需執行 standard --fix 從此和髒亂差的程式碼說再見。
提前發現風格及程式問題。 減少程式碼審查過程中反反覆覆的修改過程,節約時間。

確定了程式碼風格後,可以用檢查工具 ESLint 來保證程式碼風格的統一。每次程式碼提交前,做檢查,可以用工具:husky。對於大專案,檢查整個專案太慢。用 lint-staged 只檢查本次改動的檔案。

二、合理的命名

There are only two hard things in Computer Science: cache invalidation and naming things. 電腦科學中只有兩件事很難:快取失效和命名。 -- Phil Karlton

好的命名是“看其名而知其意”。具體來說,好的命名有如下特點:

直白的,有意義的
好的命名是易於理解的,也就是直白的,有意義的。比如:fetchUserInfo
推薦:故宮命名法
提取目標物件的關鍵特徵來命名。
推薦命名工具: CODELF。它幫你搜尋 Github、GitLab 等網站中,你想查詢的內容的不同命名。
注意,命名中的單詞不要拼錯。推薦單詞拼寫檢查工具:Code Spell Checker

遵循行業慣例
好的命名也應該遵循行業的習慣慣例。如:業界慣例 id 作為唯一標識命名,不要用 identifier。i、j、k 用來做迴圈計數變數命名。

符合程式碼風格
好的命名應該符合當前專案的程式碼風格。如:駝峰風格等。

不好的命名有如下特點:
無意義的名字
無意義的名字,如:foo, bar, var1, fun1。

太過抽象的名字
太過抽象的名字,如:data,res,temp,tools。

會有歧義的簡稱
會有歧義的簡稱,如:mod。你可能無法確認到底是 mode 或 module。

不必要的上下文資訊

// bad
function async getUserName (userId) {
  const userName = await fetchUser(userId)
  return userName
}

// good
function async getUserName (id) {
  const name = await fetchUser(id)
  return name
}

太長的名字
太長的名字,不容易理解。如:fetchGraintYoungestBoyName。優化方式:將不重要內容省略掉。如改成:fetchBoyName。

三、必要的註釋

註釋是是對程式碼的解釋和說明。好的程式碼是自我解釋的。對於不復雜的程式碼,不需要註釋。如果寫的註釋,只是解釋了程式碼做了什麼,不僅浪費讀者的時間,還會誤導讀者(註釋和程式碼功能不一致時)。

需要寫註釋的場景:

  • 當程式碼本身無法清晰地闡述作者的意圖。
  • 邏輯比較複雜。

四、沒有大檔案

大檔案指:程式碼行數很多(超過1千行)的檔案。大檔案,意味程式碼做了很多事,很難跟蹤到底發生了什麼。

可以用 ESLine 中 max-lines 規則來找出大檔案。
優化方案:按功能,將大檔案拆分成若干小檔案。

五、沒有巢狀很深的程式碼

巢狀很深的程式碼,可讀性很差,讓人產生“敬畏感”。比如:

fetchData1(data1 =>
  fetchData2(data2 =>
    fetchData3(data3 =>
      fetchData4(data4 =>
        fetchData5(data5 =>
          fetchData6(data6 =>
            fetchData7(data7 =>
              done(data1, data2, data3, dat4, data5, data6, data7)
            )
          )
        )
      )
    )
  )
)

下面是幾種常見的巢狀很深的場景。
1.回撥地獄
用回撥函式的方式來處理多個序列的非同步操作,會造成巢狀很深的情況。俗稱“回撥地獄”。如:

fetchData1(data1 =>
  fetchData2(data2 =>
    fetchData3(data3 =>
      done(data1, data2, data3)
    )
  )
)

2.if 巢狀很深
在條件語句中,如果判斷條件很多,會出現巢狀很深或判斷條件很長的情況。比如,判斷一個值是否是: 1 到 100 之間,能被 3 和 5 整除的偶數。這麼寫:

const isEvenNum = num => Number.isInteger(num) && num % 2 === 0
const isBetween = num => num > 1 && num < 100
const isDivisible = num => num % 3 === 0 && num % 5 ===  0

if (isEvenNum(num)) { // 是偶數
  if(isBetween(num)) { // 1 到 100 之間
    if(isDivisible(num)) { // 能被 3 和 5 整除
        return true
    }
    return false
  }
  return false
}
return false

三元表示式也會出現巢狀很深的情況:

a > 0 ? (b < 5 > ? (c ? d : e) : (f ? g : h)) : (i ? j : k)

3.函式呼叫巢狀
執行多個函式呼叫,每個函式輸出是下個函式的輸入,會造成很深的巢狀。如:

// 模擬炒蛋的過程:買蛋 -> 打蛋 -> 炒蛋 -> 上桌。
toTable( // 第四步: 上桌
  fry( // 第三步: 炒蛋
    handle( // 第二步: 打蛋
      buy(20) // 第一步: 買蛋
    )
  )
)

4.React 高階元件巢狀
在 React 寫的應用中,會出現一個元件被很多個高階元件(HOC)包裹,造成巢狀很深的情況。如:

class Comp extends React.Component {...}

Wrapper5(
  Wrapper4(
    Wrapper3(
      Wrapper2(
        Wrapper1(Comp)
      )
    )
  )
)

5.React Context 巢狀
在 React 寫的應用中,可以用 Context 來管理子元件間的資料共享。如果共享資料很多,而且型別不同,容易造成頂部元件 Context 巢狀很深。如:

<PageContext.Provider
  value={...}
>
  <User.Provider
    value={...}
  >
    <Project.Provider
      value={...}
    >
      <ChildComp />
    </Project.Provider>
  </User.Provider>
</PageContext.Provider>

優化方案見: 這裡


總結

符合本文提到的可讀程式碼特點的程式碼,可讀性都不會差。當然,還有很多能提升程式碼的可讀性的技巧。比如:

  • 限制函式的引數數量。
  • 限制函式的圈複雜度。
  • 禁用 with 語句。
    要了解更多提升程式碼可讀性的技巧,推薦擼一遍 ESLint 的規則

程式碼質量的下一層次就是:可複用的程式碼,將會在下一篇文章中介紹。

金偉強往期精彩文章推薦:

聊聊程式碼質量 - 《學得會,抄得走的提升前端程式碼質量方法》前言
程式碼質量第 5 層 - 只是實現了功能**
程式碼質量第 4 層 - 健壯的程式碼

image.png

《雲薦大咖》是騰訊雲加社群精品內容專欄。雲薦官特邀行業佼者,聚焦於前沿技術的落地及理論實踐之上,持續為您解讀雲時代熱點技術、探索行業發展新機。點選一鍵訂閱,我們將為你定期推送精品內容。

相關文章