Skip to content

Kotlin 演進原則

務實演進的原則

語言設計乃鐵板釘釘,

然此石尚算柔軟,

稍加努力,我們仍可日後重塑。

Kotlin 設計團隊

Kotlin 旨在成為程式設計師的務實工具。就語言演進而言,其務實本質體現於以下原則:

  • 使語言與時俱進。
  • 與使用者保持持續的意見回饋循環。
  • 讓使用者輕鬆舒適地更新至新版本。

由於這對於理解 Kotlin 如何向前發展至關重要,我們將闡述這些原則。

保持語言現代化。我們認知到系統會隨著時間累積舊包袱 (legacy)。過去曾是尖端技術的事物,如今可能已然過時。我們必須演進語言,以使其符合使用者的需求並與他們的期望同步。這不僅包括新增功能,也包括逐步淘汰不再建議用於正式環境且已成為舊包袱的功能。

舒適的更新。不相容的變更,例如從語言中移除某些事物,如果處理不當,可能會導致從一個版本遷移到下一個版本的痛苦經驗。我們將始終提前宣布此類變更,將事物標記為已棄用 (deprecated),並在變更發生之前提供自動化遷移工具。當語言變更發生時,我們希望世界上大部分程式碼已經更新,從而無需為遷移到新版本而煩惱。

意見回饋循環。經歷棄用週期需要付出大量努力,因此我們希望盡量減少未來不相容變更的數量。除了運用我們最佳的判斷力之外,我們相信在實際生活中嘗試事物是驗證設計的最佳方式。在拍板定案之前,我們希望它們經過實戰考驗。這就是為什麼我們利用一切機會,在語言的正式版本中提供我們設計的早期版本,但這些版本處於_預穩定_狀態之一:實驗性 (Experimental)、Alpha 或 Beta。此類功能不穩定,可隨時變更,選擇使用它們的使用者明確表示他們已準備好處理未來的遷移問題。這些使用者提供了寶貴的意見回饋,我們收集這些回饋以迭代設計並使其穩固可靠。

不相容變更

如果從一個版本更新到另一個版本後,某些原本可運作的程式碼不再運作,這就是語言中的_不相容變更_(有時也稱為「破壞性變更 (breaking change)」)。在某些情況下,對於「不再運作」的確切含義可能存在爭議,但它肯定包括以下內容:

  • 原本編譯和執行正常的程式碼現在因錯誤而被拒絕(在編譯時或連結時)。這包括移除語言建構和新增限制。
  • 原本正常執行的程式碼現在拋出例外。

屬於「灰色地帶」較不明顯的情況包括以不同方式處理邊角案例 (corner cases)、拋出與以前不同類型的例外、僅透過反射 (reflection) 可觀察到的行為變更、修改未文件化或未定義的行為、重新命名二進位成品 (binary artifacts) 等。有時此類變更至關重要,會顯著影響遷移體驗;有時它們則微不足道。

以下是一些肯定不屬於不相容變更的例子:

  • 新增警告。
  • 啟用新的語言建構或放寬現有語言建構的限制。
  • 變更私有/內部 API 和其他實作細節。

保持語言現代化和舒適更新的原則表明,不相容變更有時是必要的,但應謹慎引入。我們的目標是提前讓使用者了解即將發生的變更,以便他們能舒適地遷移其程式碼。

理想情況下,每個不相容變更都應透過有問題程式碼中報告的編譯時警告(通常稱為_棄用警告 (deprecation warning)_)來宣布,並輔以自動化遷移輔助工具。因此,理想的遷移工作流程如下:

  • 更新到版本 A(變更在此版本中宣布)
    • 查看有關即將發生變更的警告
    • 在工具的協助下遷移程式碼
  • 更新到版本 B(變更在此版本中發生)
    • 完全沒有問題

實際上,某些變更無法在編譯時準確檢測,因此無法報告警告,但使用者至少會透過版本 A 的發行說明得知版本 B 中即將發生變更。

處理編譯器錯誤

編譯器是複雜的軟體,儘管開發人員盡了最大努力,它們仍然存在錯誤。那些導致編譯器本身失敗、報告虛假錯誤或生成明顯失敗程式碼的錯誤,儘管惱人且常令人尷尬,但很容易修復,因為這些修復不構成不相容變更。其他錯誤可能導致編譯器生成不失敗的錯誤程式碼:例如,遺漏原始碼中的某些錯誤或僅僅生成錯誤的指令。此類錯誤的修復在技術上是不相容變更(某些程式碼過去編譯正常,但現在不行了),但我們傾向於盡快修復它們,以防止錯誤的程式碼模式在使用者程式碼中蔓延。我們認為,這支持了舒適更新的原則,因為更少的使用者有機會遇到這個問題。當然,這僅適用於在發布版本中出現後很快被發現的錯誤。

