Как стать автором
Обновить

Комментарии 121

Два вопроса:
— Какое это отношение имеет к статье?
— Нафига постить текст как картинку?
Так текст крупнее же. Не знаю насчет первого пункта.
У народа с чувством юмора неважно тут.
НЛО прилетело и опубликовало эту надпись здесь
Самый частый способ обработки исключения — это alert, messagebox и друзья в UI интерфейсе пользователя + запись в лог.
Я думаю, что всё сильно зависит от реально требуемой надежности проекта. Да, все говорят, что хотят/делают «максимально надежно», но на самом деле мало кто будет тратить кучу времени на то, чтобы разобрать все возможные исключения в «сервисе для загрузки фоток». Зато в сервсие обработки платежей наверняка будет разобран каждый потенциальный чих, причем не только в виде вывода сообщения об ошибке и записи в лог.

Обработка ошибок — это в значительной степени бизнес-логика, а не что-то чисто техническое, абстрагированное от контекста. Универсального решения нет, и вряд ли оно появится.
Согласен. Когда речь идёт о чём-то простом или чём-то, что не требует особых стараний при обработке ошибок, то и проблем нет, или они минимальны. Проблемы начинаются, когда система должны пахать 24х7 и обеспечивать максимально возможную устойчивость и давать обслуживающему персоналу актуальные сведения о проблемах, для их оперативного устранения.
Есть только один способ обеспечить надеждную работу 24х7 сервиса. Это QA.
Никакая обработка ошибок не сможет заменить QA-отдел.
Никакой QA-отдел ничего не сможет сделать, если нет обработки ошибок. Разве что смогут констатировать факт о том, что разработка — какашка, которая валится и ничего никому не объясняет. Или нет?
backtrace и способ воспроизведения. никакая дополнительная обработка не нужна.
То есть, пусть пользователь не понимает, что происходит, да? И если обработка совсем не нужна, то пусть всё падает, да? Или как — не могу понять.
О каком пользователе речь?
Еще раз: надежность софта обеспечивается с помощью QA. Исключительно.
Для QA сложные обработки ошибок не нужны.
До пользователей не должен доходить софт с ошибками.
Есть множество исключений, не говорящих об ошибке в программе, но которые нужно обрабатывать и которые требуют сложных обработчиков. Если ваша программа должна работать в условиях нехватки памяти, то обработчик соответствующего исключения может быть довольно сложен: ему надо где‐то найти память, освободить её (желательно не дав занять тому процессу, что захватил остальную память) и продолжить прерванный процесс. Есть ещё исключения, связанные с ФС: сообщение об отсутствии привелегий для чтения нужного файла или отсутствие самого файла обычно лучше донести всё же до администратора пользователя, а не до QA отдела (если только это не ошибка установщика).

И не надо называть исключения ошибками (хотя это не вы начали).
Если программа «должна» — что угодно(например, работать в условиях нехватки памяти) должна, то и надо ее проектировать с учетом ее особенностей.
Речь же не о специфичном функционале, а об общей обработке исключений.
И в данном случае когда я говорю «исключение» я имею ввиду «ошибку». Ну просто потому, что здесь все обсуждения вокруг исключений-ошибок.

Моя основная мысль — никакие супер-пупер подходы к обработке исключений-ошибок не дадут качественного софта на выходе, если нет нормально налаженного механизма тестирования.
У нас почему-то руководство все чаще и чаще приходит к крамольной мысле, что отдел тестирования не нужен. Я думаю это связано с использованием языков с GC, которые очень многие ошибки сглаживают и софт начинает «выглядеть» надежным.
Кавычки не случайны. То что софт не падает, не означает что он надежный. Это означает что он не падает и не более того. Что с ним происходит внутри — большой вопрос.
Но эффективные-менеджеры довольны и этим: софт отлично работает! ни одного краша или ANR! Отдел тестирования не нужен! А то, что софт работает через *опу — все равно с первого взгляда не видно…

Простите за сумбур. Накипело.
Присоединился я как-то к проекту, который начинали мои индийские коллеги, так вот у них все методы DAL содержали внутри обертки, перехватывающие все (!) исключения и возвращающие зоопарк, начиная от простого null и заканчивая наборами magic numbers типа 1, 9, 999. Самыми частыми дефектами, которые заводили пользовали, были «я нажимаю туда, все заканчивается как нужно, но потом в другой части приложения я не вижу результат». И начинается недельное выяснение проблемы и корявые фиксы. Первое, что я сделал — это убрал полностью все обертки и запретил им пользоваться try-catch везде, кроме методов на самой вершине иерархии вызовов (те же OnButtonClick), обязав при этом показывать красивое ссобщение пользователю с полной информацией об ошибке. После этого нам удалось буквально за неделю стабилизировать проект, что команда не могла сделать несколько месяцев. «Говорящие» сообщения и YSOD (нам повезло с пользователями, они были не против такого подхода) моментально сообщают о проблеме и не требуют практически никакой заботы со стороны разработчиков.
Ну вот и я о том же. Но жители этого топика видимо считают это не правильным, судя по стремительному падению моей кармы. :)))
Я бы скорее сказал, что «жители этого топика» не увидели в ваших сообщениях такого предложения (я его, кстати, там тоже не увидел). Вот вы в первом сообщении сказали про QA отдел, но не сказали про обработку исключений ни‐че‐го. А ведь вывод информации об ошибке в удобном для пользователя и отдела QA виде — это тоже вариант обработки.

Надо просто лучше раскрывать мысль в комментариях. Думаю не ошибусь, если скажу, что backtrace из второго комментария прежде всего ассоциируется с командной строкой. В крайнем случае — с каким‐то скрытым от пользователя логом. В любом случае — с чем‐то, с чем обычному пользователю неудобно работать.
«До пользователей не должен доходить софт с ошибками» == «До пользователей не должен доходить софт»
Ну мы же все таки об идеальном случае говорим, не?
Нет. Вы рассматриваете конкретные случаи из реальной жизни, хоть и абстрагируясь от деталей. Пожалуйста, продолжайте, мне интересно ;-)
Обеспечивается или контролируется? Это разные понятия. Надежность софта обеспечивается кодом и ничем иным. Надежность сервиса — суперпозиция надежности софта и ещё кучи вещей.
Заявление в духе: надежность софта обеспечивается движением атомов.
Код не самобытен, он зависит от многих факторов. В частности от тестирования не в последнюю очередь.
Тестирование не увеличивает надежность кода. Оно лишь даёт возможность выявить ненадежные места.
Еще одно шедевральнео высказывание. :)
Ну да, само тестирование ничего не делает. А вот программисты на основе тестирования таки улучшают надежность кода.
В вашем мире работают только прямые связи?
Слово «возможность» вы намеренно пропустили? Тестирование не гарантирует обнаружение ошибки, а даже если она обнаружена, то не гарантирует её исправление, а даже если исправлена, то не гарантируется внесение при исправлении новых. В любом случае надежность обеспечивает программист, а тестировщик лишь контролирует качество этого обеспечения, стараясь заметить ненадежные места.
После устранения ненадёжных мест код становится надёжнее. Присоединяюсь к предыдущему вопросу:
В вашем мире работают только прямые связи?
В вашем мире работают только прямые связи?
Нет, всё гораздо банальнее — господин VolCh просто очень любит спорить дискутировать. Он не тролль, просто, наверное, пытается найти в спорах то, чего там обычно нет — истину.

