Обработка исключений и ошибок

Все исключения в Dart — это непроверяемые исключения. В функциях не нужно объявлять типы выбрасываемых ошибок, их перехват не является обязательным.

Различают два вида:

  • Exception:Предсказуемые ошибки бизнес-логики (неверный формат, некорректные параметры и т.д., рекомендуется перехватывать и обрабатывать).
  • Error:Фатальные ошибки программы (несовпадение типов, нулевой указатель, нереализованные методы и т.д., обычно не перехватываются).

Любой не нулевой объект можно выбросить оператором throw. В структурированных проектах выбрасывают только подклассы Exception/Error.

Неперехваченное исключение останавливает текущий изолят и вызывает краш приложения.

throw: выброс исключения

1. Выброс системных исключений

void main() {
  // Исключение формата
  throw FormatException("Требуется как минимум один сегмент данных");
}
Code language: Dart (dart)
D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Unhandled exception:
FormatException: Описание ошибки
#0      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:3:3)
#1      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#2      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)

Этот код выбрасывает стандартное исключение с пользовательским текстом, который отобразится при запуске. Может показаться, что это бесполезно, но механизм выброса исключений критически важен для разработки. Например, при регистрации пользователя мы проверяем корректность ввода. Если данные не соответствуют правилам, сразу выбрасываем исключение, прерываем процесс и оповещаем пользователя — это блокирует попадание некорректных данных и сохраняет целостность информации.

2. Выброс любых объектов

Не рекомендуется для промышленной среды

void main() {
  throw "Текстовое описание ошибки!";
}Code language: Dart (dart)

Результат выполнения

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Unhandled exception:
Текстовое описание ошибки!
#0      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:2:3)
#1      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#2      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)

3. Использование throw в стрелочных функциях (throw является выражением)

class Point {}
void distanceTo(Point other) => throw UnimplementedError("Этот метод не реализован");

void main() {
  distanceTo(Point());
}

Code language: Dart (dart)

III. Перехват исключений через try /on/catch

Различия синтаксиса

  • on ТипИсключения:Подходит только для исключений указанного типа, объект исключения получить нельзя.
  • catch(e):Перехватывает все исключения, переменная e хранит экземпляр исключения.
  • catch(e, s):e = объект исключения, s = стек вызовов StackTrace.

1: Перехват одного типа исключения через on


// Создание собственного класса исключения, реализующего Exception
class OutOfLlamasException implements Exception {}

// Функция, выбрасывающая наше исключение
void breedMoreLlamas() {
  throw OutOfLlamasException();
}

void buyMoreLlamas() {
  print("Функция buyMoreLlamas выполнена");
}

void main() {
  try {
    breedMoreLlamas(); // Вызов функции с рискованным кодом
  } on OutOfLlamasException {
    buyMoreLlamas(); // Выполняется при перехвате OutOfLlamasException
  }
}
Code language: Dart (dart)

Результат выполнения

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Функция buyMoreLlamas выполненаCode language: CSS (css)
try  {
    // Код, где могут возникать ошибки
} on КлассИсключения {
   // Логика при перехвате ошибки
}
Code language: Dart (dart)

try on — это специальный синтаксис для типового перехвата исключений.

2: Многоуровневый перехват по ветвям

Более точные совпадения обрабатываются первыми

class OutOfLlamasException implements Exception {}

void breedMoreLlamas() {
  throw FormatException("Неверный формат данных");
}

void main() {
  try {
    breedMoreLlamas();
  }
  // 1. Точное совпадение с собственным исключением
  on OutOfLlamasException {
    print("Возникла ошибка OutOfLlamasException");
  }
  // 2. Перехват всех подклассов Exception
  on Exception catch (e) {
    print("Обычное бизнес-исключение: $e");
  }
  // 3. Запасной перехват для Error и любых других выброшенных объектов
  catch (e) {
    print("Неизвестная критическая ошибка: $e");
  }
}

Code language: Dart (dart)

Вывод: Обычное бизнес-исключение: FormatException: Неверный формат данных

Можно использовать несколько блоков on для отдельного перехвата каждого типа Exception, дополнив финальным catch как универсальный запасной вариант.

