常數讓程式中的固定值有名稱。typed string 則讓一組字串值形成語意邊界,避免任意字串到處流動。

本章目標

學完本章後,你將能夠:

  1. const 定義固定值
  2. 理解 untyped constant 與 typed constant 的差異
  3. 用 typed string 表達狀態與事件類型
  4. 集中管理協定字串與 log message

【觀察】字串散落會增加維護成本

協定字串的核心問題是:同一個語意若以裸字串散落在程式中,拼字、修改與合法值判斷都會變成隱性成本。以下是裸字串散落的典型樣子:

1if status == "active" {
2    // ...
3}
4
5if eventType == "user.created" {
6    // ...
7}
8
9log.Println("user created")

短期看起來很直接,但問題是:

  • 拼字錯誤不容易被發現
  • 修改字串時要全專案搜尋
  • 無法從型別看出哪些值是合法的
  • 不同概念可能共用同一種 string

常數可以先解決命名與集中管理問題。

【判讀】const 是把意圖寫進名稱

const 的核心用途是把固定值的意圖寫進名稱。Go 的常數宣告如下:

1const DefaultPort = 8080
2const EventUserCreated = "user.created"

使用常數後,呼叫端讀到的是語意:

1if eventType == EventUserCreated {
2    // ...
3}

這比直接看到 "user.created" 更清楚,因為名稱說明了這個字串在系統中的角色。

【策略】用 typed string 區分概念

typed string 的核心用途是用型別區分不同語意的字串。當多組資料底層都是字串,但語意不同,可以定義不同型別:

1type TaskStatus string
2
3const (
4    TaskStatusPending TaskStatus = "pending"
5    TaskStatusRunning TaskStatus = "running"
6    TaskStatusDone    TaskStatus = "done"
7    TaskStatusFailed  TaskStatus = "failed"
8)

函式簽名可以明確要求 TaskStatus

1func CanRetry(status TaskStatus) bool {
2    return status == TaskStatusFailed
3}

這不會讓 Go 變成 enum 語言,但能讓 API 更清楚。讀者看到 TaskStatus,就知道這不是任意字串。

【執行】事件類型與 action 常數

事件類型的核心規則是:同一組事件值應集中在同一個 typed string 型別下。事件驅動程式常需要管理事件類型:

1type EventType string
2
3const (
4    EventUserCreated EventType = "user.created"
5    EventUserUpdated EventType = "user.updated"
6    EventUserDeleted EventType = "user.deleted"
7)

API action 也可以用同樣方式:

1type Action string
2
3const (
4    ActionSubscribe   Action = "subscribe"
5    ActionUnsubscribe Action = "unsubscribe"
6    ActionPing        Action = "ping"
7)

處理時,switch 會變得可讀:

 1func HandleAction(action Action) error {
 2    switch action {
 3    case ActionSubscribe:
 4        return nil
 5    case ActionUnsubscribe:
 6        return nil
 7    case ActionPing:
 8        return nil
 9    default:
10        return fmt.Errorf("unknown action: %s", action)
11    }
12}

log message 也適合集中

log message 的核心規則是:會被 grep、監控或文件引用的訊息應保持穩定。這類 message 可以用常數集中:

1const (
2    LogServerStarted = "server started"
3    LogEventDropped  = "event dropped"
4    LogInvalidAction = "invalid action"
5)

這樣做的價值是讓 log 訊號穩定。當 log 是除錯入口時,穩定字串就是系統 contract 的一部分。

常見取捨

不必把所有字串都變常數

只出現一次、沒有協定意義、不需要搜尋的文字,可以直接寫在原處。過度常數化會讓讀者一直跳檔案。

常數名稱要說明清楚概念

如果常數名稱只是重複值本身,幫助不大:

1const StringActive = "active" // 不佳

比較好的名稱要說明概念:

1const TaskStatusActive TaskStatus = "active"