Более того, лично я часто с ним, как минимум частично, согласен. И даже в данной ветке. Вопрос связи между тестированием и надёжностью не так однозначен. Навскидку: популярные open source проекты отлично протестированы, и выглядят надёжными… пока ты ходишь теми же тропами, что и толпы до тебя — эти тропы уже собрали свой урожай смертников, подорвавшихся на багах, и были качественно разминированы. Но код этих проектов остаётся традиционно кривым, поэтому шаг вправо-влево от стандартных способов использования — и перед тобой непаханное поле багов. С другой стороны, хорошая архитектура, code style guide, правильный набор соглашений, unit тесты и утилиты статического анализа кода позволяют достичь высокого уровня надёжности кода без ручного тестирования — и не на типовых сценариях использования, а в целом.
Тестирование не единственный инструмент. Но очень важный.
open source не пересекается с качественным тестированием.
пользователи ходят по тропам, как вы верно заметили, а профессиональные тестеры — нет.
Насчет сервиса платежей, думал также как Вы пока не устроился работать в один из них. Это был достаточно крупный «сервис», но качество кода меня сильно удивило. Там были и методы 300+ строк, методы поменьше, но с количеством условных конструкций под 40-50. Кучи спагетти-кода. Просто магические или даже загадочные места. В плюс ко всему была куча мест которые алгоритм проведения платежа реализовывали по-разному и правильных вариантов было очень мало. Я это к тому, что МОЯ практика показывает, что говнокода полно и в «сервисах по загрузке кода» и в «платежных системах», а любую надежность можно обеспечить человеческим фактором. Не прошел платеж? Сейчас наш программист вручную проверит что с ним случилось — и этом способ работает.
Абсолютно согласен. Так всё и есть. И баги компенсируются ручной работой, зачастую. Насколько это приятно клиенту и самому разработчику — отдельный вопрос (но я думаю всем всё очевидно).
Да, все говорят, что хотят/делают «максимально надежно», но на самом деле мало кто будет тратить кучу времени на то, чтобы разобрать все возможные исключения в «сервисе для загрузки фоток». Зато в сервсие обработки платежей наверняка будет разобран каждый потенциальный чих, причем не только в виде вывода сообщения об ошибке и записи в лог.
Вы не поверите, но всё ровно наоборот. Потому что «в сервисе для загрузки» важно, чтобы фотки таки загрузились и если что-то куда-то не может записаться из-за разницы кодировок или слишком большой подписи вы можете это как-то попробовать пофиксить и загрузить плавильно хотя бы фотки без описаний или с частью описаний.

А вот в сервисе для обработки платежей куда важнее, чтобы деньги по ошибке со счёта не ушли в неизвестном направлении — так что любая ошибка трактуется однозначно: «откатываем транзакцию, пишем в лог, дальше пусть служба поддержки разбирается».
По-моему у автора явно выраженная эксепшенофилия.
смотрите в оба и не позволяйте исключениям убивать ваше приложение.


Вот какие приложения «убиваются» эксепшенами, м? Hello world консольный? Все нормальные фреймворки предоставляют коллбэки для хэндлинга эксепшенов. О качестве вашей портянки public static class Exceptions вообще промолчу.

P.S. Отдельный приз за это:
        public static bool NotFatal(this Exception ex) {
            return fatalExceptions.All(curFatal => ex.GetType() != curFatal);
        }

        public static bool IsFatal(this Exception ex) {
            return !NotFatal(ex);
        }


Дайте угадаю, на Delphi кодили раньше, да?
Убиваются любые, где Handled не будет выставлен в true, конечно же. Это всё, что вам не понравилось в статье?

Нет, на делфи не программировал. А что не так с этими методами?
А что не так с этими методами?

может то, что можно было обойтись одним методом?
Это для удобства, а то пришлось бы здесь использовать анонимную функцию или что-то другое:
Exceptions.TryFilterCatch(host.Close, Exceptions.NotFatal,
    ex => logger.Error("Исключение при закрытии хоста сервиса.", ex));

Можно было бы метод назвать по-другому, например IsNotFatal(). Хорошая практика называть методы, используя глаголы в начале имени.
а не проще поменять реализацию TryFilterCatch?
if (!isRecoverPossible(ex)) throw;

заменить на
if (isFatal(ex)) throw;


в таком случае конструкция выглядит более логичной

Exceptions.TryFilterCatch(host.Close, Exceptions.IsFatal,
    ex => logger.Error("Исключение при закрытии хоста сервиса.", ex));


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

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

Изначально всё пошло от того, что когда появились исключения их начали использовать вместо возвращения ошибок. Основных причин было две: во-первых возврат ошибок в языках где нет удобной поддержи кортежей приводил к необходимости смешивать в возвращаемом значении два типа данных (результат и ошибку) что всегда воспринималось как не элегантный подход от которого с радостью избавились; во-вторых обработка ошибок дело нудное и сложное, а исключения дали возможность с (относительно) чистой совестью избавиться от обработки ошибок здесь и сейчас, и отложить её «на потом».

Суть исключений не в том, что ими можно сигнализировать об ошибках, и не в том, что эти ошибки можно удобно сгруппировать по классам и обрабатывать фильтруя по этим классам, а в том, что исключения раскручивают стек! Поэтому выкидывать исключения нужно только тогда, когда действительно необходима именно раскрутка стека — т.е. при возникновении критической ошибки, т.е. такой, после которой нельзя восстановиться и всё-таки как-то выполнить поставленную задачу. Все остальные, обычные ошибки необходимо обрабатывать как можно ближе к месту, где они возникли — т.е. там, где вызывалась функция в которой произошла ошибка — только в той точке, где мы для чего-то вызвали эту функцию мы можем адекватно обработать все возможные варианты ошибок и принять решение как быть дальше, раз мы не получили от этой функции нужный нам результат.

