Что такое «хорошее количество» исключений для моей библиотеки?

20

Мне всегда было интересно, сколько разных классов исключений я должен реализовать и выбросить для разных частей моего программного обеспечения. Моя конкретная разработка обычно связана с C ++ / C # / Java, но я считаю, что это вопрос для всех языков.

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

Компромиссы, которые я вижу, включают:

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

Сценарии, в которых я хочу понять использование исключений, включают в себя:

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

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

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

Как разработчики, что вы предпочитаете видеть?

Если есть МНОГИЕ исключения, беспокоит ли вас ошибка обработки их в любом случае?

У вас есть предпочтения для типов обработки ошибок в зависимости от стадии операции?

пушинка
источник
1
возможно связано: programmers.stackexchange.com/questions/3713/…
Fuzz
5
«API менее доступен для пользователей», - можно справиться с помощью нескольких классов исключений, которые все наследуются от общего базового класса для API. Затем, как только вы начинаете, вы можете видеть, из какого API получено исключение, не беспокоясь о всех деталях. К тому времени, когда вы завершите свою первую программу с использованием API, тогда, если вам понадобится больше информации об ошибке, вы начнете понимать, что на самом деле означают различные исключения. Конечно, вы одновременно должны хорошо играть со стандартной иерархией исключений языка, который вы используете.
Стив Джессоп
Уместная точка Стив
Fuzz

Ответы:

18

Я держу это простым.

Библиотека имеет базовый тип исключений, расширенный от std ::: runtime_error (это из C ++, применимо в зависимости от других языков). Это исключение принимает строку сообщения, чтобы мы могли войти; каждая точка выброса имеет уникальное сообщение (обычно с уникальным идентификатором).

Вот и все.

Примечание 1 : В ситуациях, когда кто-то, поймавший исключение, может исправить исключения и перезапустить действие. Я добавлю производные исключения для вещей, которые потенциально могут быть однозначно исправлены в удаленном месте. Но это очень и очень редко (помните, что зрелище вряд ли будет близко к точке броска, поэтому решить проблему будет сложно (но все зависит от ситуации)).

Примечание 2 : Иногда библиотека настолько проста, что не стоит давать ей свое собственное исключение, что делает std :: runtime_error. Важно иметь исключение, только если способность отличить его от std :: runtime_error может дать пользователю достаточно информации, чтобы что-то с ним сделать.

Примечание 3 : В классе я обычно предпочитаю коды ошибок (но они никогда не будут выходить через общедоступный API моего класса).

Глядя на ваши компромиссы:

Компромиссы, которые я вижу, включают:

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

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

Дополнительные классы исключений позволяют включать информацию об ошибке в исключение, а не просто строковое сообщение или код ошибки

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

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

Меньше исключений, но встраивание кода ошибки, который можно использовать как поиск

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

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

Возвращение кодов ошибок и флагов непосредственно из функций (иногда невозможно из потоков)

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

Реализована система события или обратного вызова при ошибке (предотвращает раскручивание стека)

Этот метод часто используется в сочетании с другим механизмом обработки ошибок (не как альтернатива). Подумайте о своей программе Windows. Пользователь инициирует действие, выбирая пункт меню. Это создает действие в очереди событий. Очередь событий в конечном итоге назначает поток для обработки действия. Предполагается, что поток обрабатывает действие и в конечном итоге возвращается в пул потоков и ожидает другую задачу. Здесь исключение должно быть поймано в основе потоком, которому поручено задание. Результат отлова исключения обычно приводит к тому, что для основного цикла генерируется событие, которое в конечном итоге приводит к отображению пользователю сообщения об ошибке.

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

Мартин Йорк
источник
+1; Tho "В противном случае ваши пользователи теперь должны реализовать операторы switch внутри там catch (что сводит на нет весь смысл того, что catch автоматически обрабатывает вещи)." - Раскрутка стека вызовов (если вы все еще можете его использовать) и принудительная обработка ошибок - все еще большие преимущества. Но это, безусловно, отвлекает от возможности раскручивать стек вызовов, когда вам нужно поймать его в середине и перебросить.
Мерлин Морган-Грэм
9

Я обычно начинаю с:

  1. Класс исключений для ошибок аргументов . Например, «аргумент не может быть нулевым», «аргумент должен быть положительным» и т. Д. Java и C # имеют предопределенные классы для них; в C ++ я обычно создаю только один класс, производный от std :: exception.
  2. Класс исключений для ошибок предусловий . Это для более сложных тестов, например, «индекс должен быть меньше размера».
  3. Класс исключений для ошибок утверждений . Они предназначены для проверки состояния на полпути методов соответствия. Например, при переборе списка для подсчета элементов, которые являются отрицательными, нулевыми или положительными, в конце эти три должны добавить к размеру.
  4. Один базовый класс исключений для самой библиотеки. Сначала просто бросьте этот класс. Только когда возникнет такая необходимость, начните добавлять подклассы.
  5. Я предпочитаю не переносить исключения, но я знаю, что мнения по этому вопросу расходятся (и очень сильно зависят от используемого языка). Если вы выполняете перенос, вам понадобятся дополнительные классы исключений для переноса .

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

Поскольку первые 3 случая одинаковы для каждого проекта, в C ++ я обычно просто копирую их из предыдущего проекта. Поскольку многие делают то же самое, разработчики C # и Java добавили стандартные классы для этих случаев в стандартную библиотеку. [ОБНОВЛЕНИЕ:] Для ленивых программистов: одного класса может быть достаточно, и, если повезет, в вашей стандартной библиотеке уже есть подходящий класс исключений. Я предпочитаю добавлять информацию, такую ​​как имя файла и номер белья, которые классы по умолчанию в C ++ не предоставляют. [Конец обновления]

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

Подробную аргументацию о моем четвертом случае смотрите в ответе Локи Астари . Я полностью согласен с его подробным ответом.

Сьерд
источник
+1; Существует определенная разница между тем, как вы должны писать исключения записи в структуре, и тем, как вы должны писать исключения в приложении. Различие немного нечеткое, но вы упомянули об этом (перенося на Локи для дела библиотеки).
Мерлин Морган-Грэм