Эволюция Kotlin
Принципы прагматичной эволюции
Kotlin разработан как прагматичный инструмент для программистов. При эволюции языка его прагматичная природа проявляется в следующих принципах:
Поддерживать современность языка на протяжении многих лет.
Оставаться в постоянной связи с пользователями.
Обеспечить удобную обновляемость до новых версий.
Поскольку это ключевой аспект понимания дальнейшего развития Kotlin, давайте подробнее рассмотрим эти принципы.
Сохранение современности языка. Мы признаем, что системы накапливают устаревшее наследие со временем. То, что когда-то было передовыми технологиями, сегодня может быть безнадежно устаревшим. Мы должны развивать язык, чтобы поддерживать его актуальность для потребностей пользователей и соответствовать их ожиданиям. Это включает не только добавление новых функций, но и постепенное удаление устаревших, которые больше не рекомендуются для использования в производстве и стали полностью наследием.
Удобные обновления. Несовместимые изменения, такие как удаление элементов из языка, могут привести к болезненной миграции с одной версии на другую, если не будут осуществлены с должной осторожностью. Мы всегда будем заблаговременно объявлять такие изменения, помечать элементы как устаревшие и предоставлять автоматизированные инструменты миграции до внесения изменения. К тому времени, когда язык изменится, мы хотим, чтобы большая часть кода в мире уже была обновлена, и, следовательно, не возникло проблем с миграцией на новую версию.
Цикл обратной связи. Прохождение циклов устаревания требует значительных усилий, поэтому мы хотим свести к минимуму количество несовместимых изменений в будущем. Помимо использования нашего лучшего суждения, мы считаем, что проверка на практике — лучший способ подтвердить проект. Перед тем, как закрепить что-либо, мы хотим подвергнуть это тестированию в реальных условиях. Именно поэтому мы используем все возможности, чтобы сделать ранние версии наших проектов доступными в производственных версиях языка, но в одном из предоставленных статусов: Экспериментальный, Альфа или Бета. Такие функции нестабильны, их можно изменить в любое время, и пользователи, которые выбирают их использование, делают это явно, чтобы указать, что они готовы столкнуться с будущими проблемами миграции. Эти пользователи предоставляют неоценимые отзывы, которые мы собираем, чтобы итеративно улучшить проект и сделать его надежным.
Несовместимые изменения
Если при обновлении с одной версии на другую некоторый код, который раньше работал, больше не работает, это несовместимое изменение в языке (иногда упоминается как «разрушающее изменение»). В некоторых случаях могут быть дискуссии о точном значении «больше не работает», но это определённо включает в себя следующее:
Код, который успешно компилировался и выполнялся, теперь отклоняется с ошибкой (на этапе компиляции или линковки). Это включает удаление конструкций языка и добавление новых ограничений.
Код, который выполнялся нормально, теперь вызывает исключение.
Менее очевидные случаи, относящиеся к «серой зоне», включают обработку граничных случаев иначе, выброс исключения другого типа, чем раньше, изменение поведения, наблюдаемого только через рефлексию, изменения в недокументированном/неопределенном поведении, переименование бинарных артефактов и т. д. Иногда такие изменения очень важны и значительно влияют на опыт миграции, иногда они незначительны.
Примеры того, что определенно не является несовместимым изменением, включают:
Добавление новых предупреждений.
Включение новых конструкций языка или смягчение ограничений для существующих.
Изменение закрытых/внутренних API и других реализационных деталей.
Принципы «Сохранения современности языка» и «Удобных обновлений» предполагают, что несовместимые изменения иногда необходимы, но они должны вводиться с осторожностью. Наша цель — заблаговременно информировать пользователей о предстоящих изменениях, чтобы они могли комфортно мигрировать свой код.
В идеале каждое несовместимое изменение должно быть объявлено с помощью предупреждения во время компиляции, отображаемого в проблемном коде (обычно называемом предупреждением об устаревании) и сопровождаться автоматическими средствами миграции. Таким образом, идеальный рабочий процесс миграции выглядит следующим образом:
-
Обновление до версии А (где объявляется изменение)
Просмотр предупреждений о предстоящем изменении
Миграция кода с помощью инструментов
-
Обновление до версии В (где происходит изменение)
Отсутствие проблем
На практике некоторые изменения невозможно точно определить на этапе компиляции, поэтому не может быть выдано предупреждение, но пользователи будут проинформированы в примечаниях к выпуску версии А о предстоящем изменении в версии В.
Обработка ошибок компилятора
Компиляторы — это сложное программное обеспечение, и, несмотря на все усилия разработчиков, в них могут быть ошибки. Ошибки, которые приводят к сбоям компилятора или сообщению о ложных ошибках или генерации явно неправильного кода, хотя и раздражают и часто вызывают смущение, легко исправляются, так как исправления не являются несовместимыми изменениями. Другие ошибки могут привести к генерации компилятором некорректного кода, который не приводит к ошибке: например, при пропускании ошибок в исходном коде или просто при генерации неправильных инструкций. Исправления таких ошибок технически являются несовместимыми изменениями (некоторый код ранее компилировался, но теперь не будет), но мы склонны исправлять их как можно скорее, чтобы не допустить распространения плохих шаблонов кода среди пользовательского кода. По нашему мнению, это соответствует принципу удобных обновлений, поскольку меньше пользователей сталкиваются с проблемой. Конечно, это относится только к ошибкам, обнаруженным вскоре после появления в релизе.
Процесс принятия решений
JetBrains, первоначальный создатель Kotlin, продвигает его развитие с помощью сообщества и в соответствии с принципами Kotlin Foundation.
Все изменения в языке программирования Kotlin контролируются Главным разработчиком языка (в настоящее время Роман Елизаров). Главный разработчик имеет окончательное слово по всем вопросам, связанным с эволюцией языка. Кроме того, несовместимые изменения в полностью стабильных компонентах должны быть одобрены Комитетом по языку, назначенным под эгидой Kotlin Foundation (в настоящее время в его состав входят Джеффри ван Гог, Уильям Р. Кук и Роман Елизаров).
Комитет по языку принимает окончательные решения о том, какие несовместимые изменения будут внесены и какие конкретные меры необходимо принять, чтобы обновления для пользователей были удобными. При этом он опирается на свод руководств, доступных здесь.
Релизы функций и инкрементные релизы
Стабильные релизы с версиями 1.2, 1.3 и т. д. обычно считаются релизами функций, вносящими существенные изменения в язык. Обычно мы публикуем инкрементные релизы, пронумерованные 1.2.20, 1.2.30 и т. д., между релизами функций.
Инкрементные релизы включают обновления в инструментарии (часто включая новые функции), улучшения производительности и исправление ошибок. Мы стараемся поддерживать совместимость таких версий друг с другом, поэтому изменения в компиляторе, в основном, оптимизации и добавление/удаление предупреждений. Пре-стабильные функции, конечно, могут быть добавлены, удалены или изменены в любое время.
Релизы функций часто добавляют новые функции и могут удалять или изменять ранее устаревшие. Выпуск функций из состояния пре-стабильного в стабильное также происходит в релизах функций.
Сборки EAP
Перед выпуском стабильных версий мы обычно публикуем ряд предварительных сборок, названных EAP (для «Предварительного доступа»), которые позволяют нам быстрее итеративно работать и собирать отзывы от сообщества. EAP релизов функций обычно создают двоичные файлы, которые впоследствии будут отклонены стабильным компилятором, чтобы убедиться, что возможные ошибки в двоичном формате не переживут период предварительного просмотра. Окончательные кандидаты на выпуск обычно не имеют этого ограничения.
Функции пре-стабильного уровня
В соответствии с принципом цикла обратной связи, описанным выше, мы итеративно работаем над нашими проектами в открытом доступе и выпускаем версии языка, где некоторые функции имеют один из пре-стабильных статусов и предполагается, что они изменятся. Такие функции могут быть добавлены, изменены или удалены в любой момент и без предупреждения. Мы делаем все возможное, чтобы не дать неосторожному пользователю случайно использовать функции пре-стабильного уровня. Такие функции обычно требуют явного согласия, либо в коде, либо в конфигурации проекта.
Функции пре-стабильного уровня обычно переходят в стабильное состояние после нескольких итераций.
Статус различных компонентов
Чтобы проверить стабильность различных компонентов Kotlin (Kotlin/JVM, JS, Native, различные библиотеки и т. д.), обратитесь к этой ссылке.
Библиотеки
Язык ничто без своей экосистемы, поэтому мы уделяем особое внимание обеспечению плавного развития библиотек.
В идеале новая версия библиотеки может использоваться как «прямая замена» более старой версии. Это означает, что обновление двоичной зависимости не должно вызывать ошибок, даже если приложение не перекомпилировано (это возможно при динамической компоновке).
С одной стороны, для достижения этого компилятор должен предоставлять определенные гарантии стабильности ABI в рамках ограничений отдельной компиляции. Вот почему каждое изменение языка рассматривается с точки зрения двоичной совместимости.
С другой стороны, многое зависит от того, насколько осторожны авторы библиотек при внесении изменений. Поэтому очень важно, чтобы авторы библиотек понимали, как изменения исходного кода влияют на совместимость и соблюдали определенные лучшие практики для поддержания стабильности как API, так и ABI своих библиотек. Вот некоторые предположения, которые мы делаем при рассмотрении изменений языка с точки зрения эволюции библиотек:
Код библиотеки всегда должен явно указывать типы возвращаемых значений для публичных/защищенных функций и свойств, никогда не полагаясь на вывод типов для публичного API. Незначительные изменения в выводе типов могут привести к случайному изменению типов возвращаемых значений, что приведет к проблемам двоичной совместимости.
Перегруженные функции и свойства, предоставляемые одной и той же библиотекой, должны выполнять по существу одинаковые действия. Изменения в выводе типов могут привести к более точным статическим типам, которые известны в местах вызова, вызывая изменения в разрешении перегрузки.
Авторы библиотек могут использовать аннотации @Deprecated и @RequiresOptIn для управления эволюцией своей API-поверхности. Обратите внимание, что @Deprecated(level=HIDDEN) может использоваться для сохранения двоичной совместимости даже для объявлений, удаленных из API.
Также, по соглашению, пакеты с именем «internal» не считаются публичным API. Весь API, находящийся в пакетах с именем «experimental», считается пре-стабильным и может измениться в любой момент.
Мы развиваем стандартную библиотеку Kotlin (kotlin-stdlib) для стабильных платформ в соответствии с изложенными выше принципами. Изменения в контрактах для ее API проходят те же процедуры, что и изменения в самом языке.
Ключи компилятора
Ключи командной строки, принимаемые компилятором, также являются своего рода публичным API, и к ним применяются те же соображения. Поддерживаемые флаги (те, у которых нет префикса "-X" или "-XX") могут быть добавлены только в релизах функций и должны быть должным образом устаревшими перед удалением. Флаги "-X" и "-XX" являются экспериментальными и могут быть добавлены и удалены в любое время.
Инструменты совместимости
По мере удаления устаревших функций и исправления ошибок исходный язык изменяется, а старый код, который не был должным образом мигрирован, может больше не компилироваться. Обычный цикл устаревания позволяет обеспечить комфортный период для миграции, и даже после того, как он завершился, и изменение выпущено в стабильной версии, всё ещё есть способ скомпилировать немигрированный код.
Флаги совместимости
Мы предоставляем флаги -language-version X.Y и -api-version X.Y, которые заставляют новую версию эмулировать поведение старой для обеспечения совместимости. Чтобы дать вам больше времени на миграцию, мы поддерживаем разработку трёх предыдущих версий языка и API, в дополнение к последней стабильной.
Использование более старой kotlin-stdlib или kotlin-reflect с более новым компилятором без указания флагов совместимости не рекомендуется, и компилятор будет выдавать предупреждение в этом случае.
Активно поддерживаемые кодовые базы могут получить выгоду от получения исправлений ошибок как можно скорее, не дожидаясь завершения полного цикла устаревания. В настоящее время такие проекты могут включить флаг -progressive и получить доступ к таким исправлениям даже в инкрементных релизах.
Все флаги доступны в командной строке, а также в Gradle и Maven.
Развитие двоичного формата
В отличие от исходного кода, который в худшем случае можно исправить вручную, миграция двоичных файлов гораздо сложнее, и это делает обратную совместимость очень важной в случае двоичных файлов. Несовместимые изменения в двоичных файлах могут значительно затруднить обновления, и их следует вносить ещё более тщательно, чем изменения в синтаксисе исходного языка.
Для полностью стабильных версий компилятора, протокол двоичной совместимости по умолчанию следующий:
Все двоичные файлы обратно совместимы, т. е. более новый компилятор может читать более старые двоичные файлы (например, 1.3 понимает 1.0-1.2).
Более старые компиляторы отклоняют двоичные файлы, которые полагаются на новые функции (например, компилятор 1.0 отклоняет двоичные файлы, использующие сопрограммы).
В предпочтительном варианте (хотя мы не можем гарантировать этого) двоичный формат в основном совместим с последующим релизом функций, но не с более поздними (в тех случаях, когда новые функции не используются, например, 1.3 может понимать большинство двоичных файлов из 1.4, но не 1.5).
Этот протокол предназначен для удобных обновлений, так как ни один проект не может быть заблокирован от обновления своих зависимостей, даже если он использует немного устаревший компилятор.
Обратите внимание, что не все целевые платформы достигли этого уровня стабильности (но Kotlin/JVM имеет).
© 2010–2023 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/kotlin-evolution.html