Тут возникает логичный вопрос: а как мы узнаем, критическая это ошибка или нет — ведь всё зависит от требований высокоуровневой задачи. Например, ошибка DNS для задачи выкачки url — критическая или нет? Вроде бы да… но если у нас задача выкачивать url параллельно тысячами в секунду ошибка с одной из url вряд ли действительно является критической. Правильный ответ на этот вопрос — только сама высокоуровневая задача (т.е. ваше основное приложение) знает, что для неё является критической ошибкой, а что нет.

Следствием из этого является то, что любые модули/библиотеки, по определению, не знают, в каком приложении их используют, и какие ошибки являются критическими, а какие нет. Поэтому они не должны выкидывать исключения (на самом деле это не совсем так — если в них возникают ошибки вроде out of memory или segmentation fault и им подобные — это вполне себе типичные исключения, которые можно и нужно выкидывать как исключения, а не возвращать кодами ошибок — но это типичное «исключение из правила, только подтверждающее это правило»). При этом внутри себя библиотеки вполне могут пользоваться исключениями, когда необходима именно раскрутка стека — например, в задачах рекурсивного парсинга — но все свои внутренние исключения эти библиотеки должны сами и перехватывать, а наружу возвращать обычные ошибки.

При таком подходе всё работает совершенно адекватно: все ошибки, которые можно нормально обработать, обрабатываются в том месте, где они возникли (и без излишних простыней try/catch); исключения возникают крайне редко, их очень немного, и большая их часть просто честно убивает приложение (если в приложении и есть глобальный перехват всех исключений, то он используется для попытки вывода сообщения об исключении в лог перед тем как приложение будет аварийно завершено, а не для молчаливого заглушения исключений), а явно обрабатывается очень небольшое количество вполне конкретных исключений, специфичных для вашего приложения (вроде выше упомянутых примеров, когда из-за ошибки парсинга или выкачки url необходимо прервать с раскруткой стека большой блок логики приложения).
Шикарный комментарий. Отлично дополняет статью и делает её значительно лучше. Пожалуй, вы сказали то, что я сказать забыл. Большое спасибо.
Однако, у меня есть пара комментариев.

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

Ещё момент. Выкидывать исключения из библиотек именно в плане указания того, что библиотекой пользуются неправильно — хорошая практика. Это легко и просто отлаживать. Программисту остаётся только добиться того, чтобы таких исключений никогда не возникало (этих «защитных» исключений, которые защищают инварианты класса). Этого помогают добиться юнит-тесты и всякие разные практики.
Да, возвращать ошибку выше нужно. Но — не возвращать эту же ошибку выше (как нередко делают с исключениями), а возвращать выше другую ошибку: например, из вызванной функции мы получили ошибку DNS «host not found», а выше возвращаем ошибку «не могу скачать url (host not found)». Иными словами возврат ошибки выше это не какая-то раздражающая бессмысленная тупая деятельность, а часть вменяемой логики вашего кода, не менее важная чем вся остальная логика.

Насчёт выкидывания ошибок использования (функцию вызвали с некорректными параметрами) в виде исключений — согласен, в большинстве случаев это правильное использование исключений.
например, из вызванной функции мы получили ошибку DNS «host not found», а выше возвращаем ошибку «не могу скачать url (host not found)

Не согласен. Допустим, на более высоком уровне стоит логгер. И мне при отладке намного полезнее знать, что произошла вот эта вот конкретная системная ошибка, а не «извините, что-то пошло не так».
Давайте не будем смешивать обработку ошибок и их логирование. Нужна инфа по всем низкоуровневым ошибкам в логах — ну так пишите эти ошибки в логи там, где они возникают.

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

Впрочем, обычно на практике обе задачи (обработки и логирования/отладки) решаются одновременно — при возврате ошибки выше к коду/описанию проблемы в понимании текущей функции («не могу скачать url») к конец дописывается описание проблемы произошедшей на низком уровне («ошибка DNS») — первое необходимо для обработки ошибки на верхнем уровне, второе для логирования/отладки.
Естественно, и при этом подходе — «на практике» — наверх приходит и низкоуровневое описание, иначе откуда бы взялась эта информация. И в случае, кстати, возврата ошибки искусственным исключением, при создании «общего» класса «ошибка выкачки» с деталями в приложенном сообщении, это решается элегантно.
Да, вот что в исключениях хорошо, так это возможность цеплять к ним кучу структурированных данных. Но все эти приятные и элегантные особенности, к сожалению, являются приманкой в ловушке — использовать исключения вместо возврата ошибок нельзя! Хотите, чтобы одновременно было и правильно, и элегантно — используйте языки с поддержкой кортежей, в которых функции могут возвращать сразу и результат и ошибку, отдельно, в переменных разных типов, причём никто не мешает ошибку передавать как сложный объект включающий и код, и тип, и описание, и стек трейс.
Почему нельзя? Чем плохи «свои» исключения?

причём никто не мешает ошибку передавать как сложный объект включающий и код, и тип, и описание, и стек трейс.

«Свое» исключение и есть такой объект, обертка try...<в разных языках свое> — обработчик такой ошибки. Overhead на установку SEH цепочки копеечный.
«Свое» исключение и есть такой объект
Нет. Исключения раскручивают стек. Обычные ошибки не должны этого делать. Именно этим они отличаются от исключений, и именно поэтому нельзя использовать исключения вместо ошибок. Нет, конечно, если Вы мазохист, то Вы можете сэмулировать корректную обработку ошибок нарисовав отдельный try/catch вокруг вызова каждой функции, которая теоретически может вернуть ошибку…
Под «и есть такой объект» я не подразумевал тождественности. Я имел в виду — «одна из реализаций».
Что именно вы подразумеваете под раскручиванием стека? Этому словосочетанию разными людьми придаются разные смыслы — от сворачивания обычного стека до обработки цепочки исключений.

Нет, конечно, если Вы мазохист, то Вы можете сэмулировать корректную обработку ошибок нарисовав отдельный try/catch вокруг вызова каждой функции, которая теоретически может вернуть ошибку…

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

Что касается проблемы читаемости, то цена такой читаемости — отсутствие корректной обработки ошибок. Как по мне — цена слишком велика.

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

По мне, часть проблем в статье — надуманные, если не сказать большего.

отсутствие корректной обработки ошибок

