Как избежать неприятных исключений?

21

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

В частности:

  • Предположим, у вас есть метод Save, который может потерпеть неудачу, потому что a) Кто-то еще изменил запись перед вами , или b) Значение, которое вы пытаетесь создать, уже существует . Эти условия должны быть ожидаемыми и не исключительными, поэтому вместо создания исключения вы решаете создать Try-версию вашего метода TrySave, который возвращает логическое значение, указывающее, успешно ли выполнено сохранение. Но если это не поможет, как потребитель узнает, в чем проблема? Или было бы лучше вернуть перечисление с указанием результата, вроде Ok / RecordAlreadyModified / ValueAlreadyExists? С integer.TryParse эта проблема не существует, так как есть только одна причина, по которой метод может завершиться ошибкой.
  • Действительно ли предыдущий пример является неприятной ситуацией? Или выбрасывание исключения в этом случае будет предпочтительным способом? Я знаю, как это делается в большинстве библиотек и сред, включая платформу Entity.
  • Как вы решаете, когда создавать версию Try вашего метода, а не предоставлять какой-либо способ заранее проверить, будет ли метод работать или нет? В настоящее время я следую этим рекомендациям:
    • Если есть вероятность возникновения гонки, создайте пробную версию. Это предотвращает необходимость для потребителя поймать экзогенное исключение. Например, в методе Save, описанном ранее.
    • Если метод для проверки условия в значительной степени будет делать все то же, что и оригинальный метод, то создайте версию Try. Например, integer.TryParse ().
    • В любом другом случае создайте метод для проверки условия.
Майк
источник
1
Ваш пример сохранения, которое может потерпеть неудачу, на самом деле не очень неприятное исключение. Это довольно обычно и, вероятно, должно быть просто исключением.
S.Lott
@ S.Lott: Что ты имеешь в виду, это довольно обычный? Сама ситуация или исключение в этой ситуации? Во всяком случае, я согласен с вами, что не очевидно, если это на самом деле неприятная ситуация. Я обновлю вопрос.
Майк
«Сама ситуация, или исключение в этой ситуации» Оба.
S.Lott

Ответы:

24

Предположим, у вас есть метод Save, который может потерпеть неудачу, потому что a) Кто-то еще изменил запись перед вами, или b) Значение, которое вы пытаетесь создать, уже существует. Эти условия должны быть ожидаемыми и не исключительными, поэтому вместо создания исключения вы решаете создать Try-версию вашего метода TrySave, который возвращает логическое значение, указывающее, успешно ли выполнено сохранение. Но если это не поможет, как потребитель узнает, в чем проблема?

Хороший вопрос.

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

Второй вопрос, который приходит мне в голову: является ли информация, которую вы хотите вернуть пользователю, действующей ? То есть они собираются принять какое-то решение на основе этой информации?

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

Звонит ли вызывающему абоненту, почему сохранение не удалось? Собираются ли они что-нибудь с этим сделать, кроме как сдаться или попробовать снова?

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

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

Давайте предположим ради аргумента, что это не является исключением.

Четвертый вопрос, который приходит на ум: есть ли способ надежного выявления плохой ситуации заранее?

Если плохая ситуация в моем «экзогенном» ведре, то нет. Там нет никакого способа надежно сказать "другой пользователь изменил эту запись?" потому что они могут изменить его после того, как вы зададите вопрос . Ответ устарел, как только он произведен.

Пятый вопрос, который приходит на ум: есть ли способ спроектировать API, чтобы можно было предотвратить плохую ситуацию?

Например, вы могли бы сделать операцию «сохранения» требующей двух шагов. Шаг первый: получить блокировку изменяемой записи. Эта операция либо завершается успешно, либо завершается неудачей и может возвращать логическое значение. В этом случае вызывающий абонент может иметь политику действий при сбое: подождать некоторое время и повторить попытку, сдаться, что угодно. Шаг второй: как только блокировка получена, сохраните и отпустите блокировку. Теперь сохранение всегда происходит успешно, поэтому нет необходимости беспокоиться о какой-либо обработке ошибок. Если сохранение не удается, это действительно исключительное.

Эрик Липперт
источник
Все очень хорошие моменты, спасибо. Теперь вот риторический вопрос, который, вероятно, резюмирует мой пост: если бы вы изменили дизайн File.Open (), вы бы вместо этого создали File.TryOpen ()? Как бы вы сообщили потребителю о причине неудачи? Или действительно ли лучший выбор здесь - это использование экзогенного исключения?
Майк
10
@Mike: Файловые системы являются хорошим примером использования экзогенных исключений. Они терпят неудачу редко, поэтому неудача является исключительной. Они происходят непредсказуемо и по причинам, совершенно не зависящим от вызывающего абонента (вы не можете воспользоваться «блокировкой», позволяющей подключить кабель Ethernet), и сбои являются разнообразными и применимыми (то есть сбоем, поскольку файл not found vs file is found, но у вас нет прав на запись, они могут быть выполнены по-разному.) Все эти причины представляют ошибку как исключение.
Эрик Липперт
Я считаю, что на этот вопрос нужно ответить сейчас, но мне просто любопытно;) Если метод может дать сбой по двум или более причинам, сбои являются действенными, сбои являются исключительными и сбои не могут быть обнаружены заранее или предотвращены, что бы вы сделали?
Майк
@ Майк, я не могу говорить за Эрика, но это звучит как хорошее место для кодов ошибок. Возможно, возвращение члена перечисления.
Мэтью Читал
1

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

В общем, если есть условие, которое я считаю весьма вероятным (например, NoDataReturned, DivideByZero и т. Д.) ИЛИ очень легко проверить (например, пустая коллекция или значение NULL), я пытаюсь проверить наличие опередить время и разобраться с этим, прежде чем я доберусь до точки, где мне придется поймать исключение. Я признаю, что не всегда легко узнать эти условия заранее, иногда они появляются только тогда, когда код подвергается тщательному тестированию.

FrustratedWithFormsDesigner
источник
0

save()Метод должен бросить исключение.

Самый верхний слой должен перехватывать и информировать пользователя , не завершая программу, если только это не Unix-подобные программы командной строки, и в этом случае можно прекратить работу.

Возвращаемые значения не являются хорошим способом управления исключениями.

Тулаинс Кордова
источник