Spec-Zone.ru › Kotlin 1.4

Эволюция Kotlin

Принципы прагматической эволюции

                    Language design is cast in stone,
                    but this stone is reasonably soft,
                    and with some effort we can reshape it later.
                    
                    Kotlin Design Team

Kotlin разработан как прагматический инструмент для программистов. В отношении эволюции языка его прагматизм проявляется в следующих принципах:

  • Сохранение современности языка на протяжении лет.
  • Поддержание постоянной обратной связи с пользователями.
  • Обеспечение комфортного обновления до новых версий для пользователей.

Поскольку это ключевой момент для понимания того, как развивается Kotlin, давайте подробнее рассмотрим эти принципы.

Сохранение современности языка. Мы признаем, что системы со временем накапливают устаревший код. Технология, которая когда-то была передовой, сегодня может быть устаревшей. Мы должны развивать язык, чтобы он оставался актуальным для потребностей пользователей и соответствовал их ожиданиям. Это включает не только добавление новых функций, но и постепенное удаление устаревших, которые больше не рекомендуются для использования в производстве и которые полностью стали legacy.

Комфортные обновления. Несовместимые изменения, такие как удаление элементов языка, могут привести к болезненной миграции между версиями, если их внедрять без должной заботы. Мы всегда будем объявлять о таких изменениях заранее, отмечать элементы как устаревшие и предоставлять автоматизированные инструменты миграции до момента изменения. К тому времени, когда язык изменится, мы хотим, чтобы большая часть кода в мире уже была обновлена и, таким образом, не было проблем с миграцией на новую версию.

Цикл обратной связи. Прохождение циклов устаревания требует значительных усилий, поэтому мы хотим свести к минимуму количество несовместимых изменений, которые будем вносить в будущем. Помимо использования собственного суждения, мы считаем, что проверка вещей в реальной жизни — лучший способ подтвердить дизайн. Перед тем, как зафиксировать что-либо окончательно, мы хотим, чтобы это было протестировано в реальной работе. Именно поэтому мы используем любую возможность, чтобы представить ранние версии наших разработок в производственных версиях языка, но в одном из предварительных статусов: Экспериментальный, Альфа или Бета. Такие функции нестабильны, они могут быть изменены в любое время, и пользователи, которые выбирают их использование, делают это явно, чтобы указать, что они готовы к будущим проблемам миграции. Эти пользователи предоставляют ценную обратную связь, которую мы собираем, чтобы улучшить дизайн и сделать его надежным.

Несовместимые изменения

Если при обновлении с одной версии на другую какой-то код, который раньше работал, больше не работает, это несовместимое изменение в языке (иногда его называют «разрушающим изменением»). В некоторых случаях могут возникать споры о том, что именно означает «больше не работает», но это определённо включает следующее:

  • Код, который успешно компилировался и выполнялся, теперь отклоняется с ошибкой (во время компиляции или компоновки). Это включает удаление конструкций языка и добавление новых ограничений.
  • Код, который выполнялся нормально, теперь генерирует исключение.

Менее очевидные случаи, которые относятся к «серой области», включают обработку граничных случаев по-другому, генерацию исключения другого типа, чем раньше, изменение поведения, наблюдаемого только через рефлексию, изменения в недокументированном/неопределённом поведении, переименование бинарных артефактов и т. д. Иногда такие изменения очень важны и значительно влияют на процесс миграции, иногда они незначительны.

Примеры того, что определённо не является несовместимым изменением:

  • Добавление новых предупреждений.
  • Включение новых конструкций языка или ослабление ограничений для существующих.
  • Изменение закрытых/внутренних API и других деталей реализации.

Принципы сохранения современности языка и комфортных обновлений предполагают, что иногда необходимы несовместимые изменения, но их следует вводить осторожно. Наша цель — информировать пользователей о предстоящих изменениях заблаговременно, чтобы они могли комфортно мигрировать свой код.

В идеале каждое несовместимое изменение должно объявляться с помощью предупреждения во время компиляции, которое отображается в проблемном коде (обычно называемом предупреждением об устаревании), и сопровождаться средствами автоматической миграции. Идеальный процесс миграции выглядит следующим образом:

  • Обновление до версии A (где объявляется изменение)
    • Просмотр предупреждений о предстоящем изменении
    • Миграция кода с помощью инструментария
  • Обновление до версии B (где происходит изменение)
    • Отсутствие каких-либо проблем

На практике некоторые изменения нельзя точно обнаружить во время компиляции, поэтому не могут быть выведены предупреждения, но пользователи будут проинформированы в примечаниях к выпуску версии A, что изменение произойдёт в версии B.

Обработка ошибок компилятора

