Замечательные статьи публиковались в последнее время, хотелось бы добавить ещё несколько абзацев по данной теме.
Уважаемые авторы предыдущих топиков как-то упускали тот момент (или мне показалось? или это само-собой подразумевается?) что exceptions возникли как инструмент для решения весьма утилитарной задачи — передачи управления из места возникновения ошибки в то место, где она может быть обработана.
Немного истории, чтобы понятно было, откуда такая задача возникла. В каждом более-менее нетривиальном программном обеспечении (сложнее, чем «Hello, world», да) всегда существуют точки, где нормальное выполнение не может продолжаться — I/O подсистема выдала отказ, памяти для алгоритма почему-то не хватило, входные параметры для функции ей не понравились и т.п. Как именно реагировать?
Ситуация становится ещё печальней, если рассматривать создание какой-либо библиотеки, которая должна будет использоваться в других проектах. Мы не можем (как следствие) вызывать assert/abort или ещё какую-нибудь подобный обработчик — откуда мы знаем, что имеем право завершать работу всего приложения? К примеру, наша библиотека занимается сбором какой-то статистики входных данных, а из-за такого её поведения будет остановлена работа всего устройства. А пишем мы firmware для кардиостимулятора, конечно же.
Ок, abort() — не годится. Мы не хотим создавать приложения, подобные воздушному шарику — в любом месте уколол, весь шарик умер. Мы хотим пользоваться технологией, которая позволит разделить место, где возникла ошибка, и место где принимается решение о том, что именно мы будем делать с этой ошибкой. Так как для одних применений реакцией будет запрет сбора этой статистики, для других — переинициализация библиотеки (например, с другими параметрами), где-то — просто игнорирование. Так как на более высоком уровне доступно гораздо больше информации о том, как реагировать на возникшую ошибку.
Как ещё можно сигнализировать “наверх” о наших проблемах? Глобальными переменными типа errno в языке C? Не получится. Возвращаемым значением? Уже лучше, но возникают новые проблемы:
Собственно, здесь и был сделан очередной логический шаг. В языке появился инструмент, который позволяет:
Этот инструмент — exceptions. Он естественным образом получается как результат внесения в язык той концепции обработки ошибок, которая была описана выше. У этого инструмента есть свои плюсы и минусы (которые иногда заставляют полностью отказаться от их использования). Более того, «обработка ошибочных ситуаций» это общая концепция, а «обработка ошибочных ситуаций с использованием exceptions» — всего лишь один из примеров её реализации. Обработку ошибок можно делать и не задействуя механизм exceptions, разве что действий со стороны программиста несколько больше потребуется.
Собственно, написано это всё было для того, чтобы лучшее понимание «для чего именно» служат exceptions позволяло лучше понимать «а как их применять и для каких случаях».
PS: ещё было желание показать на примерах кода, какие именно минусы есть у exceptions. И как решалась задача «и ошибки удобно обрабатывать и exceptions при этом не использовать» (актуально для встроенных систем, когда «кардиостимуляторы» пишутся). Но это позже.
Уважаемые авторы предыдущих топиков как-то упускали тот момент (или мне показалось? или это само-собой подразумевается?) что exceptions возникли как инструмент для решения весьма утилитарной задачи — передачи управления из места возникновения ошибки в то место, где она может быть обработана.
Немного истории, чтобы понятно было, откуда такая задача возникла. В каждом более-менее нетривиальном программном обеспечении (сложнее, чем «Hello, world», да) всегда существуют точки, где нормальное выполнение не может продолжаться — I/O подсистема выдала отказ, памяти для алгоритма почему-то не хватило, входные параметры для функции ей не понравились и т.п. Как именно реагировать?
Ситуация становится ещё печальней, если рассматривать создание какой-либо библиотеки, которая должна будет использоваться в других проектах. Мы не можем (как следствие) вызывать assert/abort или ещё какую-нибудь подобный обработчик — откуда мы знаем, что имеем право завершать работу всего приложения? К примеру, наша библиотека занимается сбором какой-то статистики входных данных, а из-за такого её поведения будет остановлена работа всего устройства. А пишем мы firmware для кардиостимулятора, конечно же.
Ок, abort() — не годится. Мы не хотим создавать приложения, подобные воздушному шарику — в любом месте уколол, весь шарик умер. Мы хотим пользоваться технологией, которая позволит разделить место, где возникла ошибка, и место где принимается решение о том, что именно мы будем делать с этой ошибкой. Так как для одних применений реакцией будет запрет сбора этой статистики, для других — переинициализация библиотеки (например, с другими параметрами), где-то — просто игнорирование. Так как на более высоком уровне доступно гораздо больше информации о том, как реагировать на возникшую ошибку.
Как ещё можно сигнализировать “наверх” о наших проблемах? Глобальными переменными типа errno в языке C? Не получится. Возвращаемым значением? Уже лучше, но возникают новые проблемы:
- на программисте теперь лежит груз ответственности за проверку возвращаемого значения при каждом вызове подобных функций,
- вся программа на всех уровнях теперь должна поддерживать этот подход (ибо ситуация, когда вызвали функцию из нашей библиотеки, она вернула ошибку, вызывающий это обнаружил и сам вернул ошибку, а на более высоком уровне её прошляпили — скажем так, нежелательна),
- цепочка вызовов состоит из очень похожих блоков: вызвали функцию, проверили наличие ошибки, в случае её наличия — выходим сами с ошибочным признаком. А если это надо писать постоянно — почему же это не автоматизировано?
Собственно, здесь и был сделан очередной логический шаг. В языке появился инструмент, который позволяет:
- разделить место обнаружения ошибки и место реакции на неё,
- не накладывает на программиста обязанности проверять каждый вызов каждой функции, чтобы не потерять случайно возникшую ошибку,
- если ошибка не обработана на текущем уровне — ничего страшного, ей займется более верхний. Возможно, ему уже будет известно, что с ней делать,
- поддерживается самим языком — нам не нужно модифицировать существующий код, чтобы научить его пробрасывать ошибку наверх этим новым способом.
Этот инструмент — exceptions. Он естественным образом получается как результат внесения в язык той концепции обработки ошибок, которая была описана выше. У этого инструмента есть свои плюсы и минусы (которые иногда заставляют полностью отказаться от их использования). Более того, «обработка ошибочных ситуаций» это общая концепция, а «обработка ошибочных ситуаций с использованием exceptions» — всего лишь один из примеров её реализации. Обработку ошибок можно делать и не задействуя механизм exceptions, разве что действий со стороны программиста несколько больше потребуется.
Собственно, написано это всё было для того, чтобы лучшее понимание «для чего именно» служат exceptions позволяло лучше понимать «а как их применять и для каких случаях».
PS: ещё было желание показать на примерах кода, какие именно минусы есть у exceptions. И как решалась задача «и ошибки удобно обрабатывать и exceptions при этом не использовать» (актуально для встроенных систем, когда «кардиостимуляторы» пишутся). Но это позже.