代碼質量第 3 層 - 可讀的代碼

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

 

 

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

下面是一段不可讀讀的代碼:

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 的規則

代碼質量的下一層次就是:可複用的代碼。我會在下一篇文章中介紹。

 

 

 

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

代碼質量第 4 層 - 健壯的代碼

代碼質量第 5 層 - 只是實現了功能

聊聊代碼質量 - 《學得會,抄得走的提升前端代碼質量方法》前言

 

 

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

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章