Хорошее использование try catch-блоков?

15

Я всегда сталкиваюсь с этим ... пытаясь найти правильный баланс между попыткой / отловом и кодом, который не превращается в этот непристойный беспорядок вкладок, скобок и исключений, которые отбрасываются обратно в стек вызовов, как горячий картофель. Например, у меня есть приложение, которое я сейчас разрабатываю и которое использует SQLite. У меня есть интерфейс базы данных, который абстрагирует вызовы SQLite, и модель, которая принимает входящие / исходящие данные из базы данных ... Поэтому, если / когда возникает исключение SQLite, оно должно быть передано в модель (которую он вызвал) ), который должен передать его тому, кто вызвал AddRecord / DeleteRecord / что угодно ...  

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

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

попробуй поймать
источник
Какой язык программирования?
Апалала
2
C # на данный момент, но я пытаюсь думать в целом.
trycatch
C # не вызывает объявления об исключениях, которые облегчают обработку исключений там, где это разумно, и избегают искушения программистов ловить их без фактической обработки. Хейлсберг, дизайнер C #, делает дело в отношении проверяемых исключений в этой статье artima.com/intv/handcuffs.html
апалал

Ответы:

14

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

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

Layer      Handles Exception
----------------------------
UI         DataNotFoundException
Model      DatabaseRetrievalException
DAO        SQLException

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

Николь
источник
Я сделал несколько правок, плохо сформулировал исходный Q. Мой вопрос больше о том, как сохранить код в чистоте, обрабатывая все попытки, перехват и прочее. Это начинает чувствовать себя очень грязно, когда попробуйте ловить блоки повсюду ...
trycatch
1
@Ed Вас не беспокоит, что ваш пользовательский интерфейс или Модель ловит "SQLException"? Для меня это не очень актуально. Каждому свое, наверное.
Николь
1
@Ed, это может быть нормально для языков, в которых нет проверенных исключений, но в языках с проверенными исключениями действительно ужасно иметь throws SQLExceptionметод, который не подразумевает, что SQL даже задействован. А что произойдет, если вы решите, что некоторые операции должны идти в хранилище файлов? Теперь вы должны заявить throws SQLException, IOExceptionи т.д. Это выйдет из-под контроля.
Майк Дэниелс
1
@ Для конечного пользователя SQLException не имеет большого значения, особенно если ваша система может поддерживать несколько типов персистентности помимо реляционных баз данных. Будучи программистом графического интерфейса, я бы предпочел иметь дело с DataNotFoundException, а не с целым набором низкоуровневых исключений. Очень часто исключение из низкоуровневой библиотеки является лишь частью обычной жизни выше, в этом случае с ними гораздо легче справиться, когда их уровень абстракции соответствует уровню приложения.
Newtopian
1
@Newtopian - я никогда не говорил, что нужно предоставить необработанное исключение конечному пользователю. Для конечных пользователей достаточно простого сообщения «Программа перестала работать», при этом регистрируя Исключение где-то, что полезно для поддержки. В режиме отладки полезно отобразить исключение. Вы не должны заботиться обо всех возможных исключениях, которые может выдать API, если у вас нет специального обработчика для таких исключений; просто позвольте вашему необработанному ловушке исключений справиться со всем этим.
Эд Джеймс
9

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

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

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

В вашем примере маловероятно, что верхние уровни могут что-либо делать с исключением SQLite (если они это сделают, то все это настолько связано с SQLite, что вы не сможете переключить слой данных на что-то другое). Существует несколько предсказуемых причин сбоя таких вещей, как Add / Delete / Update, и некоторые из них (несовместимые изменения в параллельных транзакциях) невозможно восстановить даже на уровне данных / постоянства (нарушение правил целостности, fi). Уровень персистентности должен переводить исключения в нечто значимое в терминах модельного уровня, чтобы верхние уровни могли решить, следует ли повторить попытку или завершиться неудачно.