Почему передача ошибок (только тогда, когда это нужно — если мы не просто возвращаем состояние «удача-провал», а имеем ошибку, которая потребует дополнительных действий) исключениями — это некорректно?
Просто пытаюсь понять вашу точку зрения — почему это неправильно, почему это некорректно и как там с раскруткой стека?
Вы несколько раз употребили оборот вроде «это неправильно», причем, как мне кажется, безосновательно — поэтому я и интересуюсь. Не в плане ругани, а для того, чтобы прояснить для себя вашу позицию, узнать, возможно, что-то новое.
Да, механизм исключений более тяжеловесный. Да, не для любой ошибки применим. Так ли уж велика избыточность? Особенно, если обработка ошибки подразумевает дополнительную работу вроде записи лога, что уже отнимает время на ввод-вывод.
Какие проблемы в статье являются надуманными?
Не хочу давить на экспертократию, но даже Липперт и Хэйлсберг подтвеждают наличие проблем.
По мне, часть проблем в статье — надуманные
Это значит одно из двух — либо Вы эти проблемы давно переросли и обладаете более глубоким внутренним пониманием техники исключений, либо Вы до этих проблем ещё недоросли. Это никоим образом не попытка Вас обидеть, это просто автоматически следует из отношения «надуманные проблемы». К сожалению, по моим личным наблюдениям, многие вопросы при всём желании невозможно объяснить до того момента, когда человек сам созреет до готовности их понять. И зачастую требуется 10-15 лет (в среднем, разумеется, это очень индивидуально) опыта в программировании, пока до людей начинают доходить некоторые вещи.
Вы несколько раз употребили оборот вроде «это неправильно», причем, как мне кажется, безосновательно
Не воспримите мои слова как попытку «съехать с темы», но я честно сделал что мог и лучше объяснить не смогу. С опытом многое просто начинаешь чувствовать как правильное и неправильное, и далеко не все в состоянии внятно и аргументировано объяснить почему именно так. Раз Вы не поняли — возможно я плохо объяснил, но лучше уже не получится. А может я просто глупости говорю, и это Вы не можете мне объяснить всю глубину моих заблуждений — кто знает. ☺
У меня возникла потребность написать статью, поскольку до меня дошло по сути то, что вы пытаетесь объяснить в комментариях. И мне потребовалось всё это закрепить на бумаге.
Возможно, я где-то, что-то неправильно сформулировал, это может быть.
Предлагаю заменить ваш комментарий на содержимое топика.
Предлагаю заменить содержимое топика на комментарий powerman'a.
Предлагаю ничего ничем не заменять, а дополнить статью выдержкой из пары хороших комментариев.
Не воспринимайте мой комментарий слишком серьезно.
Мне кажется статья от powerman на эту тему была бы очень интересной, но не в качестве замены Вашей, а в виде отдельного материала.
Ваш комментарий нужно распечатать и поставить в рамочку всем более-менее начинающим Java, .NET, PHP и ещё бог весть каким разработчикам)
Этот комментарий неплохо бы дополнить примерами правильного и неправильного применения ошибок и исключений.
исключения возникают крайне редко, их очень немного, и большая их часть просто честно убивает приложение (если в приложении и есть глобальный перехват всех исключений, то он используется для попытки вывода сообщения об исключении в лог перед тем как приложение будет аварийно завершено, а не для молчаливого заглушения исключений)

Имхо, убивать приложение из-за исключения не очень хорошая практика, если это не глобальная ошибка, которая делает невозможным продолжение его работы вообще. Прежде всего это касается серверных приложений и вообще приложений, базирующихся на цикле обработки каких-то внешних независимых событий. Если уже вошли в цикл обработки, начали обрабатывать событие — поймали исключение — залогировали — освободили ресурсы — обрабатываем следующее, а не умираем.
При описанном мной подходе, все возможные ошибки, которые понятно как обрабатывать — уже обработаны; все допустимые при штатной работе исключения — тоже. Не перехваченными остаются только исключения, которые возникают по неизвестной мне, но однозначно критической причине — вроде out of memory. По определению, нельзя корректно обработать серьёзную неизвестную проблему, и нельзя предсказать, какие могут быть последствия если после возникновения такого рода ошибки попытаться продолжить работу как ни в чём не бывало. Поэтому единственное, что стоит в этой ситуации сделать — упасть. А заглушать такие ошибки в описанном Вами стиле — это очень, очень плохая идея!

Я, в основном, пишу именно упомянутый Вами тип приложений — сетевые сервисы на event loop-ах. Все они запускаются под управлением супервизора (я использую runit), поэтому после падения быстро и надёжно перезапускаются. А поскольку ошибки вроде out of memory случаются действительно очень редко и не без веских причин, то падения сервисов не являются серьёзной проблемой. Более того, в большинстве случаев у меня настроена автоматическая отправка мне письма с последними строчками лога этого сервиса если он упал — потому, что получение таких исключений и падение сервисов это не штатная ситуация, и нередко её стоит расследовать вручную.
Я как раз об ошибках вроде OfM или AD где-то в глубине конкретного обработчика конкретного запроса по неизвестным причинам. Это не штатная ситуация, это именно исключение. Но не исключено, что в целом приложение способно продолжать работу по другим запросам нормально.

А по сути наши подходы мало отличаются, просто у меня супервизор является неотъемлемой частью приложения, а у вас внешним уровнем. Но как по мне, то мой подход даёт большую гибкость и надежность. И к тому же не исключает ещё один внешний супервизоп типа runit.
не исключено, что в целом приложение способно продолжать работу
Да, это не исключено, но и гарантий на этот счёт никаких нет. Из-за некоторых ошибок банально портится внутреннее состояние приложения, и хотя оно может сделать вид что проложило работать, но творить при этом может такое, что потом фиг разберёшься, что произошло и почему — это, кстати, основная причина рекомендации обрабатывать ошибки как можно ближе к месту, где они произошли, т.к. чем мы от него дальше, тем больше накапливается вторичных ошибок и тем сложнее понять, как всё это корректно обработать. Так что как по мне — нафиг эту наивную надежду на лучшее, пусть лучше упадёт и перезапустится с нуля.
супервизор является неотъемлемой частью приложения
Я от этого подхода давно отказался, по ряду вполне объективных причин:
  • задача супервизора полноценно управлять сервисом, а не только его перезапускать — и нет смысла ни отказываться от этих дополнительных фич без причин, ни смысла реализовывать их ручками в каждом сервисе
  • всегда есть вероятность допустить ошибку, из-за которой внутренний супервизор не перезапустит то, что необходимо (и в этом случае даже если внеший супервизор есть — он об этом факте не узнает и ничем не поможет) — на внешний, проверенный супервизор надежды намного больше
  • мой процесс может быть убит ядром ОС, и у внутреннего супервизора не будет ни одного шанса — runit же фактически «держит» всю цепочку супервизоров начиная с процесса №1 — пока он работает у меня есть гарантия, что сколько ни убивай хоть моё приложение, хоть его супервизор — всё быстро перезапустится в любом случае (а если умрёт процесс №1, то сервер либо повиснет, либо перезагрузится, но в любом случае меньше всего меня будет волновать вопрос рестарта моего сервиса)
  • внешний супервизор позволяет единообразно контролировать любые сервисы проекта, вне зависимости от языка на котором они написаны, их автора, и отношения этого автора к реализации внутренних супервизоров в этих сервисах
  • …наверное, можно продолжать, но, думаю, суть Вы уловили.
