iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0

「沒有人認領的問題,和沒有人犯過的錯,會被寫進同一頁空白。」
——《阿帕契開源審計錄》¹ 卷二·認領篇

幕間
獵人按槍坐在位子上,一言不發——他的槍唯有出局那一刻才會鳴響。
他看著七號指尖沿著羊皮紙上的日誌一行行移過。
此刻唯有相信那個沒有輪迴特權、只有一條命的平民,比槍更可靠。

天亮,法官點名今天的放逐投票。長桌上的氣氛跟過去每一世都不同——村莊在決定性的白天到來之前,第一次把三隻狼的名字都擺上了檯面。三號最先撐不住,他一向急躁,話講一半就拍桌子,這次拍完自己愣住,因為全場沒有一個人被他帶走節奏。票數落定,他被請出議事廳。

剩下的兩隻狼——包括我——臉上維持著剛好的沉默。

七號沒有慶祝。她翻到羊皮紙的新一頁,畫了一欄新的標題:「該做而沒做的事」。她說,三號的破綻不是他講錯了什麼,而是他每一晚都在搶著做同一件事:第一個喊刀口。而場上有另一個人,這一欄從開局到現在,一格都沒被填過。她沒有指名,只是把欄位留在那裡,等它自己被填滿。

我忽然意識到,她正在學的東西,跟一個新人第一次走進十萬行的開源專案、要做的第一件事,是同一件:先看懂整張地圖上「誰負責什麼、哪裡還空著」,而不是急著發言。急著發言的人是三號,他每一世都死在同一個地方。真正危險的,是那個一格都不填、讓你找不到把柄的人。


先讀地圖,不要先讀程式碼

打開一個大型 repo,第一步不是讀 main,是讀這幾份檔案:README、CONTRIBUTING.md、ARCHITECTURE.md 或 docs/、OWNERS / MAINTAINERS,再把頂層目錄樹掃一遍。你要先回答三個問題:進入點在哪、測試在哪、模組怎麼切。四條賽道各有各的門道:

  • Kubernetes:kubernetes/kubernetes 極大,cmd/ 是各元件進入點、pkg/ 是實作、staging/src/k8s.io/{api,apimachinery,client-go} 是對外發布的函式庫、test/ 放整合測試;工作按 SIG 分工。good first issue 要用機器人指令 /assign 認領,較大的改動要先寫 KEP。
  • Kafka:程式碼在 GitHub,但議題追蹤在 Apache JIRA;找 newbie 或 good first issue 標籤;牽涉公開行為的改動要先在 dev@kafka.apache.org 提 KIP 討論;clients/ 不依賴 core/。
  • DataFusion:apache/datafusion,Rust,程式庫相對小、社群友善,很適合當第一個練手的專案;GitHub issue 上直接找 good first issue,crate 依 sql、expr、optimizer、physical-plan 分。
  • Airflow:apache/airflow,Python;providers/ 底下的 provider 套件是溫和的入口,因為每個 provider 邊界清楚、影響範圍小;核心排程器的改動走 AIP,日常討論在 dev@airflow.apache.org。

四個專案的共通結構是:一份講「怎麼參與」的貢獻指南、一套講「大改動怎麼提案」的流程(KEP / KIP / AIP)、一個標好 good first issue 的議題池、還有一份 OWNERS 或 MAINTAINERS 告訴你「這塊程式碼誰說了算」。先把這四樣東西找齊,你就知道自己站在地圖的哪個位置。

讀結構有幾個固定的著力點。核心模組通常是那個「被最多其他目錄 import、自己卻很少依賴別人」的地方——在 Kafka 是 clients 與 core,在 Airflow 是 airflow-core/src/airflow/models 與排程器。整合測試通常單獨一個頂層目錄(test/、tests/integration/),跑起來慢、要先起服務,跟單元測試分開放。還有一個很多人忽略的訊號:對你想改的檔案跑 git log --follow 跟 git blame。一個檔案最近三個月被十個人改過,代表它是熱區、改動風險高、審查會很嚴;一個檔案兩年沒人碰,可能是穩定,也可能是「大家都不敢碰」——這兩種要用完全不同的心態面對。


認領與溝通:本業是寫小作文

找到一個 issue,先把整串討論從頭讀完,確認它還沒過時、也還沒有人在做,再留言認領:「我想接這個,打算這樣處理……有兩點想先確認。」把該問的問題問在動手之前,不要交了 PR 才來問。

