Я борюсь с очень простым вопросом:
Сейчас я работаю над серверным приложением, и мне нужно изобрести иерархию для исключений (некоторые исключения уже существуют, но необходима общая структура). Как мне вообще начать это делать?
Я думаю о следующей стратегии:
1) Что не так?
- Что-то спрашивают, что не разрешено.
- Что-то спрашивают, это разрешено, но не работает из-за неправильных параметров.
- Что-то спрашивают, это разрешено, но не работает из-за внутренних ошибок.
2) Кто запускает запрос?
- Клиентское приложение
- Другое серверное приложение
3) Передача сообщений: поскольку мы имеем дело с серверным приложением, все дело в получении и отправке сообщений. Так что, если отправка сообщения идет не так?
Таким образом, мы можем получить следующие типы исключений:
- ServerNotAllowedException
- ClientNotAllowedException
- ServerParameterException
- ClientParameterException
- InternalException (в случае, если сервер не знает, откуда поступает запрос)
- ServerInternalException
- ClientInternalException
- MessageHandlingException
Это очень общий подход для определения иерархии исключений, но я боюсь, что мне может не хватать некоторых очевидных случаев. У вас есть идеи по тем областям, которые я не рассматриваю, вам известны какие-либо недостатки этого метода или есть более общий подход к такого рода вопросам (в последнем случае, где я могу его найти)?
заранее спасибо
источник
catch
блоков, которые я использую, я не использую намного больше исключений, чем какое сообщение об ошибке оно содержит. У меня нет ничего особенного, что я могу сделать для исключения, связанного с невозможностью прочитать файл как файл, который не выделяет память во время процесса чтения, поэтому я склонен просто ловитьstd::exception
и сообщать о сообщении об ошибке, которое в нем содержится, возможно, украшающем это с"Failed to open file: %s", ex.what()
буфером стека перед печатью.catch
блоков на одном сайте восстановления, но часто это просто игнорирование сообщения внутри исключения и печать более локализованного сообщения ...Ответы:
Основные пометки
(немного необъективно)
Обычно я бы не стал использовать подробную иерархию исключений.
Самая важная вещь: исключение сообщает вызывающей стороне, что ваш метод не завершил свою работу. И ваш собеседник должен получить уведомление об этом, чтобы он не просто продолжил. Это работает с любым исключением, независимо от того, какой класс исключения вы выберете.
Второй аспект - ведение журнала. Вы хотите найти значимые записи журнала, когда что-то идет не так. Это также не требует различных классов исключений, только хорошо разработанные текстовые сообщения (я полагаю, вам не нужен автомат для чтения ваших журналов ошибок ...).
Третий аспект - реакция вашего абонента. Что может сделать ваш абонент, когда получит исключение? Здесь может иметь смысл иметь разные классы исключений, поэтому вызывающая сторона может решить, следует ли повторить один и тот же вызов, использовать другое решение (например, вместо этого использовать запасной источник) или отказаться.
И, возможно, вы хотите использовать свои исключения в качестве основы для информирования конечного пользователя о проблеме. Это означает создание удобного для пользователя сообщения помимо текста администратора для файла журнала, но ему не нужны другие классы исключений (хотя, возможно, это может упростить генерацию текста ...).
Важным аспектом ведения журнала (и пользовательских сообщений об ошибках) является возможность дополнения исключения контекстной информацией, перехватывая его на каком-либо уровне, добавляя некоторую контекстную информацию, например, параметры метода, и перебрасывая ее.
Ваша иерархия
Кто запускает запрос? Я не думаю, что вам понадобится информация о том, кто запускает запрос. Я даже не представляю, как вы знаете это глубоко внутри некоторого стека вызовов.
Обработка сообщений : это не другой аспект, а просто дополнительные случаи для «Что идет не так?».
В комментарии вы говорите о флаге « no logging » при создании исключения. Я не думаю, что в том месте, где вы создаете и выбрасываете исключение, вы можете принять надежное решение, регистрировать это исключение или нет.
Единственная ситуация, которую я могу себе представить, состоит в том, что какой-то более высокий уровень использует ваш API таким образом, что иногда он создает исключения, и этот уровень затем знает, что ему не нужно беспокоить какого-либо администратора за исключением, поэтому он молча проглатывает исключение. Но это запах кода: ожидаемое исключение само по себе является противоречием, подсказкой для изменения API. И должен решать верхний уровень, а не код, генерирующий исключения.
источник
Главное, что нужно иметь в виду при разработке шаблона ответа на ошибку, это убедиться, что он полезен для вызывающих. Это применимо, используете ли вы исключения или определенные коды ошибок, но мы ограничимся иллюстрацией с исключениями.
Если ваш язык или структура уже предоставляют общие классы исключений, используйте их там, где они уместны и где их разумно ожидать . Не определяйте свои собственные
ArgumentNullException
илиArgumentOutOfRange
исключительные классы. Абоненты не будут ожидать, чтобы поймать их.Определите
MyClientServerAppException
базовый класс для охвата ошибок, которые являются уникальными в контексте вашего приложения. Никогда не бросайте экземпляр базового класса. Неоднозначная реакция на ошибку - это ХУМАЯ ВЕЩЬ . Если есть «внутренняя ошибка», объясните, что это за ошибка.По большей части иерархия под базовым классом должна быть широкой, а не глубокой. Вам нужно только углубить его в ситуациях, когда это полезно для звонящего. Например, если существует 5 причин, по которым сообщение может сбой от клиента к серверу, вы можете определить
ServerMessageFault
исключение, а затем определить класс исключения для каждой из этих 5 ошибок ниже. Таким образом, вызывающая сторона может просто перехватить суперкласс, если ему это нужно или нужно. Постарайтесь ограничить это конкретными, разумными случаями.Не пытайтесь определить все ваши классы исключений до того, как они будут фактически использованы. Вы закончите заново делать большую часть этого. Когда вы сталкиваетесь с ошибкой во время написания кода, решите, как лучше всего описать эту ошибку. В идеале это должно выражаться в контексте того, что пытается сделать вызывающий абонент.
В связи с предыдущим пунктом, помните, что если вы используете исключения для реагирования на ошибки, это не означает, что вы должны использовать только исключения для состояний ошибок. Имейте в виду, что создание исключений обычно дорого, а стоимость производительности может варьироваться от одного языка к другому. В некоторых языках стоимость выше в зависимости от глубины стека вызовов, поэтому, если ошибка находится глубоко в стеке вызовов, проверьте, не можете ли вы использовать простые примитивные типы (целочисленные коды ошибок или логические флаги) для проталкивания ошибка создает резервную копию стека, поэтому ее можно выбросить ближе к вызову вызывающей стороны.
Если вы включаете ведение журнала как часть ответа об ошибке, вызывающим абонентам должно быть легко добавить контекстную информацию к объекту исключения. Начните с того, где информация используется в коде регистрации. Решите, сколько информации необходимо для того, чтобы журнал был полезен (не будучи гигантской текстовой стеной). Затем вернитесь назад, чтобы убедиться, что классы исключений могут быть легко предоставлены с этой информацией.
И, наконец, если ваше приложение не может абсолютно иметь дело изящно с ошибкой из- за нехватки памяти, не пытайтесь справиться с теми или другими катастрофическими исключения во время выполнения. Просто позвольте ОС справиться с этим, потому что на самом деле это все, что вы можете сделать.
источник
Ну, я бы порекомендовал вам в первую очередь создать базовый
Exception
класс для всех проверенных исключений, которые могут быть выданы вашим приложением. Если ваше приложение было вызваноDuckType
, то создайтеDuckTypeException
базовый класс.Это позволяет вам перехватить любое исключение из вашего базового
DuckTypeException
класса для обработки. Отсюда ваши исключения должны иметь описательные имена, которые могут лучше выделить тип проблемы. Например, «DatabaseConnectionException».Позвольте мне прояснить, что все это должны быть проверены исключения для ситуаций, которые могут произойти, что вы, вероятно, захотите изящно обработать в вашей программе. Другими словами, вы не можете подключиться к базе данных, поэтому
DatabaseConnectionException
выдается сообщение, которое вы можете перехватить, чтобы подождать и повторить попытку через некоторое время.Вы не увидите проверенное исключение для очень неожиданной проблемы, такой как недопустимый запрос SQL или исключение с нулевым указателем, и я рекомендую вам позволить этим исключениям выходить за пределы большинства предложений catch (или перехватывать и перебрасывать при необходимости) до тех пор, пока вы не достигнете основного Сам контроллер, который может затем поймать
RuntimeException
его исключительно для целей регистрации.Мое личное предпочтение состоит в том, чтобы не выбрасывать непроверенное
RuntimeException
как еще одно исключение, поскольку по природе непроверенного исключения вы этого не ожидаете, и поэтому отбрасывание его под другим исключением скрывает информацию. Однако, если это ваше предпочтение, вы все равно можете пойматьRuntimeException
и бросить a,DuckTypeInternalException
что в отличие отDuckTypeException
происходитRuntimeException
и поэтому не проверяется.Если вы предпочитаете, вы можете разбить ваши исключения на подкатегории для организационных целей, например,
DatabaseException
для чего-либо связанного с базой данных, но я бы по-прежнему рекомендовал, чтобы такие исключения возникали из вашего базового исключенияDuckTypeException
и были абстрактными и, следовательно, происходили с явными описательными именами.Как правило, ваши ловушки try должны становиться все более универсальными, когда вы перемещаетесь вверх по стеку вызовов вызывающих абонентов для обработки исключений, и, опять же, в вашем главном контроллере вы бы не обрабатывали,
DatabaseConnectionException
а скорее просто простое,DuckTypeException
из которого выводятся все ваши проверенные исключения.источник
Попробуйте упростить это.
Первое, что поможет вам мыслить в другой стратегии: много пойманных исключений очень похоже на использование проверенных исключений из Java (извините, я не разработчик C ++). Это не очень хорошо по многим причинам, поэтому я всегда стараюсь их не использовать, и ваша стратегия исключений из иерархии помнит мне многое из этого.
Итак, я рекомендую вам другую и гибкую стратегию: используйте непроверенные исключения и ошибки кода.
Для примера, посмотрите этот код Java:
И ваше уникальное исключение:
Эту стратегию я нашел по этой ссылке, и вы можете найти реализацию Java здесь , где вы можете увидеть более подробную информацию о ней, потому что приведенный выше код упрощен.
Поскольку вам необходимо разделить различные исключения между приложениями «клиент» и «другой сервер», у вас может быть несколько классов кодов ошибок, реализующих интерфейс ErrorCode.
источник
Исключения составляют неограниченные ограничения и должны использоваться с осторожностью. Лучшая стратегия для них - это ограничить их. Вызывающая функция должна обрабатывать все исключения, вызванные функциями, которые она вызывает или программа умирает. Только вызывающая функция имеет правильный контекст для обработки исключения. Имея функции, расположенные дальше по дереву вызовов, обрабатывать их можно без ограничений.
Исключения не являются ошибками. Они происходят, когда обстоятельства мешают программе завершить одну ветвь кода и указывают другую ветвь, которой она должна следовать.
Исключения должны быть в контексте вызываемой функции. Например: функция, которая решает квадратные уравнения . Он должен обрабатывать два исключения: Division_by_zero и square_root_of_negative_number. Но эти исключения не имеют смысла для функций, вызывающих этот решатель квадратного уравнения. Они происходят из-за метода, используемого для решения уравнений, и простое их отбрасывание обнажает внутренние органы и нарушает инкапсуляцию. Вместо этого Division_by_zero должен быть переброшен как not_quadratic и square_root_of_negative_number и no_real_roots.
Нет необходимости в иерархии исключений. Перечень исключений (которые их идентифицируют), выданных функцией, достаточен, потому что вызывающая функция должна их обработать. Разрешение им обрабатывать дерево вызовов - это неконтролируемое (неограниченное) занятие.
источник