前端架構師神技,三招統一程式碼風格(一文講透)

楊成功發表於2021-11-12

本文從程式碼規範,程式碼檢查,程式碼格式化,以及編輯器自動化實現的方向,介紹程式碼規範統一在我們團隊的實踐應用。

大綱預覽

本文介紹的內容包括以下方面:

  • 認識程式碼規範
  • 制定和統一規範
  • 神技一:ESLint
  • 神技二:Prettier
  • 神技三:VSCode
  • 附錄:命名和專案結構規範

認識程式碼規範

先來思考兩個問題:

  1. 什麼是程式碼規範?
  2. 為什麼需要程式碼規範?

如果你是一個經驗豐富的前端開發,你一定接觸過這樣的老專案:變數名是 abcfds 這種隨意起的,或者是 name1, name2 這種帶數字起名,這樣的變數不加註釋,鬼都不知道它是幹什麼的。

這類程式碼就是一種典型的不規範程式碼。這樣的程式碼除了讓我們開發人員情緒暴躁,最重要的問題是,它極大的降低了團隊協作的效率和程式質量。

在團隊協作過程中,當組內其他人需要使用或 review 你的程式碼,看到這種情況,除了噴你,還要花費大量時間瞭解你寫的是什麼。同時這樣非常容易造成變數衝突,帶來未知隱患,除錯困難等問題,甚至可以看出一個程式設計師的編碼態度和專業程度。

當然,程式碼規範包含很多方面,變數命名規範只是最基礎的規範。不規範的地方越多,程式質量越低,團隊協作的效率也就會越低。

瞭解了不規範的程式碼以及不規範程式碼帶來的問題,作為前端架構師,我們就要思考三個問題:

  1. 如何制定規範?
  2. 如何統一團隊的規範?
  3. 如何檢測規範?

制定和統一規範

像上面給變數隨意亂起名字的情況,在早期的前端專案中非常常見。

因為早期專案規模,團隊規模有限,沒有命名規範這種意識,隨意起名貌似也沒有太大的問題,只要不重複就好。但是隨著前端專案規模越來越大,複雜度越來越高,不規範帶來的問題越來越多,這種規範意識才慢慢的被重視起來。

經過社群的不斷髮展,協定了命名包含以下幾種規範:

  • 下劃線命名:user_name
  • 中劃線命名:user-name
  • 小駝峰命名:userName
  • 大駝峰命名:UserName

有了這些規範,開發者們起名字的時候心裡就有譜了。而且這些規範目前也被大多數開發者們接受,如果不按照規範命名,很可能會被同事吐槽嘍!

當規範成為普遍共識之後,大家按照自己的喜好使用不同的規範,逐漸形成了自己的編碼習慣。在一個團隊中,每個開發者往往各自有各自的編碼習慣。

然而這又成為了問題。再拿變數舉例:一個團隊中,有的人習慣用下劃線命名變數,如 user_name;有的人習慣用駝峰命名變數,如 userName。這兩種命名方式都正確,都符合規範,但是會造成團隊的程式碼風格混亂,無法統一。

那為什麼要統一呢?

統一的好處有很多。比如我們統一規定:命名變數用下劃線,命名方法用小駝峰。那麼在團隊協作時,大家看到下劃線就知道這是一個變數,看到小駝峰就知道這是一個方法。十個人的程式碼寫出來是一個人的風格,不需要了解其他的編碼風格,實現無障礙協作。

十個人的程式碼寫出一個人的風格,說起來很理想,但是靠監督和自覺實現幾乎是不可能的。

怎麼辦呢?下面就是本文重點:祭出實現程式碼規範的三招神技

神技一:ESLint

上面說到,團隊協作開發專案,由於每個人的編碼習慣不同,會寫出各種各樣的程式碼。這樣的程式碼又亂又難以維護。

所以我們希望有這樣一個工具,可以制定一套比較完整全面的規範,如果大家的編碼不符合規範,程式就會警告甚至報錯,用這種工具來倒逼團隊成員遵守統一的程式碼風格。

這個工具是有的,我們都聽過,就是大名鼎鼎的 ESLint

ESLint 有兩種能力:

  • 檢查程式碼質量,如是否有已定義但未使用的變數。
  • 檢查程式碼風格,換行,引號,縮排等相關的規範。

這兩種能力幾乎涵蓋了絕大部分程式碼規範,並且具體規範是可配置的,團隊可以定製自己喜歡的程式碼風格。

定製規範後,專案執行或熱更新時,ESLint 就會自動檢查程式碼是否符合規範。

:ESLint 檢查與 TypeScript 檢查有啥區別?

TypeScript 只會檢查型別錯誤,而 ESLint 會檢查風格錯誤

嘗試 ESLint

首先在專案下安裝:

$ npm install eslint --save-dev

然後執行命令初始化配置:eslint --init

eslint 是一個互動式命令,可以上下切換選擇適合專案的選項;完成會生成 .eslintrc.json 檔案。

基本配置

.eslintrc.json 的基本配置如下:

{
  "env": {
    "browser": true,
    "es2021": true,
  },
  "extends": [
    "eslint:recommended"
  ],
  "parserOptions": {
    "ecmaVersion": 12,
    "sourceType": "module",
  },
  "rules": {},
};

這個基本配置包含了一套預設推薦的配置,定義在 eslint:recommended 這個擴充套件中。

React 配置

React 在預設配置的基礎上,也有一套推薦的語法配置,定義在 plugin:react/recommended 這個外掛中,如果你的前端框架是 React,要定義 eslint 規範,那麼在基本配置上新增下面標記 + 號的配置即可:

{
  "env": {
    "browser": true,
    "es2021": true
  },
  "extends": [
    "eslint:recommended",
+   "plugin:react/recommended"
  ],
  "parserOptions": {
+   "ecmaFeatures": {
+     "jsx": true
+   },
    "ecmaVersion": 12,
    "sourceType": "module"
  },
+ "plugins": [
+   "react"
+ ],
  "rules": {
  }
};

React + TS 配置

若要 React 支援 TS,還要加一些額外配置:

{
  "env": {
    "browser": true,
    "es2021": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:react/recommended"
+   "plugin:@typescript-eslint/recommended"
  ],
+ "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "ecmaVersion": 12,
    "sourceType": "module"
  },
  "plugins": [
    "react",
+   "@typescript-eslint"
  ],
  "rules": {
  }
};

程式碼檢查

上面定義好規範之後,我們現在來寫一段程式碼,並執行規範檢查。

新建 index.js 檔案,寫入內容:

const a = '13'
function add() {
  return '1'
}

從 js 角度講,這兩行程式碼是沒問題的。然後我們執行檢查命令:

$ npx eslint index.js

這時會在控制檯看到報錯:

2:7   error  'a' is assigned a value but never used  no-unused-vars
4:10  error  'add' is defined but never used         no-unused-vars

2 problems (2 errors, 0 warnings)

錯誤的意思是變數 a 和函式 add 已宣告但未使用,說明程式碼不符合約定的規範。這種異常也很常見,在腳手架構建的專案中使用 npm run devnpm start 時就會執行上面的檢查命令。

ESLint 規範

上面說過,ESLint 可以自定義檢查規範,規範定義在 .eslintrc.json 配置檔案的 rules 物件下。

比如,定義規範,字串必須使用雙引號:

{
  "rules": {
    "quotes": ["error", "double"]
  }
}

定義好之後,如果你的程式碼中字串使用單引號,ESLint 就會報錯。

quotes 表示引號規範,是眾多規範中的一個,它的值是一個陣列。

陣列第一項是錯誤級別,是以下 3 個值之一:

  • "off" or 0 - 關閉規範
  • "warn" or 1 - 警告級別規範
  • "error" or 2 - 錯誤級別規範

陣列第二項才是真正的規範,具體完整的規範參考 這裡

開啟上面的網頁,打綠鉤的表示是已配置的。需要自定義直接寫在 rules 裡即可。

神技二:Prettier

上一步我們用 ESLint 實現了規範的制定和檢查。當開發人員完成一段程式碼儲存時,專案會自動執行 eslint 檢查命令檢查程式碼,檢查到異常後輸出的控制檯,待開發人員修復異常後才能繼續開發。

如果你配置的編碼規範比較複雜和嚴格,比如字串必須單引號,程式碼結尾必須用分號,換行必須是 2 個 tab 且不可以用空格。像這種很細的規範,大家開發過程中難免會有不符合,這個時候控制檯就會頻繁報錯,開發人員就會頻繁修復一個空格一個標點符號,時間久了異常煩人。

正因為如此,在腳手架生成的專案中雖然預設都開啟了 ESLint,但是很多人使用不久後覺得煩人,效率低下,所以都手動關閉了 ESLint。

那麼,有沒有更高效的方法,讓大家非常快捷的寫出完全符合規範的程式碼呢?

有,它便是第二招神技:Prettier

Prettier 是當前最流行的程式碼格式化工具,它最主要的作用就是格式化程式碼。

