Categories
程式開發

Uber開源自動刪除舊代碼工具Piranha,支持Java、Swift和Objective-C三種語言


近日,Uber通過官方博客宣布開源了一款可自動刪除舊代碼的工具Piranha,該工具已經支持Objective-C、Swift和Java三種編程語言。

開源Piranha:自動刪除舊代碼

長期以來,Uber會為不同用戶提供不同的功能選擇,也就是定制化開發。但是,如果某項功能經過驗證後證明並不成功,這些代碼放在代碼庫裡就造成了技術負擔,可能使應用程序變得臃腫、冗雜。一些陳舊的代碼還可能會帶來不必要的風險,影響終端用戶體驗,對工程師而言,這些技術債的消除耗時耗力,還會影響他們開發新功能。

為了使刪除舊代碼的過程自動化,Uber開源了一款可以掃描源代碼並刪除其中過時舊代碼的工具Piranha(食人魚)。 Piranha可以在Uber Android和iOS代碼庫中運行,Uber已使用它刪除了大約2000行過時代碼。據介紹,該工具目前支持Objective-C、Swift和Java三種編程語言。

項目地址:https://github.com/uber/piranha

如何定位需要刪除的代碼?

為了方便定位,Uber開發的程序中存在各種各樣的標誌,開發人員在Uber的標誌管理系統中創建一個條目,並輸入屬性,例如標誌的名稱、類型、目標推出百分比、目標平台以及標誌可操作的地理位置。此外,該標誌是人工引入到源代碼中的,所以實驗平台上的標誌可與移動應用程序之間建立一致關聯。隨後,此標誌就可以用來管理應用程序的行為。

從正在運行的應用程序的角度來看,功能標誌是單個鍵,映射到兩個或多個條件,例如開/關、顏色值、大小和復製文本。在啟動時,應用程序先查詢標誌管理系統,並為應用的每個標誌檢索特定處理條件,返回的值決定了應用程序中功能的存在和行為。

當逐步推出單個功能時,我們有一個控制條件(未啟用該功能)和一個處理條件(已啟用該功能)。我們傾向於首先將處理條件應用於小部分用戶,如果推廣成功,則逐漸將應用擴展到所有相關用戶(例如,每個在特定地理位置上的人)。如果在發布期間出現問題,我們可以停止並收回該功能,盡可能減小對用戶的影響。

該系統還可以處理同一功能的各種不同實現,例如嘗試在不同的用戶組上測試不同的接口(例如A / B測試)。

最終,我們希望向全球所有用戶推廣這些功能。有時,我們希望保留某些功能標記使代碼中的功能可以正常使用,一旦應用出現問題,我們可以迅速通過功能標記控制應用狀態,這樣就不會造成整個應用程序癱瘓。但是,由於大多數功能嵌套在其他功能下,這種預留的切斷開關還是無法終止大多數功能標誌。

自動刪除與過時標誌相關的代碼

當標誌過時,我們需要在功能標誌管理系統中將其禁用,並從源代碼中刪除與該標誌有關的所有代碼,包括目前無法實現的該功能的替代版本,這樣能在很大程度上避免技術負擔。

但是,這麼簡單的清理步驟往往會被很多開發者忽略,從而留下技術負擔,這些不必要的代碼會在多個維度上影響軟件開發。

為了解決技術債務問題,Uber設計並實現了Piranha。 Piranha原意為“食人魚”,這個名字是根據工具本身特徵而得來的。 Piranha分析了抽象語法數(AST)的程序以生成適當的重構,並將其打包到diff中。將diff分配給標誌的作者以供進一步檢查,作者可以按原版進行實施(提交至master),或在實施之前執行任何其他重構。我們還圍繞Piranha構建了工作流程,以使其能以可配置的方式定期刪除過時代碼。

功能標記示例

讓我們來看一個簡單的示例,該示例說明了Uber源代碼中功能標誌的基本用法。

最初,我們在RidesExpName中的標誌列表中定義一個名為RIDES_NEW_FEATURE的新標誌,並將其註冊到標誌管理系統中。隨後,我們使用功能標誌API isTreated將標誌寫入代碼,並分別在if和else分支下提供處理/控制行為的實現:

public enum RidesExpName implements ExpName {
   RIDES_NEW_FEATURE,
   …
}
if (experiments.isTreated(RIDES_NEW_FEATURE)) {
     // implementation for treatment (on) behavior
} else {
     // implementation for control (off) behavior
}

為了使用各種標誌值測試代碼,對於每個單元測試,我們可以添加註釋以指定功能標誌的值。下面,當所選擇的標誌處於已處理狀態時,運行test_new_feature:

@Test
@RidesExpTest(treated=RidesExpName.RIDES_NEW_FEATURE)
public void test_new_feature() {
   …
}

當RIDES_NEW_FEATURE失效時,所有與之相關的代碼都需要從代碼庫中刪除。這包括:

  1. RidesExpName中的定義;
  2. isTreated API中的使用;
  3. @RidesExpTest.註釋

此外,else-branch的內容、目前無法實現的控制行為的實現都必須刪除。我們還希望刪除與那些已經刪除行為相關的所有測試代碼。不刪除這些代碼會逐漸增加源代碼的複雜性,使整體系統更難維護。

自動化挑戰

自動檢測過時標誌並刪除關聯代碼,這項技術目前還存在一定困難,這一過程中需要確定這些標誌是否被人使用了以及哪些人擁有標誌,再到其代碼編寫的細節都是很複雜的問題。因此,克服這些挑戰是Piranha發展的關鍵。

使用靜態分析構建Piranha

考慮到Piranha的應用背景,我們設想可以通過應用靜態分析來刪除因過時標誌遺留下來的廢舊代碼。

我們確定了清理的三個關鍵維度:

  • 刪除緊鄰功能標誌API的代碼。
  • 刪除由於執行上一步而無法訪問的代碼,我們將此稱為深度清潔。
  • 刪除與功能標誌有關的測試代碼。

我們根據在代碼庫中觀察到的編碼模式,選擇了一種迭代設計技術的實用方法。

我們觀察到了三種標誌API:

  • 返回布爾值的布爾型API ,用於確定執行所採用的控制路徑。
  • 更新API ,用於更新正在運行的系統中的功能標誌值。
  • 返回非布爾值原始值(整數、雙精度等)的參數API ,該值與從後端控制的實驗值相對應。

重構技術解析輸入源代碼的AST,以檢測使用功能標誌API的存在。對於布爾API,我們簡化布爾表達式。如果結果值是布爾常量,我們將適當地重構代碼。例如,如果布爾API作為if 語句的一部分出現並返回為true,我們將通過刪除整個if 語句,然後將其替換為then語句來重構代碼。

如果更新API,我們只需刪除相應的語句。我們不處理參數API,因為解決它們所需的工程工作量很大,而它們在代碼庫中出現的頻率卻低得多。

由於我們觀察到布爾API不一定要在條件語句中使用,因此我們為重構設計了第二條路徑。我們確定右側是布爾型API(Piranha已將其簡化為常量)的分配,並跟踪被分配值的變量。同樣,我們跟踪返回一個布爾API的wrapper方法,該API簡化為常量。隨後,我們確定使用被分配值變量或條件語句中的wrapper方法,以執行重構。

最後,如果標記註釋與輸入處理行為匹配,我們只需刪除測試的註釋,如果不匹配,則要丟棄整個測試來處理標記註釋測試。

Piranha在Uber的應用實踐

我們實現了Piranha在Objective-C、Swift和Java程序中的重構。 PiranhaJava能重構Java應用程序中與過時的功能標誌相關的代碼,尤其是針對Android平台的代碼。它在Java的Error Error Prone上作為Error Error Prone插件實現。 PiranhaSwift使用SwiftSyntax在Swift中實現,用於重構Swift代碼。 PiranhaObjC用於清理Objective-C程序中的代碼,並在C ++中作為Clang插件實現,內部使用AST匹配器和重寫器來解析和重寫AST。

儘管Piranha作為獨立工具執行代碼重構任務,但是開發人員總是想不起及時清理代碼,因此它的使用頻率並不算高。正如Piranha自動化標記清除一樣,我們需要一個系統來自動啟動這些清除。

在Uber,我們建立了工作流pipeline,該pipeline定期生成差異和任務以清除陳舊的功能標誌。 Piranha pipeline在標誌管理系統中查詢陳舊標誌列表,並且對於這些標誌中的每個標誌,分別啟用Piranha,輸入陳舊標誌的名稱、其所有人以及預期的輸出行為(處理或控制)。

Uber開源自動刪除舊代碼工具Piranha,支持Java、Swift和Objective-C三種語言 1

圖注:在我們的Piranha工作流中,標誌pipeline系統定期將可能過時的標誌列表發送給Piranha,Piranha會生成一個差異並將其發送給原始標誌作者。然後作者可以確定是否要放置差異。

上圖展示了Piranha工作流pipeline架構圖。 Piranha生成一個diff (即拉取請求),並將其放入代碼審閱系統中,該標誌的原始作者為默認審閱者。作者可以接受diff ,或者根據需要對其進行修改,也可以拒絕修改並將該標誌標記為不過期。 pipeline還在任務管理系統中生成了一個清理任務,以跟踪每個生成diff 的狀態。由於開發人員可能無法及時發現問題,因此我們還引入了一個名為PiranhaTidy的提醒機器人,定期添加打開Piranha相關任務的提醒。我們觀察到,目前使用Piranha自動生成diff 的時間不超過3分鐘。

使用Piranha刪除代碼

我們很高興地宣布,Piranha支持三種語言,包括Java、Swift和Objective-C。如想要使用Piranha,代碼需滿足以下條件:

  • 廣泛使用功能標誌
  • 具有特定的API以控制功能標誌的行為
  • 用Java、Swift或Objective-C實現

項目地址:
https://github.com/uber/piranha

原文鏈接:
https://eng.uber.com/piranha/

作者介紹

Murali Krishna Ramanathan
Murali Krishna Ramanathan是Uber的編程系統研究科學家。他目前致力於構建程序分析工具,以提高開發人員的生產率。

Lazaro Clapp
Lazaro Clapp是Uber編程系統團隊的高級工程師,他主要在為Java應用程序開發靜態分析工具。

Rajkishore Barik
Rajkishore Barik是Uber編程系統團隊的編程系統研究科學家和技術經理。他目前致力於構建工具來理解數據中心的性能異常,包括為Swift和Go開發靜態分析和轉換工具。

Manu Sridharan
Manu Sridharan曾是Uber的一名工程師,現在是加利福尼亞大學河濱分校計算機科學與工程副教授。