代碼行數(shù)減少 30-90%!多鄰國從Java遷移到Kotlin的奇妙體驗(yàn)
英文學(xué)習(xí) App Duolingo(多鄰國)的 Android 版最初是使用 Java 開發(fā)的,并一直沿用了 5 年。兩年后,它變成了 100% 的 Kotlin App!從代碼可維護(hù)性和開發(fā)者滿意度方面來看,這次遷移是一個(gè)巨大的成功。網(wǎng)上已經(jīng)有很多 Kotlin 的學(xué)習(xí)資源,所以在這篇文章中,我們將重點(diǎn)介紹如何將一個(gè)有百萬用戶的 App 遷移到 Kotlin。
1. 為什么選擇 Kotlin
2018 年初,我們開始考慮使用 Kotlin,當(dāng)時(shí) Android 對 Kotlin 的支持還不到一年時(shí)間,而且不知道 Kotlin 會像現(xiàn)在這樣流行,也不知道它會取代 Java 成為 Android 的開發(fā)首選語言。
我們當(dāng)時(shí)的主要預(yù)期:
- 生產(chǎn)力。Kotlin 比 Java 要簡潔得多,編寫 Kotlin 代碼速度更快,維護(hù)起來也更容易。它與 Java 的無縫互操作性以及添加語言新特性的方式讓 Android 開發(fā)者可以輕松上手。
- 穩(wěn)定性。我們的代碼庫歷史記錄中有 100 多次提交都與“修復(fù) NullPointerException 問題”有關(guān),這些問題都來自 Java 代碼。Kotlin 的 Null 安全特性避免了大部分 NullPointerException,讓我們在代碼評審期間可以更多地關(guān)注其他問題。
- 開發(fā)者滿意度。Stack Overflow 發(fā)布的 2018 年度開發(fā)者編程語言報(bào)告表明,Kotlin 是開發(fā)者最喜愛的編程語言之一,僅次于 Rust。我們的開發(fā)人員已經(jīng)對公司內(nèi)部另外兩個(gè)主要平臺的語言升級做出了積極的反應(yīng):在 iOS 應(yīng)用程序中使用 Swift,以及使用 TypeScript 完全重寫了 duolingo.com。
- 當(dāng)然,遷移也存在一些風(fēng)險(xiǎn),主要是開發(fā)人員的時(shí)間成本問題。另一個(gè)擔(dān)憂是 Kotlin 是否會像 CoffeeScript 一樣,最后可能會被它的“影子”語言打敗。
- 最后,我們的 Android 開發(fā)人員一致認(rèn)為,Kotlin 的好處很有價(jià)值,足以證明使用 Kotlin 開發(fā)新代碼是合理的,盡管我們還沒有準(zhǔn)備好全面遷移所有的代碼。
2. 讓開發(fā)人員跟上進(jìn)度
Duolingo 的所有 Android 開發(fā)人員每兩周開一次例會,討論最近和即將做出的平臺變更和非正式的事后分析,并進(jìn)行問答。前期的會議專門介紹 Kotlin,內(nèi)容主要基于 Kotlin 官方語言指南、Kotlin Koans、Android 官方文檔和 MindOrks 備忘單,等等。
然后,每個(gè) Android 開發(fā)人員都分配到一些 Java 代碼,負(fù)責(zé)將它們遷移到 Kotlin。我們讓相對有 Kotlin 經(jīng)驗(yàn)的開發(fā)人員擔(dān)任“Kotlin checker”角色,讓他們在代碼評審期間分享最佳實(shí)踐。這個(gè)角色的人數(shù)逐步增加,直到所有的 Android 開發(fā)人員都包含在內(nèi)。
3. Kotlin 相關(guān)的開發(fā)工具
從一開始,我們就對 Kotlin 工具進(jìn)行容器化,并在代碼預(yù)提交和 GitHub 拉取請求狀態(tài)檢查中強(qiáng)制使用,以此來確保代碼的一致性。
我們使用 detekt、IntelliJ Inspection、Android lint 和我們自己開發(fā)的基于正則表達(dá)式的 Splinter 來檢查所有的 Kotlin 代碼。
在代碼自動格式化方面,我們在公司范圍內(nèi)使用了 ktlint,將其作為代碼預(yù)提交 hook 的一部分。另一個(gè)工具是 IntelliJ Formatter,不過我們發(fā)現(xiàn)它在 Docker 中運(yùn)行會慢一些。
在將 Java 代碼減少到只有 10% 的時(shí)候,我們從 CI 管道中移除了 PMD、SpotBugs 和大部分檢查工具。繼續(xù)使用這些 Java 工具將會降低我們的開發(fā)速度,而且不會為我們提供太多的價(jià)值。
4. 轉(zhuǎn)換 Java 代碼
為了讓代碼轉(zhuǎn)換的評審工作盡可能輕松,我們建議每個(gè)源文件的拉取請求至少包含三個(gè)單獨(dú)的提交:
- 運(yùn)行 IDE 的自動轉(zhuǎn)換器。這個(gè)提交會造成代碼行錯(cuò)亂,但不需要仔細(xì)檢查,因?yàn)閷τ谶\(yùn)行時(shí)來說通常是安全的,盡管可能會引入編譯時(shí)錯(cuò)誤。
- 修復(fù)編譯錯(cuò)誤。這些修復(fù)通常很容易進(jìn)行,例如,在必要時(shí)添加 @JvmStatic 注解。
- 重構(gòu)。開發(fā)人員需要重構(gòu)代碼,讓代碼更符合 Kotlin 的習(xí)慣,例如使用 sumBy 而不是 for 循環(huán)。
我們發(fā)現(xiàn),將 Java 文件轉(zhuǎn)換成 Kotlin 后,行數(shù)平均減少了 30% 左右,在某些情況下甚至減少了 90%!
雖然移植代碼對于我們的 Android 平臺工程師來說不是問題,但對于我們的產(chǎn)品團(tuán)隊(duì)來說可能會相對困難。我們鼓勵產(chǎn)品團(tuán)隊(duì)的開發(fā)人員在空閑時(shí)間遷移經(jīng)常修改的代碼,還通過每天的排行榜比賽來游戲化這個(gè)過程。最終,產(chǎn)品團(tuán)隊(duì)的開發(fā)人員擔(dān)起了一半的工作量。
5. 絆腳石
Kotlin 的工具生態(tài)系統(tǒng)比 Java 的要小得多。盡管如此,它已經(jīng)足夠滿足我們的需求了。
我們偶爾還是會遇到 NullPointerException 和 IllegalArgumentException,這些異常來自第三方 Java 依賴庫(比如 Android 框架本身),它們沒有遵循最佳實(shí)踐,沒有使用可空注解,以至于 Kotlin 編譯器無法知道某些方法的參數(shù)或返回值可以為空。隨著谷歌給它們的公共 API 加入注解,這種情況得到了改善。
不過,Kotlin 仍然缺乏對一些 Java 特性的原生支持,包括不太常見的超類靜態(tài)受保護(hù)方法調(diào)用和神秘的超類構(gòu)造函數(shù)調(diào)用,但這類問題很容易解決。
6. 結(jié)果
在 2018 年初引入 Kotlin 之前,我們的 Android 代碼庫的代碼行數(shù)每年增長 46%。兩年過去了,我們加入了很多新特性,活躍的貢獻(xiàn)者數(shù)量增加了一倍多,而我們的代碼庫現(xiàn)在幾乎只和以前一樣大!
根據(jù) NPS 數(shù)據(jù),這一次 Android 開發(fā)人員的滿意度增加了 129 個(gè)點(diǎn),大多數(shù)開發(fā)人員認(rèn)為是采用 Kotlin(以及我們的工具)起到了主要作用。NPS 的數(shù)據(jù)具體為:
https://en.wikipedia.org/wiki/Net_Promoter
我們現(xiàn)在也同時(shí)使用 Python 和 Java 作為后端服務(wù)開發(fā)語言,這幾乎不需要額外的工作量,因?yàn)槲覀兛梢灾赜矛F(xiàn)有服務(wù)中的 Java 代碼和 Android 代碼庫中的 Kotlin 工具。
總的來說,在遷移到 Kotlin 之后,我們感到非常開心。我們也很高興能夠看到它在我們的公司內(nèi)部和整個(gè)軟件行業(yè)中的采用率不斷增長!