Уловил. Надо подумать.
Поясните, пожалуйста, в чем по-вашему разница между ошибкой и исключением? По моему опыту это синонимы. Просто исключением вы называете проброс ошибки наверх, а ошибкой — оборачивание её в какой-то другой тип, если судить по вашему коментарию:
например, из вызванной функции мы получили ошибку DNS «host not found», а выше возвращаем ошибку «не могу скачать url (host not found)».


В свое время мне очень помогла в этом плане эта статья Сергея. И отсюда весьма четко следует, что как раз-таки библиотека, которая не знает, смертельная ошибка или нет, должна сообщить о ней выше. А самый простой способ это сделать — выкинуть исключение, потому что смысл в раскрутке стека именно в том, чтобы найти ближайшее место, где это исключение обрабатывается. Таким образом, своим принципом «ошибки != исключения» вы затем только подтверждаете, что как раз-таки они являются одним и тем же. Вернее, исключение — следствие ошибки.
Есть много софта, который использует исключения для проброса сигналов, а не для сообщения об ошибках.
Спорное решение, но имеет место быть.
Сам механизм исключений не ограничивает область их использования одними лишь ошибками.
Это именно та статья, которая явилась одной из причин по которой я решил написать эту статью. Я, как вы уже поняли, не считаю исключения панацеей. Да и как вы уже, наверное, поняли, мало кто так считает из числа программистов реальных сложных приложений.
Каждому инструменту — свое применение. Ничто не панацея. Иногда выгодно использовать такой вариант, иногда — другой, иногда комбинировать. Использование искусственных исключений для сообщений об ошибках — достаточно распространенная практика. Это очень удобно, когда надо «подняться» на 3-4 уровня вверх, попутно освобождая ресурсы, и там, наверху, все обработать (именно в случае ошибки). В противном случае на каждом уровне вам надо будет иметь обертку для ошибки.
Можно сравнить оба подхода в коде. Этого, кстати, очень не хватает в дискуссии в комментариях — на пальцах обсуждать сложно.
Вот именно! А у Сергея в статье «всё прекрасно» и «просто». Чувства боли совсем не возникает и это негативно отражается на сознании начинающих разрабов.
Грубо говоря, ошибка — это пускай не главный, но обычный, предусмотренный сценарий, исключение же — что-то из ряда вон выходящее.
Обычно ошибка — это непредусмотренный сценарий, который должен был быть предусмотрен (неверный код, написанный программистом), а исключение — объект, при «выбрасывании» которого разворачивается стёк.

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

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


Обработать обычно не проблема, проблема найти все эти обычные ошибки. В C++, C# непросто узнать хотя бы какие типы исключений может метод бросить, но чаще всего только типа исключения недостаточно чтобы понять это обычная ситуация или исключительная. Из за этого как правило такие обычные ошибки приходиться отлавливать уже при тестирование тестировщиками или даже пользователями, чего и хотелось бы избежать.
Поэтому выкидывать исключения нужно только тогда, когда действительно необходима именно раскрутка стека — т.е. при возникновении критической ошибки

А как же, например, быть с Python? Там везде исключения используются, даже для окончания итераций, например, и подоных вещей в ядре языка и в стандартной библиотеке. То есть, повсеместное использование исключений заложено в саму сущность языка и всячески приветствуется. Что плохого использовать их в таком вот случае?
Раскручивание стека, не самая большая причина, которая к тому же зависит от реализации конкретного языка. В С++ — Zero-Cost model exceptions, в Java — try стоит(ил?) времени, но в любом случае, если программа не валит эксепшены сплошным потоком, то ничего страшного тут нет.

Код ошибки заставляет постоянно его проверять условными переходами, свитчами и тп, причем добавление нового кода вы не увидите на компиляции (обратная сторона примера с добавлением нового checked-exception). Представьте теперь пять-десять вызовов подряд с проверкой результата. В инсталшилде, например, почти каждая функция может вернуть ошибку, как найти потом это место без проверки руками и логирования каждого результата? Ну хорошо, ошибка локально понятная, но надо же теперь выйти из метода, while()/break; + плюс проверка флага и очистка ресурсов опять же руками? Это ли не райское управление потоком?

Потом вы решили, что какую-то ошибку не можете обработать локально, что делать? Передать код ошибки выше или бросить эксепшен с кодом ошибки? Код просто так не передашь, надо ведь знать какой функции это код/что с ним делать, надо предусмотреть проверку кодов еще где-то (двойную уже). Завести новую пачку кодов? Эксепшен кинуть? Тогда у нас будет тот же самый try/catch/catch/catch + проверка кодов.

В Java исключение сделано для некоторых методов, вроде mkdir(), для упрощения кода, но расплата за это — невозможность узнать причину.

Рантайм эксепшен — отдельная беда порой. Мы хотим надежную коммуникацию через RMI, а в ответ получаем 10 райнтайм ошибок! Как их все правильно обработать? Может надо рестартануть RMI, может сеть пропала на 5сек, может хост не заресолвился и надо подождать? А мы хотим аккуратно сделать ретрай, ведь мы отослали 25GB данных и что теперь все откатить и убить/рестартануть джоб?

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

Короче совсем непросто все.
Есть еще пример из жизни. Надо было оптимизировать xml-парсер написанный на as3. Там как раз был использован эксепшен для выхода из вложенных методов (довольно часто использовался) и я задумался — может это медленно? Переделал на код ошибки и никакого статистически значимого результата не получил.
Исключения — больная тема. Вроде, такой красивый и продуманный механизм… в теории. На деле — либо ловим, либо нет. Причем, если ловим, то в 90% случаев просто оборачиваем в другое исключение с поясняющим сообщением. А наверху — в лог. Проверка конкретных типов исключений — редкость.
p.s.
Смысл кода в конце статьи не очень понял. Только ради out of memory и stack overflow? Так программа и так свалится с такими исключениями. Можно было вообще не заморачиваться.
Не совсем. Если случится StackOverflow и он попадёт в catch(Exception), то он съестся. После этого программа будет некоторое кол-во времени (неизвестное) будет находиться а работе и что случится с данными никому неизвестно. Это важно. Кроме означенных исключений есть ещё некоторые, которые лучше не ловить. Если вы глянете в Enterprise Framework, вы увидите примерно такой же подход — фильтрацию.
Да, у МС подобный код много где можно найти. При этом в Framework Guidelines, если память не изменяет, подобная практика не рекомендуется.
Да, не рекомендуется, однако Enterpise Framework раздел обработки исключений построен именно на политиках, по сути, фильтрационных. Ну, там несколько посложнее, но суть та же.
Мне просто казалось, что Stack Overflow явление сигнализирующее о серьезной ошибке в логике программы (рекурсии где-то). Обычно, такое ловится на этапе отладки. Если же пролезло в релиз, то фиксится как можно быстрее. Поэтому и удивило, что в коде выше это исключение ожидается повсеместно, будто оно норма.