3: Перехват исключения + вывод стека вызовов StackTrace

void main() {
  try {
    int a = null;
    print(a + 1);
  } catch (e, stack) {
    print("Сообщение исключения: $e");
    print("Полный стек вызовов:\n$stack");
  }
}

Code language: Dart (dart)

Этот код выводит не только текст ошибки, но и всю цепочку вызовов функций для отладки.

D:\dartdemo\firstdart>dart run
Building package executable...
Failed to build firstdart:firstdart:
bin/firstdart.dart:3:13: Error: A value of type 'Null' can't be assigned to a variable of type 'int'.
    int a = null;Code language: PHP (php)

IV. rethrow — повторный выброс

Частичная обработка с повторным выбросом выше по стеку

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

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

void misbehave() {
  try {
    dynamic foo = true;
    print(foo++); // Тип true не поддерживает инкремент, возникает ошибка времени выполнения
  } catch (e) {
    print("Локальная обработка в misbehave: ${e.runtimeType}");
    rethrow; // Повторно выбросить для внешнего try
  }
}

void main() {
  try {
    misbehave();
  } catch (e) {
    print("Конечная обработка в main: ${e.runtimeType}");
  }
}

Code language: Dart (dart)

Порядок выполнения:

Локальная обработка в misbehave: NoSuchMethodError
Конечная обработка в main: NoSuchMethodError

В примере ошибка обрабатывается как в функции misbehave, так и в main.

V. Блок finally

Выполняется всегда, независимо от возникновения ошибки

Два основных сценария использования:

  1. Только try + finally: исключение не перехватывается, программа крашится после выполнения finally
  2. try + catch + finally: сначала обработать ошибку, затем выполнить finally (предназначен для освобождения ресурсов)

1: try + finally

void cleanLlamaStalls() => print("Очистка ресурсов, гарантированный блок выполнения");
void breedMoreLlamas() => throw Exception("Произошла ошибка");

void main() {
  try {
    breedMoreLlamas();
  } finally {
    cleanLlamaStalls();
  }
}

Code language: Dart (dart)

Код очистки запустится, но неперехваченное исключение завершит работу программы.

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Очистка ресурсов, гарантированный блок выполнения
Unhandled exception:
Exception: Произошла ошибка
#0      breedMoreLlamas (file:///D:/dartdemo/firstdart/bin/firstdart.dart:2:27)
#1      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:6:5)
#2      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#3      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)

2: try + catch + finally

Стандартный синтаксис для освобождения ресурсов, например закрытие открытых файлов

void cleanLlamaStalls() => print("Закрыть файлы / освободить системные ресурсы");
void breedMoreLlamas() => throw FormatException("Ошибка парсинга");

void main() {
  try {
    breedMoreLlamas();
  } catch (e) {
    print("Исключение перехвачено: $e");
  } finally {
    cleanLlamaStalls();
  }
}

Code language: Dart (dart)

Порядок выполнения: выброс исключения → обработка в catch → выполнение finally

Результат выполнения

D:\dartdemo\firstdart>dart run
Building package executable...
Built firstdart:firstdart.
Исключение перехвачено: FormatException: Ошибка парсинга
Закрыть файлы / освободить системные ресурсы

VI. assert — утверждения

Работает только при разработке и отладке, полностью отключен в релизной сборке

assert(логическоеУсловие, [опциональныйТекстОшибки])

Если условие равно false, выбрасывается AssertionError. В промышленной сборке весь код assert игнорируется без потерь производительности.

1: Простой пример утверждения

void main() {
  String? text;
  int number = 120;
  String url = "http://foxdevelop.com";

  assert(text != null); // Проверка на непустое значение
  assert(number < 100); // Проверка диапазона числа
  assert(url.startsWith("https")); // Проверка протокола ссылки
}

Code language: Dart (dart)

При запуске этого кода по умолчанию ничего не выведется!

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

2: Пользовательский текст ошибки для утверждения

void main() {
  String url = "http://test.com";
  assert(
    url.startsWith("https"),
    "Адрес $url должен начинаться с https",
  );
}

