Допустим, валидность аргументов я буду проверять перед вызовом данного метода самостоятельно, так что первые три типа исключений (ArgumentException, ArgumentNullException и PathTooLongException) сразу же отпадают. Существование директории лучше не проверять предварительно, ведь в общем случае добиться атомарности в данном случае всё равно не получится:
if (Directory.Exists(path))
{
// Директория удаляется
ZipFile.CreateFromDirectory(path, "some.zip"); // Выбрасывается исключение из-за отсутствия sourceDirectory
}
Остальные типы исключений (IOException, UnauthorizedAccessException и NotSupportedException) также могут возникнуть из-за каких-то внешних обстоятельств, проверять которые заранее мы либо запаримся, либо просто не сможем атомарно, как в предыдущем случае.
Теперь представьте (и в моём случае это вполне частая задача), что реагировать на все типы исключений мне необходимо одним и тем же образом (например, писать текст исключения в лог и завершать выполнение текущего метода). Чем в таком случае плох принцип перехвата исключений по базовому классу (в данном случае Exception)?
Подливают масла в огонь ещё и third-party libraries, по документации которых (если она вообще есть) далеко не всегда понятно, какие именно типы исключений какие методы выбрасывают. Лезть в реализацию? Во-первых, она на всегда доступна, а, во-вторых, надеяться на такую информацию, я считаю, не стоит. Проще уж ловить Exception.
Ещё один момент. Что если разработчики используемой Вами библиотеки / фреймворка решат добавить с новой версией дополнительные типы исключений? Например, было исключение NotSupportedException, которое возникало по двум следующим причинам:
NotSupportedException
sourceDirectoryName or destinationArchiveFileName contains an invalid format.
-or-
The zip archive does not support writing.
А теперь представьте, что его "разбили" на два типа -- InvalidFormatException и ZipArchiveDoesNotSupportWritingException. И это хорошо, если они оба будут наследоваться от NotSupportedException! А что, если нет? Читать changelon? А что, если упустите эту информацию из виду? Писать юнит-тесты? Это тоже не идеальный инструмент, т.к. их составляют люди, и они вполне могут забыть проверить какую-то определённую ситуацию.
Единственная проблема, которая мне приходит на ум -- это возможный перехват "лишних" исключений (например, перехват нажатия Ctrl-C или отмены таска), но для таких случаев многие библиотеки и фреймворки специально создают отдельную иерархию классов, которая начинается не с Exception.
В общем, что Вы думаете по этому поводу? Как у Вас в компании относятся к перехвату исключений по базовому классу и какие правила для этого заведены?
Здравствуйте, FrozenHeart, Вы писали:
FH>Почему все настолько плохо относятся к практике перехвата исключений по базовому классу?
По моему, всё зависит от того, что с исключением делать собираешься. Если более конкретные исключения можно/нужно как-то особо обрабатывать, то естественно надо и отдельно ловить. Если же логика будет одинаковой, то обрабатывать именно базовый класс вполне естественно.
Другое дело, что ловить исключение просто чтобы проигнорировать, как правило, плохо.
Здравствуйте, FrozenHeart, Вы писали:
FH>Вот, что я имею ввиду:
Перехватывать стоит только те исключения, которые знаешь как обрабатывать. Т.е. в примере с Zip-архивом: IOException (заодно и все потомки отловятся) + UnauthorizedAccessException. Остальное говорит скорее о багах в коде, чем об обрабатываемой ситуации.
Отлавливание всех исключений имеет всего один, зато исключительно неприятный недостаток: в общем случае нельзя гарантировать, что приложение находится в адекватном состоянии, данные не повреждены и что повторный вызов того же кода не приведёт к ещё более забавным спецэффектам.
Определиться с тем, что отлавливать, очень просто: представляем, что помимо DoSmth() есть метод TryDoSmth(). Прикидываем, в каких случаях TryDoSmth() должен возвращать false, в каких — падать, ловим исключения из первой группы.
А ещё лучше — не представлять, а написать этот метод самому. По крайней мере, не придётся исправлять по всему коду, если промахнёшься с выбором.
P.P.S. Тут нужно ещё разделять чисто инфраструктурный код, в котором стандарт — "Где-то ошибка? Падай, код выше по стеку разберётся". И бизнес-логику, в которой ожидаемое поведение слегка другое: при ошибке откатываем все изменения + выдаём пользователю диагностику. Для каждого из вариантов подходы будут разные.
Здравствуйте, FrozenHeart, Вы писали:
FH>Почему все настолько плохо относятся к практике перехвата исключений по базовому классу?
Все — это кто?
FH>Мне необходимо вызвать метод ZipFile.CreateFromDirectory. Смотрим, что он может выбросить:
<> FH>Теперь представьте (и в моём случае это вполне частая задача), что реагировать на все типы исключений мне необходимо одним и тем же образом (например, писать текст исключения в лог и завершать выполнение текущего метода). Чем в таком случае плох принцип перехвата исключений по базовому классу (в данном случае Exception)?
Если проблема лишь в кривой, слишком расплющенной, иерархии исключений, — то это вопрос к автору библиотеки.
У С++ стандартные исключения образуют иерархию.
Вообще же, механизм исключений в некоторых языках сделан "шоб было", и для комфортной работы с ними придумывают всякие хаки.
Например, кейс
void CommonCatches() {
try { throw; } // ожидается, что CommonCatches вызывается из catch-блока. этот хак ретранслирует исключение в местные catch-блокиcatch(const E1& e) { .......... }
catch(const E2& e) { ..........; throw; } // если надо, можем и выбросить обратноcatch(const E3& e) { .......... }
catch(const exception& e) { ..........; throw; }
catch(...) { MoreCommonCatches(); } // пакеты обработчиков могут образовывать цепочку
}
void YourFunction() {
try {
try { .......... }
catch(...) { CommonCatches(); } // обрабатываем известным способом
}
catch(const E2& e) { .......... } // в том числе, недообработанное ранее исключениеcatch(const E4& e) { .......... }
catch(const E5& e) { .......... }
}
FH>Подливают масла в огонь ещё и third-party libraries, по документации которых (если она вообще есть) далеко не всегда понятно, какие именно типы исключений какие методы выбрасывают. Лезть в реализацию? Во-первых, она на всегда доступна, а, во-вторых, надеяться на такую информацию, я считаю, не стоит. Проще уж ловить Exception.
Если ты не знаешь, какие исключения кидает библиотека, значит, тебе это не нужно.
Максимум, что можешь сделать — это написать finally-код (RAII, using, что там ещё есть в любимом языке).
FH>Ещё один момент. Что если разработчики используемой Вами библиотеки / фреймворка решат добавить с новой версией дополнительные типы исключений? Например, было исключение NotSupportedException, которое возникало по двум следующим причинам: FH>А теперь представьте, что его "разбили" на два типа -- InvalidFormatException и ZipArchiveDoesNotSupportWritingException. И это хорошо, если они оба будут наследоваться от NotSupportedException! А что, если нет? Читать changelon? А что, если упустите эту информацию из виду? Писать юнит-тесты? Это тоже не идеальный инструмент, т.к. их составляют люди, и они вполне могут забыть проверить какую-то определённую ситуацию.
А если разработчики рукожопые пионеры, которые в ударе/угаре сломали обратную совместимость, то почему ты думаешь, что они только в этом месте нарушили старые контракты?
FH>В общем, что Вы думаете по этому поводу? Как у Вас в компании относятся к перехвату исключений по базовому классу и какие правила для этого заведены?
В приведённом примере вы не можете гарантировать, что вы проверите аргументы методов. Может проверите, а может опечатаетесь и пропустите баг. Если вы будете ловить все исключения, то вы про этот баг можете и не узнать. А если будете ловить только те, которые ожидаете, то про этот баг вы узнаете и исправите его.
Если ваша библиотека так ломает свои внешние интерфейсы, это не очень хорошая библиотека и от неё лучше избавиться.
Считаю, что по базовому классу можно ловить только на самом верхнем уровне, где и нужно залоггировать это исключение. Ну или в инфраструктурном коде вроде обработки транзакций.
В общем и целом ловля всех исключений это порочная практика. Она существует давно и у неё много своих сторонников. Не проверять коды возврата, подавлять исключения, и тд. Мол софт работает без падений, пользователь не видит ошибок и тд. Я категорически против такой практики.
Здравствуйте, Sinix, Вы писали:
S>Здравствуйте, FrozenHeart, Вы писали:
S>P.P.S. Тут нужно ещё разделять чисто инфраструктурный код, в котором стандарт — "Где-то ошибка? Падай, код выше по стеку разберётся". И бизнес-логику, в которой ожидаемое поведение слегка другое: при ошибке откатываем все изменения + выдаём пользователю диагностику. Для каждого из вариантов подходы будут разные.
В бизнес-логике все то же самое, никаких отличий. Обрабатывать надо только там, где это необходимо методу для выполнения своего контракта. Если структура данных осталась в неконистистентном состоянии, это проблема вызывающей стороны. То есть design by contract в самом очевидном виде. Но стоит сказать, что в самой распространенной STM-модели бизнес-логики (то есть изменения копии данных с атомарной фиксацией) откат транзакции по Exception — это единственно разумный паттерн (иное показатель — криворукости) и помимо того реализуется всеми автоматическими менеджерами транзакций. В shared memory бизнес логика должна быть не потокобезопасна, вся синхронизация должна производится снаружи. Да, шальной exception может привести к потере целостности in-memory модели, но практика показывает, что лучше в этом случае убить полностью логическую единицу (то есть игру, коммуникацию и т.п.), так как это свидтельствует о явной ошибке, нежели пытаться восстанавливаться.
Социализм — это власть трудящихся и централизованная плановая экономика.
DE>По моему, всё зависит от того, что с исключением делать собираешься. Если более конкретные исключения можно/нужно как-то особо обрабатывать, то естественно надо и отдельно ловить. Если же логика будет одинаковой, то обрабатывать именно базовый класс вполне естественно.
Да, этого я и не отрицал. Я говорю лишь о ситуациях, когда на все выбрасываемые определённым методом исключения необходимо реагировать одним и тем же образом.
DE>Другое дело, что ловить исключение просто чтобы проигнорировать, как правило, плохо.
Почему же? Зависит от задачи.
Например, не смогли отправить пакет при помощи TCP -- да и ладно, следующий отправим (если получателю необходимо знать лишь конечное состояние).
S>Перехватывать стоит только те исключения, которые знаешь как обрабатывать. Т.е. в примере с Zip-архивом: IOException (заодно и все потомки отловятся) + UnauthorizedAccessException. Остальное говорит скорее о багах в коде, чем об обрабатываемой ситуации.
А что по поводу NotSupportedException, например? Это разве говорит о багах в коде?
NotSupportedException
sourceDirectoryName or destinationArchiveFileName contains an invalid format.
Здравствуйте, LaPerouse, Вы писали:
LP>В бизнес-логике все то же самое, никаких отличий.
Не, я про т.н. бизнес-операции. Когда совершается последовательность действий и с точки зрения пользователя она должна или завершиться целиком, или не оставлять побочных эффектов.
Это необязательно транзакция в классическом понимании. Если вернуться к примеру топикстартера — код не должен создавать битых архивов.
Как правило такие вещи решают без отлова исключений, откатом неудачных действий в finally, но всякое бывает
начинают плеваться в монитор.
К>Если проблема лишь в кривой, слишком расплющенной, иерархии исключений, — то это вопрос к автору библиотеки.
Ну, я привёл пример с ZipFile.CreateFromDirectory из .NET Framework'а. Там кривая иерархия исключений?
К>У С++ стандартные исключения образуют иерархию.
Но проблема одна и та же.
К>Если ты не знаешь, какие исключения кидает библиотека, значит, тебе это не нужно.
Что это значит? Не ловить исключения вообще?
К>А если разработчики рукожопые пионеры, которые в ударе/угаре сломали обратную совместимость, то почему ты думаешь, что они только в этом месте нарушили старые контракты?
Здравствуйте, FrozenHeart, Вы писали:
FH>Почему все настолько плохо относятся к практике перехвата исключений по базовому классу?
Собственно, зависит от того, кто эти все, и что предполагается делать при перехвате.
1) Тупой catch(Exception) { ... do nothing... } готовит для автора личный котёл в аду — мы не понимаем, что произошло, улики уничтожены
2) Охватывающий catch, который логирует произошедшее и терминирует приложение или пробрасывает наружу этот же (или обёртывающий) эксепшн, или код ошибки, выглядит куда более логично
Базовый класс тем и удобен, что можно опираться на некоторый уровень достоверности "моё/чужое", чтобы не схватить чужую ошибку (а то будут думать на нас). Не случайно рекомендуется бросать свои кастомные исключения, наследованные от ApplicationException, но чтобы при этом контекст произошедшего был понятен и в базовом классе (все эти What, Message, ToString, которые должны переопределяться наследниками).
Здравствуйте, FrozenHeart, Вы писали:
FH>А что по поводу NotSupportedException, например? Это разве говорит о багах в коде?
Охх... а вот фиг его знает. Это как раз пример дурацкой ситуации "что-то пошло не так". Т.е. по исключению нельзя точно установить причину проблемы, и что с ней делать — непонятно.
Если встречается редко — пускай падает с человекочитаемым сообщением об ошибке (т.е. отлавливать на общих правах выше по стеку).
Если часто — придётся обрабатывать как частный случай (т.е. как минимум проверяем на запись или пробуем другое имя).
Здравствуйте, FrozenHeart, Вы писали:
S>>Перехватывать стоит только те исключения, которые знаешь как обрабатывать. Т.е. в примере с Zip-архивом: IOException (заодно и все потомки отловятся) + UnauthorizedAccessException. Остальное говорит скорее о багах в коде, чем об обрабатываемой ситуации.
FH>А что по поводу NotSupportedException, например? Это разве говорит о багах в коде?
Нет, не говорит. Метод должен обработать эту ситуацию в соответствии со своим контрактом. Например, ничего не сделать или выдать исключение (желательно на своем логическом уровне, а не какой нибудь IOException нижележащей библиотеки).
Социализм — это власть трудящихся и централизованная плановая экономика.
Здравствуйте, FrozenHeart, Вы писали:
FH>Аудитория различных форумов, которые уже при виде <> начинают плеваться в монитор.
Ну не знаю, это не "все". Либо (ты) им показывал говнокод, и народ плевался на говнокод как таковой, а не на исключения как таковые.
К>>Если проблема лишь в кривой, слишком расплющенной, иерархии исключений, — то это вопрос к автору библиотеки. FH>Ну, я привёл пример с ZipFile.CreateFromDirectory из .NET Framework'а. Там кривая иерархия исключений?
Если не различать тонкости, то, как минимум, от трёх обработчиков можно избавиться.
К>>Если ты не знаешь, какие исключения кидает библиотека, значит, тебе это не нужно. FH>Что это значит? Не ловить исключения вообще?
Да вообще не программировать, что уж.
Исключения из этой библиотеки — да, не ловить. Если что-то оттуда вылетает, то оно вылетает не для тебя (в вышестоящий слой), либо вылетает по ошибке (забыли поймать или ретранслировать в известный публичный тип).
Тебе оно надо — компенсировать чужие баги.
К>>No woman no cry. FH>Свои минусы этот подход тоже имеет. Прокидывать return code'ы на несколько уровней вверх? Жесть.
Во-первых, это повод уменьшать количество уровней, ответственных за обработку проблемы.
Во-вторых, вода дырочку найдёт. Есть всякие обходные способы и (анти)паттерны — например, заменить исключения сигналами/слотами разной степени долгоживучести.
Здравствуйте, FrozenHeart, Вы писали:
FH>Приветствую.
FH>Почему все настолько плохо относятся к практике перехвата исключений по базовому классу?
FH>Вот, что я имею ввиду:
FH>
Где-то попалась статейка о практике перехвата исключений.
как-то так
Для начала надо разделить исключения по такому признаку — 1)косяки разработчика и 2)косяки пользователя.
По первым надо выудить информацию типа : какое исключение, в каком месте программы, в какой функции, с какими параметрами;
записать в лог и может быть аварийно завершиться. Ловушки на такие исключения ставятся на самом верхнем уровне и их можно ловить по базовому классу.
По вторым нужно как можно ближе к месту возникновения проанализировать, что же пользователь сделал не так,
превратить в понятное пользователю сообщение, выдать это сообщение пользователю и вернуть систему в согласованное состояние.
Вот тут надо ставить ловушки на конкретный тип.
Здравствуйте, jazzer, Вы писали:
J>Да сколько ж можно это бездумно цитировать...
А я не бездумно цитирую.
Был вопрос "чего придерживаются в вашей компании".
К>>No woman no cry.
J>Вообще-то у этой фразы другой смысл, нежели "нет человека — нет проблемы"
Боб Марли выслал Тане мячик
обратно в Краснодарский край
с пометкой "Выловил. No woman
no cry"
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, jazzer, Вы писали:
J>>Да сколько ж можно это бездумно цитировать...
К>А я не бездумно цитирую. К>Был вопрос "чего придерживаются в вашей компании".
У вас их не используют по той же причине, что и в опенсорсе для гугла?
Т.е. у вас слишком много древнего овнокода, который писался без единой мысли об исключениях?
Потому что именно это написано в их объяснении запрета на исключения.
А то обычно эту часть как-то опускают, просто пишут, что, мол, Сам Гугл исключения запретил, уж им-то виднее.
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, jazzer, Вы писали:
J>>У вас их не используют по той же причине, что и в опенсорсе для гугла?
К>Для того, чтобы как можно менее болезненно синхронизироваться с гугловским опенсорсом.