апалала
источник
Я согласен и отредактировал мой вопрос, чтобы отразить то, что я плохо сформулировал. Я хотел, чтобы это было больше вопросом о том, как не допустить попыток и ловушек, чтобы не загромождать вещи.
trycatch
Вы можете применять принципы @Ed James и я упоминали в слое или модуле. Вместо того, чтобы вызывать SQLite напрямую, есть несколько методов / функций, которые общаются с SQLite и либо восстанавливаются из исключений, либо переводят их в более общие. Если транзакция включает в себя несколько запросов и обновлений, вам не нужно обрабатывать каждое возможное исключение в каждом: одна внешняя попытка перехвата может преобразовать исключения, а внутренняя - частичные обновления с откатом. Вы также можете переместить обновления в их собственную функцию для упрощения обработки исключений.
Апалала
1
этот материал будет легче понять с помощью примеров кода.
Нажмите Upvote
Вероятно, этот вопрос лучше подходил для стекового потока с самого начала.
Апалала
5

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

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

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

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

Эд Джеймс
источник
Поймать и заставить замолчать исключения - ужасная вещь. Если программа не работает, она должна завершиться с исключением и трассировкой. Это должно сбивать с толку, молча регистрируя вещи, но внося беспорядок в данные.
S.Lott
1
@ S.Lott Я согласен, что не следует молчать об исключениях, потому что они находят их раздражающими, но просто сбой приложения - это нечто экстремальное. Есть много случаев, когда можно перехватить исключение и сбросить систему в известное состояние, в таких случаях глобальный обработчик всего достаточно полезен. Если, однако, процесс сброса, в свою очередь, завершается сбоем, что не может быть безопасно решено, тогда да, пусть его сбой гораздо лучше, чем засовывать голову в песок.
Newtopian
2
Опять же, все зависит от того, что вы строите, но я, как правило, не согласен с тем, чтобы позволить им пузыриться, поскольку это создает утечку абстракции. Когда я использую API, я бы предпочел увидеть, что он предоставляет исключения, которые соответствуют модели API. Я также ненавижу сюрпризы, поэтому я ненавижу, когда API просто позволяет исключению, связанному с его реализацией, смотреть без предупреждения. Если я не знаю, что на меня пойдет, как я могу отреагировать! Я слишком часто использую гораздо более широкие ловушки, чтобы предотвратить неожиданное падение моего приложения без предупреждения.
Newtopian
@Newtopian: исключения «Обтекания» и «Перезапись» уменьшают утечку абстракций. Они все еще всплывают соответствующим образом. «Если я понятия не имею, что на меня пойдет, как я могу реагировать! Я слишком часто использую гораздо более широкие ловушки». Это то, что мы предлагаем вам прекратить делать. Вам не нужно ловить все. В 80% случаев правильно ловить нечего. 20% времени есть значимый ответ.
S.Lott
1
@Newtopian, я думаю, что мы должны различать исключения, которые обычно генерируются объектом и, следовательно, должны быть обернуты, и исключения, которые возникают из-за ошибок в коде объекта и не должны быть обернуты.
Уинстон Эверт
4

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

  1. ArrayIndexError - возникает, когда пользователь пытается выскочить из пустого стека
  2. NullPtrException - возникает из-за ошибки в реализации, приводящей к попытке ссылки на нулевую ссылку

Упрощенный подход к переносу исключений может решить включить оба этих исключения в класс исключений StackError. Тем не менее, это действительно упускает смысл упаковки исключений. Если объект выдает исключение низкого уровня, это должно означать, что объект сломан. Однако есть один случай, когда это приемлемо: когда объект фактически сломан.

Смысл обертывания исключений заключается в том, что объект должен давать правильные исключения для обычных ошибок. Стек должен вызывать StackEmpty, а не ArrayIndexError при извлечении из пустого стека. Намерение не состоит в том, чтобы не вызывать другие исключения, если объект или код поврежден.

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

Вернемся к вашему примеру SQLException: почему вы получаете SQLException? Одна из причин в том, что вы передаете недопустимые запросы. Однако, если ваш уровень доступа к данным генерирует неверные запросы, он поврежден. Он не должен пытаться переразметить свою поломку в исключении DataAccessFailure.

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

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

Уинстон Эверт
источник