Я застрял, решая, как обрабатывать исключения в моем приложении.
Во многом мои проблемы с исключениями возникают из-за 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;
}
Я думаю, что этот подход хорош, но мне действительно любопытно узнать, каковы лучшие практики для управления исключениями (действительно ли я должен пузырить исключение на всем пути вверх по стеку вызовов?).
Вкратце по ключевым вопросам:
- Можно ли просто перехватывать исключения, но не всплывать или формально уведомлять систему (через журнал или уведомление для пользователя)?
- Какие существуют передовые практики для исключений, которые не приводят ко всему, что требует блока try / catch?
Продолжить / Редактировать
Спасибо за все отзывы, нашел несколько отличных источников по управлению исключениями в Интернете:
- Лучшие практики обработки исключений | O'Reilly Media
- Рекомендации по обработке исключений в .NET
- Лучшие практики: управление исключениями (статья теперь указывает на копию archive.org)
- Антипаттерны обработки исключений
Кажется, что управление исключениями - одна из тех вещей, которые различаются в зависимости от контекста. Но самое главное, нужно быть последовательным в том, как они управляют исключениями в системе.
Кроме того, следите за гниением кода из-за чрезмерного количества попыток / уловок или отказа от исключения (исключение - предупреждение системы, о чем еще нужно предупредить?).
Кроме того, это прекрасный комментарий от m3rLinEz .
Я склонен согласиться с Андерсом Хейлсбергом и вами в том, что большинство звонящих заботятся только о том, прошла ли операция успешно или нет.
Из этого комментария возникает ряд вопросов, о которых следует подумать при работе с исключениями:
- Какой смысл генерировать это исключение?
- Как имеет смысл с этим справляться?
- Действительно ли вызывающий абонент заботится об исключении или его просто волнует, был ли вызов успешным?
- Является ли изящным принуждение вызывающего абонента к управлению потенциальным исключением?
- Вы уважаете языковые идомы?
- Вам действительно нужно возвращать флаг успеха, например логическое? Возврат логического значения (или int) - это скорее мышление C, чем Java (в Java вы просто обрабатываете исключение).
- Следуйте конструкциям управления ошибками, связанным с языком :)!
источник
Ответы:
Мне кажется странным, что вы хотите перехватывать исключения и превращать их в коды ошибок. Как вы думаете, почему вызывающий абонент предпочел бы коды ошибок исключениям, если последнее является значением по умолчанию как в Java, так и в C #?
Что касается ваших вопросов:
источник
Это зависит от приложения и ситуации. Если вы создаете библиотечный компонент, вы должны выдавать исключения, хотя они должны быть обернуты, чтобы соответствовать вашему компоненту. Например, если вы создаете базу данных Xml и, скажем, вы используете файловую систему для хранения данных, и вы используете разрешения файловой системы для защиты данных. Вы не хотели бы выдувать исключение FileIOAccessDenied, поскольку это приводит к утечке вашей реализации. Вместо этого вы должны обернуть исключение и выдать ошибку AccessDenied. Это особенно верно, если вы распространяете компонент третьим лицам.
Что касается того, можно ли проглатывать исключения. Это зависит от вашей системы. Если ваше приложение может обрабатывать случаи сбоя и нет никакой пользы от уведомления пользователя о том, почему оно не удалось, продолжайте, хотя я настоятельно рекомендую вам записать сбой. Мне всегда было неприятно обращаться за помощью в устранении неполадок и обнаруживать, что они проглатывают исключение (или заменяют его и генерируют новое без установки внутреннего исключения).
В целом я использую следующие правила:
Я считаю, что следующий код является запахом:
Такой код не имеет смысла и не должен включаться.
источник
// do something
включает какие-либоtry/finally
блоки вокруг точки, которая выбрасывает,finally
блоки будут выполняться доcatch
блока. Без этогоtry/catch
исключение будет перемещаться до вершины стека без выполнения каких-либоfinally
блоков. Это позволяет обработчику верхнего уровня решать, выполнять лиfinally
блоки или нет .Я хотел бы порекомендовать еще один хороший источник по этой теме. Это интервью с изобретателями C # и Java, Андерсом Хейлсбергом и Джеймсом Гослингом, соответственно, на тему проверенного исключения Java.
Неудачи и исключения
Внизу страницы также есть отличные ресурсы.
Я склонен согласиться с Андерсом Хейлсбергом и вами в том, что большинство звонящих заботятся только о том, прошла ли операция успешно или нет.
РЕДАКТИРОВАТЬ: добавлены дополнительные сведения о преобразовании
источник
FetchData
исключение неожиданного типа, он не может узнать, означает ли это исключение только то, что данные недоступны (в этом случае способность кода обойтись без этого "разрешит" его) или означает ли это, что ЦП горит и система должна выполнить «безопасное отключение» при первой возможности. Похоже, г-н Хейлсберг предполагает, что код должен предполагать первое; возможно, это лучшая стратегия с учетом существующих иерархий исключений, но, тем не менее, она кажется неприятной.Проверенные исключения - это спорный вопрос в целом и в Java в частности (позже я попытаюсь найти несколько примеров для тех, кто поддерживает и противостоит им).
Как правило, обработка исключений должна происходить вокруг этих рекомендаций, без определенного порядка:
printStackTrace()
или тому подобное, есть вероятность, что один из ваших пользователей в конечном итоге получит одну из этих трассировок стека и точно не знает, что с ней делать.Exception
, вы, скорее всего, проглотите важные исключения.Error
с !! , что означает: никогда не ловитеThrowable
s, посколькуError
s являются подклассами последнего.Error
s проблемы, с которыми вы, скорее всего, никогда не сможете справиться (напримерOutOfMemory
, или другие проблемы с JVM)Что касается вашего конкретного случая, убедитесь, что любой клиент, вызывающий ваш метод, получит правильное возвращаемое значение. Если что-то не удается, метод, возвращающий логическое значение, может вернуть false, но убедитесь, что места, в которых вы вызываете этот метод, могут справиться с этим.
источник
Вы должны перехватывать только те исключения, с которыми можете иметь дело. Например, если вы имеете дело с чтением по сети, а время ожидания соединения истекает, и вы получаете исключение, вы можете попробовать еще раз. Однако, если вы читаете по сети и получаете исключение IndexOutOfBounds, вы действительно не можете справиться с этим, потому что вы не знаете (ну, в этом случае вы не знаете), что его вызвало. Если вы собираетесь вернуть false, -1 или null, убедитесь, что это для определенных исключений. Мне не нужна библиотека, которую я использую, возвращая ложь при чтении по сети, когда выбрасывается исключение, когда куча не в памяти.
источник
Исключения - это ошибки, которые не являются частью нормального выполнения программы. В зависимости от того, что делает ваша программа и как она используется (например, текстовый процессор или кардиомонитор), вы захотите делать разные вещи при возникновении исключения. Я работал с кодом, который использует исключения как часть нормального выполнения, и это определенно запах кода.
Ex.
Этот код заставляет меня нервничать. ИМО, вы не должны восстанавливаться после исключений, если это не критическая программа. Если вы выбрасываете исключения, то происходят плохие вещи.
источник
Все вышеперечисленное кажется разумным, и часто на вашем рабочем месте может быть определенная политика. У нас мы определились с типами Exception:
SystemException
(не отмечено) иApplicationException
(отмечено).Мы договорились, что
SystemException
их вряд ли можно будет восстановить и что они будут обработаны один раз наверху. Чтобы обеспечить дополнительный контекст, нашиSystemException
s расширены, чтобы указать, где они произошли, напримерRepositoryException
,ServiceEception
и т. Д.ApplicationException
s может иметь бизнес-значение, какInsufficientFundsException
и должен обрабатываться клиентским кодом.Вот конкретный пример, сложно комментировать вашу реализацию, но я бы никогда не использовал коды возврата, это проблема обслуживания. Вы можете проглотить исключение, но вам нужно решить, почему, и всегда регистрировать событие и трассировку стека. Наконец, поскольку ваш метод не имеет другой обработки, он довольно избыточен (кроме инкапсуляции?), Поэтому
doactualStuffOnObject(p_jsonObject);
может возвращать логическое значение!источник
После некоторого размышления и просмотра вашего кода мне кажется, что вы просто повторно генерируете исключение как логическое. Вы можете просто позволить методу передать это исключение (вам даже не нужно его перехватывать) и обработать его в вызывающей программе, поскольку именно здесь это имеет значение. Если исключение заставит вызывающего абонента повторить попытку этой функции, вызывающий должен быть тем, кто перехватил исключение.
Иногда может случиться так, что возникшее исключение не будет иметь смысла для вызывающего абонента (т. Е. Это исключение сети), и в этом случае вам следует заключить его в исключение, специфичное для домена.
С другой стороны, если исключение сигнализирует о неисправимой ошибке в вашей программе (т.е. конечным результатом этого исключения будет завершение программы), мне лично нравится делать это явным, перехватывая его и генерируя исключение времени выполнения.
источник
Если вы собираетесь использовать шаблон кода в своем примере, назовите его TryDoSomething и перехватите только определенные исключения.
Также рассмотрите возможность использования фильтра исключений при регистрации исключений в диагностических целях. VB имеет языковую поддержку фильтров исключений. В ссылке на блог Греггма есть реализация, которую можно использовать с C #. Фильтры исключений имеют лучшие свойства для возможности отладки по сравнению с перехватом и повторным выбросом. В частности, вы можете зарегистрировать проблему в фильтре и позволить исключению продолжать распространяться. Этот метод позволяет подключить отладчик JIT (Just in Time) для получения полного исходного стека. Повторный бросок обрезает стопку в том месте, где она была повторно брошена.
Случаи, когда TryXXXX имеет смысл, - это когда вы обертываете стороннюю функцию, которая вызывает случаи, которые не являются действительно исключительными или которые просто сложно протестировать без вызова функции. Примером может быть что-то вроде:
Используете ли вы такой паттерн, как TryXXX, или нет - это скорее вопрос стиля. Вопрос о том, чтобы отловить все исключения и проглотить их, не является проблемой стиля. Убедитесь, что разрешено распространение непредвиденных исключений!
источник
Я предлагаю взять подсказки из стандартной библиотеки для используемого вами языка. Я не могу говорить за C #, но давайте посмотрим на Java.
Например, java.lang.reflect.Array имеет статический
set
метод:Способ C был бы
... с возвращаемым значением, являющимся индикатором успеха. Но вы больше не в мире C.
Как только вы примете исключения, вы обнаружите, что это делает ваш код более простым и понятным, убирая код обработки ошибок из вашей основной логики. Стремитесь иметь много операторов в одном
try
блоке.Как отмечали другие, вы должны быть как можно более конкретными в типе исключения, которое вы перехватываете.
источник
Если вы собираетесь перехватить Exception и вернуть false, это должно быть очень конкретное исключение. Вы этого не делаете, вы ловите их все и возвращаете false. Если я получу MyCarIsOnFireException, я хочу сразу об этом узнать! Остальные исключения меня могут не волновать. Таким образом, у вас должен быть стек обработчиков исключений, которые говорят «эй, эй, здесь что-то не так» для некоторых исключений (повторное генерирование или перехват и повторное генерирование нового исключения, которое лучше объясняет, что произошло) и просто возвращают false для других.
Если это продукт, который вы будете запускать, вам следует где-то регистрировать эти исключения, это поможет вам настроить ситуацию в будущем.
Изменить: Что касается вопроса об упаковке всего в try / catch, я думаю, что ответ - да. Исключения в вашем коде должны быть настолько редкими, что код в блоке catch выполняется так редко, что это вообще не влияет на производительность. Исключением должно быть состояние, когда ваш конечный автомат сломался и не знает, что делать. По крайней мере, повторно вызовите исключение, которое объясняет, что происходило в то время, и содержит исключение catch внутри него. «Исключение в методе doSomeStuff ()» не очень полезно для тех, кто должен выяснить, почему он сломался, пока вы в отпуске (или на новой работе).
источник
Моя стратегия:
Если исходная функция вернула void, я изменяю ее на возвращение bool . Если произошло исключение / ошибка, верните false , если все в порядке, верните true .
Если функция должна что-то возвращать, тогда при возникновении исключения / ошибки возвращается null , в противном случае - возвращаемый элемент.
Вместо того , чтобы Ая строка может быть возвращена , содержащим описание ошибки.
В каждом случае перед возвратом чего-либо регистрируйте ошибку.
источник
Здесь есть отличные ответы. Я хотел бы добавить, что если вы все-таки получите что-то вроде того, что вы опубликовали, по крайней мере, распечатайте больше, чем трассировку стека. Скажите, что вы делали в то время, и Ex.getMessage (), чтобы дать разработчику шанс побороться.
источник
Блоки try / catch образуют второй набор логики, встроенный в первый (основной) набор, поэтому они представляют собой отличный способ избавиться от нечитаемого и трудно поддающегося отладке кода спагетти.
Тем не менее, при разумном использовании они творит чудеса в удобочитаемости, но вы должны просто следовать двум простым правилам:
используйте их (экономно) на низком уровне, чтобы уловить проблемы с библиотекой и передать их обратно в основной логический поток. Большая часть необходимой нам обработки ошибок должна исходить из самого кода, как части самих данных. Зачем ставить особые условия, если возвращаемые данные не особенные?
используйте один большой обработчик на более высоком уровне для управления любыми или всеми странными условиями, возникающими в коде, которые не обнаруживаются на низком уровне. Сделайте что-нибудь полезное с ошибками (журналы, перезагрузки, восстановление и т. Д.).
За исключением этих двух типов обработки ошибок, весь остальной код в середине должен быть свободен от кода try / catch и объектов ошибок. Таким образом, он работает просто и, как и ожидалось, независимо от того, где вы его используете или что вы с ним делаете.
Павел.
источник
Возможно, я немного опоздал с ответом, но обработка ошибок - это то, что мы всегда можем изменить и развить со временем. Если вы хотите прочитать что-то еще по этой теме, я написал об этом сообщение в моем новом блоге. http://taoofdevelopment.wordpress.com
Удачного кодирования.
источник