Kotlin 演進原則
務實演進的原則
語言設計如同鑄石,
但這石頭還算柔軟,
只要花些功夫,日後還能重塑.
Kotlin 設計團隊
Kotlin 的設計宗旨是成為程式設計師的務實工具。在語言演進方面,其務實本質體現在以下原則中:
- 隨著時間推移,保持語言的現代性。
- 與使用者保持持續的回饋迴圈。
- 讓使用者能輕鬆舒適地更新到新版本。
由於這對於理解 Kotlin 的發展方向至關重要,因此讓我們詳細闡述這些原則。
保持語言的現代性 (Keeping the Language Modern)。我們認識到系統會隨著時間累積舊有程式碼 (legacy)。曾經是最尖端的技術,如今可能已過時。我們必須發展語言,使其與使用者的需求保持相關性,並與他們的期望保持同步。這不僅包括新增功能,還包括逐步淘汰不再建議用於生產環境且已成為舊有程式碼的舊功能。
舒適的更新 (Comfortable Updates)。不相容的變更,例如從語言中移除某些東西,如果沒有妥善處理,可能會導致從一個版本到下一個版本的痛苦遷移。我們將始終提前宣布此類變更、將事物標記為已棄用,並在_變更發生之前_提供自動遷移工具。在語言變更時,我們希望世界上大多數程式碼已經更新,因此遷移到新版本不會有任何問題。
回饋迴圈 (Feedback Loop)。經歷棄用週期需要大量的努力,因此我們希望盡量減少未來將進行的不相容變更的數量。除了運用我們最好的判斷之外,我們相信在現實生活中嘗試事物是驗證設計的最佳方式。在將事物鑄成定局之前,我們希望它們經過實戰考驗。這就是為什麼我們利用一切機會在語言的生產版本中提供早期版本的設計,但處於_pre-stable_(預穩定)狀態之一:Experimental(實驗性)、Alpha(Alpha 版)或 Beta(Beta 版)。這些功能不穩定,可以隨時變更,選擇使用它們的使用者會明確表示他們已準備好處理未來的遷移問題。這些使用者提供了寶貴的回饋,我們收集這些回饋以迭代設計並使其堅如磐石。
不相容變更 (Incompatible changes)
如果在從一個版本更新到另一個版本時,某些曾經可以運作的程式碼不再運作,則表示語言中存在_不相容變更_(有時稱為「重大變更 (breaking change)」)。在某些情況下,對於「不再運作」的確切含義可能會存在爭議,但它絕對包括以下內容:
- 曾經可以正常編譯和執行的程式碼現在因為錯誤而被拒絕(在編譯或連結時)。這包括移除語言結構和新增限制。
- 曾經正常執行的程式碼現在會擲出例外 (exception)。
屬於「灰色地帶」的不太明顯的情況包括以不同的方式處理邊角案例 (corner case)、擲出與之前不同的例外類型、僅透過反射 (reflection) 觀察到的變更行為、修改未記載或未定義的行為、重新命名二進位檔案構件 (binary artifact) 以及其他。有時此類變更至關重要,會極大地影響遷移體驗,有時則無關緊要。
以下是一些絕對不是不相容變更的範例:
- 新增警告。
- 啟用新的語言結構或放寬現有結構的限制。
- 變更私有/內部 API 和其他實作細節。
保持語言現代性和舒適更新的原則表明,有時不相容的變更是必要的,但應該謹慎引入。我們的目標是讓使用者充分了解即將發生的變更,以便他們能夠舒適地遷移其程式碼。
理想情況下,每個不相容的變更都應該透過在有問題的程式碼中報告的編譯時警告(通常稱為_棄用警告 (deprecation warning)_)來宣布,並輔以自動遷移輔助工具。因此,理想的遷移工作流程如下:
- 更新到版本 A(宣布變更的版本)
- 查看有關即將發生的變更的警告
- 借助工具遷移程式碼
- 更新到版本 B(發生變更的版本)
- 完全沒有看到任何問題
實際上,某些變更無法在編譯時準確檢測到,因此無法報告任何警告,但至少使用者會透過版本 A 的發行說明得知版本 B 即將發生的變更。
處理編譯器錯誤 (Dealing with compiler bugs)
編譯器是複雜的軟體,儘管其開發人員盡了最大的努力,但它們仍然存在錯誤。導致編譯器本身出現故障或報告虛假錯誤或產生明顯失敗程式碼的錯誤雖然令人討厭且常常令人尷尬,但很容易修復,因為這些修復不會構成不相容的變更。其他錯誤可能會導致編譯器產生不正確但不失敗的程式碼:例如,遺漏來源中的某些錯誤或僅僅產生錯誤的指令。對此類錯誤的修復在技術上是不相容的變更(某些程式碼曾經可以正常編譯,但現在不行了),但我們傾向於盡快修復它們,以防止錯誤的程式碼模式在使用者程式碼中傳播。我們認為,這支持了舒適更新的原則,因為更少的使用者有機會遇到此問題。當然,這僅適用於在發布版本中出現後不久發現的錯誤。
決策 (Decision making)
JetBrains 是 Kotlin 的原始建立者,在社群的幫助下並與 Kotlin 基金會 (Kotlin Foundation) 合作推動其發展。
對 Kotlin 程式語言的所有變更均由 Lead Language Designer(目前為 Michail Zarečenskij)監督。首席設計師對與語言演進相關的所有事項擁有最終決定權。此外,對完全穩定的元件的不相容變更必須經過 Language Committee 的批准,該委員會是在 Kotlin 基金會 (Kotlin Foundation) 下指定的(目前由 Jeffrey van Gogh、Werner Dietl 和 Michail Zarečenskij 組成)。
語言委員會對將進行哪些不相容的變更以及應採取哪些確切措施以使使用者更新盡可能無縫做出最終決定。在這樣做時,它依賴於一套 Language committee guidelines。
語言和工具發布 (Language and tooling releases)
具有版本(例如 2.0.0)的穩定版本通常被視為_語言發布 (language release),為語言帶來重大變更。通常,我們在語言發布之間發布編號為 x.x.20 的_工具發布 (tooling release)。
工具發布帶來工具中的更新(通常包括功能)、效能改進和錯誤修復。我們盡量保持這些版本彼此相容,因此對編譯器的變更主要是最佳化和新增/移除警告。Pre-stable(預穩定)功能可以隨時新增、移除或變更。
語言發布通常會新增新功能,並可能移除或變更先前已棄用的功能。功能從 pre-stable(預穩定)到 stable(穩定)的畢業 (graduation) 也會在語言發布中發生。
EAP 建置 (EAP builds)
在發布語言和工具發布的穩定版本之前,我們會發布許多名為 EAP(「Early Access Preview」的縮寫)的預覽建置,這使我們能夠更快地迭代並收集社群的回饋。語言發布的 EAP 通常會產生二進位檔案,這些二進位檔案稍後會被穩定的編譯器拒絕,以確保二進位檔案格式中可能存在的錯誤不會超過預覽期。最終的候選發布版本通常沒有此限制。
Pre-stable(預穩定)功能
根據上述回饋迴圈原則,我們公開迭代我們的設計並發布一些功能具有_pre-stable_(預穩定)狀態之一且_應該變更_的語言版本。此類功能可以在任何時間點新增、變更或移除,恕不另行通知。我們會盡力確保毫無戒心的使用者不會意外使用 pre-stable(預穩定)功能。此類功能通常需要在程式碼或專案配置中進行某種明確的選擇加入 (opt-in)。
Kotlin 語言功能可以具有以下狀態之一:
-
Exploration and design(探索和設計)。我們正在考慮將新功能引入該語言。這包括討論它如何與現有功能集成、收集用例以及評估其潛在影響。我們需要使用者提供有關此功能將解決的問題及其解決的用例的回饋。只要有可能,我們都會嘗試估計這些用例和問題發生的頻率,這也將是有益的。通常,這些想法會記錄為 YouTrack 問題,討論會在此處繼續。
-
KEEP discussion(KEEP 討論)。我們相當確定應將該功能新增到該語言。我們的目標是在名為 KEEP 的文件中提供動機、用例、設計和其他重要細節。我們希望使用者回饋的重點是討論 KEEP 中提供的所有資訊。
-
In preview(在預覽中)。功能原型已準備就緒,您可以使用特定於功能的編譯器選項啟用它。我們徵求您對該功能體驗的回饋,包括它如何輕鬆地集成到您的程式碼庫中、它如何與現有程式碼互動以及任何 IDE 支援問題或建議。該功能的設計可能會發生重大變更,或者可能會根據回饋完全撤銷。當功能處於_預覽中_時,它具有 stability level(穩定性級別)。
-
Stable(穩定)。該語言功能現在是 Kotlin 語言中的一等公民。我們保證其向後相容性,並且我們將提供工具支援。
-
Revoked(已撤銷)。我們已撤銷該提案,並且不會在 Kotlin 語言中實作該功能。如果某個功能不適合 Kotlin,我們可以撤銷該功能(處於_預覽中_)。
不同元件的狀態 (Status of different components)
了解有關 Kotlin 中不同元件的穩定性狀態 的更多資訊,例如 Kotlin/JVM、JS 和 Native 編譯器以及各種程式庫。
程式庫 (Libraries)
沒有生態系統,語言什麼都不是,因此我們格外注意使程式庫能夠順利發展。
理想情況下,新版本的程式庫可以用作舊版本的「drop-in replacement」。這表示升級二進位檔案相依性不應破壞任何內容,即使應用程式未重新編譯也是如此(這在動態連結下是可能的)。
一方面,為實現這一目標,編譯器必須在單獨編譯的約束下提供某些 應用程式二進位檔案介面 (Application Binary Interface, ABI) 穩定性保證。這就是為什麼從二進位檔案相容性的角度檢查語言中的每個變更的原因。
另一方面,很大程度上取決於程式庫作者是否謹慎地決定哪些變更可以安全進行。因此,程式庫作者了解來源變更如何影響相容性並遵循某些最佳實務來保持其程式庫的 API 和 ABI 穩定至關重要。以下是我們從程式庫演進的角度考慮語言變更時所做的一些假設:
- 程式庫程式碼應始終明確指定 public/protected 函數和屬性的返回類型,因此永遠不要依賴於 public API 的類型推斷。類型推斷中的細微變更可能會導致返回類型在不經意間發生變更,從而導致二進位檔案相容性問題。
- 同一程式庫提供的多載函數和屬性應執行基本相同的事情。類型推斷中的變更可能會導致在呼叫站點已知更精確的靜態類型,從而導致多載解析中的變更。
程式庫作者可以使用 @Deprecated
和 @RequiresOptIn
註釋來控制其 API 表面的演進。請注意,@Deprecated(level=HIDDEN)
可用於即使對於從 API 中移除的宣告也能保留二進位檔案相容性。
此外,按照慣例,名為「internal」的套件不被視為 public API。駐留在名為「experimental」的套件中的所有 API 均被視為 pre-stable(預穩定)且可以隨時變更。
我們會根據上述原則為穩定平台演進 Kotlin 標準程式庫 (kotlin-stdlib
)。對其 API 合約的變更與語言本身的變更經過相同的程序。
編譯器選項 (Compiler options)
編譯器接受的命令列選項也是一種 public API,它們也受到相同的考慮。支援的選項(那些沒有「-X」或「-XX」前綴的選項)只能在語言發布中新增,並且在移除之前應正確棄用它們。「-X」和「-XX」選項是實驗性的,可以隨時新增和移除。
相容性工具 (Compatibility tools)
隨著舊有功能被移除和錯誤被修復,來源語言會發生變更,並且未正確遷移的舊程式碼可能無法再編譯。正常的棄用週期允許舒適的遷移時間,即使它已結束且變更已在穩定版本中發布,仍然有一種方法可以編譯未遷移的程式碼。
相容性選項 (Compatibility options)
我們提供 -language-version X.Y
和 -api-version X.Y
選項,使新版本能夠模擬舊版本的行為,以實現相容性。為了讓您有更多的時間進行遷移,我們 support 除了最新的穩定版本之外,還支援三個先前的語言和 API 版本。
主動維護的程式碼庫可以從盡快獲得錯誤修復中受益,而無需等待完整的棄用週期完成。目前,此類專案可以啟用 -progressive
選項,即使在工具發布中也可以啟用此類修復。
所有選項都可以在命令列以及 Gradle 和 Maven 中使用。
演進二進位檔案格式 (Evolving the binary format)
與最壞情況下可以手動修復的來源不同,二進位檔案更難遷移,這使得向後相容性在二進位檔案的情況下至關重要。對二進位檔案的不相容變更可能會使更新非常不舒服,因此應該比來源語言語法中的變更更加謹慎地引入。
對於編譯器的完全穩定版本,預設的二進位檔案相容性協定如下:
- 所有二進位檔案都向後相容;這表示較新的編譯器可以讀取較舊的二進位檔案(例如,1.3 了解 1.0 到 1.2)。
- 較舊的編譯器拒絕依賴於新功能的二進位檔案(例如,1.0 編譯器拒絕使用協程的二進位檔案)。
- 最好(但我們無法保證),二進位檔案格式在很大程度上與下一個語言發布向前相容,但不能與後面的版本相容(在不使用新功能的情況下,例如,1.9 可以理解來自 2.0 的大多數二進位檔案,但不能理解 2.1)。
此協定旨在實現舒適的更新,因為即使專案使用稍微過時的編譯器,也不會阻止其更新相依性。
請注意,並非所有目標平台都已達到此穩定性級別,但 Kotlin/JVM 已達到。
Kotlin klib 二進位檔案 (Kotlin klib binaries)
Kotlin klib 二進位檔案已在 Kotlin 1.9.20 中達到 Stable 級別。但是,您需要記住一些相容性詳細資訊:
- klib 二進位檔案從 Kotlin 1.9.20 開始向後相容。例如,2.0.x 編譯器可以讀取 1.9.2x 編譯器產生的二進位檔案。
- 不保證向前相容性。例如,不能保證 2.0.x 編譯器可以讀取 2.1.x 編譯器產生的二進位檔案。
Kotlin cinterop klib 二進位檔案仍處於 Beta 階段。目前,我們無法為 cinterop klib 二進位檔案的 Kotlin 不同版本之間提供特定的相容性保證。