揭密 Google的「死神」專案!啟用機器人大規模刪除二十多年累積下來數十億行中的無用程式碼

揭密 Google的「死神」專案!啟用機器人大規模刪除二十多年累積下來數十億行中的無用程式碼

ADVERTISEMENT

任何大型專案都一定會積累下「死碼」(Dead Code),也就是那些不再使用的模組,或者在早期開發期間存在但已經多年沒跑過的程式。事實上,很多專案在創建完成後都會先運行一段時間,之後再也無人問津。 

這些死碼會持續產生成本:自動化測試系統並不知道哪些程式碼不需要再測試,負責大規模清理的人們也會把很多不再運行的程式碼移來挪去。所以雖然這些程式碼的生產成本很高,但它同時也需要耗費大量時間加以維護。這類維護工作不能輕易跳過,否則未來就一定會造成更大的回溯管理成本。 

那麼,能不能靠削減程式碼量來降低維護成本?程式碼庫裡的內容真的都有存在的必要嗎? 

Google的「死神」專案 

我們通常不清理程式碼,清理它們需要大量的時間和精力,證明其到底還有沒有用更是一件麻煩事:我們不能只靠「Chesterton's fence」法則,就是「看不出這個有什麼用,那就讓我們把它清除掉」, 因為有一些災難警報、閏年觸發程式碼閒置時間更長,如果被清除了就有可能帶來大麻煩。 

在Google裡,程式碼的清除更為困難。 

Google跟業界其他公司不同,它只有一個程式碼庫,全公司的程式碼都放在這個庫裡,二十多年來,上萬名軟體工程師為同一個包含數十億行的程式碼庫提交貢獻。這套程式碼庫存儲在 Piper 系統當中,與編碼相關的共用庫原始碼、生產服務、實驗程式、診斷和調試工具等一切都被集中在這裡。 

這種開放方法極為強大。如果工程師不確定如何使用某個庫,可以通過搜尋找到示例;好心的貢獻者還可以對整個程式碼庫做重要更新,包括轉向更新的 API、引入 Python 3 或 Go 泛型等語言特性等。 

編寫程式碼對應著極高的成本,所以程式碼往往被企業視為重要資產。然而,不再使用的程式碼也會在維護和清理等層面持續耗費時間和精力。一旦程式碼庫達到一定規模,投入工程時間來做自動化清理就開始具有現實意義,特別是像Google這樣擁有數十億行程式碼的情況下。

揭密 Google的「死神」專案!啟用機器人大規模刪除二十多年累積下來數十億行中的無用程式碼

但在這樣的單一程式碼庫的條件下,最壞最壞的情況就是不小心刪掉了「原始碼」,Google SRE 首席軟體工程師說,這種情況「意味著Google使用的每個資料中心、每個工作站都會突然停止運行——不僅僅是關閉,甚至連存儲都無法使用。(雖然只有在世界末日時才會發生)。」 

那麼,他們是怎麼清理這些死碼的?Google最近在其部落格中介紹了 Sesenmann「自動刪除程式碼」專案,該專案的目標是自動辨識出無效程式碼,再發送程式碼審查請求(變更列表)以將其刪除。 

Sesenmann 在德語中代表「死神」的無情收割之義。據Google介紹,該專案非常成功,每週可提交超過 1000 個待刪除的變更列表,而且截至目前已經刪除了Google全部 C++程式碼中的 5%。 

如何判斷哪些程式碼能刪? 

Google的構建系統 Blaze(即 Bazel 的內部版本)是達成這個目標的關鍵:它會以一致且可造訪的方式表示二進位目標、庫、測試和原始檔案之間的依賴關係,幫助維護者據此建立起依賴關係圖。如此一來,大家就能找到未連結至任何二進位檔案的庫,並將其作為潛在的刪除對象。 

但這還只是問題的一小部分:那些二進位檔案又該如何處理?所有一次性資料移轉程式和已經被棄用的系統診斷工具呢?如果不把它們清理掉,相對應的各個依賴庫也將被保留下來。 

瞭解程式是否有用的唯一完美方法,就是檢查它們是否正在運行。所以對於內部二進位檔案(即運行在Google資料中心或員工工作站上的程式),程式在運行時會寫入一個日誌條目,記錄下時間和對應的特定二進位檔案。透過匯總,得到Google內部所使用的各個二進位檔案的活躍度訊號。如果一個程式很長時間都沒有被用到,該專案就會嘗試發送相應的刪除變更列表。 

當然,其中也有例外:某些程式碼僅僅是 API 的使用示例;有些程式的運行位置根本就沒有對應的日誌訊號。對於凡此種種的各類情況,貿然刪除程式碼一定會惹出大麻煩。有鑑於此,建立一套阻止遮罩清單系統就非常重要,可供大家標記異常,避免用虛假的變更列表打擾到已經忙碌不堪的軟體工程師。 

細節決定成敗

在Google的部落格上,Google的工程師 Phil Norman 舉了一個簡單的例子。 

假定有兩個二進位檔案,它們各自依賴於不同的庫,另外還同時共用第三個庫。忽略原始檔案和其他依賴項的話,我們將這種關係繪製成以下結構:

揭密 Google的「死神」專案!啟用機器人大規模刪除二十多年累積下來數十億行中的無用程式碼

假如 main1 正在使用,但 main2 的最後一次使用卻是在一年多之前,那就可以構建起樹狀傳播活動訊號將 main1 及其依賴的所有內容均標記為活動。餘下的部分則可以去掉;由於 main2 依賴於 lib2,所以這次我們希望在一次變更中同時刪除這兩個目標:

揭密 Google的「死神」專案!啟用機器人大規模刪除二十多年累積下來數十億行中的無用程式碼

