Spec-Zone.ru › Kotlin 1.7

Эволюция Kotlin

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

Дизайн языка застыл,

но этот камень достаточно мягок,

и приложив усилия, мы можем его позже переформировать.

Команда разработчиков Kotlin

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

  • Сохранять актуальность языка с течением времени.

  • Поддерживать постоянный цикл обратной связи с пользователями.

  • Обеспечить удобство обновления до новых версий.

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

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

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

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

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

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

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

  • Код, который выполнялся нормально, теперь генерирует исключение.

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

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

  • Добавление новых предупреждений.

  • Включение новых конструкций языка или ослабление ограничений для существующих.

  • Изменение частных/внутренних API и других деталей реализации.

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

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

  • Обновление до версии A (где объявляется изменение)

    • Просмотр предупреждений о предстоящем изменении

    • Миграция кода с помощью средств

  • Обновление до версии B (где происходит изменение)

    • Отсутствие проблем

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

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

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

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

JetBrains, первоначальный разработчик Kotlin, управляет его развитием с помощью сообщества и в соответствии с Kotlin Foundation.

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

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

Релизы функций и инкрементные релизы

Стабильные релизы с версиями 1.2, 1.3 и т. д. обычно считаются релизами функций, вносящими существенные изменения в язык. Обычно мы публикуем инкрементные релизы, пронумерованные 1.2.20, 1.2.30 и т. д., между релизами функций.

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

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

Сборки EAP

Перед выпуском стабильных версий мы обычно публикуем ряд предварительных сборок, именуемых EAP (Early Access Preview), что позволяет нам быстрее итеративно работать и собирать отзывы от сообщества. 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 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 — да).

Последнее изменение: 11 января 2022 г.
Вопросы и ответы Стабильность компонентов Kotlin

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

Spec-Zone.ru

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