文字處理的核心規則是:格式化輸出交給 fmt,字串查找、裁切、替換與組合交給 strings。本章將用 CLI 輸出、設定值清理與簡單 parser 建立標準庫文字處理基礎。

fmt 負責格式化

fmt 的核心責任是把資料轉成可閱讀的文字。它可以輸出到標準輸出,也可以把格式化結果組成字串,常用於 CLI 訊息、錯誤訊息與簡單除錯。

1name := "worker"
2count := 3
3
4fmt.Printf("%s handled %d jobs\n", name, count)
5message := fmt.Sprintf("%s handled %d jobs", name, count)
6
7fmt.Println(message)

Printf 會直接輸出,Sprintf 會回傳字串。這個差異很重要:函式內部如果只是要建立訊息,通常應該用 Sprintf 回傳字串,而不是直接印出。

格式動詞描述輸出形狀

格式動詞的核心作用是告訴 fmt 如何呈現資料。常見動詞包括 %s 表示字串,%d 表示十進位整數,%v 表示一般值,%+v 顯示 struct 欄位名稱,%#v 顯示更接近 Go 語法的表示。

 1type User struct {
 2    ID   int
 3    Name string
 4}
 5
 6user := User{ID: 7, Name: "alice"}
 7
 8fmt.Printf("%v\n", user)  // {7 alice}
 9fmt.Printf("%+v\n", user) // {ID:7 Name:alice}
10fmt.Printf("%#v\n", user) // main.User{ID:7, Name:"alice"}

%v 適合一般輸出,%+v 適合快速檢查 struct 欄位,%#v 適合除錯或理解實際型別。正式 log 通常應該使用結構化 log,而不是把所有資料塞進格式化字串。

錯誤訊息要包含可行動資訊

錯誤訊息的核心原則是描述失敗的操作與關鍵資料。fmt.Errorf 可以建立帶格式的 error,也可以用 %w 包裝原始錯誤,保留錯誤鏈。

1func loadUser(id string) error {
2    if id == "" {
3        return fmt.Errorf("load user: id is required")
4    }
5
6    return nil
7}

錯誤訊息是給工程師定位問題的線索。像 "failed" 這類訊息太籠統,讀者無法知道是哪個操作失敗。

1if err := saveConfig(path, config); err != nil {
2    return fmt.Errorf("save config %q: %w", path, err)
3}

這裡的訊息包含操作 save config、目標 path 與原始錯誤。呼叫端可以顯示完整錯誤,也可以用 errors.Iserrors.As 檢查被包裝的錯誤。

strings 負責字串操作

strings 的核心責任是提供不需要正規表示式的常見字串操作。裁切空白、檢查前後綴、切割、替換、大小寫轉換,都應該先考慮 strings

1raw := "  api,worker,admin  "
2raw = strings.TrimSpace(raw)
3
4parts := strings.Split(raw, ",")
5for _, part := range parts {
6    fmt.Println(strings.TrimSpace(part))
7}

這段程式先移除整體前後空白,再用逗號切割,最後清理每個片段。這種處理很常見於環境變數、設定檔與簡單文字輸入。

查找與判斷應該直接表達意圖

字串判斷的核心原則是使用最貼近意圖的函式。檢查包含關係用 Contains,檢查開頭用 HasPrefix,檢查結尾用 HasSuffix

1path := "/api/users"
2
3if strings.HasPrefix(path, "/api/") {
4    fmt.Println("api route")
5}
6
7if strings.Contains(path, "users") {
8    fmt.Println("user resource")
9}

Index 判斷是否存在也是可行的,但意圖比較間接。

1if strings.Index(path, "/api/") == 0 {
2    fmt.Println("api route")
3}

這段程式需要讀者理解 Index 回傳 0 表示出現在開頭;HasPrefix 則直接說出規則。入門階段應優先選擇語意清楚的 API。

組合字串要看資料量

組合少量字串時,+fmt.Sprintf 通常足夠;大量或迴圈內組合字串時,strings.Builder 更適合。核心判斷是:資料量小時重視可讀性,資料量大時避免反覆建立中間字串。

1name := "alice"
2message := "hello, " + name
3fmt.Println(message)

少量字串串接很直覺,不需要過度設計。當你在迴圈中累積文字,strings.Builder 能更明確地表達「正在逐步建構一段文字」。

1var builder strings.Builder
2
3for _, name := range []string{"alice", "bob", "carol"} {
4    builder.WriteString("- ")
5    builder.WriteString(name)
6    builder.WriteString("\n")
7}
8
9fmt.Print(builder.String())

strings.Builder 不是每次組字串都必須使用。若資料量小、流程簡單,普通串接往往更好讀。

簡單 parser 可以先用標準庫

簡單文字解析的核心策略是先用清楚的步驟切割資料,再逐步驗證格式。只有當格式本身複雜到難以維護時,才需要引入 parser 或正規表示式。

 1func parsePair(input string) (string, string, error) {
 2    parts := strings.SplitN(input, "=", 2)
 3    if len(parts) != 2 {
 4        return "", "", fmt.Errorf("parse pair %q: missing =", input)
 5    }
 6
 7    key := strings.TrimSpace(parts[0])
 8    value := strings.TrimSpace(parts[1])
 9    if key == "" {
10        return "", "", fmt.Errorf("parse pair %q: empty key", input)
11    }
12
13    return key, value, nil
14}

SplitN 限制最多切成兩段,避免 value 裡再次出現 = 時被過度切割。這個例子也先處理格式錯誤,再回傳正常結果,讓流程保持清楚。

小結

下一章會進入 time,說明時間點、時間長度與 timeout 的標準表示方式。