代碼行數(shù)減少 30-90%!多鄰國(guó)從Java遷移到Kotlin的奇妙體驗(yàn)
英文學(xué)習(xí) App Duolingo(多鄰國(guó))的 Android 版最初是使用 Java 開(kāi)發(fā)的,并一直沿用了 5 年。兩年后,它變成了 100% 的 Kotlin App!從代碼可維護(hù)性和開(kāi)發(fā)者滿意度方面來(lái)看,這次遷移是一個(gè)巨大的成功。網(wǎng)上已經(jīng)有很多 Kotlin 的學(xué)習(xí)資源,所以在這篇文章中,我們將重點(diǎn)介紹如何將一個(gè)有百萬(wàn)用戶的 App 遷移到 Kotlin。

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





