決策制定

JetBrains 作為 Kotlin 的原創者,正在社群的協助下並與 Kotlin Foundation 合作推動其發展。

Kotlin 程式語言的所有變更均由 首席語言設計師 (Lead Language Designer) 監督(目前為 Michail Zarečenskij)。首席設計師在所有與語言演進相關的事務上擁有最終決定權。此外,對完全穩定元件的不相容變更必須由 Kotlin Foundation 下設的 語言委員會 (Language Committee) 批准(目前由 Jeffrey van Gogh、Werner Dietl 和 Michail Zarečenskij 組成)。

語言委員會就將進行哪些不相容變更以及應採取哪些確切措施以使使用者更新盡可能順暢做出最終決定。為此,它依賴一套 語言委員會準則

語言與工具版本

諸如 2.0.0 等穩定版本,通常被視為帶來語言重大變更的_語言版本 (language releases)。通常,我們會在語言版本之間發布版本號為 x.x.20 的_工具版本 (tooling releases)

工具版本帶來工具的更新(通常包括功能)、性能改進和錯誤修復。我們盡力保持這些版本彼此相容,因此對編譯器的變更主要是最佳化以及警告的增減。預穩定功能可能隨時被新增、移除或變更。

語言版本通常會新增功能,並可能移除或變更先前已棄用的功能。功能從預穩定到穩定的升級也發生在語言版本中。

EAP 建置版本

在發布語言和工具的穩定版本之前,我們會發布一些稱為 EAP(即「早期預覽版 (Early Access Preview)」)的預覽建置版本,這讓我們能夠更快地迭代並從社群收集意見回饋。語言版本的 EAP 通常會產生稍後會被穩定編譯器拒絕的二進位檔案 (binaries),以確保二進位格式中可能存在的錯誤不會存在超過預覽期。最終的發布候選版本 (Release Candidates) 通常沒有此限制。

預穩定功能

根據上述的意見回饋循環原則,我們公開迭代我們的設計,並發布語言版本,其中某些功能具有_預穩定_狀態,且_預期會發生變更_。此類功能可隨時新增、變更或移除,且不發出警告。我們盡力確保預穩定功能不會被不知情的用戶意外使用。此類功能通常需要在程式碼中或專案配置中進行某種形式的明確選擇加入 (opt-in)。

Kotlin 語言功能可具有以下狀態之一:

  • 探索與設計。我們正在考慮在語言中引入新功能。這涉及討論它如何與現有功能整合、收集使用案例,並評估其潛在影響。我們需要使用者就此功能將解決的問題和它所處理的使用案例提供意見回饋。在可能的情況下,我們也會盡力估計這些使用案例和問題發生的頻率,這也將有所助益。通常,想法會被記錄為 YouTrack 問題,討論會在那裡繼續進行。

  • KEEP 討論。我們相當確定該功能應新增至語言中。我們的目標是在一份名為 KEEP 的文件中提供動機、使用案例、設計和其他重要細節。我們期望使用者意見回饋的重點是討論 KEEP 中提供的所有資訊。

  • 預覽中。功能原型已準備就緒,您可以使用特定功能的編譯器選項啟用它。我們尋求您對該功能的體驗回饋,包括它如何輕鬆整合到您的程式碼庫中、它如何與現有程式碼互動,以及任何 IDE 支援問題或建議。該功能的設計可能會顯著改變,或者根據意見回饋被完全撤銷。當功能處於_預覽中_時,它具有一個穩定性層級

  • 穩定。該語言功能現在是 Kotlin 語言中的一等公民 (first-class citizen)。我們保證其向後相容性,並將提供工具支援。

  • 已撤銷。我們已撤銷該提案,將不會在 Kotlin 語言中實作此功能。如果某個功能_處於預覽中_且不適合 Kotlin,我們可能會撤銷它。

查看 Kotlin 語言提案及其狀態的完整列表

不同元件的狀態

深入了解 Kotlin 中不同元件的穩定性狀態,例如 Kotlin/JVM、JS 和 Native 編譯器,以及各種函式庫 (libraries)。

函式庫

沒有生態系統,語言便形同虛設,因此我們格外關注函式庫的順暢演進。

理想情況下,新版本的函式庫可以作為舊版本的「直接替換 (drop-in replacement)」。這表示升級二進位依賴項 (binary dependency) 不應破壞任何東西,即使應用程式未重新編譯(在動態連結下這是可能的)。

一方面,為實現此目的,編譯器必須在獨立編譯 (separate compilation) 的限制下,提供一定的_應用程式二進位介面 (Application Binary Interface)_ (ABI) 穩定性保證。這就是為什麼語言中的每個變更都從二進位相容性 (binary compatibility) 的角度進行審查的原因。