Если же конкретный алгоритм сам по себе подвержен такому исключению, то не проще ли только там воспользоваться catch (StackOverflowExcetion) ?
Обычно-не обычно, такое случается и именно в таких случаях надо упасть. Ловить такое — глупость.
Если же пролезло в релиз, то фиксится как можно быстрее. Поэтому и удивило, что в коде выше это исключение ожидается повсеместно, будто оно норма.

Неважно насколько быстро вы всё пофиксите. Достаточно одного проглатывания такого исключения, чтобы «нанести компании убыток на миллиард», или «убить пациента».
Так разве StackOverflowException ловится? По-моему, без специальной настройки среды — нет. Поэтому можно не париться.
Ловится. Без настроек среды.
Да, не ловится, начиная с 2.0. Ловится только user thrown StackOverflow. Однако OutOfMemory ловится легко. Без каких-либо настроек среды.
Верно, обработка ошибок — это тоже поведение функционала. На практике бывает хватает 4 типов — отказ функционала, предупреждение о неприятном поведении, посмотрим потом, это нормально. Тогда все бросаемые исключения сводятся к этим, в зависимости от критичности функционала. Затем типы эскалируются: кому — пользователь, сисадмин, начальник, система; как — алерт, смс, ночной телефонный звонок, лог.
Фотохостингу, скажем, нерабочая загрузка котиков будет фатальна, для блога — неприятна, для банка — параллельна.
Не факт. Фотохостингу нерабочая загрузка котиков не будет фатальна, даже если она у всех не работает — главное чтобы уже загруженные котики показывались. А вот для банка незагрузка изображений документов и фото клиента может как раз оказаться фатальна.
Именно. Программист API не имеет достаточной информации для обработки исключений. Решения принимаются на уровне бизнес-логики. На фоне этого статья кажется мне немного странной. А заголовок — желтоватым.
А в чем проблема получить программисту API достаточно информации для обработки исключений?
Или проблемы нет, и Ваше утверждение просто ставит под сомнение содержание статьи?
Не пойму суть проблемы:
Подкину ещё проблему: что, если вам нужно в цикле вызывать код, который всё время выбрасывает исключения в случае, если что-то не так?


Если exception обрабатывается в catch тут же, в цикле, то в цикле стек расти не будет, если в цикле только finally, то цикл и вообще весь код по стеку вызовов прервется, до того места, где это обработано в catch, стек тоже расти не будет
Начнём с версионирования, потому что тут проблемы легко увидеть. Скажем я создаю метод foo, который декларирует проброс исключений A, B и C. Во второй версии метода я хочу добавить пару фишек и теперь метод foo может выкинуть также исключение D. Добавление нового исключения является несовместимым изменением, потому что существующие пользователи этого метода почти 100% не будут ловить это исключение.

И все в команде с этим единодушно согласились… Видимо потому что никто не умел правильно готовить проверяемые исключения.

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

Т.е. если для пользователей метода foo не важно какое из проверяемых исключений еще может быть добавлено в обновленной версии метода, то они могут оборачивать все Exception в RuntimeException. Обратите внимание! В Java системные исключения, которые наследуются от Error, что решает проблему глобального перехвата Exception как в C#.
В Java (в стандартной библиотеке) проверяемые исключения (не Runtime) используются в тех случаях, когда возникновение исключения зависит от внешних факторов, которые заранее не проверить.
Яркий пример — IOException. Файл, например, может внезапно исчезнуть, пока программа его читает, и заранее это никак не проверить.
Соответственно, такие события, как выход за пределы массива (выбрасывает ArrayIndexOutOfBoundsException), легко предотвращаются. поэтому исключение это Runtime.
Беда в том, что в подавляющем большинстве прикладного софта отсутствует стратегия обработки отказов (будь то исключения, коды ошибок или банальные null/NaN/undefined).
Программисты более-менее легко пишут «оптимистичную» часть кода, которая делает работу. если всё хорошо.
И с большим скрипом — пессимистичные ветки, отвечающие за различные сбои.
Да и тестировать первое намного приятнее второго — не так-то просто вызывать катаклизмы типа out of memory, database gone или там device read error для каждой относящейся к делу строки программы.
А потом у реальных юзеров shit таки happens (часто по причине действий самих юзеров, но и из-за багов в софте тоже), и среди не-программистов твердо укоренилось мнение, что все ваши программы — глючат, а компы это что-то по части магии.
Если конструктивно, то должно быть понимание, что в реальной жизни пессимистичные ветки есть и их игнорирование — это просто-напросто нереализованная часть кода.
Общая стратегия пессимистичных веток должна быть следующая:
1. Сохранить пользовательские данные, для этого откатиться на last-known-good точку. У вас нет транзакций, журнала или undo/redo? У вас плохой софт.
2. Максимально ограничить расползание сбоя на другие подсистемы, файлы, запросы, пользователей или что там у вас.
3. Восстановить целостность управляющих данных самого приложения, если возможно — вернуться в режим штатной обработки запросов, если нет — перейти в «безопасный режим» и дать юзеру возможность что-то сделать (поднять сеть, закрыть лишние программы, освободить место на диске, оживить бд и т.д.) вместо того, чтобы просто отвалиться с несохраненными данными.
Когда вот такая стратегия для конкретного приложения и конкретных видов сбоев понятна, тогда и больших проблем с обработкой ошибок не возникает, это становится просто написанием еще одной части кода.
Да всего навсего записать грустные сценарии в спецификации, тем самым превратив их в радостные.

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

Например, нам нужно собрать проект, для этого мы запускаем параллельно 3 задачи. Если хотябы одна из них упадет, то нужно остановить и другие 2, после чего завершиться с ошибкой. Исключения тут идут лесом. А если нам нужно при указании специального параметра не завершать приложение, а вывести сообщение в консоль, продолжая параллельные задачи, после чего ожидая изменения файлов, после чего запускать только те задачи, которые зависят от измененных файлов?

Исключения — это и правда зло, потому что полагаются на стек. Вместо этого хорошо иметь отдельный стек «доменов» (что-то типа nodejs.org/api/domain.html) и при создании колбэка привязывать к нему текущий домен. А в домене уже при его создании устанавливать стратегию реагирования на исключительные ситуации.
>> Например, нам нужно собрать проект, для этого мы запускаем параллельно 3 задачи. Если хотябы одна из них упадет, то нужно остановить и другие 2, после чего завершиться с ошибкой. Исключения тут идут лесом.

