Рекомендации по управлению исключениями в Java или С#

Я пытаюсь решить, как обрабатывать исключения в моем приложении.

Многое, если мои проблемы с исключениями исходят из 1) доступа к данным через удаленную службу или 2) десериализации объекта JSON. К сожалению, я не могу гарантировать успех для любой из этих задач (вырезать сетевое соединение, искаженный объект JSON, который не входит в мой контроль).

В результате, если я столкнулся с исключением, я просто поймаю его внутри функции и верну ЛОЖЬ к вызывающему. Моя логика заключается в том, что все вызывающие действительно заботятся о том, была ли задача успешной, а не почему она не была успешной.

Вот пример кода (в JAVA) типичного метода)

public boolean doSomething(Object p_somthingToDoOn)
{
    boolean result = false;

    try{
        // if dirty object then clean
        doactualStuffOnObject(p_jsonObject);

        //assume success (no exception thrown)
        result = true;
    }
    catch(Exception Ex)
    {
        //don't care about exceptions
        Ex.printStackTrace();
    }
    return result;
}

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

В заключение ключевых вопросов:

  • Можно ли просто перехватывать исключения, но не пузыривать их или формально уведомлять систему (либо через журнал, либо уведомление для пользователя)?
  • Какие лучшие методы существуют для исключений, которые не приводят ко всему, что требует блок try/catch?

Follow Up/Edit

Спасибо за все отзывы, нашли отличные источники в управлении исключениями в Интернете:

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

Кроме того, следите за кодовым гнили через чрезмерные попытки/уловы или не давая исключение его уважение (исключение предупреждает систему, что еще нужно предупредить?).

Кроме того, это замечательный выбор из m3rLinEz.

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

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

  • В чем смысл броска этого исключения?
  • Как имеет смысл справиться с этим?
  • Действительно ли вызывающий абонент заботится об исключении или им просто нужно, чтобы вызов был успешным?
  • Заставляет вызывающего пользователя управлять потенциальным исключением изящным?
  • Вы относитесь с уважением к идомам языка?
    • Вам действительно нужно вернуть флаг успеха, например boolean? Возвращение boolean (или int) больше зависит от C, чем Java (в Java вы просто обрабатываете исключение).
    • Следуйте за конструкциями управления ошибками, связанными с языком:)!

Ответ 1

Мне кажется странным, что вы хотите поймать исключения и превратить их в коды ошибок. Почему, по вашему мнению, вызывающий абонент предпочтет коды ошибок по исключениям, если последний является значением по умолчанию в Java и С#?

Что касается ваших вопросов:

  • Вы должны только перехватывать исключения, которые вы действительно можете обрабатывать. Просто в большинстве случаев это неверно. Существует несколько исключений (например, протоколирование и исключение сортировки между потоками), но даже для тех случаев вы, как правило, должны Восстановить исключения.
  • У вас определенно не должно быть много заявлений try/catch в вашем код. Опять же, идея состоит в том, чтобы улавливать только исключения, с которыми вы можете справиться. Вы можете включить самый верхний обработчик исключений, чтобы включить любой необработанный исключения в что-то несколько полезное для конечного пользователя, но в противном случае вы не должны пытаться поймать каждое исключение в всевозможные место.

Ответ 2

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

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

В общем, я использую следующие правила:

  • В моих компонентах и ​​библиотеках я получаю только исключение, если я намерен обработать его или сделать что-то на его основе. Или если я хочу предоставить дополнительную контекстуальную информацию в исключении.
  • Я использую общий захват try в точке входа приложения или на самом высоком уровне. Если здесь появляется исключение, я просто зарегистрирую его и дам сбой. В идеале исключения никогда не должны появляться здесь.

Я считаю, что следующий код является запахом:

try
{
    //do something
}
catch(Exception)
{
   throw;
}

Код, подобный этому, не имеет смысла и не должен быть включен.

Ответ 3

Я хотел бы порекомендовать еще один хороший источник по этой теме. Это интервью с изобретателями С# и Java, Андерсом Хейльсбергом и Джеймсом Гослином соответственно, по теме Java Checked Exception.

Отказ и исключения

В нижней части страницы есть большие ресурсы.

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

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