另一方面,許多事情取決於函式庫作者仔細判斷哪些變更是安全的。因此,函式庫作者理解原始碼變更如何影響相容性,並遵循某些最佳實踐以保持其函式庫的 API 和 ABI 穩定,至關重要。以下是我們從函式庫演進角度考慮語言變更時做出的一些假設:

  • 函式庫程式碼應始終明確指定公有/保護函式和屬性的回傳型別,因此絕不依賴型別推斷 (type inference) 來處理公有 API。型別推斷中的細微變更可能導致回傳型別在不經意間改變,從而導致二進位相容性問題。
  • 同一函式庫提供的多載函式 (overloaded functions) 和屬性應基本執行相同的操作。型別推斷中的變更可能導致在呼叫站點 (call sites) 處已知更精確的靜態型別,從而導致多載解析 (overload resolution) 的變更。

函式庫作者可以使用 @Deprecated@RequiresOptIn 註解 (annotations) 來控制其 API 表面的演進。請注意,即使對於從 API 中移除的宣告,也可以使用 @Deprecated(level=HIDDEN) 來保持二進位相容性。

此外,按照慣例,名稱為「internal」的套件不被視為公共 API。位於名稱為「experimental」套件中的所有 API 都被視為預穩定,並且可能隨時變更。

我們根據上述原則,為穩定平台演進 Kotlin 標準函式庫 (kotlin-stdlib)。其 API 合約 (contracts) 的變更遵循與語言本身變更相同的程序。

編譯器選項

編譯器接受的命令列選項 (command line options) 也算是一種公共 API,它們受相同考量所約束。支援的選項(那些沒有「-X」或「-XX」前綴的選項)只能在語言版本中新增,並應在移除前適當地棄用。「-X」和「-XX」選項是實驗性的,可隨時新增和移除。

相容性工具

隨著舊功能被移除和錯誤被修復,原始碼語言會發生變更,而未經適當遷移的舊程式碼可能無法再編譯。正常的棄用週期為遷移提供了舒適的時間,即使週期結束且變更已在穩定版本中發布,仍然有一種方法可以編譯未遷移的程式碼。

相容性選項

我們提供 -language-version X.Y-api-version X.Y 選項,使新版本能夠模擬舊版本的行為,以達到相容性目的。為了給您更多的遷移時間,我們除了最新的穩定版本外,還支援前三個語言和 API 版本。

積極維護的程式碼庫可以從盡快獲得錯誤修復中受益,而無需等待完整的棄用週期完成。目前,此類專案可以啟用 -progressive 選項,即使在工具版本中也能啟用此類修復。

所有選項都可以在命令列以及 GradleMaven 中使用。

二進位格式的演進

與在最壞情況下可以手動修復的原始碼不同,二進位檔案 (binaries) 的遷移要困難得多,這使得向後相容性 (backwards compatibility) 在二進位檔案的情況下至關重要。對二進位檔案的不相容變更會使更新非常不舒服,因此應比原始碼語言語法中的變更更加謹慎地引入。

對於完全穩定的編譯器版本,預設的二進位相容性協定如下:

  • 所有二進位檔案都向後相容;這表示較新的編譯器可以讀取舊的二進位檔案(例如,1.3 版理解 1.0 到 1.2 版)。
  • 較舊的編譯器會拒絕依賴新功能的二進位檔案(例如,1.0 版編譯器拒絕使用協程 (coroutines) 的二進位檔案)。
  • 最好(但我們無法保證),二進位格式與下一個語言版本在大多數情況下是向前相容的,但與之後的版本則否(在不使用新功能的情況下,例如,1.9 版可以理解 2.0 版的大部分二進位檔案,但不能理解 2.1 版)。

此協定旨在實現舒適的更新,因為即使專案使用略舊的編譯器,也不會被阻止更新其依賴項。

請注意,並非所有目標平台都已達到此穩定性水平,但 Kotlin/JVM 已達到。

Kotlin klib 二進位檔案

Kotlin klib 二進位檔案已在 Kotlin 1.9.20 版中達到穩定層級。然而,您需要記住一些相容性細節:

  • klib 二進位檔案從 Kotlin 1.9.20 版開始向後相容。例如,2.0.x 版編譯器可以讀取由 1.9.2x 版編譯器產生的二進位檔案。
  • 不保證向前相容性。例如,不保證 2.0.x 版編譯器能夠讀取由 2.1.x 版編譯器產生的二進位檔案。

Kotlin cinterop klib 二進位檔案仍處於 Beta 狀態。 目前,我們無法為 cinterop klib 二進位檔案在不同 Kotlin 版本之間提供具體的相容性保證。