Эволюция Kotlin
Принципы прагматической эволюции
Kotlin разработан как прагматичный инструмент для программистов. В контексте эволюции языка его прагматизм отражается в следующих принципах:
Сохранять современность языка на протяжении многих лет.
Поддерживать постоянную обратную связь с пользователями.
Обеспечивать удобство обновления до новых версий для пользователей.
Поскольку это ключевой момент для понимания того, как Kotlin развивается дальше, давайте подробнее разберем эти принципы.
Сохранение современности языка. Мы признаем, что системы накапливают устаревшее кодовое основание со временем. То, что когда-то было передовыми технологиями, сегодня может быть совершенно устаревшим. Мы должны развивать язык, чтобы он оставался актуальным для потребностей пользователей и соответствовал их ожиданиям. Это включает не только добавление новых функций, но и постепенное удаление устаревших, которые больше не рекомендуются для использования в производстве и стали наследием.
Удобные обновления. Несовместимые изменения, такие как удаление элементов из языка, могут привести к болезненной миграции от одной версии к другой, если не будут выполнены должным образом. Мы всегда будем заблаговременно объявлять о таких изменениях, помечать элементы как устаревшие и предоставлять автоматизированные инструменты миграции до того, как изменения произойдут. К тому времени, когда язык будет изменен, мы хотим, чтобы большая часть кода в мире уже была обновлена, и поэтому проблем с миграцией на новую версию не возникло.
Цикл обратной связи. Прохождение циклов устаревания требует значительных усилий, поэтому мы стремимся минимизировать количество несовместимых изменений, которые будем вносить в будущем. Помимо использования своего опыта, мы считаем, что самый лучший способ проверки дизайна — это его практическое применение в реальных условиях. Перед тем, как окончательно закрепить что-то, мы хотим его апробировать в реальных условиях. Именно поэтому мы используем любую возможность для предоставления ранних версий наших проектов в производственных версиях языка, но в одном из предоставленных статусов: Экспериментальный, альфа или бета. Такие функции нестабильны, они могут быть изменены в любое время, и пользователи, которые выбирают их для использования, делают это сознательно, указав, что готовы к будущим проблемам миграции. Эти пользователи предоставляют бесценную обратную связь, которую мы собираем, чтобы итеративно улучшать дизайн и сделать его надежным.
Несовместимые изменения
Если при обновлении с одной версии на другую какой-то код, который раньше работал, больше не работает, это несовместимое изменение в языке (иногда называемое «разрушающим изменением»). В некоторых случаях может быть дискуссия о том, что именно означает «больше не работает», но это определенно включает в себя следующее:
Код, который компилировался и выполнялся без проблем, теперь отклоняется с ошибкой (на этапе компиляции или линковки). Это включает удаление конструкций языка и добавление новых ограничений.
Код, который выполнялся нормально, теперь выбрасывает исключение.
К менее очевидным случаям, которые относятся к «серой области», относятся обработка граничных случаев по-другому, выбрасывание исключения другого типа, чем раньше, изменение поведения, наблюдаемого только через рефлексию, изменения в недокументированном/неопределённом поведении, переименование бинарных артефактов и т. д. Иногда такие изменения очень важны и сильно влияют на опыт миграции, иногда они незначительны.
Вот некоторые примеры того, что определённо не является несовместимым изменением:
Добавление новых предупреждений.
Включение новых конструкций языка или ослабление ограничений для существующих.
Изменение частных/внутренних 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 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–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