Code language: JavaScript (javascript)

Можно самостоятельно задать текст, выводящийся при сбое утверждения.

Как включить assert

  1. Режим Debug Flutter: утверждения включены по умолчанию
  2. Запуск через команду dart: dart --enable-asserts имя_файла.dart
  3. Релизная сборка: весь код утверждений удаляется, нет падения производительности
D:\dartdemo\firstdart\bin>dart --enable-asserts firstdart.dart
Unhandled exception:
'file:///D:/dartdemo/firstdart/bin/firstdart.dart': Failed assertion: line 6 pos 10: 'text != null': is not true.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:67:4)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:49:5)
#2      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:6:10)
#3      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#4      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)

Команды dart run и простой dart отключают assert по умолчанию, нужно добавить флаг --enable-asserts:

Пример запуска

dart run --enable-asserts main.dartCode language: CSS (css)

Если терминал находится в корне проекта, можно использовать сокращённую версию:

Команда запуска:

dart run --enable-asserts

dart run:Прямой запуск исходного кода Dart для разработки и отладки

dart имя_файла:Упрощённый синтаксис для одного файла

dart compile exe:Компиляция в самостоятельный исполняемый файл, assert всегда отключен в скомпилированном бинаре

Файлы Dart лежат в папке bin проекта. Можно перейти в bin и запустить файл напрямую, или подняться в корень проекта для запуска всего проекта.

dart run —enable-asserts

Ниже два варианта: запуск из папки bin и запуск из корня проекта.

D:\dartdemo\firstdart\bin>dart --enable-asserts firstdart.dart
Unhandled exception:
'file:///D:/dartdemo/firstdart/bin/firstdart.dart': Failed assertion: line 6 pos 10: 'text != null': is not true.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:67:4)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:49:5)
#2      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:6:10)
#3      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#4      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)

D:\dartdemo\firstdart\bin>dart run
Building package executable...
Built firstdart:firstdart.

D:\dartdemo\firstdart\bin>cd ..

D:\dartdemo\firstdart>dart run --enable-asserts
Building package executable...
Built firstdart:firstdart.
Unhandled exception:
'file:///D:/dartdemo/firstdart/bin/firstdart.dart': Failed assertion: line 6 pos 10: 'text != null': is not true.
#0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:67:4)
#1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:49:5)
#2      main (file:///D:/dartdemo/firstdart/bin/firstdart.dart:6:10)
#3      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:314:19)
#4      _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:193:12)Code language: PHP (php)

Если условие утверждения равно true, исключения не возникнет.

Измените пример: замените http на https в ссылке, замените text != null на text == null, присвойте number значение 99

void main() {
  String? text;
  int number = 99;
  String url = "https://foxdevelop.com";

  assert(text == null); // Проверка на ноль
  assert(number < 100); // Проверка диапазона числа
  assert(url.startsWith("https")); // Проверка протокола ссылки
}Code language: Dart (dart)

При повторном запуске ошибок не появится.

D:\dartdemo\firstdart>dart run --enable-asserts
Building package executable...
Built firstdart:firstdart.Code language: CSS (css)

VII. Полный пример

void cleanResource() => print("finally: освободить занятые ресурсы");

void testError() {
  try {
    int num = 150;
    assert(num < 100, "Число должно быть меньше 100");
    throw FormatException("Неверный формат данных");
  } on FormatException catch (e) {
    print("Перехвачено исключение формата: $e");
    rethrow;
  } finally {
    cleanResource();
  }
}

void main() {
  try {
    testError();
  } catch (e) {
    print("main перехватывает повторно выброшенное исключение: $e");
  }
}
Code language: PHP (php)

Результат выполнения

D:\dartdemo\firstdart>dart run --enable-asserts
Building package executable...
Built firstdart:firstdart.
finally: освободить занятые ресурсы
main перехватывает повторно выброшенное исключение: 'file:///D:/dartdemo/firstdart/bin/firstdart.dart':
 Failed assertion: line 6 pos 12: 'num < 100': Число должно быть меньше 100Code language: PHP (php)

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *