Использование исключений для управления потоком - PullRequest
7 голосов
/ 16 февраля 2012

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

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


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

  • Когда я должен выбросить исключение и когда я должен написать код с strong nothrowгарантия , которая может просто вернуть bool, чтобы указать успех или неудачу?

  • Стоит ли пытаться свести к минимуму количество ситуаций, когда метод вызываетисключение или, наоборот, должно ли оно быть максимально развернуто для обеспечения гибкости при работе с такими ситуациями?

  • Должен ли я придерживаться исключения, выбрасывающего конention , установленный средами / средами исполнения, которые я использую при разработке своих приложений, или я должен обернуть все эти вызовы , чтобы они соответствовали моей собственной стратегии исключения?

  • Мне также посоветовали использовать коды ошибок для обработки ошибок, что кажется довольно эффективным, но некрасивым с синтаксической точки зрения (также при их использовании разработчик теряет возможность указывать выходные данные для метода).Что вы думаете по этому поводу?


Пример для третьего вопроса (я использовал платформу ввода / вывода и столкнулся сследующая ситуация):

Описанная структура не использует исключения для обработки ошибок, , но другой код использует их.Должен ли я обернуть каждую возможную ошибку, указанную '???', и выдать исключение в этом случае?Или я должен изменить сигнатуру моего метода на bool PrepareTheResultingOutputPath и указать только, была ли операция успешной или нет?

