Безусловно, null-safety — важный шаг в развитии языка. Команда Dart анонсировала бета-релиз версии с null-safety! Мы перевели на русский новость об этом релизе, в котором вы узнаете, как мигрировать на новые версии, какие преимущества получите, и в чем польза null-safety для всех нас.

Сегодня мы объявляем, что бета-версия с надежной null-safety доступна для Dart и Flutter. Null-safety — это наше последнее важное достижение, призванное помочь вам избежать ошибок обращения к null-значениям — класса ошибок, которые часто трудно обнаружить. Это видео в общих чертах объясняет причину нашей радости:


С переходом на бета-версию с null-safety пришло время для миграции тысяч пакетов, доступных на pub.dev. Мы перенесли библиотеки ядра Dart, фреймворк Flutter и более 40 пакетов Dart и Flutter. При этом мы надеемся, что комьюнити примет null-safety, мигрируя свои пакеты.



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

Выбор в пользу null-safety


Прежде чем обсуждать миграцию на null-safety, хотим повторить, что (как указано в наших принципах null-safety) у вас есть возможность выбирать, когда именно начинать переход. Приложения и пакеты будут работать только с null-safety, если их минимальное ограничение Dart SDK принадлежит по крайней мере к пред-релизной версии Dart 2.12:

environment:
 sdk: ">=2.12.0-0 <3.0.0"

Чтобы испытать ее, попробуйте создать небольшое null-safety приложение hello (например, с помощью dart create), содержащее код, как показано ниже. Затем можете попробовать запустить приложение до и после изменения ограничения SDK и запуска dart pub get и посмотреть, как меняется поведение программы. (Убедитесь, что dart --version возвращает вам именно 2.12).

bin/hello.dart:
...
void main() {
  var hello = 'Hello Dart developers';
  if (someCondition) {
	hello = null;
  }
  print(hello);
}
 
Before changing the SDK constraint:
$ dart run
 
null
 
After changing the SDK constraint (and running dart pub get):

$ dart run
 
bin/hello.dart:6:13: Error: Null can't be assigned to a variable of 
type 'String' because 'String' is not nullable.
 
	hello = null;
        	^

Переход к null-safety


Чтобы мигрировать пакет (или простое приложение) в режим null-safety, выполните следующие пять шагов, которые подробно описаны в руководстве по миграции на dart.dev.

Шаг 1: проверьте, готовы ли ваши зависимости


Настоятельно рекомендуем переносить код по порядку, начиная с «листьев» графа зависимостей. Например, если C зависит от B, который зависит от A, сначала мигрируйте на null-safety A, затем B, затем C. Этот порядок применим вне зависимости от того, являются A, B и C библиотеками, пакетами или приложениями.



Почему порядок так важен? Можно добиться некоторого прогресса в миграции кода до миграции зависимостей, но есть риск столкнуться с необходимостью повторного прогона, если ваши зависимости изменят свои интерфейсы во время миграции. Если некоторые из ваших зависимостей не null-safety, подумайте о том, чтобы связаться с издателями пакетов, используя контактные данные, указанные для каждого пакета на pub.dev.

Проверка готовности зависимостей


Чтобы проверить, готово ли ваше приложение или пакет к началу миграции, можете выполнить dart pub outdated в режиме null-safety. Приведенный ниже пример показывает, что приложение будет готово к миграции, если обновит свои зависимости на path, process и pedantic до пред-релизных версий, перечисленных в столбце Resolvable.



Если поддержка null-safety доступна в новых минорных версиях, вы увидите их в столбце Upgradable. Часто поддержка null-safety будет доступна в мажорных новых версиях; в этом случае вы увидите версии, перечисленные в разделе Resolvable в выводе утилиты outdated. Для перехода на них отредактируйте файл pubspec.yaml, чтобы разрешить эти мажорные версии. Например, можете поменять
process: ^3.0.13 на process: ^4.0.0-nullsafety.

Также вы можете найти пакеты с поддержкой null-safety на pub.dev, используя новые теги Null safety на страницах пакетов (например, collection 1.15) и новую опцию Расширенного поиска null safety search.



Шаг 2: перенос с помощью инструмента миграции


Если зависимости готовы, можете приступать к переносу вашего приложения или пакета с помощью инструмента миграции dart migrate.

Инструмент миграции интерактивен, поэтому вы можете просмотреть свойства null-safety, выведенные этим инструментом. Если вы не согласны с каким-либо результатом работы инструмента, можно добавить подсказки о допустимости пустых значений, чтобы изменить его. Добавление всего нескольких подсказок может оказать огромное влияние на качество миграции.



Несколько авторов пакетов Dart протестировали миграцию с использованием ранних предварительных сборок null-safety, и их отзывы были воодушевляющими. В руководстве по миграции есть дополнительные подсказки по использованию инструмента миграции.

Шаг 3: статический анализ перенесенного кода


Обновите пакеты с помощью pub get в вашей IDE или в командной строке. Затем используйте IDE или командную строку для выполнения статического анализа вашего кода Dart:

$ dart pub get
$ dart analyze


Или на коде Flutter:

$ flutter pub get
$ flutter analyze

Шаг 4: убедитесь, что тесты проходят


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

Шаг 5: опубликуйте свой null-safety пакет