一個 PR 只處理一件事,diff 越小越好。授權簽署因專案而異:Kubernetes 要先簽 CLA(透過 EasyCLA,PR 上的機器人會引導你);有些 CNCF 專案則要求每個 commit 帶 DCO 簽署(git commit -s);Kafka、DataFusion、Airflow 這類 ASF 專案不要求 -s,以 Apache License 2.0 為準——動手前先讀 CONTRIBUTING.md 確認。溝通的基調是非同步、有耐心、用英文、給足上下文——不要私訊去催維護者。維護者的時間是全專案最稀缺的資源,你每一則留言都在花它;把問題想清楚、一次問完、附上你已經查過什麼,就是對這份資源的尊重。

還要提醒一件事:被貼上 good first issue 標籤的,不一定真的適合當你的第一個 issue。標籤常常是幾個月前貼的——問題可能已經被別的 PR 順手解掉、討論串早就吵成一團、或它其實牽動一個大重構只是當初沒看出來。挑的時候看三個訊號:最後一則留言是不是最近的、有沒有維護者明確描述過期望的解法、範圍是不是真的收斂在一兩個檔案內。一個好的第一個 issue,是「維護者已經知道怎麼修、只是沒空動手」的那種。

CONTRIBUTING.md 是最多人跳過、卻最該逐行讀的檔案。它通常寫明了:用哪套 commit message 格式、要簽 CLA 還是 DCO、跑哪個指令做本地檢查、PR 標題有沒有規範、要不要先開 issue 再開 PR。這些規則不是形式主義,是維護者把「要重複講一百次的話」寫下來省時間——你照著做,等於幫他省下第一輪來回。

還有一個很多人忽略的入口:測試就是最好的說明書。想搞懂某個模組怎麼用,先去讀它的測試檔,看它被餵什麼輸入、斷言什麼輸出、mock 掉哪些依賴。這比讀註解可靠,因為測試會被 CI 強制保持正確,而註解不會。你的第一個 PR 如果能「補一個現有功能缺的測試案例」,往往比改功能本身更容易被接受,因為風險低、價值明確。「源來適你」社群的 chia7712 反覆講過一個觀點:貢獻開源其實是副業,committer 的本業是寫小作文——把背景、取捨、你考慮過又放棄的方案講清楚,比多改十行程式碼更能建立信任(他的鐵人賽系列)。


fork 到第一個 PR 的完整路線

真正動手前,先在本機把專案完整編譯、把測試全綠跑過一次——這是你的基準線。沒有綠色基準,你之後根本分不清紅燈是你造成的還是本來就有的。這一步在大型專案往往要花好幾個小時,甚至得先裝一堆工具鏈,但它是省不掉的:一個連本機都建不起來的人,開的 PR 維護者連看都不會看。

PR 開出去之後,review 通常不會馬上來,來了也往往是「Request Changes」。這不是壞事——把每一條意見當成一次免費的、一對一的架構指導。回應時針對每一點說明你改了什麼、或為什麼選擇不改,然後 push 新的 commit(不要 force-push 蓋掉歷史,除非維護者要求 squash)。整個過程可能來回三、五輪,這很正常,一個 junior 的第一個 PR 被要求改十次也很正常。

下面這段 Go 程式碼,是一個很典型的「good first issue」長相:舊的錯誤訊息只說「invalid value」,你把它改成指名欄位、帶上壞掉的輸入,並補一個測試案例。

package config

import (
	"fmt"
	"strconv"
)

// LoadTimeout parses a timeout string. Good-first-issue: the old error just said
// "invalid value"; make it name the field and echo the bad input.
func LoadTimeout(raw string) (int, error) {
	seconds, err := parseSeconds(raw)
	if err != nil {
		// Before: return 0, errors.New("invalid value")
		return 0, fmt.Errorf(
			"config %q: timeout %q must be a positive integer number of seconds: %w",
			"request_timeout", raw, err,
		)
	}
	return seconds, nil
}

// parseSeconds is the existing helper the issue does not touch.
func parseSeconds(raw string) (int, error) {
	n, err := strconv.Atoi(raw)
	if err != nil {
		return 0, err
	}
	if n <= 0 {
		return 0, fmt.Errorf("%d is not positive", n)
	}
	return n, nil
}
package config

import "testing"

func TestLoadTimeout(t *testing.T) {
	cases := []struct {
		name    string
		raw     string
		want    int
		wantErr bool
	}{
		{name: "plain seconds", raw: "30", want: 30},
		{name: "empty string", raw: "", wantErr: true},
		{name: "negative", raw: "-5", wantErr: true}, // new case added by this PR
		{name: "not a number", raw: "30s", wantErr: true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got, err := LoadTimeout(tc.raw)
			if (err != nil) != tc.wantErr {
				t.Fatalf("LoadTimeout(%q) err = %v, wantErr %v", tc.raw, err, tc.wantErr)
			}
			if err == nil && got != tc.want {
				t.Errorf("LoadTimeout(%q) = %d, want %d", tc.raw, got, tc.want)
			}
		})
	}
}

這個例子小到不像個貢獻,但它包含了一個完整 PR 的所有要素:一個明確的行為改變(錯誤訊息變得有用)、對應的測試(新增的 negative 案例證明你想過邊界)、範圍收斂在一個函式內。維護者審這種 PR 三分鐘就夠,合併機率遠高於一個動了五個檔案、還沒附測試的「大改進」。

https://ithelp.ithome.com.tw/upload/images/20260924/20183684S0gxVJoJSA.png


認領之外:讓代理人也蹲在 CI 裡值班

上面畫的整條路線,有一段是人類貢獻者才做得到的——盯著 review 意見、逐條回應、判斷哪些是誤解、哪些是真的漏洞。但「先把地圖看懂、先建立綠色基準」這件事,其實可以有代理人幫忙分攤一部分。Anthropic 官方提供了 Claude Code GitHub Actions:把 Claude Code 接到專案的 CI 流程裡,讓它在 issue 或 PR 上被 @claude 提及時自動介入,讀懂整個 PR 的上下文、跑測試、甚至依照維護者的指示做出小範圍修改後推回同一個 PR。

這跟第一段講的「維護者的時間是全專案最稀缺的資源」是同一件事的延伸:與其讓維護者一輪一輪手動解釋「這裡風格不對」「那裡漏了一個邊界條件」,不如讓這些機械性的來回先由代理人接住一輪,維護者只在真正需要判斷力的地方出手。這不是取代 CONTRIBUTING.md 那套規矩,而是把它自動化成 CI 裡的一道關卡——跟 Kubernetes 用機器人指令 /assign 認領 issue 是同一種思路:把重複性的協作動作交給工具,把人的注意力留給真正需要判斷的地方。


這個答案在牌桌上的後果

三號出局,長桌上少了一隻搶話的狼,但七號的視線沒有跟著離開——她盯著那欄「該做而沒做的事」,其中一格屬於二號:從開局到現在,他沒有一次「先於別人的獨立提議」。她把這件事寫下來,一個字的評語都沒加。我在對面看得心裡發涼,因為我知道那一欄遲早會填滿。

缺席也是證據,這件事在牌桌上跟在 code review 上一樣成立:一個從不主動提案、只在別人開口後附議的貢獻者,帳面上沒有任何錯誤,卻也從沒證明過自己的判斷力。維護者看的正是這個。我們 2N1P 走到這個檢查點才真正明白:整個實戰階段練的不是語法,是「在一堆看起來都合理的發言裡,認出哪一種結構不自然」——這一世村莊之所以能在決定性的白天之前把三隻狼都擺上檯面,靠的就是這個。

三號被請出議事廳的時候,我又想起那個一直甩不掉的感覺:這張長桌、這套規則、這片永遠停在同一個夜晚的天空,都太工整了,工整得像有誰專門造出來、就為了把某個東西關在裡面——而穹頂上那個報時、敲槌的法官,不過是負責看門的人。

讀完今天,你應該能做到:把四大專案任選一個 fork 下來、在本機編譯並跑到全綠、在 issue 列表裡找到並留言認領一個 good first issue、開一個帶完整描述的 PR。先去 Kubernetes 官方 Contribute 指南 看清楚 SIG 分工與認領流程,再照 opensource4you 的 GitHub 找到對應賽道的入口,對著 Developer Certificate of Origin 官方原文 弄懂 git commit -s 簽署的到底是什麼承諾(只有要求 DCO 的專案才需要)。

參考資料與延伸閱讀


¹ 註:本書名為情境設定之虛構文獻,非真實歷史或開源紀錄。


上一篇
Day 23|連線池與 ACID:女巫的藥是有限資源
系列文
狼人自爆的心路歷程:一個「AI人」的30天自學修煉 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言