В чем разница?

Фон

  • В Dart Exception означает ожидаемое плохое состояние, которое может произойти во время выполнения. Поскольку эти исключения ожидаются, вы должны их перехватить и обработать соответствующим образом.
  • Error, с другой стороны, предназначен для разработчиков, использующих ваш код. Вы выдаете ошибку, чтобы сообщить им, что они неправильно используют ваш код. Как разработчик, использующий API, вы не должны обнаруживать ошибок. Вы должны позволить им разрушить ваше приложение. Пусть авария станет для вас сообщением о том, что вам нужно выяснить, что вы делаете не так.
  • assert похож на Error в том, что он предназначен для сообщения о плохих состояниях, которые никогда не должны происходить. Разница в том, что утверждения проверяются только в режиме отладки. В производственном режиме они полностью игнорируются.

Подробнее о разнице между Exception и Error читайте здесь.

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

Пример выброса исключения

Это происходит из platform_channel.dart в репозитории Flutter:

@optionalTypeArgs
Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async {
  assert(method != null);
  final ByteData? result = await binaryMessenger.send(
    name,
    codec.encodeMethodCall(MethodCall(method, arguments)),
  );
  if (result == null) {
    if (missingOk) {
      return null;
    }
    throw MissingPluginException('No implementation found for method $method on channel $name');
  }
  return codec.decodeEnvelope(result) as T;
}

MissingPluginException здесь - это запланированное плохое состояние, которое может произойти. Если это произойдет, пользователи API канала платформы должны быть готовы с этим справиться.

Пример выдачи ошибки

Это происходит из artifacts.dart в репозитории flutter_tools.

TargetPlatform _currentHostPlatform(Platform platform) {
  if (platform.isMacOS) {
    return TargetPlatform.darwin_x64;
  }
  if (platform.isLinux) {
    return TargetPlatform.linux_x64;
  }
  if (platform.isWindows) {
    return TargetPlatform.windows_x64;
  }
  throw UnimplementedError('Host OS not supported.');
}

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

Пример использования утверждений

Это происходит из overlay.dart в репозитории Flutter:

OverlayEntry({
  @required this.builder,
  bool opaque = false,
  bool maintainState = false,
}) : assert(builder != null),
      assert(opaque != null),
      assert(maintainState != null),
      _opaque = opaque,
      _maintainState = maintainState;

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

Резюме

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

Изначально я опубликовал эту статью как ответ на вопрос о переполнении стека здесь.