Skip to main content

避開魔法字串 - constant 和 enum object 使用時機

何謂魔法字串

在程式設計中直接使用一般字串進行硬編碼 (hard coded),是非常不推薦的,我們可以參考以下範例

// bad: 魔法字串
if (user.role === "admin") {
// ...
}

或許你會覺得這例子還好,也很好懂,但是一旦程式碼規模開始龐大,也把許多 function export 給許多地方使用的時候,我們會很難追查這些字串是什麼意思

(雖然 switch case 不是很被推薦,但目前程式碼很多這樣的情境)

// src/very/very/very/very/deep/fn.js

function beNinja(type, value) {
switch (type) {
case "heheMagic":
break;
case "surprise":
break;
case "rickRoll":
break;
case "ninja1":
break;
case "ninja2":
// ...
default:
}
}
// src/so/so/so/deep/MyGod.jsx
beNinja("ninja", value);
// src/so/so/so/so/so/deep/HolyCow.jsx
beNinja("heheMagic", value);

當你看到這些寫死的字串以參數的方式傳入函數,==後人要理解邏輯或追查起來會非常花時間==,並且還有其他缺點

  1. 因為是寫死的,如果要更改,需要直接以 IDE 的搜尋取代,其中這非常容易出包
  2. 以寫死的字串驅動程式碼運作,你會發現 debug 到天荒地老,才發現是打錯字,因為字串打錯在 IDE 上幾乎沒任何明顯提示

好的作法

// good: 使用 constant 替代魔法字串
const ROLE_ADMIN = "admin";

if (user.role === ROLE_ADMIN) {
// ...
}

其實更建議的作法是將 constant 集結起來變成一個 js 檔,放在 constants 資料夾 (當然其他也要是情況而定)

假設今天需要把 admin 變更成 administer,只要在源頭 constant.js 進行改動,其他 import 進來的資料夾都會雨露均沾了

另外一個好處是,因為已經化成變數,變數在 IDE 的提示和支援就強大很多,==打錯會很明顯知道==

延伸作法 : 類 Enum 物件

因為純 JavaScript 沒有 真正的 enum,我們可以使用物件來達到類似的效果

const DIRECTIONS = {
WEST: "west",
EAST: "east",
SOUTH: "south",
NORTH: "north",
};

// 通常使用大寫和底線

直接調用時就直接使用 DIRECTIONS.NORTH 來指向真實的字串內容,除了能獲得良好的 IDE 提示之外,也明確表達字串的使用範圍都在這個 object 裡面

暫時改不動又難維護的程式,先自建 Enum Object

曾經有一個共用 modal 元件,它的設計是傳入一個 close handler 作為 prop

內部的設計是,只要按下關閉,元件內部就會執行 handleClose(0),按下確認,元件內部就會執行 handleClose(1) (厲害了)

如果需要調用這個 modal,外層設計 handler 的時候就要 if 1 then... if 0 then... 可讀性會非常的差,例如

function handleClose(closeType) {
if (closeType === 0) {
//...
} else {
//...
}
}

return <CoolModal onClose={handleClose} />;

但如果這個 Modal 目前還改不動,那只好自己做個 enum object 來讓可讀性好一點了

const MODAL_CLOSE_TYPE = {
CLOSE: 0,
CONFIRM: 1,
};

function handleClose(closeType) {
if (closeType === MODAL_CLOSE_TYPE.CONFIRM) {
// ...
}

if (closeType === MODAL_CLOSE_TYPE.CLOSE) {
// ...
}
}

return <CoolModal onClose={handleClose} />;