Действительно ли иерархии исключений полезны?

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

Иерархии исключений действительно полезны, xor все исключения должны быть получены из класса исключения на основе языка?

Ответ 1

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

Включение всех исключений приложений в одном месте является наиболее распространенным вариантом использования. Это позволяет вам поймать MyAppException в любое время, когда вы захотите уловить все ошибки, поступающие из вашего приложения, но при необходимости поймать более конкретные исключения. (В .NET для этого был предназначен класс ApplicationException, но он был устаревшим по разным причинам.)

Но вы также можете группировать исключения вместе на границах модулей или любым другим способом, который имеет смысл. Используйте FooModuleException для исключений, исходящих из модуля Foo, но улавливайте и обрабатывайте FooModuleMustFrobnicate специально для Foo. Или любая эквивалентная ситуация.

Я использовал все эти шаблоны в разное время.

Ответ 2

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

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

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

Рассмотрим следующий пример:

public abstract class Base
{
   /// Perform some business-logic operation
   /// and throw BaseFooException in certain circumstances
   public abstract void Foo();
}

public class Derived1 : Base
{
   /// Perform some business-logic operation
   /// and throw DerivedFooException in certain circumstances.
   /// But caller could catch only BaseFooException
   public abstract void Foo() {}
}

class BaseFooException : Exception {}

class DerivedFooException : BaseFooException {}

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

Ответ 3

TL; DR

  • Иерархии исключений бесполезны для обнаружения исключений (за исключением базовых различий между неподдерживаемыми системными ошибками (Java Error) и "нормальными" исключениями времени выполнения. Понятие о том, что "вам нужна разная степень детализации при поиске в разных места" является ИМХО в основном ошибочным. (за исключением повторения, чтобы различать исключения и утверждения и другие системные неустранимые вещи.)
  • Маленькие иерархии исключений имеют смысл в современных языках как средство построения различных ошибок с максимально возможной информацией.

Я медленно приступаю к рассмотрению всей концепции произвольно типизированных исключений, поскольку я вижу, что они реализованы и предположительно будут использоваться в С++, С# и Java как абсолютно бесполезные.

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

Во всех моих кодировках на С++, Java и С# я обнаружил - за исключительные исключения, которые не были использованы для потока управления - не один случай, когда я хотел отфильтровать (т.е. поймать или не поймать) исключения по их "иерархическому" типу. То есть, есть редкие случаи, когда функция выбрасывает 3 разных исключения, и я хочу специально обрабатывать один из них и обрабатывать два других как общий "сбой" (как описано ниже), но я никогда не встречал этого в случай, когда иерархия исключений была полезна в том, что я мог бы осмысленно группировать два случая по базовым классам.

Если какая-либо операция (где мне небезопасна) в моей программе завершается с ошибкой с каким-либо исключением, то эта операция считается неудачной и:

  • Я получаю информацию, которую я могу получить от пойманного объекта
  • продолжить обработку контекстуальных ошибок (повторить, отобразить ошибку, журнал, повторить попытку, восстановить и т.д.)

Я думаю, что Java является единственным из трех языков, которые по крайней мере имеют правильный подход с их иерархией, а именно:

  • Throwable не используется в коде пользователя
  • Error материал, который вы никогда не захотите поймать - просто дамп ядра
  • Exception - для всех вещей, которые вы в основном всегда хотите поймать (за исключением случаев, когда интерфейс поврежден и использует исключение, если он действительно не должен)

//

Иерархия С++ <stdexcept> имеет правильный базовый подход при дифференциации между logic_error - в основном утверждениями - и runtime_error - вещи, которые просто неожиданно потерпели неудачу. (Подклассы тезисов в значительной степени не имеют значения при ловле.)

Кроме того, что слишком много, происходит непосредственно из std::exception, поэтому вы не можете использовать дифференциацию, предоставляемую средой выполнения и логическая ошибка. (И, конечно, любой код может свободно бросать все, что захочет, поэтому есть хорошие шансы, что есть куча логических классов, которые действительно должны быть runtime_errors и наоборот.


Чтобы процитировать другой ответ:

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

но по моему опыту я никогда не хочу "ловить в разных местах", потому что это просто не имеет смысла.

Я либо хочу поймать, и в этом случае я поймаю все (по модулю такие вещи, как исключения класса Error из Java), или я не хочу ловить, и в этом случае иерархия не нужна, потому что нет уловки.


Возьмите открытие и чтение из файла:

Если есть (будет) какой-либо тип исключения, о котором я особенно забочусь, потому что я могу что-то с этим сделать ( "восстановить" ), тогда это не должно сообщаться как исключение вообще, потому что это нормальный поток управления. (Если ошибка такова и такая, я могу сделать такое-то в основном локально.)

Если, с другой стороны, я ничего не могу сделать об ошибке, но запись операции с файлом не удалась, тогда наличие какой-либо гранулярности catch совершенно бесполезно.