什麼是格式化?上面我們用 ESLint 定製了編碼規範,當檢測到不規範的程式碼,提示異常,然後需要我們開發人員按照提示手動修復不規範的地方。

而格式化的威力,是將不規範的程式碼,按照規範一鍵自動修復

聽起來很振奮人心,我們來試一下。

首先在專案下安裝:

$ npm install prettier --save-dev

然後新建 .prettierrc.json 檔案:

{
  "singleQuote": true,
  "semi": true
}

這個配置檔案和上面 ESLint 下的 rules 配置作用一致,就是定義程式碼規範 ——— 沒錯,Prettier 也支援定義規範,然後根據規範格式化程式碼。

列一下 Prettier 的常用規範配置:

{
  "singleQuote": true, // 是否單引號
  "semi": false, // 宣告結尾使用分號(預設true)
  "printWidth": 100, // 一行的字元數,超過會換行(預設80)
  "tabWidth": 2, // 每個tab相當於多少個空格(預設2)
  "useTabs": true, // 是否使用tab進行縮排(預設false)
  "trailingComma": "all", // 多行使用拖尾逗號(預設none)
  "bracketSpacing": true, // 物件字面量的大括號間使用空格(預設true)
  "jsxBracketSameLine": false, // 多行JSX中的>放置在最後一行的結尾,而不是另起一行(預設false)
  "arrowParens": "avoid" // 只有一個引數的箭頭函式的引數是否帶圓括號(預設avoid)
}

定義好配置後,我們在 index.js 檔案中寫入內容:

const a = "13"
function add() {
  return "1"
}

然後在終端執行格式化命令:

$ npx prettier --write index.js

格式化之後,再看 index.js 檔案變成了這樣:

const a = '13';
function add() {
  return '1';
}

看到變化了吧,雙引號自動變成了單引號,行結尾自動加了分號,剛好與配置檔案中定義的規範一致。

喜大普奔!終於不用再手動修復不規範的程式碼了,一個命令就能搞定!

上面是格式化一個檔案,當然也支援批量格式化檔案。批量格式化通過模糊匹配查詢檔案,比較常用,建議定義在 script 指令碼中,如下:

// package.json
"scripts": {
  "format": "prettier --write \"src/**/*.js\" \"src/**/*.ts\"",
}

Prettier 還支援針對不同字尾的檔案設定不同的程式碼規範,如下:

{
  "semi": false,
  "overrides": [
    {
      "files": "*.test.js",
      "options": {
        "semi": true
      }
    },
    {
      "files": ["*.json"],
      "options": {
        "parser": "json-stringify"
      }
    }
  ]
}

:ESLint 與 Prettier 有啥區別?

相同點:都可以定義一套程式碼規範。

不同點:ESLint 會在檢查時對不規範的程式碼提示錯誤;而 Prettier 會直接按照規範格式化程式碼。

所以,ESLint 和 Prettier 定義的規範要一致,不能衝突

神技三:VSCode

上面,我們通過 ESLint 和 Prettier 兩招神技,實現了程式碼規範制定,程式碼規範檢查,以及根據規範一個命令格式化程式碼,使得統一團隊程式碼風格變的非常容易。

然而,突破效率的挑戰是沒有極限的。這時候又有小夥伴發聲了:雖然是容易了,但是檢查程式碼還是得依賴檢查命令,格式化程式碼也得依賴格式化命令,這樣總顯得不夠優雅。

好吧,不夠優雅,那還有優雅的解決方案嗎?

答案是有。它就是我們的第三招神技 —— VSCode

強大的外掛

VSCode 對我們前端來說都不陌生,是我們日日相伴的開發武器。當前 VSCode 幾乎統一了前端圈的編輯器,功能強大,倍受好評。

既然能得到如此廣泛的認可,那麼就必然有它的優越性。VSCode 除了輕量啟動速度快,最強大的是其豐富多樣的外掛,能滿足不用使用者各種各樣的需求。

在眾多外掛中,ESLint 就是非常強大的一個。沒錯,這個外掛就是我們前面說到的神技第一招 ESLint 在 VSCode 上支援的同名外掛。截圖如下:

image.png

安裝了這個外掛之後,之前需要在終端執行 eslint 命令才能檢查出來的異常,現在直接標記在你的程式碼上了!

即使是你敲錯了一個符號,該外掛也會實時的追蹤到你錯誤的地方,然後給出標記和異常提醒。這簡直大大提升了開發效率,再也不用執行命令來檢查程式碼了,看誰還說不優雅。

