為複雜專案做出貢獻
作為一位經常維護與貢獻開源專案的人,我常被問到:你從哪裡開始?你如何以做出有意義的改動為目標來接觸一個新專案?你又如何能夠理解一個複雜專案的內部運作?
這些問題適用於任何軟體專案,無論是開源還是專有、業餘還是專業。我採取的方法在任何情況下都相同。不過,專業工作有一個關鍵差異:你可以直接接觸到願意——甚至有義務——幫助你的其他工程師,而在開源專案中,你大多只能靠自己。
我已經建立了一套反覆用來接觸複雜專案的模式,並在這篇文章中記錄下來。我不指望這個模式對每個人都適用,但希望它能幫助他人建立信心,去嘗試學習並參與複雜專案。
我用複雜專案這個詞來描述任何實作無法被輕易理解的軟體專案。這個定義是主觀的;有些人認為是複雜的專案,其他人可能不這麼認為,反之亦然。
步驟一:成為使用者
理解任何專案內部運作的第一步,就是成為該專案的使用者。你不必成為專家級的使用者,但我個人對這個階段的結業標準是,嘗試用這個專案打造一個真實的作品,即使它很小或很簡單。舉例來說,在對Zig 程式語言做出貢獻之前,我建立了數個 真實的 函式 庫。
作為使用者,你將對專案的能力有廣泛的理解。閱讀參考文件與實際使用專案之間有著明顯的差異,而建立玩具專案是彌合從理論到實務理解差距的重要一步。
此外,你將開始學習專案的慣用語,這些慣用語構成了文化的底蘊,有助於引導理解專案為何以這樣的方式運作、為何具備這些功能等等。這很重要,因為它有助於培養對專案中其他人的同理心,也能作為判斷哪些改動可能適合或不適合該專案的指引。
在這個階段,我也強烈建議加入社群。加入 IRC 或 Discord、參加在地聚會、觀看演講等等。花一段時間多聽少說。這裡的目標是培養同理心並了解專案的運作方式。我總是很驚訝,光是觀察他人學習,就能學到這麼多。
步驟二:建置專案
學會如何建置專案並取得可運行的二進位檔(或同等產物)。不用費心去理解建置系統、相依套件等等。只要跟著指南、網站上的說明依樣畫葫蘆,想辦法讓你在自己的系統上能夠穩定且反覆地從原始碼產生出可執行的二進位檔就好。
在學會建置專案之前,不要閱讀程式碼。我太常看到人們在學會建置專案之前,就陷於試圖理解專案原始碼的泥沼。對我而言,學習過程的一部分是實驗與破壞,而如果無法建置軟體專案,就很難進行實驗與破壞。
不用擔心功能完整的建置。複雜專案通常有些功能只有在具備正確的相依套件、正確的系統、正確的設定等情況下才能使用。如果是這種情況,完全不用擔心。目標是在你的系統上取得一個堪用的二進位檔。隨著你在後續步驟中累積經驗與信心,你會開始有能力去追求更完整功能的建置。
在這個階段,我也建議學習如何執行測試套件並讓它通過。這會讓你在後續步驟中更容易進行實驗與破壞。複雜專案通常也有複雜的測試套件,所以有時候這意味著只讓測試套件的子集跑起來——只要足以讓你進行實驗就好。
步驟三:學習核心路徑的內部運作
為了學習內部運作,我喜歡使用一種我稱為「向下追蹤、向上學習」的方法。
向下追蹤
我從一個功能或使用情境開始,由外而內追蹤該功能所遵循的程式碼路徑。在這個過程中,我會記下所經過的檔案、行數與函式,但還不會嘗試去理解任何東西是如何運作的。這就是「向下追蹤」階段。
舉例來說,在研究 Zig 編譯器時,我從追蹤zig build-exe這個用來從 Zig 原始碼建置執行檔的指令開始。這個追蹤過程讓我找到了zig CLI 的原始碼、build-exe子指令,進而進入「Compilation」子系統,該子系統接著會呼叫詞法分析器、剖析器等。我除了追蹤路徑所需之外,並未閱讀實作細節。
從這些追蹤筆記中,你通常可以得到某個功能如何運作的「全貌」。根據檔名、函式等,你通常可以開始辨識專案的主要子系統。這有助於之後將學習過程切分成更合理、更容易掌握的區塊。
不要試圖學會所有東西。我常見的一個錯誤是,人們試圖逐行讀完整個專案,結果花上數週或數月,最終感到氣餒。保持專注,一次只學習一個功能。
提示:在挑選功能時,選擇一個你作為使用者已經熟悉的功能。此外,如果可能的話,盡量挑一個表面上看起來很簡單的功能。舉例來說,我為了學習編譯器而嘗試追蹤的第一個 Zig 程式,是一個只做兩數相加且零輸出的程式。
向上學習
追蹤完一個功能後,就該真正學習各個對應子系統是如何運作的。與追蹤階段從最外層(如 CLI 或 API 呼叫)開始不同,在學習階段,我傾向於從最內層開始。
我選擇從最內層開始,是因為它通常是最基礎且最少抽象化的。隨著你往上層移動,往往伴隨著抽象程度的提升,如果不了解組成元件,就更難學習。
要開始學習特定的子系統,我會遞迴地「向下追蹤、向上學習」。我會先檢視公開、對外暴露的 API 介面,然後學習每個 API 呼叫是如何運作的。這是上層如何使用這個子系統的方式,因此它既能為我的學習提供指引,也能在我往上層移動時讓一切更為清晰。
實驗與破壞
在「向下追蹤、向上學習」的過程中,我發現透過實驗與破壞來學習某個東西如何運作非常有幫助。這就是為什麼在嘗試閱讀內部程式碼之前,先學會如何建置專案是如此關鍵。
加入新的日誌陳述式、實作一小塊新功能、修改現有功能等,然後重新建置專案,看看會發生什麼事。這也是真正檢驗你對某個東西如何運作的理解程度的好方法。
舉例來說,在學習 Zig 詞法分析器時,我加入了新的詞記(token),看到它們能被詞法分析,但接著看到剖析器失敗了。當我進入下一個系統(剖析器)時,我讓我的新詞記真正發揮作用。依此類推。
搭配媒體資源輔助
在整個階段中,用任何可取得的媒體資源來輔助深入的程式碼探索:書籍、影片、部落格文章等。如果有涵蓋內部運作的現有文獻,就去讀它!
然而,你不該期望單靠這些資料就能達到專家程度。與步驟一「成為使用者」類似,在嘗試「成為維護者」時,沒有任何補充資料可以取代親自動手把玩實際原始碼。這是理論與實務應用的另一個例子。
提示:如果不存在學習內部運作的資源,試著自己寫一份吧!我在 Zig 上就是這麼做的,因為找不到類似且最新的資源,所以寫了關於Zig 編譯器內部運作的文章。書寫某個主題是強化學習的好方法,也能幫助未來的貢獻者。
步驟四:閱讀並重現近期的提交
作為學習內部運作的最後一步,我會閱讀與我研究過的子系統相關的近期提交,並檢驗自己是否完全理解為何要做出該變更。這是我「做教科書後面習題」的學習部分。
我會查看專案的提交紀錄,或是與我研究過的子系統相關的特定檔案或資料夾的提交紀錄。然後,我會先研究解法(提交中的變更)或者先看它修復的錯誤,嘗試自己修復,看看是否能得出類似的解法。
為了「實際解題」,我會將儲存庫簽出到我正在研究的提交的前一個提交。我會重現它所修復的錯誤(如果是錯誤修正),然後嘗試自己實作解法。最後,我會將自己的成果與維護者或貢獻者所做的提交進行比較。
我給自己的唯一提示是所需的變更規模(VCS diff 上的+/-行數)。我建議一開始避免需要超過 50 到 100 行變更的提交。
步驟五:做出小而精的改動
我喜歡從小處著手,逐步承擔越來越大的任務。在這個階段,你已經理解了專案的技術層面,現在是時候了解人的層面了。這裡的目標是做出一個小改動,並學習貢獻與審查的流程。
最困難的部分通常是找到一個可以做的小改動。這方面沒有萬靈丹。我會瀏覽議題,尋找看起來對新貢獻者友善的項目。我通常會經歷幾次錯誤的嘗試,或完全放棄某個議題再嘗試其他議題。最終,我會找到一個。尋找議題或甚至修復議題所花的時間可能會長到令人沮喪,但這是入場的代價。專案通常會有「對新貢獻者友善」的標籤來引導新貢獻者。
如今大多數專案都會妥善記錄其貢獻流程,所以一旦實作好改動,就請完全按照流程進行。如果你已在較早的步驟中加入了社群,這是個好機會去詢問某人是否能提供幫助,或請人幫你再次確認流程。
舉例來說,我對 Zig 的第一個貢獻是一個三行的改動,花了我大約四到五個小時、橫跨兩個晚上才完成(不包括先前步驟所投入的時間)。如果這個錯誤今天再次出現,我幾分鐘內就能修好,但要達到那種熟練程度是需要時間的。
成功
到此為止,你已經學會了一個複雜專案並成功做出了貢獻!
不要害怕複雜。我認為太多工程師將那些刻板印象中的複雜專案——例如程式語言、瀏覽器、資料庫等——視為魔法,或認為那是注定屬於更高層次的人的領域。我喜歡提醒自己,所有的專案都是由其他人類開始的。如果他們做得到,我也做得到。你也一樣做得到。
我希望透過分享我的過程,能讓其他人覺得複雜專案更容易親近。
隨機一篇部落格