Kotlin 演進原則
務實演進原則
語言設計是刻在石頭上的,
但這石頭相當柔軟,
只要付出一點努力,我們稍後就能重塑它。
Kotlin 設計團隊
Kotlin 被設計為開發人員的務實工具。談到語言演進時,其務實的本質體現在以下原則中:
- 保持語言隨著時間推移而現代化。
- 與使用者保持持續的回饋循環。
- 讓使用者能輕鬆且舒適地更新到新版本。
由於這是理解 Kotlin 如何向前發展的關鍵,讓我們進一步展開這些原則。
保持語言現代化。我們意識到系統會隨著時間累積舊有包袱。曾經尖端的技術在今天可能已無可救藥地過時。我們必須演進語言,使其符合使用者的需求並跟上他們的期待。這不僅包括增加新功能,還包括逐步淘汰那些不再建議用於生產環境且已成為舊有包袱的舊功能。
舒適的更新。如果不慎重處理,不相容的變更(例如從語言中移除內容)可能會導致版本遷移過程非常痛苦。我們始終會提前宣佈此類變更,將相關內容標記為已棄用,並在_變更發生之前_提供自動化遷移工具。當語言發生變更時,我們希望世界上大部分的程式碼都已經完成更新,從而能毫無問題地遷移到新版本。
回饋循環。經歷棄用週期需要付出巨大的努力,因此我們希望儘量減少未來不相容變更的數量。除了運用我們的最佳判斷外,我們相信在現實生活中進行嘗試是驗證設計的最佳方式。在將設計刻在石頭上之前,我們希望它們經過實戰測試。這就是為什麼我們利用一切機會,在語言的正式版本中提供設計的早期版本,但處於以下其中一種_預先穩定_狀態:Experimental、Alpha 或 Beta。這些功能尚不穩定,隨時可能更改,選擇使用它們的使用者是明確表示他們已準備好處理未來的遷移問題。這些使用者提供了無價的回饋,讓我們能據此反覆調整設計,使其堅如夯實。
不相容的變更
如果在從一個版本更新到另一個版本時,以前可以運行的某些程式碼不再運行,這就是語言中的_不相容變更_(有時稱為「破壞性變更」)。 在某些情況下,「不再運行」的確切定義可能存在爭議,但它肯定包括以下內容:
- 曾可編譯並正常執行的程式碼現在被報錯拒絕(在編譯或連結時)。 這包括移除語言結構和增加新的限制。
- 正常執行的程式碼現在拋出例外。
屬於「灰色地帶」且較不明顯的情況包括:以不同方式處理邊緣情況、拋出與以前不同類型的例外、更改僅能透過反射觀察到的行為、修改未記載或未定義的行為、重新命名二進制構件等。 有時這類變更至關重要,會極大影響遷移體驗,有時則微不足道。
肯定不屬於不相容變更的範例包括:
- 增加新的警告。
- 啟用新的語言結構或放寬現有結構的限制。
- 更改私有(private)/內部(internal)API 和其他實作細節。
保持語言現代化和舒適更新的原則表明,不相容的變更時有必要,但應謹慎引入。我們的目標是讓使用者提前知曉即將到來的變更,以便他們能舒適地遷移程式碼。
理想情況下,每個不相容的變更都應透過在有問題的程式碼中報告編譯期警告(通常稱為_棄用警告_)來宣佈,並輔以自動化遷移工具。 因此,理想的遷移工作流程如下:
- 更新到版本 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)。
語言委員會(Language Committee)就將進行哪些不相容變更,以及應採取哪些確切措施來使使用者更新盡可能無縫銜接做出最終決定。 在執行此類工作時,它依賴一套 Language committee guidelines。
語言與工具發佈
具有版本號(如 2.0.0)的穩定發佈通常被視為帶來語言重大變更的_語言發佈_。 通常,我們會在語言發佈之間發佈編號為 x.x.20 的_工具發佈_。
工具發佈帶來工具方面的更新(通常包括功能)、效能改進和錯誤修復。 我們儘量保持這些版本相互相容,因此對編譯器的變更大多是最佳化和警告的增加/移除。 預先穩定功能可能隨時被增加、移除或更改。
語言發佈通常會增加新功能,並可能移除或更改先前已棄用的功能。 功能從預先穩定階段過渡到穩定階段也發生在語言發佈中。
EAP 組建
在發佈語言和工具的穩定版本之前,我們會發佈一些名為 EAP(Early Access Preview,早期體驗體計劃)的預覽組建,以便我們能更快迭代並收集社群回饋。 語言發佈的 EAP 通常會產生稍後會被穩定編譯器拒絕的二進制檔案,以確保二進制格式中可能存在的錯誤不會超出預覽期。 最終的版本候選(Release Candidates)通常不受此限制。若要了解更多,請參閱 參與 Kotlin 早期體驗體計劃。
預先穩定功能
根據上述的回饋循環原則,我們在公開環境中反覆調整設計,並發佈一些功能處於_預先穩定_狀態且_預期會發生變更_的語言版本。 這些功能可以隨時增加、更改或移除,且無需事先通知。 我們會盡力確保不知情的使用者不會意外使用預先穩定功能。 這些功能通常需要在程式碼或專案配置中進行某種形式的顯式選擇加入(opt-in)。
Kotlin 語言功能可以具有以下狀態之一:
探索與設計 (Exploration and design)。我們正在考慮向語言引入一項新功能。 這包括討論它將如何與現有功能整合、收集使用案例並評估其潛在影響。 我們需要使用者就該功能將解決的問題和它所處理的使用案例提供回饋。 盡可能估算這些使用案例和問題發生的頻率也會非常有益。 通常,想法會被記載為 YouTrack 問題,討論在那裡繼續進行。
KEEP 討論 (KEEP discussion)。我們相當確定該功能應被添加到語言中。 我們的目標是在名為 KEEP 的文件中提供動機、使用案例、設計和其他重要細節。 我們希望使用者回饋集中於討論 KEEP 中提供的所有資訊。
預覽中 (In preview)。功能原型已就緒,您可以使用功能專用的編譯器選項來啟用它。 我們尋求關於您使用該功能體驗的回饋,包括它整合到您的程式碼庫中的難易程度、它與現有程式碼的互動情況,以及任何 IDE 支援問題或建議。 功能的設計可能會發生重大變化,或者根據回饋被完全撤銷。當一項功能處於_預覽中_時,它具有 Experimental 或 Beta 穩定等級。
穩定 (Stable)。該語言功能現在是 Kotlin 語言中的一等公民。 我們保證其回溯相容性,並將提供工具支援。
已撤銷 (Revoked)。我們已撤回該提案,且不會在 Kotlin 語言中實作該功能。 如果處於_預覽中_的功能不適合 Kotlin,我們可能會將其撤銷。
不同組件的狀態
進一步了解 Kotlin 中不同組件的穩定狀態,例如 Kotlin/JVM、JS 和 Native 編譯器,以及各種程式庫。
程式庫
沒有生態系統,語言就什麼都不是,因此我們特別注意實現流暢的程式庫演進。
理想情況下,新版本的程式庫可以作為舊版本的「直接替換」來使用。 這意味著升級二進制相依性不應破壞任何內容,即使應用程式沒有重新編譯(這在動態連結下是可能的)。
一方面,為了實現這一點,編譯器必須在獨立編譯的限制下提供一定的_應用程式二進制介面 (ABI)_ 穩定性保證。 這就是為什麼語言中的每項變更都會從二進制相容性的角度進行審查。
另一方面,很大程度上取決於程式庫作者是否謹慎對待哪些變更是可以安全進行的。 因此,程式庫作者必須了解原始碼變更如何影響相容性,並遵循某些最佳實務來保持其程式庫的 API 和 ABI 穩定。 以下是我們從程式庫演進的角度考慮語言變更時所做的一些假設:
- 程式庫程式碼應始終顯式指定 public/protected 函式和屬性的傳回型別,因此絕不應依賴公開 API 的型別推論。型別推論的細微變化可能會導致傳回型別意外更改,從而導致二進制相容性問題。
- 由同一程式庫提供的多載函式和屬性應執行基本相同操作。型別推論的變更可能會導致在呼叫點得知更精確的靜態型別,從而導致多載解析的變更。
程式庫作者可以使用 @Deprecated 和 @RequiresOptIn 註解來控制其 API 表層的演進。請注意,@Deprecated(level=HIDDEN) 可用於即使是從 API 中移除的宣告,也能保留二進制相容性。
此外,按照慣例,名為「internal」的套件不被視為公開 API。 所有位於名為「experimental」套件中的 API 都被視為預先穩定,並可能隨時發生變更。
我們根據上述原則為穩定平台演進 Kotlin 標準函式庫(kotlin-stdlib)。 其 API 合約的變更經歷與語言本身變更相同的程序。
編譯器選項
編譯器接受的命令列選項也是一種公開 API,它們也遵循同樣的考慮。 受支援的選項(那些沒有「-X」或「-XX」前綴的選項)只能在語言發佈中增加,且在移除之前應經過適當的棄用程序。 「-X」和「-XX」選項是實驗性的,可以隨時增加或移除。
相容性工具
隨著舊有功能被移除和錯誤被修復,原始語言會發生變化,未經適當遷移的舊程式碼可能無法再編譯。 正常的棄用週期為遷移提供了充裕的時間,即使週期結束且變更在穩定版本中發佈,仍有辦法編譯未遷移的程式碼。
相容性選項
我們提供相容性選項,讓新的 Kotlin 版本模擬舊版本的行為以實現相容性:
-language-version X.Y- Kotlin 語言版本 X.Y 的相容模式,對所有之後推出的語言特性報告錯誤。-api-version X.Y- Kotlin API 版本 X.Y 的相容模式,對所有使用 Kotlin 標準函式庫中較新 API 的程式碼(包括編譯器產生的程式碼)報告錯誤。
為了給您更多時間進行遷移,除了最新的穩定版本外,我們還支援至少三個先前的語言和 API 版本。
主動維護的程式碼庫可以受益於儘快獲得錯誤修復,而無需等待完整的棄用週期完成。 目前,此類專案可以啟用 -progressive 選項,即使在工具發佈中也能啟用這些修復。
所有選項都可以在 IDE、命令列以及 Gradle 和 Maven 中使用。
演進二進制格式
原始碼在最壞的情況下可以手動修復,但二進制檔案的遷移要困難得多,這使得回溯相容性在二進制檔案的情況下至關重要。 對二進制檔案的不相容變更會使更新變得非常不舒適,因此引入時應比原始語言語法變更更加謹慎。
對於完全穩定版本的編譯器,預設的二進制相容性協定如下:
- 所有二進制檔案都是回溯相容的;這意味著較新的編譯器可以讀取較舊的二進制檔案(例如,1.3 了解 1.0 到 1.2)。
- 較舊的編譯器會拒絕依賴新功能的二進制檔案(例如,1.0 編譯器拒絕使用協同程式的二進制檔案)。
- 最好(但我們不能保證)二進制格式與下一個語言發佈版本基本向前相容,但不能與之後的版本相容(在未使用新功能的情況下,例如,1.9 可以理解 2.0 的大部分二進制檔案,但不能理解 2.1)。
該協定旨在實現舒適的更新,因為即使專案使用的是稍舊的編譯器,也不會被阻礙更新其相依性。
請注意,並非所有目標平台都達到了這種穩定程度,但 Kotlin/JVM 已經達到了。
Kotlin klib 二進制檔案
Kotlin klib 二進制檔案在 Kotlin 1.9.20 中已達到 穩定 (Stable) 等級。 然而,您需要牢記一些相容性細節:
- 從 Kotlin 1.9.20 開始,klib 二進制檔案具有回溯相容性。例如,2.0.x 編譯器可以讀取由 1.9.2x 編譯器產生的二進制檔案。
- _不_保證向前相容性。例如,2.0.x 編譯器_不_保證能讀取由 2.1.x 編譯器產生的二進制檔案。
Kotlin cinterop klib 二進制檔案仍處於 Beta 階段。 目前,我們無法為不同 Kotlin 版本之間的 cinterop klib 二進制檔案提供具體的相容性保證。