既然編輯器有 ESLint 外掛,那是不是也有 Prettier 外掛呢?猜對了,當然有外掛,外掛全名叫 Prettier - Code formatter,截圖如下,在 VSCode 中搜尋安裝即可。

image.png

Prettier 外掛安裝之後會作為編輯器的一個格式化程式。在程式碼中右鍵格式化,就可以選擇 Prettier 來格式化當前程式碼。

如果要想 Prettier 實現自動化,則還需要在編輯器中配置。

編輯器配置

VSCode 中有一個使用者設定 setting.json 檔案,其中儲存了使用者對編輯器的自定義配置。

這個配置非常豐富,詳見官網。首先我們在這個配置當中將 Prettier 設定為預設格式化程式:

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

設定好這一步之後,重點來了! 我們再來配置儲存檔案自動格式化:

{
  "editor.formatOnSave": true
}

配好之後,神奇的事情發生了:當你寫完程式碼儲存的時候,發現你正在編輯的檔案立刻被格式化了。也就是說,無論你的程式碼按不按照規範寫,儲存的時候自動幫你格式化成規範的程式碼。

這一步其實是儲存檔案的時候自動執行了格式化命令。因為我們上面配置了預設格式化程式為 Prettier,現在又配了儲存時格式化,相當於將檔案儲存和 prettier 命令連線了起來。

到這一步,在三大神技的加持之下,我們已經實現了程式碼的自動檢查與自動格式化,現在你編碼的時候不需要考慮什麼格式規範的問題,只要正常儲存,編輯器會自動幫你做好這些事情。

共享編輯器配置

上面我們在編輯器經過一頓配置,終於實現了自動格式化。現在我們要把這些設定同步給團隊內的其他成員,該怎麼辦,難道要一個一個再配一遍?

別慌,不用這麼麻煩。VSCode 的設定分為兩類:

  • 使用者設定:應用於整個編輯器
  • 工作區設定:應用於當前目錄/工作區

這兩類的配置內容是一模一樣的,區別只是優先順序的問題。如果你開啟的專案目錄包含工作區設定,那麼這個工作區設定會覆蓋掉當前的使用者設定。

所以要想將設定同步給團隊內的其他成員,我們不需要去改動使用者設定,只需要在專案目錄下新建一個工作區設定即可。

新增工作區設定方法:在專案根目錄下新建 .vscode/setting.json 檔案,在這裡寫需要統一的編輯器配置。所以我們把上面的 Prettier 配置寫在這裡即可實現共享。

附錄:命名和專案結構規範

上面介紹了程式碼規範,程式碼檢查和程式碼格式化,統一程式碼風格已經很全面了。

在團隊開發過程當中,我們也積累了一些並不會寫在配置檔案裡的規範,這些規範在一個團隊當中也是非常重要。這部分算是我們的團隊規範的分享吧。

主要說兩部分:命名規範和專案結構規範。

命名規範

命名規範,文章開頭也說了,變數的四種命名規範。但是什麼地方用哪種規範,我們也是有約定的。

  • 變數命名:下劃線 user_id
  • CSS-Class 命名:中劃線 user-id
  • 方法函式命名:小駝峰 userId
  • JS-Class 命名:大駝峰 UserId
  • 資料夾命名:中劃線 user-id
  • 資料夾下元件命名:中劃線 user-id
  • 元件匯出命名:大駝峰 UserId

專案結構規範

專案結構規範主要是指 src 資料夾下的結構組織。

|-- src
    |-- index.tsx # 入口檔案
    |-- assets # 靜態資源目錄
    |-- components # 公共元件目錄
    |   |-- header
    |   |   |-- index.tsx
    |   |   |-- index.less
    |-- stores # 狀態管理目錄,與 pages 結構對應
    |   |-- admins
    |   |   |-- index.tsx # 狀態檔案
    |   |   |-- types.ts  # 定義狀態型別
    |   |-- index.tsx
    |-- pages # 頁面目錄,與 stores 結構對應
    |   |-- admins
    |   |   |-- index.tsx
    |   |   |-- index.less
    |-- request
    |   |-- index.ts # axios 例項,全域性請求處理
    |-- router
    |   |-- home.tsx
    |   |-- index.tsx
    |   |-- root.tsx
    |-- styles # 全域性樣式
    |   |-- common.less
    |   |-- index.less
    |-- utils # 工具目錄
        |-- index.ts

往期精彩

專欄會長期輸出前端工程與架構方向的文章,已釋出如下:

如果喜歡我的文章,請點贊支援我吧!也歡迎關注我的專欄,感謝??

相關文章