Компиляторы — сложные программы, и, несмотря на все усилия их разработчиков, в них могут быть ошибки. Ошибки, которые приводят к сбою самого компилятора или к генерации ложных ошибок или очевидно неправильного кода, хотя они раздражают и часто бывают неловкими, легко исправить, потому что исправления не являются несовместимыми изменениями. Другие ошибки могут привести к тому, что компилятор сгенерирует некорректный код, который не вызывает сбоев: например, пропуская некоторые ошибки в исходном коде или просто генерируя неправильные инструкции. Исправления таких ошибок технически являются несовместимыми изменениями (некоторые коды раньше компилировались без ошибок, но теперь не будут), но мы стремимся к их исправлению как можно скорее, чтобы предотвратить распространение плохих образцов кода среди пользовательского кода. По нашему мнению, это способствует принципу комфортных обновлений, так как меньше пользователей столкнутся с проблемой. Конечно, это относится только к ошибкам, которые обнаруживаются вскоре после появления в выпущенной версии.

Процесс принятия решений

JetBrains, первоначальный создатель Kotlin, продвигает его развитие при помощи сообщества и в соответствии с принципами Фонда Kotlin.

Все изменения в языке программирования Kotlin контролируются Главным разработчиком языка (в настоящее время Андрей Бреслав). Главный разработчик имеет решающее слово во всех вопросах, связанных с эволюцией языка. Кроме того, несовместимые изменения для полностью стабильных компонентов должны быть одобрены Комитетом по языку, назначенным Фондом Kotlin (в настоящее время в него входят Джеффри ван Гох, Уильям Р. Кук и Андрей Бреслав).

Комитет по языку принимает окончательные решения о том, какие несовместимые изменения будут внесены и какие конкретные меры необходимо принять, чтобы обеспечить комфорт при обновлении пользователей. При этом он руководствуется набором руководящих принципов, доступных здесь.

Выпуски функций и инкрементные выпуски

Стабильные релизы с версиями 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 Standard Library (kotlin-stdlib) для стабильных платформ в соответствии с вышеуказанными принципами. Изменения в контрактах для ее API проходят ту же процедуру, что и изменения в самом языке.

Ключи компилятора

Ключи командной строки, принимаемые компилятором, также являются своего рода публичным API, и они подчиняются тем же соображениям. Поддерживаемые флаги (те, у которых нет префикса "-X" или "-XX") могут быть добавлены только в релизах функций и должны быть должным образом помечены как устаревшие перед их удалением. Флаги "-X" и "-XX" являются экспериментальными и могут быть добавлены и удалены в любое время.

Инструменты совместимости

По мере удаления устаревших функций и исправления ошибок исходный язык меняется, и старый код, который не был должным образом мигрирован, может больше не компилироваться. Нормальный цикл устаревания позволяет комфортный период времени для миграции, и даже когда он закончился, и изменение выпущено в стабильной версии, все еще есть способ скомпилировать немигрированный код.

Флаги совместимости

Мы предоставляем флаги -language-version и -api-version, которые позволяют новой версии эмулировать поведение старой для целей совместимости. Как правило, поддерживается хотя бы одна предыдущая версия. Это фактически предоставляет временной интервал в два полных цикла выпуска функций для миграции (что обычно составляет около двух лет). Использование более старой kotlin-stdlib или kotlin-reflect с более новым компилятором без указания флагов совместимости не рекомендуется, и компилятор сообщит об предупреждении при этом.

Активно поддерживаемые базы кода могут извлечь выгоду из получения исправлений ошибок как можно скорее, не дожидаясь завершения полного цикла устаревания. В настоящее время такие проекты могут включить флаг -progressive и получить доступ к таким исправлениям, даже в инкрементных релизах.

Все флаги доступны в командной строке, а также в Gradle и Maven.

Эволюция двоичного формата

В отличие от исходного кода, который в худшем случае можно исправить вручную, с двоичными файлами гораздо сложнее мигрировать, и это делает обратную совместимость очень важной в случае с двоичными файлами. Несовместимые изменения в двоичных файлах могут сделать обновления очень неудобными, поэтому к ним нужно подходить еще более осторожно, чем к изменениям в синтаксисе исходного языка.

Для полностью стабильных версий компилятора стандартный протокол двоичной совместимости следующий:

  • Все двоичные файлы совместимы в обратном направлении, т.е. более новый компилятор может читать более старые двоичные файлы (например, 1.3 понимает 1.0–1.2),
  • Более старые компиляторы отказываются от двоичных файлов, которые полагаются на новые функции (например, компилятор 1.0 отказывается от двоичных файлов, использующих корутины).
  • В идеале (но мы не можем гарантировать этого), двоичный формат в основном совместим с последующим релизом функций, но не с более поздними (в тех случаях, когда новые функции не используются, например, 1.3 может понимать большинство двоичных файлов из 1.4, но не из 1.5).

Этот протокол предназначен для удобных обновлений, так как ни один проект не может быть заблокирован от обновления зависимостей, даже если он использует немного устаревший компилятор.

Обратите внимание, что не все целевые платформы достигли этого уровня стабильности (но Kotlin/JVM — да).

© 2010–2020 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/reference/evolution/kotlin-evolution.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API