Завершив миграцию и прогнав тесты, можете опубликовать свой пакет в качестве пререлиза. Вот краткое изложение наилучших практик:

  • Обновите версию до следующей мажорной версии (например, с 2.3.x до 3.0.0). Это гарантирует, что пользователи вашего пакета не обновятся до него, пока не будут готовы использовать null-safety. Это дает вам свободу рефакторинга API, чтобы наилучшим образом применить null-safety.
  • Переведите и опубликуйте свой пакет в качестве предварительной версии на pub.dev. (Например, используйте 3.0.0-nullsafety.0, а не 3.0.0.)

Для более подробной информации о миграции и управлении версиями см. руководство по миграции.

Преимущества гарантированной null-safety


В наших предыдущих публикациях, посвященных техническим превью null-safety в Dart и Flutter, на ряде примеров обсуждались преимущества этих перемен. Теперь, когда null-safety близится к завершению, мы видим несколько реальных примеров этого преимущества.

Более безопасный код


Совсем недавно мы обнаружили ошибку в основной ветке Flutter, из-за которой различные команды инструмента flutter вылетали на определенных конфигурациях машины с ошибкой null: The method '>=' was called on null. Основной проблемой был недавний пулл-реквест на добавление поддержки для обнаружения Android Studio 4.1. Этот пулл-реквест добавил такой код:

final int major = version?.major;
final int minor = version?.minor;
if (globals.platform.isMacOS) {
  /// plugin path of Android Studio changed after version 4.1.
  if (major >= 4 && minor >= 1) {
    ...

Можете найти ошибку? Поскольку версия может быть null, как основная, так и дополнительная версия также могут быть null. Этот баг может показаться легко находимым здесь в изоляции, но на практике подобный код проскальзывает постоянно, даже при строгом процессе код-ревью, который используется в репозитории Flutter. При null-safety статический анализ сразу же отлавливает эту проблему:



Это была довольно простая ошибка. На ранних этапах использования null-safety во внутреннем коде Google мы видели, как благодаря null-safety обнаруживаются, а затем решаются гораздо более сложные ошибки. Вот несколько примеров:

  • Внутренняя группа обнаружила, что они часто проверяют наличие null значений в коде, которые null-safety определяла как никогда не null. Эта проблема чаще всего встречается в коде, использующем protobuf, где необязательные поля возвращают значение по умолчанию, когда оно не задано, и никогда не имеют значения null. Это приводило к тому, что код неправильно проверял условие по умолчанию, смешивая значения по-умолчанию и null значения.
  • Команда Google Pay обнаружила баги в своем коде Flutter, из-за которых они не могли получить доступ к объектам Flutter State вне контекста Widget. До null-safety они возвращали null и маскировали ошибку; при null-safety анализ определил, что эти свойства никогда не могут быть null, и выдал ошибку анализа.
  • Команда Flutter обнаружила ошибку, из-за которой движок Flutter потенциально мог выйти из строя, если null был передан параметру scene в Window.render(). Во время миграции на null-safety они добавили подсказку, чтобы пометить Scene как не допускающую нулевое значение, а затем смогли легко предотвратить потенциальные сбои приложения, которые мог вызвать переданный null.

Использование надежной null-safety во время компиляции


Надежность null-safety Dart имеет еще одно важное значение: компиляторы Dart могут использовать информацию о допустимости значений null. Это может сделать ваши программы меньше и быстрее. Пока что у нас не так много реальных приложений, полностью переведенных на null-safety (ведь мы только сейчас начинаем миграцию экосистемы пакетов, от которых эти приложения зависят в плане надежности), но мы видим очень обнадеживающие результаты из основного фреймворка.

Недавно мы провели тестовую перекомпиляцию образца hello_world, чтобы измерить влияние null-safety на размер приложения. Это минимальный пример, который просто отображает «hello world». При сравнении общего размера скомпилированного кода размер несжатого (установленного на устройстве) кода сократился на 3,5% без каких-либо действий кроме перекомпиляции с надежной null-safety. Это стало возможным несмотря на то, что все приложение состояло из 10 строк кода, потому что размер кода всех включенных библиотек сократился; например, сам фреймворк Flutter (package:flutter) сократился на 3,9%.

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

В некоторых случаях мы уже видели, как null-safety приводила к росту производительности, когда переход обнаруживал изъян в логике кода. Например, мы обнаружили проблему в кеше позиционирования текста во Flutter web. Этот кеш использовал ключ, допускающий значение null, а затем по заданной логике использовал TextAlign.start при null. Эта логика вызывала ошибку в кеше, когда элементы выглядели так, будто они изменились, даже если у них все еще было значение по умолчанию. В результате часто случались нерезультативные обращения в кеш. Добавление геттера textAlign, не допускающего значения null, помогло исправить ошибку кеширования, что привело к увеличению производительности рендеринга текста в 14 раз в случаях кешируемого текста.

Начните сегодня!


Бета-версии Dart и Flutter, содержащие null-safety, уже готовы. Если вы пишете на Flutter, можете переключиться на бета-версию с помощью flutter channel beta, а затем flutter upgrade. А если вы не используете Flutter, то можете получить автономный Dart SDK из архива Dart SDK.

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

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

Спасибо за вашу поддержку и обратную связь! Мы работаем над тем, чтобы сделать Dart более надежным языком, а Flutter — более мощным фреймворком.

Michael Thomsen, продакт-менеджер Dart и Flutter, опубликовал эту статью в официальном блоге Dartlang. Если вы хотите послушать доклад Майкла и пообщаться с ним лично, приходите на DartUP 2020 Online 4 и 5 декабря и обсудите последние обновления языка с командой Dart и сообществом.