public void PrepareTheResultingOutputFile(
    String templateFilePath, String outputFilePath)
{
    if (!File.Exists(templateFilePath))
        // ???

    if (!Directory.MakePath(outputFilePath))
        // ???

    if (File.Exists(outputFilePath))
        if (!File.Remove(outputFilePath))
            // ???

    if (!File.Copy(templateFilePath, outputFilePath)
        // ???
}

Другой пример - даже.NET Framework не придерживается строгой стратегии исключения.Документировано, что некоторые методы генерируют более 10 различных типов исключений, включая тривиальные типы исключений, такие как NullArgumentException, но некоторые из них просто возвращают bool, чтобы указать на успех или неудачу операций.

Спасибо!

Ответы [ 5 ]

3 голосов
/ 16 февраля 2012

Проблема с исключениями состоит в том, что они по сути являются прославленными gotos, которые способны раскрутить стек вызовов программы.Итак, если вы «используете исключения для управления потоком», вы, вероятно, используете их как gotos, а не как указание на состояние исключений.В этом и заключается смысл исключений и причина их названия: они должны использоваться только в исключительных случаях .Таким образом, если метод не предназначен для исключения (например, .NET int.TryParse), то можно генерировать исключение в ответ на исключительные обстоятельства.

Хорошая вещь в C # в отличие от Javaв том, что в C # вы можете по существу возвращать два или более значений, возвращая тип кортежа или используя параметры out.Так что возвращать код ошибки в качестве основного возвращаемого значения метода не так уж и страшно, так как вы можете использовать параметры для остального.Например, общая парадигма для вызова int.TryParse:

string s = /* Read a string from somewhere */;
int n;
if (int.TryParse(s, out n))
{
    // Use n somehow
}
else
{
    // Tell the user that they entered a wrong number
}

Теперь для вашего третьего вопроса, который кажется наиболее существенным.В отношении вашего примера кода вы спрашиваете, должны ли вы возвращать bool, чтобы указать успех / неудачу, или вам следует использовать исключения, чтобы указать неудачу.Есть и третий вариант.Вы можете определить перечисление, чтобы сообщить, как метод может завершиться ошибкой, и вернуть значение этого типа вызывающей стороне.Затем у вызывающей стороны есть широкий выбор: вызывающей стороне не нужно использовать кучу операторов try / catch или if, которая дает небольшое представление о том, как произошел сбой метода, но он может написать либо

if (PrepareTheResultingOutputFile(templateFilePath, outputFilePath) == Status.Success)
    // Do  something
else
    // It failed!

или

switch (PrepareTheResultingOutputFile(templateFilePath, outputFilePath))
{
    case Status.Success:
        // Do something
        break;
    case Status.FileNotPresent:
        // Do something else
        break;
    case Status.CannotMakePath:
        // Do something else
        break;
    // And so on
    default:
        // Some other reason for failure
        break;
}

Вы можете найти больше по этому вопросу здесь и здесь , но особенно в пост Джоэла Спольски , которыйЯ настоятельно рекомендую.

2 голосов
/ 16 февраля 2012

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

Что касается ваших конкретных вопросов, я выскажу свое мнение по каждому из них.


Когда я должен выбросить исключение, и когда я должен написать код с сильная гарантия nothrow, которая может просто возвращать bool для указания успех или неудача?

Вы не можете написать метод в Java с гарантией «не выбрасывать». Как минимум, JVM может выдать ошибку времени выполнения, например, OutOfMemoryError. Вы не обязаны их подавлять, просто дайте им всплеснуть иерархией вашего звонка, пока вы не достигнете наиболее подходящего места для их обработки. Изменение типа возвращаемого значения метода на bool, указывающее на успех или неудачу, на самом деле является антитезой хорошего дизайна, тип возвращаемого вами метода должен быть продиктован их контрактом (что они должны делать), а не тем, как они это сделали.


Стоит ли пытаться свести к минимуму количество ситуаций, когда метод выдает исключение или, наоборот, должно быть максимально обеспечить гибкость при работе с этими ситуациями?

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

  1. Ваш метод не обязан обрабатывать возникающие исключения в результате действий он не предпринимает. Т.е., OutOfMemory или Ошибка StackOverflow, выдаваемая JVM
  2. Это является ответственность метода для обработки всех исключения, выданные как часть его выполнения, которые являются результатом отложенные вызовы в другие модули, которые явно не видны вызывающий метод. Так, например, если вы используете Apache Commons Библиотека ввода-вывода для обработки входных потоков в методе, предназначенном для чтения файл, вам нужно обработать все исключения, которые выдает библиотека. Это потому что вызывающий метод не может знать, что вы используете это библиотека в вашем методе. Наиболее типичная манера обращения такого рода исключения являются, оборачивая их в некоторых случаях исключение во время выполнения (без проверки). Вы также можете обернуть их в проверенном Исключение, если вы хотите четко указать вызывающему методу, что оно должно быть готово к исключительным обстоятельствам.
  3. Это является обязанностью метода вызвать исключение (проверено или не проверено), если по какой-либо причине оно не может выполнить его контракт (иначе он не может успешно завершить). Дело в точку, каждое из условных (если) утверждений в вашем PrepareTheResultingOutputFile Метод является допустимой точкой для выдачи исключения при неудаче желаемого результат.

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

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


Мне также посоветовали использовать коды ошибок для обработки ошибок, что кажется довольно эффективный, но уродливый с синтаксической точки зрения (также, при их использовании разработчик теряет возможность указывать вывод для метода). Что вы думаете об этом?

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

1 голос
/ 16 февраля 2012

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

По другим вопросам:

Стоит ли пытаться свести к минимуму количество ситуаций, когда метод выдает исключение или, наоборот, должно быть максимально обеспечить гибкость при работе с этими ситуациями?

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

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

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

Мне также посоветовали использовать коды ошибок для обработки ошибок, что кажется довольно эффективный, но уродливый с синтаксической точки зрения (также, при их использовании разработчик теряет возможность указывать вывод для метода). Что вы думаете об этом?

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

1 голос
/ 16 февраля 2012

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

0 голосов
/ 16 февраля 2012

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

Чтобы продемонстрировать, что я имею в виду, вот пример

var pop3Clinet=new pop3Client();

    try
    {
    pop3Client.SetServer("server-address");
    pop3Client.SetUserName("username");
    pop3Client.SetPassword("password");
    var mails=pop3Client.ReceiveMails();
    }
    catch(NullReferenceException exp)
    {
     throw new Exception("can not connect to server.server-address is wrong or the server is down",exp);
    }
    catch(UnauthorizedAccess)  //the exception is meaningful and can be rethrown or this block can be removed.
    {
    throw;
    }

Этот пример является реальным :) этот pop3client, который мы используемстранным образом не выдает исключение, связанное с сервером, каждый раз, когда он пытается подключиться к серверу, поэтому нам пришлось обернуть исключение NullReference в осмысленное.

Возвращать bool, чтобы показать, что метод был успешным, не рекомендуется, если толькоРеасоn что метод был написан. например, в .net у нас есть некоторые методы TryParse примитивных типов, такие как int, long и т. д., которые возвращают bool, если они успешны, и это то, что они делают, они пытаются что-то сделать, и если онине успех, они сообщают об этом.В этом сценарии вам просто все равно, что пошло не так и почему в большинстве случаев предоставленные данные в неправильном формате для анализа.другими словами, вы можете использовать такие методы, чтобы проверить ситуацию, чтобы взять все под свой контроль.но если у вас есть метод, который берет список целых чисел в строковом формате и пытается вычислить их, вы должны вызвать исключение, если ваш метод не может проанализировать некоторые из этих значений. Вы должны даже сообщить, какое из них не имеетправильный формат.

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