Андерс Хейлсберг: начнем с версии, поскольку проблемы довольно легко увидеть там. Скажем, я создать метод foo, который объявит его выдает исключения A, B и C. В версия 2 из foo, я хочу добавить куча функций, и теперь foo может бросать исключение D. Это разрыв измените для меня, чтобы добавить D к броскам это метод, потому что существующий вызывающий объект этого метода будет почти наверняка не исключение.

Добавление нового исключения в броски в новой версии ломает клиент код. Это похоже на добавление метода к интерфейс. После публикации интерфейс, это для всех практических цели неизменны, поскольку любые ее реализация может иметь методы, которые вы хотите добавить в следующая версия. Так что вам нужно создать вместо этого новый интерфейс. по аналогии с исключениями, вы либо имели бы для создания целого нового метода, называемого foo2, который выдает больше исключений или вам придется поймать исключение D в новый foo и преобразовать D в A, B или C.

Bill Venners: Но разве вы не нарушаете их код в этом случае в любом случае, даже на языке без проверок исключения? Если новая версия foo собирается выпустить новое исключение, которое клиенты должны думать об обработке, это не их код, сломанный факт, что они не ожидали, что исключение, когда они написали код?

Андерс Хейлсберг: Нет, потому что много случаев, людям все равно. Они не обращаясь ни к одному из этих исключения. Там нижний уровень обработчик исключений вокруг своего сообщения петля. Этот обработчик просто собирается поднимите диалог, в котором говорится, что произошло неправильно и продолжать. Программисты защитить свой код, написав try наконец, везде, поэтому они вернутся правильно, если возникает исключение, но их фактически не интересуют обработки исключений.

Предложение бросков, по крайней мере, способ он реализован на Java, не обязательно заставит вас справиться с исключения, но если вы не их, это заставляет вас признать какие исключения могут пройти через. Это требует от вас уловить объявленные исключения или поместить их в вашей собственной броской. Работать вокруг этого требования люди смешные вещи. Например, они украсить каждый метод, "бросает Исключение". Это просто полностью проигрывает эту функцию, и вы только что сделали программист пишет больше gobbledy дрянь. Это никому не помогает.

EDIT: добавлено более подробное описание конвертации

Ответ 4

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

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

  • Для удобства обслуживания всегда регистрируйте исключения, чтобы при обнаружении ошибок журнал помог указать вам место, где ваша ошибка, вероятно, началась. Никогда не оставляйте printStackTrace() или подобных ему, скорее всего, один из ваших пользователей получит одну из этих трассировок стека и имеет ровно нулевое знание относительно того, что с ним делать.
  • Вы можете использовать исключения, которые вы можете обрабатывать, и только те, и обрабатывать их, не просто бросают их в стек.
  • Всегда улавливайте определенный класс исключений, и обычно вы никогда не должны ловить тип Exception, вы, скорее всего, проглотите иначе важные исключения.
  • Никогда (никогда) не поймайте Error s!!, что означает: Никогда не ловите Throwable s, поскольку Error являются подклассами последнего. Error - проблемы, с которыми вы, скорее всего, никогда не сможете справиться (например, OutOfMemory или другие проблемы с JVM)

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

Ответ 5

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

Ответ 6

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

Исх.

try
{
   sendMessage();

   if(message == success)
   {
       doStuff();
   }
   else if(message == failed)
   {
       throw;
   }
}
catch(Exception)
{
    logAndRecover();
}

Этот код делает мне barf. ИМО вы не должны восстанавливаться из-за исключений, если только это не критическая программа. Если вы выбрасываете исключения, тогда происходят плохие вещи.

Ответ 7

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

Мы согласились, что SystemException вряд ли будут восстановлены и будут обрабатываться один раз в верхней части. Чтобы обеспечить дополнительный контекст, наш SystemException расширяется, чтобы указать, где они произошли, например. RepositoryException, ServiceEception и т.д.

ApplicationException может иметь деловое значение как InsufficientFundsException и должно обрабатываться клиентским кодом.

Витохут конкретный пример, сложно комментировать вашу реализацию, но я никогда не буду использовать коды возврата, это проблема обслуживания. Вы можете усвоить исключение, но вам нужно решить, почему и всегда регистрировать событие и stacktrace. Наконец, поскольку ваш метод не имеет другой обработки, он довольно избыточен (за исключением инкапсуляции?), Поэтому doactualStuffOnObject(p_jsonObject); может возвращать логическое значение!

Ответ 8

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

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

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

