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

StackOverflow https://stackoverflow.com/questions/409563

Вопрос

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

Во многом, если мои проблемы с исключениями связаны с 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?

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

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

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

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

Кроме того, это красивый комментарий от m3rLinEz.

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

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

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

Решение

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

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

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

Другие советы

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

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

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

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

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

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

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

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

Неудачи и исключения

Внизу страницы также есть отличные ресурсы.

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

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

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

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

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

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

Предложение бросков, по крайней мере, так, как он реализован в Java, не обязательно заставляет вас справиться с исключениями, но если вы не справляетесь с ними, это заставляет вас точно признать, какие исключения могут пройти.Это требует, чтобы вы либо поймали заявленные исключения, либо поместили их в свой собственный пункт о бросках.Чтобы обойти это требование, люди делают нелепые вещи.Например, они украшают каждый метод «бросает исключение». Это просто полностью побеждает эту функцию, и вы только что заставили программиста написать больше Gubbledy Gunk.Это никому не помогает.

РЕДАКТИРОВАТЬ:Добавлена ​​более подробная информация о конвертации.

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

Как правило, обработка исключений должна соответствовать этим рекомендациям, в произвольном порядке:

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

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

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

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

Бывший.

try
{
   sendMessage();

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

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

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

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

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

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

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

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

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

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

Также рассмотрите возможность использования Фильтр исключений при регистрации исключений в диагностических целях.VB имеет языковую поддержку фильтров исключений.Ссылка на блог Греггма содержит реализацию, которую можно использовать из C#.Фильтры исключений имеют лучшие свойства для отладки по сравнению с перехватом и повторным созданием.В частности, вы можете зарегистрировать проблему в фильтре и позволить исключению продолжать распространяться.Этот метод позволяет подключить отладчик 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, или нет, это скорее вопрос стиля.Вопрос перехвата всех исключений и их обработки не является проблемой стиля.Убедитесь, что неожиданным исключениям разрешено распространение!

Я предлагаю воспользоваться стандартной библиотекой для используемого вами языка.Я не могу говорить за C#, но давайте посмотрим на 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 блокировать.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Павел.

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

Приятного кодирования.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top