How to Organize Utility Functions

如何管理工具函式

前言

近期維護專案時發現有許多通用的函式散亂的分布在專案當中,於是趁有空時整理出一個統一的結構規則以便團隊使用。很多時候我們對這些通用工具函式的印象只停留在要抽離到某個資料夾,至於要怎麼管理或是維護這些函式就沒有太多想法,導致許多亂象產生,像是:

  • 過度抽象:邏輯抽離是抽離了,但好像意義也不大,反而增加理解維護成本
  • 意義不明的命名:因為太過於通用,所以名稱也取得很攏統
  • 神級函式:一個函式做太多事情,導致難以維護

面對真實世界問題

根據我在專案中實際遇到的問題:

  • 有的函式算不上常用,但卻被特地抽離到全域函示庫當中管理,造成毫無用途的抽象
  • 一些陳舊代碼命名為 parse.tsformat.ts……等,明顯超攏統的分類方式,導致未來很難找到想要的功能
  • 理論上可以拆為更小功能的函式因為方便所以都寫成一坨

必須說除了胡亂命名之外其他問題背後大概都有取捨,不同人有不同的出發點和未知的背景,希望可以透過定調一個統一開發規則,讓團隊成員可以遵循規則更容易的找到想要的通用函式以提升開發體驗。

過程中研究了不同通用庫的組織方式,像是:lodash🔗radash🔗underscore🔗vueuse🔗,可以發現共通點是他們都非常優雅純粹 😅,可能沒辦法替參雜太多商業邏輯的專案提供建議,但我還是觀察學習一下並總結出規律。

通用函式定義

  • 執行特定通用任務的小型可重複使用之函式
  • 不依賴於特定上下文或狀態之函式(純粹函式

什麼時候抽離通用函式

發現函式執行的動作在不同地方重複,請將函式抽離出來於對應層級倉庫中管理,並在需要時引入,方便統一管理與追蹤。

管理通用函式要點

  1. 檔名與函式名需依小駝峰命名法命名
  2. 函式名稱需能清楚表達函式功能
  3. 鼓勵純粹函式,鼓勵添加測試
  4. 最多一層資料夾描述功能群組
  5. 職責單一且用動詞形容功能,拆分為小檔案方便從檔名就能預覽其功能
  6. 善用現有通用函式庫 (Lodashdate.js),不要自己造輪子
  7. 盡可能少的抽象
    1. 如果預期不會很常使用,乾脆不需要抽離
    2. 如果預期只會用在 A 專案,只抽離到 A 專案通用函式庫,不要抽離到全局函式庫
  8. 必須詳細註解記錄函式用途,並透過 TypeScript 紀錄型別。

實際案例

以下是一些可以快速參照的範例,可以大致了解概念即可,實際應用可能會更複雜。

通用函式應該如何定義

libs/shared/utility/src/lib/thousandth.ts
/**
* 將正整數轉換成具有千位分隔符的字串
*
* @param number - 整數,如無輸入預設為 0
* @returns 具有千位分隔符的字串
* @example thousandth(1000) // '1,000'
*/
export function thousandth(number: number) {
const isInteger = Number.isInteger(number);
if (isInteger) {
return number.toString().replace(/\B(?=(\d{3})+$)/g, ',');
} else {
throw new RangeError('輸入必須是整數');
}
}

整體資料夾結構

有考慮過要不要將相似的功能都放置在相同的檔案統一輸出過,不過最終還是決定分清楚,原因是因為:

  1. 不會耗費太多時間
  2. 單看資料夾結構便能找到想要的功能
Terminal window
lib/
├── dialog/
├── showDialog.ts
└── hideDialog.ts
├── date/
└── transformToYYYYMMDD.ts
├── cookie/
└── getCookieExpiredAbsoluteTime.ts
├── localStorage/
├── getLocalStorage.ts
├── setLocalStorage.ts
└── removeLocalStorage.ts
├── shadcn/
└── cn.ts
└── tanstackTableVue/
└── valueUpdater.ts