Ответ 9

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

Также рассмотрите использование Исключительный фильтр при регистрации исключений для целей диагностики. VB имеет поддержку языков для фильтров Exception. Ссылка на блог Greggm имеет реализацию, которая может быть использована с С#. Фильтры исключений обладают лучшими свойствами для отладки при перехвате и реверберации. В частности, вы можете зарегистрировать проблему в фильтре и позволить исключению продолжать распространяться. Этот метод позволяет подключить отладчик JIT (Just in Time), чтобы иметь полный исходный стек. Ретролл сокращает стопку в момент, когда он был сброшен.

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

// throws NumberNotHexidecimalException
int ParseHexidecimal(string numberToParse); 

bool TryParseHexidecimal(string numberToParse, out int parsedInt)
{
     try
     {
         parsedInt = ParseHexidecimal(numberToParse);
         return true;
     }
     catch(NumberNotHexidecimalException ex)
     {
         parsedInt = 0;
         return false;
     }
     catch(Exception ex)
     {
         // Implement the error policy for unexpected exceptions:
         // log a callstack, assert if a debugger is attached etc.
         LogRetailAssert(ex);
         // rethrow the exception
         // The downside is that a JIT debugger will have the next
         // line as the place that threw the exception, rather than
         // the original location further down the stack.
         throw;
         // A better practice is to use an exception filter here.
         // see the link to Exception Filter Inject above
         // http://code.msdn.microsoft.com/ExceptionFilterInjct
     }
}

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

Ответ 10

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

Например, java.lang.reflect.Array имеет статический метод set:

static void set(Object array, int index, Object value);

Путь C будет

static int set(Object array, int index, Object value);

... при этом возвращаемое значение является индикатором успеха. Но вы больше не находитесь в мире C.

Как только вы примете исключения, вы должны обнаружить, что он делает ваш код более простым и понятным, перемещая код обработки ошибок от вашей основной логики. Цель состоит в том, чтобы иметь множество операторов в одном блоке try.

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

Ответ 11

Если вы собираетесь поймать исключение и вернуть false, это должно быть очень специфическое исключение. Вы этого не делаете, вы ловите всех и возвращаете ложь. Если я получу MyCarIsOnFireException, я хочу знать об этом сразу же! Остальные Исключения, которые мне могут не волновать. Таким образом, у вас должен быть стек обработчиков исключений, который говорит "whoa whoa something is wrong here" для некоторых исключений (переосмыслить или перехватить новое исключение, которое лучше объясняет, что произошло) и просто вернуть false для других.

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

Изменить: Что касается вопроса об упаковке всего в try/catch, я думаю, что ответ да. Исключения должны быть настолько редкими в вашем коде, что код в блоке catch выполняется так редко, что он вообще не влияет на производительность. Исключением должно быть состояние, в котором ваша государственная машина сломалась и не знает, что делать. По крайней мере, повторите исключение, объясняющее, что происходило в то время, и в нем есть исключенное исключение. "Исключение в методе doSomeStuff()" не очень полезно для всех, кто должен понять, почему он сломался, когда вы в отпуске (или на новой работе).

Ответ 12

Моя стратегия:

Если исходная функция возвращает void, я меняю ее, чтобы вернуть bool. Если возникло исключение/ошибка, верните false, если все было нормально, верните true.

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

Вместо bool может быть возвращена строка, содержащая описание ошибки.

В каждом случае перед возвратом ничего регистрировать ошибку.

Ответ 13

Некоторые отличные ответы здесь. Я хотел бы добавить, что если вы закончите с чем-то вроде опубликованного, по крайней мере напечатайте больше, чем трассировку стека. Скажите, что вы делали в то время, и Ex.getMessage(), чтобы дать разработчику шанс на бой.

Ответ 14

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

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

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

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

Помимо этих двух типов обработки ошибок, весь остальной код в середине должен быть свободным и свободным от кода try/catch и объектов ошибок. Таким образом, он работает просто и, как ожидалось, независимо от того, где вы его используете, или что вы с ним делаете.

Павел.

Ответ 15

Я могу немного опоздать с ответом, но обработка ошибок - это то, что мы всегда можем изменить и развиваться вместе. Если вы хотите прочитать что-то по этому поводу, я написал сообщение в своем новом блоге об этом. http://taoofdevelopment.wordpress.com

Счастливое кодирование.