Как лучше всего преобразовать старый код Dart, который вызывает ошибку «Необходимо назначить ненулевую переменную»?

#dart #dart-null-safety #null-safety

Вопрос:

Возьмите следующий ненулевой безопасный код дротика:

 static String appBarShiftTitleString(int fromEpochSeconds) {  String monthWord;   String dayWord;   DateTime dt = DateTime.fromMillisecondsSinceEpoch(fromEpochSeconds * 1000);   switch (dt.month) {  case 1:  monthWord = "Jan";  break;  case 2:  monthWord = "Feb";  break;  case 3:  monthWord = "Mar";  break;  case 4:  monthWord = "Apr";  break;  case 5:  monthWord = "May";  break;  case 6:  monthWord = "Jun";  break;  case 7:  monthWord = "Jul";  break;  case 8:  monthWord = "Aug";  break;  case 9:  monthWord = "Sep";  break;  case 10:  monthWord = "Oct";  break;  case 11:  monthWord = "Nov";  break;  case 12:  monthWord = "Dec";  break;  }   switch (dt.weekday) {  case 1:  dayWord = "Mon";  break;  case 2:  dayWord = "Tue";  break;  case 3:  dayWord = "Wed";  break;  case 4:  dayWord = "Thu";  break;  case 5:  dayWord = "Fri";  break;  case 6:  dayWord = "Sat";  break;  case 7:  dayWord = "Sun";  break;  }   return dayWord   ' '   monthWord   ' '   dt.day.toString(); }  

Android Studio говорит: «Локальная переменная ‘dayWord’, не имеющая значения null, должна быть назначена, прежде чем ее можно будет использовать».

Я понимаю ошибку и обнаружил, что могу просто изменить первые две строки метода следующим образом:

 String monthWord = "error!"; String dayWord = "error!";  

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

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

Спасибо!

Комментарии:

1. Как ты получил этот код? Вы использовали Dart migrate в рабочем проекте или это сделано вручную? Потому что, если это устаревший код, он должен работать нормально, если только вы не изменили pubspec.yaml minsdk на 2.12 вручную.

2. Это мой собственный код из старого проекта. Да, я эффективно изменил min на 2.12, создав новый стандартный проект flutter с использованием последних версий Android Studio Flutter Dart. Затем я начал вырезать и вставлять из старого проекта. Это было сделано специально-я бы предпочел столкнуться с ошибками, исправить их и учиться в процессе, чем использовать инструмент миграции.

Ответ №1:

В общем, у вас есть несколько вариантов:

1. Инициализируйте переменную до некоторого ненулевого значения sentinel, а assert затем:

 String monthWord = ''; // ... switch (dt.month) {  // ... } assert(monthWord.isNotEmpty);  

Это приведет к тому, что отладочные сборки будут запускаться AssertionError во время выполнения, если вы не обработаете обращение для этого в switch .

2. Сделайте переменную обнуляемой и используйте оператор утверждения null:

 String? monthWord; // ... switch (dt.month) {  // ... } monthWord!;  // Since `monthWord` is a local variable, it will now be promoted to a // non-nullable `String` type.  

Это приведет к появлению a TypeError во всех типах сборки, если вы не зададите переменной ненулевое значение.

3. Сделайте переменную late

Объявление переменных как late состояний означает, что вы обещаете, что переменные будут инициализированы до того, как они будут прочитаны. Компилятор создаст проверки во время выполнения, которые проверят, что переменная инициализирована при попытке доступа к ней. Это приведет к появлению a LateInitializationError во всех типах сборки, если вы не зададите переменную.

4. Добавьте default случай, который бросает

Если все ваши case s задают локальную переменную, добавление default случая, который вызывает, позволяет компилятору сделать вывод, что эта переменная всегда должна быть установлена, если код после выполнения switch инструкции достигнут:

 String monthWord; // No explicit initialization required! // ... switch (dt.month) {  case 1:  monthWord = "Jan";  break;   // ... etc. ...   default:  throw AssertionError('Unhandled case: ${dt.month}');  }  // The compiler now can deduce that `monthWord` is guaranteed to be // initialized.  

(Обратите внимание, что для этой цели не default следует добавлять регистр, если вы используете switch инструкцию для enum типа. Для enum s компилятор и анализатор могут определить, являются ли ваши case s исчерпывающими, и выдадут предупреждения об анализе, если вы случайно пропустите какие-либо случаи.)


Что касается того, какой подход использовать, это в основном вопрос предпочтений. Все они в основном эквивалентны в том смысле, что они приведут к ошибкам во время выполнения. Я лично выбрал бы #1 ( assert ) или #4 ( default случай), чтобы избежать ненужных проверок в сборках выпуска.

В вашем конкретном примере я бы также просто использовал DateTime.month и DateTime.day в качестве индексов в List названиях месяцев и дней соответственно:

 const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];  assert(months.length == 12); assert(days.length == 7);  var monthWord = months[dt.month - 1]; var dayWord = days [dt.day - 1];