到目前為止一切順利,但真正的生產程式碼需要經過單元測試,其構建目標由測試的庫決定。這就讓整個遍歷結構變得更加複雜:

揭密 Google的「死神」專案!啟用機器人大規模刪除二十多年累積下來數十億行中的無用程式碼

 

測試基礎設施會運行所有測試,包括 lib2_test,可是 lib2 從未被「真正」執行過。也就是說,我們不能單純將測試運行作為「活躍度」訊號:在這種情況下,可以誤以為 lib2_test 保持活動,並導致 lib2 永遠存在。只能清理未經測試的程式碼,而這會嚴重阻礙清理工作的有效進行。

根本目標是讓每個測試都能共用所測試庫的使用情況,所以我們可以讓庫和測試相互依賴來達成這個目標,據此在圖中創建迴圈:

揭密 Google的「死神」專案!啟用機器人大規模刪除二十多年累積下來數十億行中的無用程式碼

 

這樣就將各個庫及其測試轉化成了強連接元件,可以使用與以往相同的方法標記出「活」節點,之後尋找有待刪除的「死」節點集合。區別在於這次使用了 Tarjan 強連通分量演算法來處理迴圈。

這樣做很簡單,但前提是能輕鬆看出測試及所測庫之間的關係。遺憾的是,情況並不總是這麼樂觀。在以上示例中,由於遵循簡單的命名約定,所以大家能將測試與庫快速匹配起來。但這種方法在實際生產系統中往往並不奏效。

比如以下兩種情況:

揭密 Google的「死神」專案!啟用機器人大規模刪除二十多年累積下來數十億行中的無用程式碼

左邊的是 LZW 壓縮演算法實現,分別存在單獨的壓縮器和解壓縮器庫。該測試實際上是對兩者都進行測試,以確保資料在壓縮和解壓縮後不致損壞。在右側,web_test 負責測試 Web 伺服器庫,它使用 URL 編碼器庫來提供支援,但實際上並不會測試 URL 編碼器本身。這就希望將左側的 LZW 測試和兩個 LZW 庫視為同一連接元件,而在右側則希望排除掉 URL 編碼器,只將 web_test 和 web_lib 視為連接元件。儘管需要不同的處理方式,但這兩種情況的基本結構是相同的。在實踐當中,可以建議工程師將 url_encoder_lib 之類的庫標記為「純供測試」(即僅用於支援單元測試),這樣就能解決 web-test 需求。 

除此之外,Phil 表示目前Google的方法是使用測試和庫名稱之間的編輯距離來選擇最可能與給定測試相匹配的庫。至於如何辨識 LZW 這類一項測試對應兩個庫的情況,這可能需要涉及測試覆蓋率資料,Google並沒有討論這類方法。 

如何消除抵觸情緒? 

自動程式碼刪除對很多工程師來說可能是個陌生的概念,就如同 20 年前單元測試剛剛誕生一樣,那時候很多人對此也抱有抵觸態度。 

雖然刪除死碼最終會給軟體工程師自己帶來助益,大家也一定希望管理的程式碼專案能夠保持整潔,但「Sesenmann」運行過程中,Google也發現很多工程師並不願意經常收到用於刪除程式碼的自動變更列表。這就是專案當中社會工程的部分了,而且重要程度絲毫不亞於軟體工程。 

改變人們的想法需要時間和努力,更需要大量細緻的溝通。Sensenmann 的溝通策略主要分三個部分。 

最重要的就是變更描述,這也是審查人員首先看到的內容。變更描述必須簡明扼要,同時又保證能為審查人員提供充分的背景資訊以做出正確判斷。這樣的平衡其實很難達到:內容太短,很多人會找不到自己需要的資訊;內容太長,則可能導致滿屏文字令人頭痛。事實證明,如果能附上標注清晰的支援文檔和常見問題解答連結,會大大提高變更描述的易讀性和接納度。 

第二部分則是配套檔,這裡同樣要使用簡潔明瞭的措辭和良好的導航結構。不同的人需要不同的資訊:有些人需要保證原始碼控制系統中的刪除可以回滾,有些人希望瞭解要如何處理變更造成的負面影響,例如修復對構建系統的誤用。通過認真思考和反覆運算使用者回饋,支援文檔將成為滿足這些需求的寶貴資源。 

第三部分是處理使用者回饋。有時候,這可能也是最困難的部分:由於負面回饋多於正面回饋,往往就需要冷靜的頭腦甚至是不少「外交手腕」。總之,務必牢記這些回饋總體上反映出系統改進的最佳方式,儘量讓使用者更滿意、避免未來繼續出現類似的負面回饋。 

寫在最後 

Phil 在Google部落格中講道,以Google的業務規模出發,初估自動刪除程式碼已經為他們帶來了數十倍的投入回報,大大節約了維護成本。 

自動刪除程式碼需要解決技術和文化這兩大難題。在部落格中,他總結道,「雖然我們已經在這兩個領域取得了顯著進展,但仍不能說徹底解決。不過隨著改進的繼續,自動刪除的接納度會越來越高,產生的積極影響也將越來越大。這筆投資的價值因人而異,如果您也掌握著一個巨大的單體程式碼庫,那不妨認真考慮一下。至少在Google,將 C++程式碼總量的維護負擔降低 5%已經標誌著一場巨大的勝利。」 

如果刪除程式碼也能帶來巨大的收益,那是否意味著是時候為刪除程式碼行設定 KPI 了? 

參考連結:

InfoQ
作者

InfoQ 是一家全球性社群網站,基於實踐者驅動的社群模式建立。軟體正在改變世界。促進軟體開發及相關領域知識與創新的傳播是我們的使命。

使用 Facebook 留言
發表回應
謹慎發言,尊重彼此。按此展開留言規則