建立前端開發準則,讓團隊能夠有效率的開發好維護的程式碼 —— 均一前端工程師宜陞技術分享

【前言】均一的程式碼基礎 junyiacademy 從 2013 年 fork Khan Academy 原始碼,一直發展到現在,程式碼的複雜度不可同日而語,開發和維護的難度越來越高,很根本而且不好償還的技術債逐漸浮上檯面,有些甚至成為新功能開發的巨大阻力。

於是,我們在 2020 六月決定制定出前端的開發準則,具體行動有停止混用多個框架,使用一致的目錄結構,並為選用的框架及相對應的目錄、檔案制定 style guide。最終希望前端能在有規範情況下,有效率的開發出可維護的程式碼。

行動 1:訂出 Client 端與 Server 端的狀態管理規範

狀態管理一直是複雜的前端專案需要面臨的問題,除了選一個好用的狀態管理工具,也需要為 UI 相關的 Client 端狀態與 API 資料相關的 Server 端狀態設計不同的管理方式。均一在此行動中分為兩階段進行,在階段一我們先選了 Redux(註 1)來統一我們的管理工具,並在階段二中持續優化 Client 端與 Server 端的狀態管理方式:

階段一:使用 Redux 統一狀態管理工具

其實均一的 codebase 中原本就有用到 Redux,但前端的狀態管理方式一直沒有統一的規範,常見的問題像是直接在 React 的 UI 元件中呼叫 API,導致 server 端的狀態邏輯混雜在應該只用來處理 UI 邏輯的元件之中。此外,在 React 推出了 Context API(註 2)之後,到底要用 Redux 還是 Context API 在前端團隊中一直是個懸而未決的大哉問。

因此在訂定狀態管理規範初期,最先開始處理的就是「Redux 有的人喜歡,有的人不喜歡」的問題。在經歷一段時間的研究與一場技術分享之後,考量到許多狀態管理機制在 Redux 中早已實作並有一套明確的 design pattern 可以遵循,對前端團隊來說,與其選擇 Context API 並自行訂定、維護一套狀態管理機制,不如先以設計優良的 Redux 出發,讓均一的團隊成員熟悉這個做法,之後要換其他套件都是可以討論的。

但 Redux 常為人詬病的一個問題就是,只為了處理一些狀態管理需求就需要寫一堆樣板程式,為了優化這個問題,我們採用了 Redux 官方推薦的 Redux Toolkit。使用 Redux Toolkit 中的 Slice 可以大幅簡化需要撰寫的樣板程式。此外,善用 Toolkit 中的 Selector 也讓均一的程式碼更解耦,在疊加新功能時可感受到新架構有更高的開發彈性。

在前面文章提到「直接在 UI 元件中呼叫 API」的非同步問題,則是藉由把呼叫 API 以及相關的 side effect 都搬移到 Redux 的 middleware,讓 UI 元件的邏輯更乾淨。而 Redux 的 middleware 種類繁多,常見的如:Redux-Saga、Redux-Thunk、基於 RxJS 的 Redux-Observable,考量到 RxJS(註 3)的泛用性較高,且在各個語言幾乎都有對應的套件,即使未來不再使用 Redux-Observable 還是可以持續使用 Rx 的概念進行開發,儘管 Redux-Observable 的學習成本較高,最後均一仍選擇了 Redux-Observable。

階段二:使用 React Query 管理 server 端狀態;Redux 只用來管理「複雜」的 client 端狀態

由於觀察到越來越多人推薦以 React Query、SWR(註 4)等套件來處理 data fetching,在訂定 Redux 規範時就持續在關注這些套件的發展,經過前端團隊進行相關的研究與試用後,決定以 React Query 取代 Redux-Observable。此決定的原因一部分是使用 React Query 確實可以少寫非常多樣板程式,讓程式碼變得更簡潔,另一部分則是考量到未來均一打算 migrate 到 GraphQL,而目前 React Query 對 GraphQL 的支援度較佳。

使用 React Query 的另一個好處是可以將 server 端的狀態以及 cache 機制統一交給 React Query 管理,如此一來就能明確的分離 server 端與 client 端的狀態。在大部分的專案中,當把 server 端狀態都交給 React Query 處理後,通常只會剩下少量的 client 端狀態,而 client 端的狀態變少,其實就不太需要使用 Redux 這個專門用來處理龐雜 client 端狀態的管理工具。在理想的情況下,均一的前端只需要 React + React Query 即可進行完善的狀態管理,只有在 client 端狀態較複雜的情況,才會請出 Redux 來幫忙管理。

行動 2:在新專案導入 TypeScript

均一前端團隊長期使用弱型別的 JavaScript 進行開發,TypeError 一直是工程師心中的痛,且在型別未明的情況下,常需要耗費大量的腦中記憶體來記住程式碼中的型別。考量到 TypeScript 的安全性明顯比 JavaScript 來得高,且各大套件也開始 migration 到 TypeScript 或甚至以 TypeScript 為主,均一團隊也開始在 codebase 中導入 TypeScript。

不過均一並沒有在原本的 codebase 導入 TypeScript,而是在新的前端 repo 導入,因此沒有經歷到太多 migration 的痛苦,否則就需要一步一步地將 strict mode 打開,慢慢解 error。而原本使用 JavaScript 的前端 repo,則使用 JSDoc 來進行基本的型別撰寫。