Не понимаю вашей боли. В .NET элементарно реализуется с помощью Task.Run, Task.WaitAll/WaitAny, CancellationToken.
Task'и прекрасно работают с исключениями.
Сразу оговорюсь что с node.js не знаком.

Я так понимаю что домены в node.js это костыль реализующий логику try/catch для асинхронных операций.
Вот так делается в последних дотнетах. Операции внутри try вполне себе асинхронные:

try
{
await Task.Delay(new Random().Next(42));
await Task.Run(DoSomeJobAndThrowAwesomeException);
}
catch (HelloWorldException)
{
// We've caught an exception that was thrown on BG thread at undefined time
}
давно мечтаю о похожем синтаксическом сахаре в node.js… чтобы можно было асинхронные операции писать в синхронном стиле (исключительно для улучшения читабельности конечно, без блокировки процесса).

try {
    var user = db.users.find(userId);
    var cars = db.cars.findByUser(user);
} catch (e) {
...
}

// имхо это всякое лучше чем

db.users.find(userId, function(err, user) {
    if (err) {
        // тут что-то сделать
        return;
    }
    db.cars.findByUser(user, function(err, cars) {
        if (err) {
            // тут опять что-то сделать
            return;
        }
        // работаем с результатом
    })
})


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

Что касается многопоточности, то, возможно, это специфично для конкретных языков, но, к примеру, в Limbo есть возможность во-первых объединять нити в группы, а во-вторых настроить, что должно произойти, если в одной из нитей произойдёт не перехваченное исключение: ничего, сгенерировать это же исключение во всех процессах этой группы, или убить все процессы этой группы кроме одного (лидера группы) и сгенерировать это исключение в нём. Но в любом случае в любом языке возможно перехватить все исключения на самом верху каждой нити, после чего принять решение что делать и сообщить об этом другим нитям при необходимости — так что с многопоточностью исключения работают вполне адекватно.
Никак не адекватно, если их нужно руками ловить в одном месте, а потом перекидывать в другое. Это ничем не лучше пресловутых «кодов возврата» Представь себе 5 параллельно запущенных воркеров, каждый качает по сотне файлов. Что делать при ошибке загрузки одного из них? Библиотека не знает и кидает исключение, останавливая все файлы. Занимайтесь ерундой с перезапуском воркеров. Для этих целей кто во что горазд пишет костыли. Например, можно кидать событие «ошибка загрузки файла», на которое можно подписаться и реализовать свою стратегию. Но это все вручную и зачастую переопределить стратегию на 2 уровня ниже уже не получится ибо автор либы поленился реализовать соответствующее прокидывание событий и стратегий. Как это должно реализовываться: мы создаем домен и описываем в нем стратегии реагирования на различные исключительные ситуации и далее все запускаемые, создаваемые и любые другие сущности имеют связь с этим доменом. В случае ахтунга — исполняется обработчик домена, который реализует альтернативные стратегии, либо инициирует освобождение всех привязанных к нему ресурсов — откручивается стек, останавливаются потоки, освобождаются файлы и тп.

более наглядный пример — есть календарик, он ловит события клика, нажатия на клавиатуру, скролл мышью, прикосновения пальцами и кучу других событий из разных мест. С исключениями приходится в каждый обработчик вставлять try-catch чтобы в случае ошибки календарик не умирал благородной смертью японского самурая. А с доменами можно было бы сделать так: объявяляем домен в котором указываем обработчик ошибок, в этом домене создаем календарик — все обработчики автоматом наследуют домен и в случае любых ошибок раскручиваться будет стек доменов, а не стек исполнения. в ноде как раз долгое время была проблема из-за отсутствия доменов — если разработчик библиотеки, в которой используются асинхронные операции, забыл где-то поставить try-catch, то ронялась вся нода и никак из внешнего кода на это было не повлиять, потому что стек честно раскручивался до конца.
Как я уже писал, библиотеки не должны кидать никаких исключений (кроме совсем критических вроде out of memory). Все исключения должны кидаться только вашим основным приложением, и только в тех случаях, когда действительно необходима раскрутка стека. При таком подходе описанные Вами ситуации просто не возникнут: при ошибке загрузки одного из файлов функция просто вернёт ошибку; календарик должен будет ловить только те исключения и в тех местах, где Вы посчитали нужными их использовать (и мне кажется, что в типовом приложении каледарика скорее всего можно было бы вообще без них обойтись).
Что лишний раз подтверждает, что исключения не справляются со своей основной задачей — обработкой исключительных ситуаций. А хорошем приложении ничего кроме библиотек быть и не должно.
И мне не надо, чтобы функция возвращала ошибку, мне надо, чтобы я мог ей сказать, что в случае определенных ситуаций нужно использовать такую-то стратегию восстановления. На выбор:
1. остановить загрузку всех картинок, написать в консоль ошибку
2. написать в консоль, продолжить грузить остальное
3. попробовать загрузить то же самое с другого домена
4. отложить загрузку на 5 минут
и 100500 других стратегий, про которые библиотека не должна знать превращаясь в кухонный комбайн.

вообще без исключений в календарике я не могу обойтись банально потому, что они сами чуть что возникают в самых интересных местах.
Я писал свои исключения лишь с целью отладки, чтобы проверять корректность параметров при инициализации. Язык Objective C. В основном же использую коды возврата, если в чистом C, или NSError. Что касается смешения ошибки и возвращаемого значения, то есть хорошая практика: по return возвращаем ошибку, а все значения через ссылки как аргументы. Не говоря уже о том, что и ошибку и значение вы можете завернуть в структуру и вернуть вместе ещё и с текстовым описанием.
Пара дополнений:
1. Cocoa так и предписывает делать. Исключение == ошибка программиста, однозначный крэш. NSError == ошибки I/O, входных данных, etc. К тому же исключения в режиме ARC приводят к утечкам по умолчанию.
2. Опять же Cocoa предписывает возвращать результат через return, а NSError через выходной аргумент.
Отличная статья.
Проблема действительно есть — набор исключений принимаемых в данной конкретной точке кода весьма многообразен, и как их обрабатывать в данной точке кода — не всегда ясно.
И цельного решения, описанного в деталях и всеми признанного — нету.
Вы можете привести исключения в систему в рамках своего собственного кода, но есть ещё фреймворк, провайдеры данных, интероп и сторонние компоненты.
Поэтому на практике я лично склоняюсь к способу —
Четверть сказали, что использовали что-то вроде метода проб и ошибок – отладка, тестирование, чтение логов, получения краш-дампов для того, чтобы выяснить какие исключения следует ловить.
Не знаешь что делать? Пиши в лог!
(в блог) ;-)
По данной проблеме, нужно понимать с чем мы работаем.

