Глубокое погружение в Null и безопасность Null в Dart


Введение

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

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

В этой статье мы рассмотрим Null и безопасность Null в Dart.

В частности, мы рассмотрим следующее:

  • Введение в Null и Null safety в Dart
  • Nullable и Non-nullable типы данных Dart
  • Операторы с поддержкой Null
  • Ключевое слово late.

Рассказав об этом, давайте начнем:

Null и безопасность Null в Dart

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

Класс Null в Dart предоставляет это нулевое значение. Представьте, что нет возможности для нулевых значений, мы могли бы просто присвоить пустую строку ( «» ), чтобы представить отсутствие среднего имени. Но тогда нам пришлось бы указать это в комментарии, чтобы наши коллеги могли понять.

String middleName = “”;

Использование пустой строки, например, работает, но нам пришлось бы рассказать об этом другим коллегам. Что если мы действительно можем установить middleName в null, которым оно и является?

String middleName = null;

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

Приведенный выше код будет прекрасно работать в версиях Dart до 2.12. Однако в версиях выше этой будет возникать ошибка (во время компиляции).

A value of type ‘Null’ can’t be assigned to a variable of type ‘String’

До внедрения Null safety, Dart позволял нам передавать значения, которые могут быть null, вызывая метод на них во время компиляции (т.е. во время набора кода) без возникновения ошибки. В какой-то момент это может быть опасно, потому что если это значение в конечном итоге окажется null, то наш код сломается во время выполнения. Мы же не хотим, чтобы наши пользователи видели наши недостатки, верно?

Давайте рассмотрим пример:

Предположим, мы хотим проверить длину нашей переменной middleName, мы можем определить функцию:

int capitalize(String middleName){
  return middleName.toUpperCase();
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Давайте попробуем этот код в нашем файле main.dart, в функции main.

void main(){
    print (capitalize(‘Adeyemi’)); // ‘ADEYEMI’
    print(capitalize(‘learning flutter’)); // 'LEARNING FLUTTER’
        }
Войти в полноэкранный режим Выйти из полноэкранного режима

Все работает нормально до тех пор, пока мы не передаем null в качестве значения в функцию capitalize. Если в Dart <2.12 (до null safety) мы передаем null в качестве параметра в capitalize, код не выдает ошибку при компиляции, но если мы его запустим, то код оборвется с ошибкой.

NoSuchMethodError: The getter ‘length’ was called on null.

Мы не хотим, чтобы наши пользователи видели это, и, возможно, не сможем поймать ошибку, поскольку она не появляется до выполнения кода. Вот почему нам нужна безопасность нуля. Если тот же код, что и выше, написать в Dart версии >2.12 (т.е. null safe version), то при передаче null в качестве аргумента для capitalize немедленно возникнет ошибка.

print(capitalize(null));
Ошибка показана:

The argument type ‘Null’ can’t be assigned to the parameter type ‘String’

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

Nullable и Non-nullable типы данных

С появлением в Dart null safety, Dart гарантирует нам, что все его типы данных по умолчанию не являются null. Это означает, что все типы данных должны иметь значение, если мы не хотим, чтобы было иначе (т.е. мы хотим, чтобы оно было null).

Non-nullable типы — это обычные простые для понимания типы, которые мы знаем, они объявляются без использования ? (знак вопроса) в конце.

Примеры ненулевых типов:

  • Строка: «Flutter», «Dart», ‘Google’.
  • int: 2, 35, 7, 22, 100
  • double: 0.003, 22.7, 3.142
  • bool: true, false
  • Спорт: баскетбол, футбол

Это означает, что все эти значения допустимы. Например, мы можем написать в нашем редакторе.

int myAge = 20;
bool userRegistered = true;
String myWord = “I love Flutter”;
Sport football = Sport(name: “Football”, players: 11);
Войти в полноэкранный режим Выйти из полноэкранного режима

Все это будет работать нормально, поскольку они не являются нулевыми. Но когда мы пытаемся это сделать:

String myName = null; //A red underline shows on compile time

Сразу же появляется ошибка компиляции.

A value of type 'Null' can’t be assigned to a variable of type ‘String’.

Но как же тогда присвоить переменной значение null?

Ссылка на изображение: express.co.uk

Здесь на помощь приходит Dart nullable types.

Чтобы сделать тип nullable, достаточно добавить символ ‘?’ перед типом.

int? myAge = null; //This works

Тип nullable допускает нулевое значение в дополнение к своему исходному типу. Это означает, что тип ‘double?’ может принимать такие значения, как 1.42, 23.5, null, 0.005 и другие.

Вопросительный знак (?) перед типом не является операцией над типом данных, скорее он указывает на совершенно новый тип данных.

String? — это совершенно другой тип данных, отличный от String.

Ссылка: Dart doc

Каждый ненулевой тип Dart имеет соответствующий нулевой тип

Также можно сказать, что ненулевой тип является подтипом соответствующего ему нулевого типа. То есть, int является подтипом int?, поскольку int? может принимать значение int.

Наконец, если переменной с нулевым значением изначально не присваивается значение, ей присваивается тип Null.

int? year;
String? middleName;
Вход в полноэкранный режим Выход из полноэкранного режима

Если это выводится на консоль:

print(‘Year is $year’);
print(‘Middle name is $middleName’);
Войти в полноэкранный режим Выйти из полноэкранного режима

У нас есть:

Year is null
Middle name is null
Войти в полноэкранный режим Выйти из полноэкранного режима

Операторы с нулевой безопасностью

Одной из основных проблем до внедрения null safety было то, как легко разработчики могли забыть обработать значения, которые могут оказаться нулевыми. Теперь, с null safety, Dart гарантирует, что разработчики не смогут выполнять определенные операции над значениями с нулевым значением, если они не обработали возможность null.
Например:

int? age;
print(age.isEven);
Войти в полноэкранный режим Выйти из полноэкранного режима

Мгновенно, во время компиляции, Dart выдает ошибку unchecked_use_of_nullable_value:

The property ‘isEven’ can’t be unconditionally accessed because the receiver can be ‘null’

Затем он продолжает предлагать:

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

Dart предоставляет набор операторов для обработки возможности нулевых значений, которые известны как операторы null-aware.

Ниже перечислены операторы null-aware:

  • Оператор if-null (??)
  • Оператор доступа с нулевым значением (?.)
  • Оператор утверждения нуля (!)
  • Null-aware оператор присваивания (??=)
  • Null-aware каскадный оператор(?..)
  • Null-aware index operator (?[ ])

Теперь давайте посмотрим, как работают эти операторы

Оператор if-null (??)

Если:

int? numberOfYears;
final age = numberOfYears ?? 0;
Войти в полноэкранный режим Выйти из полноэкранного режима

Это можно прочитать как «Если numberOfYears не равно null, то set age равен numberOfYears, если равно null, то set age равен 0».

Следует отметить, что использование ?? гарантирует, что значение age никогда не может быть нулевым. Следовательно, Dart определяет переменную как int, а не int?
Из вышесказанного следует, что использование оператора ?? аналогично написанию:

  int? numberOfYears;
  int? age;
  if(numberOfYears != null){
    age = numberOfYears;
  }else{
    age = 0;
  }
Войти в полноэкранный режим Выйти из полноэкранного режима

Это примерно 6 дополнительных строк кода вместо одной строки с использованием оператора ??

Оператор доступа с нулевым значением (?.)

Ранее мы пробовали использовать age.isEven, и увидели, что Dart выдает ошибку во время компиляции. Чтобы обойти это, один из предложенных способов — использовать оператор ?. для age. То есть, age?.isEven.

Это можно объяснить просто: «Если возраст равен null, верните null, иначе, если возраст имеет значение, верните true/false, если он четный или нет».

Оператор ?. следует использовать, когда мы не полностью уверены, будет ли переменная нулевой или нет. Таким образом, если она окажется нулевой, мы легко справимся с этим, если же она не будет нулевой, мы справимся с этим соответствующим образом.

Оператор утверждения Null (!)

В отличие от оператора доступа с учетом Null (?.), когда мы уверены, что переменная, с которой мы работаем, точно не является null, мы можем использовать оператор ! (оператор bang).

age!.isEven

В этом случае мы говорим: «Да, я уверен, что возраст не будет равен нулю, поэтому просто проверьте, не равен ли он». Однако за это приходится платить, поскольку если возраст в конечном итоге окажется равным null, наше приложение упадет во время выполнения.

Оператор ! следует рассматривать как опасный оператор и использовать только тогда, когда мы очень уверены, что наша переменная не будет равна null. Используя этот оператор, мы просто говорим Dart: «Смотри, я выхожу из режима null safety для этой переменной, потому что я уверен, что она null, так что позволь мне самому с этим разобраться».

Оператор присваивания с нулевым значением (??=)

Он работает аналогично оператору if-null (??), но мы используем ??= при обновлении одного и того же значения.
Например:

Когда приходит время использовать переменную address, целесообразно проверить, не был ли адрес присвоен ранее. Это можно сделать с помощью оператора ?? as:

country = country ?? “Nigeria”;

Но поскольку это та же переменная, которую мы хотим обновить, мы можем просто написать:

country ??= “Nigeria”;

Проще говоря, оператор ??= просто говорит: «Если страна равна null, установите страну в «Нигерия», в противном случае сохраните текущее значение страны».

Каскадный оператор с учетом нуля (?..)

В Dart каскадный оператор (…) позволяет нам вызывать несколько методов для одного и того же объекта за один раз ИЛИ устанавливать несколько свойств для одного и того же объекта.

Например, зададим класс Sport следующим образом:

class Sport{
  String? name;
  int? players;
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Если класс Sport не является nullable, мы можем использовать каскадный оператор следующим образом:

    Sport sport = Sport()
        ..name = "Football"
        ..players = 11;
Войти в полноэкранный режим Выйти из полноэкранного режима

Чаще всего каскадный оператор с нулевым значением используется на объекте Path во время рисования во Flutter.

Path? path;
Path
   ?..moveTo(0, 0)
    ..lineTo(0, 4)
    ..lineTo(4, 4)
    ..lineTo(4, 0)
    ..lineTo(0,0);
Вход в полноэкранный режим Выйти из полноэкранного режима

Каскадный оператор null-aware может быть замкнут, его нужно использовать только в первый раз в цепочке, и если переменная (path) равна null, все операции в цепочке не вызываются.

Null-aware Index Operator (?[])

В Dart индексирование используется для доступа к элементу списка.

List<String> myColors = [“Blue”, “Green”, “Red”];

Индексы начинаются с 0, следовательно, чтобы вызвать «Green», мы делаем следующее:

var green = myColors[1];

Теперь с null-safety мы знаем, что список может быть нулевым, поэтому для индексации элемента этого списка у нас есть вспомогательный оператор ?[].

Рассмотрим пример:

Несмотря на то, что Список может быть нулевым, но так как мы присвоили ему значение, он был проиндексирован.

Устанавливаем значение myColors равным null.

myColors = null;

Теперь, когда мы снова пытаемся получить значение «Green», мы используем оператор null-aware index таким образом:

String? green = myColors?[1];

В буквальном смысле это означает: «Если myColors равен null, присвойте зеленому значение null. В противном случае присвоить зеленому цвету 1-й индекс myColors».
Если бы мы попытались получить доступ к значению из нулевого списка до появления null-safety, это привело бы к аварийному завершению работы нашего приложения. Но опять же, благодаря нулевой безопасности.

Ключевое слово late

Иногда мы хотим сделать нашу переменную ненулевой, но у нас нет значения для ее инициализации. Ключевое слово late помогает нам пообещать Dart, что значение будет присвоено переменной до ее использования, но это произойдет позже. Использование ключевого слова late означает, что значение переменной не будет вычислено до тех пор, пока мы не обратимся к ней в первый раз. Это называется ленивой инициализацией. Существуют различные способы, с помощью которых мы можем присвоить значение этой переменной перед ее использованием, например, использование initState (для StatefulWidget) или вызов функции, возвращающей это значение.

Всякий раз, когда мы пытаемся получить доступ к переменной, которую мы специфицировали с помощью ключевого слова late без последующего присвоения ей значения, мы нарушаем свое обещание, поэтому получаем ошибку LateInitializationError.
Пример:

class Sport{
  late String name;
}
Вход в полноэкранный режим Выход из полноэкранного режима

И в другом месте мы имеем нечто подобное.

Sport sport = Sport();
print(sport.name) //an error would be thrown since we have not initialized name.
Вход в полноэкранный режим Выход из полноэкранного режима

LateInitializationError: Field ‘name’ has not been initialized

Заключение

В этой статье мы рассмотрели концепцию Null и красоту Null safety. При правильном использовании Null safety может помочь вам в написании более качественных и менее подверженных ошибкам кодов.

Дополнительные ресурсы

  • Понимание безопасности Null
  • Dart Null Safety: Окончательное руководство по не нулевым типам
  • Видео — Null Safety в Dart и Flutter

Оцените статью
devanswers.ru
Добавить комментарий