首頁 > 易卦

又一巨頭從 Java 遷移到 Kotlin:關鍵應用全部開始切換

作者:由 InfoQ 發表于 易卦日期:2023-01-26

線纜標籤怎麼打

又一巨頭從 Java 遷移到 Kotlin:關鍵應用全部開始切換

Facebook 母公司 Meta 正在將其 Android 應用的 Java 程式碼遷移到 Kotlin。根據 Meta 的官方部落格

所述

截至今天,其 Android 程式碼庫已經有超過 1000 萬行 Kotlin 程式碼

,旗下包括 Facebook、Instagram、Messenger、Portal 和 Quest 在內的應用都已經開始從 Java 轉向 Kotlin。

將程式碼庫轉換為 Kotlin

Kotlin 是一種更年輕的程式語言,也依賴於 Java 虛擬機器。Kotlin 由軟體工具製造商 JetBrains 建立,於 2011 年首次亮相,2016 年釋出 1。0 版本。次年,它被 Google 採用為 Android 開發的一級語言,並由其基金會管理,該基金會由 JetBrains 和 Google 資助。

到 2019 的 Google I/O 大會,Google 正式

宣佈

,Kotlin 程式語言已成為 Android 應用開發人員的首選語言,並在當年年底表示前 1000 個 Android 應用程式中有近 60% 包含 Kotlin 程式碼。

從 Google 自身來看,明面上它說自己選擇 Kotlin 的理由是它更簡潔、更安全、支援結構化併發,能更輕鬆地編寫非同步程式碼,並且可以與 Java 互操作。不過,另一個業界推測是可能跟那宗與 Oracle 曠日持久的 Java 侵權案有關—— Oracle 花了十多年的時間追究 Google 在 Android 中使用 Java API 的侵權索賠,最終 Oracle 敗訴。

回到 Meta,Facebook 軟體工程師 Omer Strulovich 對選擇 Kotlin 如此解釋道:“Kotlin 通常被認為是一種比 Java 更好的語言,在年度 Stack Overflow 開發人員調查中,

其受歡迎程度高於 Java

,”他還指出,由於近年來 Kotlin 已成為 Android 開發的流行語言,“因此,在努力使我們的開發工作流程更加高效的過程中,我們在 Meta 的安卓開發中轉向 Kotlin 是非常合理的……”

除了受歡迎之外,Meta 認為 Kotlin 擁有的主要優勢包括可空性、函數語言程式設計、程式碼更短、以及領域特定語言(DSL)等等。

不過,Strulovich 指出,過渡到 Kotlin 也有一些不可忽視的缺點,比如混合程式碼庫可能難以維護,以及 Kotlin 雖然流行,但與 Java 相比還是有比較大的差距,工具集還不夠成熟。所有 Kotlin 工具都需要考慮 Kotlin 和 Java 的互操作性,這使得它們的實現變得複雜。

但 Meta 最大的擔憂還是構建時間。“我們從一開始就知道 Kotlin 的構建時間會比 Java 的要長。該語言及其生態系統更加複雜,Java 在最佳化其編譯器方面領先了 20 年。由於我們擁有多個大型應用程式,較長的構建時間可能會對我們的開發人員體驗產生負面影響。”

為什麼不只用 Kotlin 來寫新程式碼

Strulovich 沒有透露 Meta 何時開始這種轉變。Meta 本來可以選擇只用 Kotlin 編寫新程式碼,但它最終還是決定將所有的 Android 應用程式都轉換過來。

根據 Strulovich 的說法,如果是隻使用 Kotlin 來編寫新程式碼,繼續保留大部分現有 Java 程式碼的話,工作量明顯更低,但相應的也有兩個缺點:首先就是要在 Kotlin 和 Java 程式碼之間實現互操作性,就需要引入 Kotlin 中的 platform 型別。Platform 型別會導致執行時中的空指標取消引用,進而引發崩潰,這就破壞了純 Kotlin 程式碼提供的靜態安全優勢。在某些複雜情況下,Kotlin 的空檢查省略可能會漏掉空值,意外引發空指標異常。例如,如果 Kotlin 程式碼呼叫由 Java 介面實現的 Kotlin 介面,就會發生這種情況。其他的問題還包括 Java 無法將型別引數標記為可空(最近才剛剛修復);Kotlin 的過載規則考慮到了可空性,Java 的過載規則卻沒有考慮到。

第二個缺點是,這種方式要求對 Meta 已經開發的大多數軟體進行程式碼修改。如果繼續把大部分程式碼保留為 Java 形式,那開發人員就沒法充分發揮 Kotlin 的優勢。

Kotlin 遷移大法

如今,Meta 旗下的 Android 版 Facebook、Messenger 和 Instagram 應用都擁有超過百萬行 Kotlin 程式碼,而且轉換率也一路走高。縱觀整個 Android 程式碼庫,其中的 Kotlin 程式碼量已經超過千萬行。

起步階段

事實上,在嘗試為現有應用程式引入 Kotlin 時,Meta 遇到了不少麻煩。例如,團隊得更新 Redex 才能支援 Java 無法生成的位元組碼模式。另外,其使用的某些內部庫要求在編譯期間進行位元組碼轉換來獲取更好的效能。而在將這些庫納入 Kotlin 編譯過程時,這部分程式碼無法正常起效。為此,Meta 針對這些問題構建了專門的解決工具。

Meta 還發現,現有工具之間存在不少衝突。例如,程式碼審查和 wiki 工具無法對 Kotlin 語法進行高亮顯示。“我們還更新了之前使用的 Pygments 庫,確保其體驗與處理 Java 程式碼時一致。我們更新了一些內部程式碼修改工具,使其能夠支援 Kotlin。我們也構建了 Ktfmt,一款基於 google-java-format 編碼理念的確定性 Kotlin 格式化程式。”

遷移加速階段

在工具準備齊全之後,Meta 現在已經能將程式碼中的任意部分轉換為 Kotlin。但每次遷移都需要大量樣板設計工作,只能由員工們手動完成。J2K 是一種通用工具,並不會去理解所轉換的程式碼是在表達什麼。因此,某些特定部分就只能進行手動調整。

最典型的例子就是 Junit 測試規則的使用。假設使用 ExpectedException 規則,來驗證是否丟擲了正確的異常:

@Rule public ExpectedException expectedException = ExpectedException。none();

複製程式碼

當 J2K 將這部分程式碼轉換成 Kotlin 時,得到的就是:

@Rule var expectedException = ExpectedException。none()

複製程式碼

這段程式碼乍看之下與原先的 Java 程式碼等價,但由於 Kotlin 使用了 site 註解,所以其實際上等價於:

@Rule

private

ExpectedException expectedException = ExpectedException。none();

public

ExpectedException getExpectedException() {

return

expectedException

}

複製程式碼

嘗試執行後,此測試會失敗並返回一個錯誤:“The @Rule expectedException must be public”,這是因為 Junit 發現了一條帶有 @Rule 註解的私有欄位。這是個常見問題,論壇上面也已經有成熟答案:要麼在欄位中新增“@JvmField”;要麼在註解中添加註解 use-site,也就是“@get:Rule”:

// 方案一:使用“get”作為註解的use-site

@get:Rule

var

expectedException = ExpectedException。none()

// 方案二:只為沒有getter的Java欄位生成JVM程式碼

@JvmField

@Rule

var

expectedException = ExpectedException。none()

複製程式碼

由於 J2K 無法(可能也不應該)感知 JUnit 的複雜性,所以沒能正確完成轉換。但即使 JUnit 不存在這個問題,J2K 在處理其他小眾框架的時候也肯定會掉類似的坑。

例如,很多 Android Java 程式碼會使用 android。text。TextUtils 中的實用方法,例如 isEmpty,來簡化對某些字串的檢查。但在 Kotlin 中,其實是有內建的標準庫方法 String。isNullOrEmpty 的。該方法之所以更好,是因為它能透過契約來告知 Kotlin 編譯器如果它返回 false,則被測試的物件不得再為 null,並將其智慧轉換為 String。

Java 程式碼也有不少類似的輔助方法,也有很多庫都實現了相同的基本方法。這一切都需要替換成標準的 Kotlin 方法,藉此簡化程式碼並保證編譯器能正確檢測出不可為空的型別。

Strulovich 表示,內部發現了許許多多類似的小小修復例項。有些難度不大(例如替換 isEmpty),有些則需要研究一番才能搞明白(例如 JUnit 規則)。還有一些其實屬於 J2K 出的錯,可能導致構建錯誤、執行時行為錯亂等問題。

為了解決這些問題,Meta 團隊將 J2K 轉換流程劃分成三個步驟:

首先,取一個 Java 包並準備將其轉換為 Kotlin。這個步驟主要解決錯誤,並完成相應的內部工具轉換。

第二步就是執行 J2K。團隊已經能夠以無頭模式執行 Android Studio 並呼叫 J2K,由此將整個管道作為指令碼來執行。

最後一步,對新的 Kotlin 檔案進行後處理。具體包括大部分自動重構與修復步驟,例如將 JUnit 規則標記為 @JvmField。在此步驟中,團隊還應用了自動更新 linter,並在無頭模式下應用各種 Android Studio 建議。“當然,自動化並不足以解決所有問題,但至少能幫我們優先處理那些最常見的問題。”Strulovich 說。

在 Java 重構方面,Meta 使用的是 JavaASTParser 等工具,它能幫助解析某些型別。而在 Kotlin 這邊,團隊還沒有找到能夠解析型別的好辦法,所以選擇使用 Kotlin 編譯器 API。

Meta 還發布了一組自動重構方法(

https://github。com/fbsamples/kotlin_ast_tools

)。雖然不是很多,但希望能幫助更多開發者利用 Kotlin 編譯器解析器高效完成工作。

下一步

平均而言,Meta 發現遷移後的程式碼行數減少了 11%。儘管

網上各種案例引用的數字往往要比這高得多

,但他們還是對這個數字感到滿意。

Strulovich 說,Meta 向 Kotlin 的遷移仍在進行中並在加速。“Kotlin 仍然缺乏一些我們在使用 Java 時已經習慣了的工具和最佳化,但我們正在努力縮小這些差距。隨著我們取得的進展和這些工具和庫的成熟,我們也將努力把它們反饋給社群。”

參考連結:

https://www。theregister。com/2022/10/25/meta_java_kotlin/

https://engineering。fb。com/2022/10/24/android/android-java-kotlin-migration/