行動 3:制定 style guide

為了讓團隊中的前端工程師有統一的標準能夠遵循,我們開始為選用的框架、技術以及相對應的目錄結構制定 style guide,同時也期待實習生與其他的開發者能夠參考這些規範來上手均一的前端開發,減少因為不同人撰寫的程式碼差異過大導致維護困難的情況發生。

前端的 style guide 以 Google JS style guide 為基礎,在此基礎上針對以下幾個大項目撰寫實作規範:

  1. Naming,如:React handler functions 要怎麼命名
  2. Import/Export module statements,如:import module 的排列順序
  3. File’s implementation,例如:component 要怎麼寫
  4. Framework/Package usage,例如:Material-UI 的 styles API 要怎麼用

此外,我們也訂定了目錄結構的規範,希望能做到當想要改一個功能時,工程師不需要打開編輯器就已經大概知道要去哪個資料夾中的哪個檔案修改。

行動 4:將準則導入開發環境

要讓工程師快速的將 style guide 導入日常開發,我們想到最快的方法就是將這些原則直接透過自動化工具引入團隊成員的 IDE、Git flow 之中。

使用 Prettier 自動排版程式碼

均一長期以來都是透過 ESLint 在進行語法檢查,雖然 ESLint 確實提升了程式碼的一致性,但許多語法錯誤仍須透過肉眼辨識來進行手動修改,為了加速日常的排版工作,我們開始使用 Prettier(註 5)在 IDE 進行自動排版。

過去均一的 codebase 還沒有使用過類似的自動排版工具,因此在全面套用 Prettier 前我們先取一個 package 用 Prettier 進行排版,確定沒問題後,再一次性的將全部的 JS files 都進行排版。此後,前端寫 code 的體驗大升級,無時無刻都受益於 Prettier 搭配 VSCode 的 Format On Save 功能,在過去需要人工判斷的排版問題,現在只要按下 save 即瞬間完成。

使用 code snippet 省去複製貼上程式碼的時間

通常在寫 code 前會去複製貼上其他已符合 coding style 的程式碼,而 code snippet 其實就只是將程式碼的模板做成 snippet。snippet 比較有趣的地方在於,它能夠根據檔名自動將某些程式碼的 naming 設定為對應的文字,例如:component 要叫什麼名字,這也讓 snippet 除了節省複製、貼上、找模板的時間外,也省去了更新 naming 的時間。

在 commit 前檢查排版、語法的正確性

除了前面提到的在 IDE 中使用自動排版工具,在 Git 流程中,我們也設定了 commit 前的自動檢查機制(pre-commit)。最後均一是透過 husky(註 6)這個套件在 pre-commit 階段檢查 code change 是否有通過 Prettier 和 ESLint 檢查。多了這個工具在 commit 前督促工程師做好排版工作,省去不少在 code review 時溝通排版問題的時間。

附帶一提,由於團隊成員的 commit message 也長期沒有統一的規範,我們也為此導入了 Commitizen 來讓大家照著一定的格式撰寫 commit message。

總結與未來方向

建立準則後的差別

訂定了以上準則後,在均一開發前端明顯比過去容易得多。在一致的準則下開發,有更高的確定感,不會再苦於找不到檔案、不知道檔案要放哪、不知道該用哪個套件。此外,遵循設計優良的架構進行開發,明顯感受到模組間的耦合度降低,side effect 減少,在開發時有更高的彈性,對新進成員來說整體的開發環境也比過去友善多了。

未來的規劃

下一階段,均一的前端會使用 Nx(註 7)幫助我們管理 monorepo 中的多個 apps,並將均一常用的元件提取為共用元件,整理到 monorepo 中的 Storybook(註 8)。

另外,我們相信 GraphQL 是未來的趨勢,前端預計會在下一代的架構中使用 GraphQL,選擇技術時也會以該技術是否支援 GraphQL 為考量點。


註解

  1. Redux 是一個善於集中管理前端狀態的套件。
  2. Context API 是 React 負責傳遞資料到各個前端元件的 API,透過 Context API 可實作出類似 Redux 的狀態集中管理功能。
  3. RxJS 是透過 JavaScript 實作的 ReactiveX API。ReactiveX API 集成了 design pattern 中的 Observer pattern, Iterator pattern 以及 functional programming 的概念。
  4. React Query、SWR 都是用來管理前端非同步資料的套件,這些套件善於處理資料的取得、更新與快取。
  5. Prettier 是一套 code formatter,可透過 Prettier 改善程式碼的可讀性與排版的一致性
  6. husky 是一個可在 git commit, git push 階段執行 linter, tests 的開發工具
  7. Nx 是一套用來管理 full-stack monorepo 的開發工具
  8. Storybook 是一套用來管理 UI 元件與頁面的工具

均一招募中!

如果你對前端軟體開發充滿熱忱,而且跟宜陞一樣期待用科技來影響教育,我們正在尋找軟體工程師,馬上到招募頁面查看職位詳情!


作者|江宜陞(均一平台教育基金會前端工程師)
編輯|江芳瑜(均一平台教育基金會實習生)

分享這篇文章:

相關文章