Рекомендации по управлению исключениями в Java или C# [закрыто]

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

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

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

вот пример кода (на 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;
}

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

в резюме ключевых вопросов:

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

Следить / Редактировать

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

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

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

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

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

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

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

15 ответов


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

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

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

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

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

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

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

Я нахожу следующий код запахом:

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

такой код не служит никакой цели и не должен быть включен.


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

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

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

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

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

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

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

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

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

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

EDIT: добавлена дополнительная информация о converstaion


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

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

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

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


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


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

Ex.

try
{
   sendMessage();

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

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


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

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

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

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


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

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

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


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

и используйте Фильтр Исключений при регистрации исключений в диагностических целях. VB имеет языковую поддержку фильтров исключений. Ссылка на блог Greggm имеет реализацию, которую можно использовать С C#. Фильтры исключений имеют лучшие свойства для отладочности над catch и rethrow. В частности вы можете зарегистрируйте проблему в фильтре и позвольте исключению продолжать распространяться. Этот метод позволяет присоединить отладчик JIT (как раз вовремя), чтобы иметь полный исходный стек. А оператор режет стек в точке его повторно.

случаи, когда 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 или нет, это больше вопрос стиля. Вопрос о том, чтобы поймать все исключения и проглотить их, не является проблемой стиля. Убедитесь, что непредвиденные исключения могут распространяться!


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

например java.ленг.отражать.Массив имеет статический set способ:

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

путь C был бы

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

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

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

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


Если вы собираетесь поймать исключение и вернуть false, это должно быть очень конкретное исключение. Вы не делаете этого, вы ловите их всех и возвращаете ложь. Если я получаю исключение MyCarIsOnFireException, я хочу знать об этом сразу! Остальные исключения могут меня не волновать. Таким образом, у вас должен быть стек обработчиков исключений, которые говорят: "Вау, что-то здесь не так" для некоторых исключений (rethrow или catch и rethrow новое исключение, которое лучше объясняет, что произошло) и просто возвращать false для других.

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

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


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

если исходная функция возвратила пустота Я меняю его на return bool. Если произошло исключение / ошибка return false, если все было в порядке возврата правда.

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

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

в каждом случае, прежде чем возвращать что-либо, зарегистрируйте ошибку.


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


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

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

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

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

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

Павел.


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

удачи в кодировании.