Изначально алгоритм программ строилась на основе условных переходов. Когда в языках появилась процедурной парадигмы, стали использовать процедуры и процедурные конструкции: while, for, do. Но на низком уровне они все равно трансформируются в условные переходы. Поэтому будем считать, что это наш примитив. Из набора таких примитивов строится программа.

Теперь посмотрим на исключения. Что такое исключение? А это такой же механизм для управления поведением программы, как условный переход. Но исключение в отличие от условного перехода имеет дополнительные «плюшки».

Так что требуется делать, когда процедура может вернуть исключение? Обрабатывать его или кидать наверх? В этом вопросе я полностью солидарен с мыслью из этого комментария: habrahabr.ru/post/221723/#comment_7555889

Суть в том, что все мы люди и задумки у каждого человека свои. Нетрудно представить библиотеку, в которой исключение выбрасывается даже в случае, когда вся библиотечная функция отработала без ошибок. Такое себе исключение, подтверждающее корректность исполнения. И что, выбрасывать это исключение наверх? Нет, конечно мы отработаем его здесь и сейчас. Точно так же нужно отрабатывать и другие исключения, которые могут высыпаться, но стоит всегда держать в голове смысл исключения, и продумывать, к чему халатная обработка этого исключения может привести. Является ли такое исключение критичным и стоит ли его скрыть в бизнес слое от пользователя.

ЗЫ: Уже не первый раз вижу, как сильные мира сего из мухи делают слона. Эта ваша проблема обрабатывать или не обрабатывать исключение — чистый академизм.
>>>Exception handling is the process of responding to the occurrence, during computation, of exceptions – anomalous or exceptional events requiring special processing – often changing the normal flow of program execution. (http://en.wikipedia.org/wiki/Exception_handling)
По этому определению понятно, что исключение — для исключительных ситуаций. Соответственно, исключение, сигнализирующее о корректном завершении операции, как-раз таки довольно трудно представить =) Не могли бы Вы привести пример?

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

А в остальном — согласен, да и трудно не согласиться с Вами упомянутым комментарием =)
На самом деле в том комментарии я описал не все проблемы, связанные с исключениями (чтобы не усложнять описание). Есть ещё одна: исключения нередко используют как ещё один способ управления потоком выполнения. И в этом случае выкидывание исключения где-то глубоко внутри для того, чтобы быстро и просто доставить полученный результат (не ошибку, а именно результат!) наверх — вполне обычное дело.

Честно говоря, я не готов ответственно заявить, что это кошмар и так делать ни в коем случае нельзя. Ситуации бывают разные, и иногда такой подход действительно позволяет сделать код проще и понятнее. Оператор goto тоже иногда уместен и полезен, просто это бывает очень-очень редко. С использованием исключений для управления нормальным потоком выполнения то же самое. А проблемы из-за этого возникает тогда, когда исключения используются и для ошибок, и для исключений, и для ленивого возвращения результатов одновременно и в больших количествах — что встречается намного чаще, чем лично мне хотелось бы.
Может статью? Было бы очень интересно почитать.
Мне кажется что это дырка в архитектуре. Не захотели рефакторить кучу кода ради какого то одного кейса, решили что проще подняться наверх просто через exception. Дело в том что такой код однозначно создаст сложности в будущем, и оправдываться тем что когда то было проще разматывать стек а не переписывать архитектуру придется перед самим собой.

С goto тоже самое. Все случаи использования goto прекрасно решаются через дробление на методы, через использование функциональных литералов, с помощью того же return. Но тут ИМХО не так критично. goto сам по себе проблемы не создает. Надо относиться к нему как к break или множественному return. Без них можно жить, но все почему то накинулись именно на бедный goto.
сорри за оффтоп. не буду в дальнейшем повторять данные ошибки.
Посмотрел. Таким образом, вы согласны, что простых, не приносящих боли способов работы с ошибками нет? Вы также согласны с тем, что и исключения и коды ошибки имеют право на жизнь? Или вы исключительно за использование исключений (извиняюсь за каламбур)? А если вы считаете что нужно использовать только исключения, то какие контр-аргументы вы можете привести против того, что писал в комментариях powerman?
У меня в конце слайды с кактусом — это не спроста :). Если мы используем языки и библиотеки с исключениями, то вопрос «имеют они право на жизнь или не имеют» не стоит — это данность. Приходится кушать кактус :(
У Мартина на эту тему в чистом коде неплохо написано. Не поленитесь, откройте книжку :)
Я все эпизоды видел. Ничего у него толком не написано)
Кроме всего прочего, лично Мартину вопрос задавал по поводу исключений и кодов возврата.
Несколько замечаний:

1. StackoverflowExcpetion невозможно обработать. Пруф:

Starting with the .NET Framework version 2.0, a StackOverflowException object cannot be caught by a try-catch block and the corresponding process is terminated by default. Consequently, users are advised to write their code to detect and prevent a stack overflow. For example, if your application depends on recursion, use a counter or a state condition to terminate the recursive loop. Note that an application that hosts the common language runtime (CLR) can specify that the CLR unload the application domain where the stack overflow exception occurs and let the corresponding process continue.


2. OutOfMemoryException — тоже обработать сложно. Точнее, существует две версии OOM — синхронная, которая бросается при выделении большого куска, который GC выделить не может, и асинхронная — которое может сгенерировать CLR, например, при попытке выделить новый сегмент для GC0.

3. Метод FullMessage является адским велосипедом, который не понятно зачем нужен. Вы же выводите в builder целиком объект исключения на каждой итерации. A ex.ToString будет выводить каждый раз полное исключение, включая все вложенные. Просто создайте исключение с пятком вложенных и вызовите этот метод.

4. Существует два стандартных способа фильтрации исключений: по типу в блоке catch, с помощью фильтров исключений (начиная с C# 6.0), зачем делать еще один обобщенный обработчик на основе Func-ов и как он помогает решать проблему обработки исключений, мне не ясно.
1. Согласен.
2. Согласен. Мы его не обрабатываем. Это исключение входит в список фатальных. Если мы встречаем такое, то падаем.
3. Согласен. Срочно убираю этот бред. Доверившись, скопировал у кого-то индуса.
4. Верно. Мы фильтруем внутри на список фатальных исключений для конкретного приложения. Т.е., мы ловим все исключения, кроме списка фатальных, плюс логирование встроенное. В блоке Exceptions в Enterprise Library делается нечто похожее, только в более ООП стиле со всякими рюшками. Для нас подходит описанное решение в статье. C# 6.0 мы использовать не можем. И не нравится мне его система фильтрования, да ещё с какими-то подводными камнями.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории