CodeGym /課程 /Go SELF /個人成長策略:作品集、程式碼審查與閱讀 stdlib

個人成長策略:作品集、程式碼審查與閱讀 stdlib

Go SELF
等級 72, 課堂 4
開放

1. 作品集作為信任櫥窗

初學者的作品集常常長這樣:十個儲存庫,每個裡面都有 final_final2.go,而 README 裡驕傲地寫著 “My cool project”。在面試時,這對信任幾乎沒有幫助,因為看不到最重要的東西:你的程式碼如何運作、它有哪些 契約,以及它是否 值得信賴。作品集不是在比數量,而是在展示品質。

想像你的專案是一台小型裝置。使用者不需要知道裡面的齒輪是怎麼咬合的。使用者在意的是:它能不能開機、螢幕顯示什麼、出錯時會怎樣、按錯鍵會不會爆掉。在軟體裡,「螢幕」就是 CLI/HTTP 契約,而「不會爆掉」則是測試、清楚的錯誤與可預測性。

要讓作品集有「形狀」,最好記住三件事:展示 → 保證 → 可重現性。展示回答「它做什麼、為什麼要做」,保證回答「為什麼能信任它」,可重現性回答「要怎麼啟動並驗證它」。在好的專案裡,這些內容 2–3 分鐘內就看得出來,不需要進入考古模式。

學習型應用程式的展示用迷你骨架

先看一個假想的課程最終專案——任務管理器(就叫 tasker 吧),它有 CLI 和 HTTP API。在展示時,你幾乎總是會秀同一類東西:happy-path 與一到兩個 error-path,但重點是要透過契約來呈現。

下面是一小段「正確」的 CLI 進入點,因為行為集中且容易理解,所以非常適合放進作品集:


package main

import (
	"os"
)

func main() {
	os.Exit(run(os.Args[1:]))
}

再來是你有意識地管理 CLI 的「外部契約」的地方(引數 → 離開代碼):

package main

import (
	"errors"
	"fmt"
	"os"
)

var ErrUsage = errors.New("usage")

func run(args []string) int {
	if len(args) == 0 {
		fmt.Fprintln(os.Stderr, "usage: tasker <command>")
		return 2
	}
	return 0
}

這是一個很小的例子,但它顯示出成熟度:你不是把 os.Exit 到處散佈在程式裡,而是把外部契約集中在一個地方。

2. README 作為專案介面

README 不是一篇「我和 Go 一起度過暑假」的散文。它是一份說明文件,幫助使用者快速回答四個問題:專案做什麼、如何啟動、如何驗證品質、去哪裡看範例。如果 README 回答不了這些問題,那它就不是 README,而是一張裝飾用明信片。

作品集特別重視「理解速度」:審查者越快感受到「這個專案很整潔」的訊號,就越可能真的去讀程式碼。諷刺的是,好的 README 往往比漂亮的架構更重要,因為沒有讀者的架構,就像沒有觀眾的笑話:作者覺得好笑,其他人一片安靜。

一個很好的做法是把 README 寫成「quickstart + contract」的風格。下面是一段可作為參考的範例(這是文字,但結構比具體措辭更重要):

tasker — 最小任務管理器(CLI + HTTP)。

Quickstart:
  go test ./...
  go run ./cmd/tasker help

CLI contract:
  stdout — 資料
  stderr — 錯誤/usage
  exit codes: 0 ok, 2 usage, 1 runtime

HTTP contract:
  JSON, Content-Type: application/json
  錯誤: { "error": { "code": "...", "message": "...", "fields": {...} } }

請注意:這裡沒有「我們使用了帶有量子 enum 的微服務架構」。這裡有的是你可以在一分鐘內動手驗證的內容。

Example 測試作為作品集的一部分

README 裡的普通範例很容易過時。Example 測試 可以編譯,而且能被 go test 驗證,所以它是「活的範例」。這是展示品質最被低估的方法之一:你不是用話術說服別人,而是直接展示一個可驗證的情境。

例如,你希望 ID 解析函式既讓人看得懂,也讓測試執行器看得懂:

package tasker

import (
	"fmt"
	"strconv"
)

func ExampleParseID() {
	id, err := strconv.Atoi("42")
	fmt.Println(id, err == nil) // 42 true
	// Output: 42 true
}

在作品集中,這就像一個小小的保證:我不只是寫了程式碼,我還把行為固定下來,讓它不會「漂移」。

3. 程式碼審查作為一種能力

很多初學者把程式碼審查看成考試:「現在要來挑我毛病了」。實際上,程式碼審查是降低錯誤風險、提升溝通品質的一種方式。它是共同設計。有時候它真的讓人不舒服,但更常見的不舒服並不是審查本身,而是習慣不足:看不懂的 diff、混亂的變更、沒有測試、沒有說明「為什麼要改」。

好消息是:程式碼審查是一項可以訓練的技能。更好的消息是:即使你沒有團隊,也可以從今天開始做審查,因為有個很強的技巧——自我審查。你先替自己做一個 PR,走過品質檢查,然後再給別人看。這會大幅降低像「嗯,gofmt 本來也可以先跑一下吧?」這類意見的數量。

在作品集中,程式碼審查是間接可見的:從 commit 歷史、變更是否原子化、是否有測試、以及錯誤處理是否清楚,都能看出來。即使沒有人在 GitHub 上留言,專案也可能看起來像是「經過一位成熟工程師審過的」。

以 tasker 的變更為例,進行小型自我審查儀式

假設你新增了任務標題驗證。差的版本通常長這樣:函式「就地堆上去」、錯誤風格不一致、沒有測試、驗證邏輯散落在三層。

整潔的版本會從一個小函式開始,而且這個函式很容易測試:

package tasker

import "errors"

var ErrTitleEmpty = errors.New("title is empty")

func ValidateTitle(title string) error {
	if title == "" {
		return ErrTitleEmpty
	}
	return nil
}

是的,這是一個很簡單的函式。但正是這類東西,會塑造你對程式碼的「品味」:小、可讀、可測試的片段。

接著是一個小小的 table-driven 測試,用來固定契約:

package tasker

import "testing"

func TestValidateTitle(t *testing.T) {
	if err := ValidateTitle(""); err == nil {
		t.Fatalf("expected error")
	}
	if err := ValidateTitle("buy milk"); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

這樣的程式碼在審查時通常不太會被挑剔。不是因為它「完美」,而是因為它不需要別人猜。

把品質檢查變成習慣

重點是不要讓品質取決於心情(「今天我很棒,明天我很累」)。所以多數團隊會把品質做成固定儀式。你已經知道工具:格式化、測試、靜態分析。接下來不是「要跑哪些」,而是「怎麼把它變成習慣」。

這裡有個很簡單的想法:手動步驟越少,你真的做出來的機率就越高。即使是在單人專案中,也可以準備一條你每次推送前都會執行的命令。有時候會透過 Makefile,有時候是腳本,有時候是 IDE 的執行設定——不重要。重要的是,「檢查自己」應該像打開瀏覽器分心一樣容易。

4. 閱讀標準函式庫原始碼

閱讀標準函式庫原始碼聽起來像是只有穿黑斗篷的人才會做的事,通常是在編譯器不編譯的時候。其實這是一個非常實用的習慣。Go 的標準函式庫是一整本解法集:如何處理錯誤、如何圍繞介面設計 API、如何寫出能活上好幾年的程式碼,以及為什麼簡單有時候不是靠魔法,而是靠紀律。

閱讀 stdlib 時,不要抱著「我現在就要全部看懂」的心態,而是抱著「我現在只要懂一小塊,並從中偷一個好習慣」的心態。這就像健身房:你不會去一次就變成超級英雄。你會持續去,幾個月後你才會突然發現,自己的程式碼變得更穩定、更乾淨了。

還有一個好處:當你閱讀 stdlib 時,你會不再相信迷思。你會看到真實程式碼如何以 context 包裝錯誤、如何維持小介面、如何對輸入邊界投入大量注意力——然後「魔法思維」就會開始死亡。

如何找到某個套件的原始碼

在 Go 裡這件事相當直接:你有 GOROOT,標準函式庫就放在那裡。如果你使用 IDE,通常只要對 net/http 或 encoding/json 做「Go to definition」,就能跳到原始碼。如果你喜歡終端機(或 IDE 顯示了路徑),也可以從 GOROOT 來定位。

如果你想真正「感受」Go 不只是語言,還是一整套工具生態,那麼你應該知道 go 指令有很多模式,以及專案周邊的各種檔案。例如 workspaces(go.work)是一起管理多個模組的方法之一;它不是初學者每天都必須用到的東西,但它被文件化並且被工具支援,這本身就是「Go 文化」的一部分。

此外,還要記得某些指令是流程中的獨立步驟。例如 go generate 的確存在,但它不會「自動魔法般」加入建置流程:你需要明確執行它,而這對可重現性非常重要。即使你現在不用 go generate,理解這種哲學也有助於寫出沒有隱藏魔法的專案。

如果你是新手,stdlib 該先讀什麼

不需要從 runtime 開始。最好從你在課程中已經用過的套件開始:errors、fmt、io、bufio、net/http、encoding/json、context、sync。在那裡,你會看到熟悉的概念以「理想版本」呈現。

一個非常有用的閱讀技巧是:從介面走向實作。先看契約長什麼樣子(例如 io.Reader),再看誰實作它,最後看它周圍包了哪些 wrapper/decorator。這和我們的課程一樣:先看契約,再看實作,再看可測試性。

迷你練習:stdlib 風格的裝飾器

在閱讀 stdlib,尤其是 io 周邊的套件時,會有一種感覺:「他們把所有東西都包成某個實作介面的東西了。」這的確是真的:Go 非常喜歡小型 wrapper,它們不會「破壞」任何東西,而是新增行為。這是成長的絕佳訓練:你學會用組合而不是重寫來擴充系統。

讓我們在 tasker 裡做一個小步驟:為 io.Writer 加上一層 wrapper,用來計算我們寫了多少位元組。這很適合用在日誌、指標或測試,而這正是你在標準函式庫中會反覆看到的思維方式。

package tasker

import "io"

type CountingWriter struct {
	W io.Writer
	N int
}

func (cw *CountingWriter) Write(p []byte) (int, error) {
	n, err := cw.W.Write(p)
	cw.N += n
	return n, err
}

現在你可以在任何有 writer 的地方使用它(stdout、檔案、緩衝區)。這非常「Go」:最少的魔法,最多的透明度。

例如,你想在 CLI 中計算你輸出了多少位元組給使用者:

package main

import (
	"fmt"
	"os"

	"example.com/tasker"
)

func main() {
	cw := &tasker.CountingWriter{W: os.Stdout}
	fmt.Fprintln(cw, "hello")   // hello
	fmt.Println("bytes:", cw.N) // bytes: 6
}

沒錯,這個範例很玩具化。但它教了三件成熟的事:介面、組合與可觀測性。而在 stdlib 裡,這種小小的「積木」非常多——這正是閱讀它的原始碼如此能鍛鍊腦力的原因。

5. 成長節奏

幾乎所有成長計畫都會被同一件事打敗:人們給自己訂下「把所有東西都學完」的目標,然後一週後就開始討厭自己和 Go。為了不要重演這種經典黑色喜劇,最好接受一個簡單觀念:成長必須是小步的、規律的、可衡量的。這裡不是指 KPI 式的數字,而是指「我可以誠實地說我做了一步」。

一種很方便的形式是每週節奏,裡面有幾個重複活動:稍微改善一點專案(作品集)、稍微練習審查、稍微讀一點好程式碼。重點不是數量,而是穩定性。如果你每週只做 20 分鐘,但每週都做,那會比兩個月才一次的「週末馬拉松」更有用。

下面是一個很貼近現實的節奏範例,即使你是邊工作/邊讀書也能套用:

習慣 時間 什麼算「完成」
專案小幅改善 30–60 分鐘/週 一個 PR:測試、修 bug、小功能
自我審查 10–15 分鐘/PR 跑完檢查、重新讀過 diff
閱讀 stdlib 20–30 分鐘/週 一個檔案/一個函式 + 筆記
文件 15 分鐘/週 改善 README 或新增 Example

請注意:這裡沒有「學會 Kafka」這一項。不是因為 Kafka 不好,而是因為如果沒有節奏,你連 fmt.Printf 都學不好——最後只會剩下罪惡感,還有一個「之後再看」的課程分頁。

還有一個重要觀念:留意平台的變化。Go 會持續演進,工具會改變,新的預設值會出現。例如,在 Go 1.25 中,容器內 GOMAXPROCS 的行為有所改變:runtime 會預設考慮 CPU limits,以避免不愉快的 throttling 延遲。你不需要現在就「深入學容器」,但培養閱讀這類說明、理解平台是活的的習慣,會很有幫助。

6. 個人成長策略中的典型錯誤

錯誤 1:把作品集做成「數量導向」,而不是「信任導向」。
當你有很多儲存庫,但沒有任何一個具備清楚的 quickstart、測試與明確契約時,審查者很難相信你的程度。更有說服力的是一個第一次就能跑起來、錯誤穩定、測試完善,而且有清楚 README 的專案。

錯誤 2:把程式碼審查當成懲罰。
如果你把審查想成「判決」,你就會開始藏變更、把變更做得又小又沒意義,或者反過來一口氣塞進超大的 commit,「免得等一下又想改」。健康的模型不是這樣:審查是在共同降低風險。送出前先自我審查,是讓審查變得平靜又有用的最好方式。

錯誤 3:把 stdlib 當小說來讀(然後受苦)。
想一次看懂整個 net/http 或 encoding/json,很容易讓人失去動力。更有效的方法是小口小口讀:一個型別、一個函式、一個測試。重點是累積習慣與「專業品味」,而不是一次性的壯舉。

錯誤 4:把「我知道」和「我能展示」混為一談。
在面試與工作中,大家重視的不是你「大概懂」,而是你能不能快速重現:跑測試、展示 demo、解釋錯誤契約。所以作品集必須可展示:啟動命令、情境流程、預期結果。

錯誤 5:把品質改善留到「有空再說」。
如果品質沒有被內建進流程,它就不會自己出現。某個時候專案開始活起來、bug 累積、你把測試一拖再拖。更省成本的做法是:一個小儀式——格式化、測試、靜態分析,最後才 commit。

1
任務
Go SELF, 等級 72, 課堂 4
上鎖
命令管理器
命令管理器
1
任務
Go SELF, 等級 72, 課堂 4
上鎖
審查報告
審查報告
1
任務
Go SELF, 等級 72, 課堂 4
上鎖
行為範例
行為範例
1
任務
Go SELF, 等級 72, 課堂 4
上鎖
寫入計數器
寫入計數器
1
問卷/小測驗
課程總結,等級 72,課堂 4
未開放
課程總結
課程總結
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION