На этом форуме была уже не одна дискуссия на тему преимуществ и недостатков исключений. Желающие могут легко глянуть поиском. И я уже отмечался в них, в основном с негативным взглядом на данную технику. Точнее я вижу в ней только инструмент обработки реально критических ошибок (типа нехватки памяти и т.п.), приводящих всегда к аварийному завершению приложения (один глобальный try/catch). Тут они конечно хороши. Если же использовать исключения для банального возвращения неудач из функций, то в итоге это становится даже менее удобно чем древние коды возврата (особенно если в языке реализована удобная работа с кортежами и типами реализующими концепцию монады maybe), из-за непрерывных убогих try/catch на каждом углу.
Я эту мысль уже высказывал раньше и моё мнение в общем то не изменилось. Но недавно я наткнулся на обсуждение выступления Александреску (http://habrahabr.ru/post/270545/ там же есть ссылка на само выступление) на близкую тему и увидел вариант реально удобного повсеместного использования исключений. Кстати, там тоже в начале высказываются мысли о неудобности классического подхода исключений, созвучные с моей позицией.
В общем если использовать такой подход, то исключения становятся уже реально удобным инструментом для построения логики приложения. Только вот это должно применяться системно, т.е. везде в приложение, а не эпизодическими моментами. )))
Здравствуйте, alex_public, Вы писали:
_>то в итоге это становится даже менее удобно чем древние коды возврата (особенно если в языке реализована удобная работа с кортежами и типами реализующими концепцию монады maybe), из-за непрерывных убогих try/catch на каждом углу.
Не припомню чтобы кто-то из сторонников исключений выступал за использование "try/catch на каждом углу".
_>Кстати, там тоже в начале высказываются мысли о неудобности классического подхода исключений, созвучные с моей позицией.
Подобное неудобство было бы и при кодах возврата. Да и вообще, там не "про неудобство классического подхода" в целом, а лишь о нескольких конкретных use-case'ах, которые разгуливаются несколькими дополнениями в язык, которые кардинально не меняют "классический подход", а лишь дополняют его в нескольких случаях.
P.S. Я думал что ты говоришь про его выступление об expected<T> — это ближе к сабжу, нежели scope(exit/success/failure)
Здравствуйте, alex_public, Вы писали:
_>Я эту мысль уже высказывал раньше и моё мнение в общем то не изменилось. Но недавно я наткнулся на обсуждение выступления Александреску (http://habrahabr.ru/post/270545/ там же есть ссылка на само выступление) на близкую тему и увидел вариант реально удобного повсеместного использования исключений. Кстати, там тоже в начале высказываются мысли о неудобности классического подхода исключений, созвучные с моей позицией.
Гхм... может я чего-то не улавливаю? По-моему это абсолютно стандартный finally, только на макросах и с вариациями для fault и success clauses (последняя по-моему добавлена "чтоббыло", ну да фиг с ним).
Здравствуйте, Sinix, Вы писали:
S>Гхм... может я чего-то не улавливаю? По-моему это абсолютно стандартный finally, только на макросах и с вариациями для fault
Это всё же не вариация finally. В одном из предыдущих выступлений есть наглядный пример (link — первые полчаса).
Основная фишка в простой линейной композиции без необходимости углублять scopes.
На try/catch/finally же будет несколько уровней вложенности, с более сложной структурой кода.
Но, опять-таки, это лишь дополнение для некоторых случаев, а не другой подход.
S>и success clauses (последняя по-моему добавлена "чтоббыло", ну да фиг с ним).
Он в одном из выступлений так и говорит — что scope(success) требуется реже.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
_>>то в итоге это становится даже менее удобно чем древние коды возврата (особенно если в языке реализована удобная работа с кортежами и типами реализующими концепцию монады maybe), из-за непрерывных убогих try/catch на каждом углу. EP>Не припомню чтобы кто-то из сторонников исключений выступал за использование "try/catch на каждом углу".
А оно автоматом возникает в случае применения исключений не только для критических сбоев. Потому как в таком случае обработка ошибки происходит не где-то на высоком уровне, а почти всегда на уровне вызова функции, кидающей исключения. В предыдущих темах я уже приводил примеры на эту тему.
EP>Подобное неудобство было бы и при кодах возврата. Да и вообще, там не "про неудобство классического подхода" в целом, а лишь о нескольких конкретных use-case'ах, которые разгуливаются несколькими дополнениями в язык, которые кардинально не меняют "классический подход", а лишь дополняют его в нескольких случаях.
Дело в том, что при применение исключений везде, этот случай становится как раз самым распространённым. Вот к примеру ошибка при невозможности сохранить файл — она должна обрабатываться с помощью исключений или же нет? ) И если с помощью исключений, то каким подходом лучше? )
EP>P.S. Я думал что ты говоришь про его выступление об expected<T> — это ближе к сабжу, нежели scope(exit/success/failure)
Здравствуйте, Sinix, Вы писали:
_>>Я эту мысль уже высказывал раньше и моё мнение в общем то не изменилось. Но недавно я наткнулся на обсуждение выступления Александреску (http://habrahabr.ru/post/270545/ там же есть ссылка на само выступление) на близкую тему и увидел вариант реально удобного повсеместного использования исключений. Кстати, там тоже в начале высказываются мысли о неудобности классического подхода исключений, созвучные с моей позицией. S>Гхм... может я чего-то не улавливаю? По-моему это абсолютно стандартный finally, только на макросах и с вариациями для fault и success clauses (последняя по-моему добавлена "чтоббыло", ну да фиг с ним).
Здравствуйте, alex_public, Вы писали:
EP>>P.S. Я думал что ты говоришь про его выступление об expected<T> — это ближе к сабжу, нежели scope(exit/success/failure) _>Тут скорее уж optional актуальнее. )
Почему? expected<T> это считай variant<T, exception_ptr>, как раз позволяет передать конкретную информацию об ошибке.
Вот выступление.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>>>P.S. Я думал что ты говоришь про его выступление об expected<T> — это ближе к сабжу, нежели scope(exit/success/failure) _>>Тут скорее уж optional актуальнее. ) EP>Почему? expected<T> это считай variant<T, exception_ptr>, как раз позволяет передать конкретную информацию об ошибке. EP>Вот выступление.
Так в том то и дело, что expected<T> — это как раз полный аналог классических исключений. В то время как на практике чаще нужен именно optional<T> (или вообще просто bool). Я на эту тему уже когда-то давно здесь писал (например это http://rsdn.ru/forum/cpp/4623107
Здравствуйте, alex_public, Вы писали:
_>Я эту мысль уже высказывал раньше и моё мнение в общем то не изменилось.
У нас есть 2 возможных случая
Кейс 1. обработка ошибок находится на N уровней выше и ты даже не знаешь, а есть ли она. Это исключения, фактически способ передать управление на место обработки.
Кейс 2. обработка ошибок находится ровно на один уровень выше и ты всегда знаешь, что это так. Это коды ошибок, как правило низкоуровневый код.
По твоей ссылке изобретается известный способ использования конструкторов-деструкторов, т.к. любое исключение вызовет раскрутку стека.
Такой способ был изобретён еще в 90х, только тогда не было нормальных шаблонов и нужно было совершать бОльше телодвижений.
Про кейс 1, который убер основной, ты не сказал ничего внятного ни тогда, ни сейчас
Здравствуйте, Ikemefula, Вы писали:
I>У нас есть 2 возможных случая I>Кейс 1. обработка ошибок находится на N уровней выше и ты даже не знаешь, а есть ли она. Это исключения, фактически способ передать управление на место обработки. I>Кейс 2. обработка ошибок находится ровно на один уровень выше и ты всегда знаешь, что это так. Это коды ошибок, как правило низкоуровневый код.
I>По твоей ссылке изобретается известный способ использования конструкторов-деструкторов, т.к. любое исключение вызовет раскрутку стека. I>Такой способ был изобретён еще в 90х, только тогда не было нормальных шаблонов и нужно было совершать бОльше телодвижений.
Просто деструкторы — это по сути некий аналог finally, который не является чем-то особо интересным (тот же scope_exit есть в Бусте чуть ли не с рождения, но повсеместного его применения что-то не видно). А вот возможность понять чем вызван запуск деструктора (раскруткой стека исключением или же штатным выходом) даёт совершенно другие возможности, как раз являющиеся заменой уже catch.
I>Про кейс 1, который убер основной, ты не сказал ничего внятного ни тогда, ни сейчас
Про этот случай (это вариант обработки критических сбоев, обрабатываемый одним глобальным try/catch на приложение, ну или на поток) я как раз чётко написал, что тут классические исключения являются оптимальным инструментом. Однако в случае применения исключений не только для критических сбоев, но и для обработки логики, данный случае будет составлять жалкую долю от всей обработки ошибок в приложение.
Здравствуйте, alex_public, Вы писали:
_>Просто деструкторы — это по сути некий аналог finally, который не является чем-то особо интересным (тот же scope_exit есть в Бусте чуть ли не с рождения, но повсеместного его применения что-то не видно).
finally это очень интересно, а вот деструкторы — так себе. Например, потому что могут вызвать terminate
>А вот возможность понять чем вызван запуск деструктора (раскруткой стека исключением или же штатным выходом) даёт совершенно другие возможности, как раз являющиеся заменой уже catch.
Нету такой возможности.
I>>Про кейс 1, который убер основной, ты не сказал ничего внятного ни тогда, ни сейчас
_>Про этот случай (это вариант обработки критических сбоев, обрабатываемый одним глобальным try/catch на приложение, ну или на поток)
OMG !
1 На поток у нас и так есть uncaught хандлер и ничего специального обычно не нужно
2 Глобальный ровно ничего не даёт —
а из за потоков
б из за эвентлупа — реально приложение обрабатывает сообщения.
Все предельно просто — ты не знаешь есть у тебя обработчик выше или нет, решение ровно одно — throw.
Где ты обработаешь — решает вызывающий код. Фактически это разница между исключениями и кодами возврата есть в некотором смысле инверсия управления — не ты передаёшь управление, а у тебя его перехватывают.
Вопрос в том, где у тебя catch. catch всегда находится там, где есть 100% возможностей обработать ошибку.
Например некоторая операция с глубоким стеком сфейлилась.
варианты обработки
1. запрос юзера — retry, cancel
2. игнор результата всей операции
3. отложить операцию напозжа
4. игнор промежуточного результат
5. восстановить
...
N. дополняем информацию об ошибке и/или пробрасываем дальше
Итого, где может быть catch реально
1. публичный метод компонента — пробросить уточненное/скорректированое исключение
Пример — компонент mailClient. метод attach бросает вагон исключений, как то UnableEncode, UnableLocate, UnableRead, AccessDenied, NotAuthorizedUsingAttachments.
Что интересно, readFile и подобные вещи может и не использовать никаких исключений, а может использовать, но совершенно друие типы.
2. топовый хандлер некоторого _слоя_
Пример — OnSendMailButtonClick
3. обработка аппаратных или системных исключений.
Например страница памяти еще не подмаплена, это еще не повод выходить из приложения. Вместо этого подмапливаем нужную страницу и продолжаем с того места, где произошло исключение
4. упрощение логики
например в длинных вычислениях произошло деление на ноль. нас все устраиват, надо только результат выдать какой нибудь дефолтный в текстбоксе — Not a Number и тд.
5. в любом месте кода, что бы принять исключение с неизвестный глубины
примеры — с целью логировать, повторить, отложить, дополнить, игнорировать и тд, см выше
6 Самая 'нижняя' функция некоторого слоя — если нижележащий слой ведет себя непойми как.
Например System.Drawing в норме бросает OutOfMemory когда просто не может отрисовать слишком мелкий объект. OutOfMemory пудозреваю это ошибка маппинга где то в нативном коде, потому и проходит мимо имеющихся try catch
Здравствуйте, Ikemefula, Вы писали:
_>>Просто деструкторы — это по сути некий аналог finally, который не является чем-то особо интересным (тот же scope_exit есть в Бусте чуть ли не с рождения, но повсеместного его применения что-то не видно). I>finally это очень интересно, а вот деструкторы — так себе. Например, потому что могут вызвать terminate
Как раз scope_exit гораздо удобнее finally — Евгений уже показал это в сообщение выше.
>>А вот возможность понять чем вызван запуск деструктора (раскруткой стека исключением или же штатным выходом) даёт совершенно другие возможности, как раз являющиеся заменой уже catch. I>Нету такой возможности.
А данная возможность как раз продемонстрирована по ссылке в первом сообщение темы. )))
I>OMG ! I>...
Что-то ты понаписал кучу всего, включая какую-то свою личную классификацию ситуаций. А пользы от этого ноль. На самом деле на практике существует ровно две известных практики применения исключений (ну если не считать варианта их запрета, как в гугловских гайдах):
1. Применение исключений для обработки реально критических ситуаций. В этом случае существует один специальный обработчик, который обычно выдаёт сообщение об ошибке (возможно с какими-то опциями, типа сохранения отладочной информации и т.п.) и аварийно завершает приложение. С таким применением исключений согласны очень многие и я в том числе.
2. Применение исключений и для обработки критических ошибок и для обработки банальных неудачных вызовов. С таким применением согласны уже далеко не все. И вот лично мне он тоже не особо нравится. Потому что на мой вкус код вида (схематичный пример):
void OnSaveButton()
{
if(!SaveDocument()) MessageBox("Не могу сохранить документ");
}
гораздо удобнее и нагляднее чем
void OnSaveButton()
{
try{
SaveDocument();
}catch(exception&){
MessageBox("Не могу сохранить документ");
}
}
Так вот моя мысль в данной теме была в том, что если применить для второго варианта исключения в формате описанном в той статье, т.е. что-то вроде
void OnSaveButton()
{
SCOPE_FAIL{ MessageBox("Не могу сохранить документ"); };
SaveDocument();
}
то это будет уже не такой уж и плохой вариант для большинства случаев (если не критично быстройдействие, т.к. исключения всё же заметно тормозные). Конечно на таких простых примерах это не так уж и видно, однако если спроектировать это на случай более сложных функций со множеством условий, то преимущества должны быть очевидны.
Здравствуйте, alex_public, Вы писали:
_>На этом форуме была уже не одна дискуссия на тему преимуществ и недостатков исключений. Желающие могут легко глянуть поиском. И я уже отмечался в них, в основном с негативным взглядом на данную технику. Точнее я вижу в ней только инструмент обработки реально критических ошибок (типа нехватки памяти и т.п.)
ну ты блин философ.
OutOfMemException — это идин из тех трех гадов, после катча которых процесс уже не спасти ни чем.
плиз иксплейн свою мысль.
P.S. Кстати, а если в Catch(OutOfMemoryException) написать
{
new ManualResetEvent(false).WaitOne();
}
выживет ли процесс?
Кто пробовал?
P.P.S. я пробовал только что. процесс не мрёт, даже если не повесить поток в катч(OutOfMemoryException). разве так и должно быть?
Здравствуйте, alex_public, Вы писали:
_>>>то в итоге это становится даже менее удобно чем древние коды возврата (особенно если в языке реализована удобная работа с кортежами и типами реализующими концепцию монады maybe), из-за непрерывных убогих try/catch на каждом углу. EP>>Не припомню чтобы кто-то из сторонников исключений выступал за использование "try/catch на каждом углу". _>А оно автоматом возникает в случае применения исключений не только для критических сбоев. Потому как в таком случае обработка ошибки происходит не где-то на высоком уровне, а почти всегда на уровне вызова функции, кидающей исключения. В предыдущих темах я уже приводил примеры на эту тему.
Я об этом и говорю, не припомню чтобы сторонники исключений агитировали за использование их в тех случаях, где обработка происходит на том же уровне.
Есть конечно неоднозначные случаи, где аргументация сильна и за тот и за другой вариант (в смысле исключения или return code) — но это всё же редкость. И тут, кстати, частично помогает expected<T> и аналоги.
EP>>Подобное неудобство было бы и при кодах возврата. Да и вообще, там не "про неудобство классического подхода" в целом, а лишь о нескольких конкретных use-case'ах, которые разгуливаются несколькими дополнениями в язык, которые кардинально не меняют "классический подход", а лишь дополняют его в нескольких случаях. _>Дело в том, что при применение исключений везде, этот случай становится как раз самым распространённым.
Какой? scope(failure/success)? Посмотри например как часто они применяются в библиотеке D Phobos и в каких контекстах — то есть там где они присутствуют из коробки.
_>Вот к примеру ошибка при невозможности сохранить файл
Файл это не самый распространённый случай. Самый распространённый это выделение памяти, причём deallocate не фейлится.
_>- она должна обрабатываться с помощью исключений или же нет? )
Зависит от контекста.
_>И если с помощью исключений, то каким подходом лучше? )
Самый ванильный это ручное проставление .flush_may_throw() в конце scope (отдалённо напоминающее scope guard).
Альтернативные варианты подробно описывал тут
Здравствуйте, VladCore, Вы писали:
_>>На этом форуме была уже не одна дискуссия на тему преимуществ и недостатков исключений. Желающие могут легко глянуть поиском. И я уже отмечался в них, в основном с негативным взглядом на данную технику. Точнее я вижу в ней только инструмент обработки реально критических ошибок (типа нехватки памяти и т.п.) VC>ну ты блин философ. VC>OutOfMemException — это идин из тех трех гадов, после катча которых процесс уже не спасти ни чем.
Здравствуйте, alex_public, Вы писали:
_>Как раз scope_exit гораздо удобнее finally — Евгений уже показал это в сообщение выше.
Да, для ненужных кейсов удобнее, линейная композиция и все такое.
>>>А вот возможность понять чем вызван запуск деструктора (раскруткой стека исключением или же штатным выходом) даёт совершенно другие возможности, как раз являющиеся заменой уже catch. I>>Нету такой возможности.
_>А данная возможность как раз продемонстрирована по ссылке в первом сообщение темы. )))
Если ты в деструкторе вызываешь какую то логику, то надо ждать и исключения. Опаньки !
_>Что-то ты понаписал кучу всего, включая какую-то свою личную классификацию ситуаций. А пользы от этого ноль.
Это просто примеры. Что характерно, ты их ниасилил, подозреваю, просто не читал
>На самом деле на практике существует ровно две известных практики применения исключений:
Вообще то кроме 'практик' есть следующее
1 обработка ошибок
2 распространение ошибок — ты уже в который раз эту часть игнорируешь и не даёшь ответа, как же эту часть реализовать
_>2. Применение исключений и для обработки критических ошибок и для обработки банальных неудачных вызовов. С таким применением согласны уже далеко не все. И вот лично мне он тоже не особо нравится. Потому что на мой вкус код вида (схематичный пример):
_>
_>void OnSaveButton()
_>{
_> if(!SaveDocument()) MessageBox("Не могу сохранить документ");
_>}
_>
А ты понимаешь, что все зависит от реализации SaveDocument ?
Типичный SaveDocument это очень длинная операция и имеет глубокий стек вызовов. Например потому, что у нас там скорее всего работает врайтер-форматтер который по сути своей или рекурсивен, например (обход графа-дерева и тд и тд) или имеет слоёную структуру.
И вот где то глубоко-глубоко произошло исключение. Опаньки ! Как доставить на самый верх ?
Вобщем пример в студию !
_>Так вот моя мысль в данной теме была в том, что если применить для второго варианта исключения в формате описанном в той статье, т.е. что-то вроде _>
_>void OnSaveButton()
_>{
_> SCOPE_FAIL{ MessageBox("Не могу сохранить документ"); };
_> SaveDocument();
_>}
_>
_>то это будет уже не такой уж и плохой вариант для большинства случаев
1 что будет, если код, там где вызов MessageBox бросит исключение ?
2 как переписать на скопы такой пример:
Expression resultFromString(String str) {
try {
Expression expression = new Expression(str);
// считаем долго и рекурсивно
return Expression.Transform(expression.calculate(), Expression.Type('Miles'));
} catch(CalculationFailure ex) {
return Expression.NotANumber;
}
}
Здравствуйте, Evgeny.Panasyuk, Вы писали:
_>>А оно автоматом возникает в случае применения исключений не только для критических сбоев. Потому как в таком случае обработка ошибки происходит не где-то на высоком уровне, а почти всегда на уровне вызова функции, кидающей исключения. В предыдущих темах я уже приводил примеры на эту тему. EP>Я об этом и говорю, не припомню чтобы сторонники исключений агитировали за использование их в тех случаях, где обработка происходит на том же уровне. EP>Есть конечно неоднозначные случаи, где аргументация сильна и за тот и за другой вариант (в смысле исключения или return code) — но это всё же редкость. И тут, кстати, частично помогает expected<T> и аналоги.
Ты то может и не агитировал, но сторонников подхода "вся обработка ошибок через исключения" на этом форуме предостаточно.
_>>И если с помощью исключений, то каким подходом лучше? ) EP>Самый ванильный это ручное проставление .flush_may_throw() в конце scope (отдалённо напоминающее scope guard). EP>Альтернативные варианты подробно описывал тут
Здравствуйте, Ikemefula, Вы писали:
>>На самом деле на практике существует ровно две известных практики применения исключений: I>Вообще то кроме 'практик' есть следующее I>1 обработка ошибок I>2 распространение ошибок — ты уже в который раз эту часть игнорируешь и не даёшь ответа, как же эту часть реализовать
При использование исключений не для критических ошибок, а для обычной логики нет никакого распространения ошибок — всё обрабатывается на том же уровне. Максимум бывает перепаковка исключений (т.е. всё равно отлов на том же уровне), но это как раз вообще мрак с точки зрения удобства.
I>А ты понимаешь, что все зависит от реализации SaveDocument ? I>Типичный SaveDocument это очень длинная операция и имеет глубокий стек вызовов. Например потому, что у нас там скорее всего работает врайтер-форматтер который по сути своей или рекурсивен, например (обход графа-дерева и тд и тд) или имеет слоёную структуру. I>И вот где то глубоко-глубоко произошло исключение. Опаньки ! Как доставить на самый верх ? I>Вобщем пример в студию !
Тебе надо рассказать как вернуть булево значение из функции? )))
_>>Так вот моя мысль в данной теме была в том, что если применить для второго варианта исключения в формате описанном в той статье, т.е. что-то вроде _>>
_>>void OnSaveButton()
_>>{
_>> SCOPE_FAIL{ MessageBox("Не могу сохранить документ"); };
_>> SaveDocument();
_>>}
_>>
_>>то это будет уже не такой уж и плохой вариант для большинства случаев I>1 что будет, если код, там где вызов MessageBox бросит исключение ?
Ты видишь какую-то разницу в этом смысле с обычным подходом исключений? )))
I>2 как переписать на скопы такой пример: I>
Здравствуйте, alex_public, Вы писали:
>>>На самом деле на практике существует ровно две известных практики применения исключений: I>>Вообще то кроме 'практик' есть следующее I>>1 обработка ошибок I>>2 распространение ошибок — ты уже в который раз эту часть игнорируешь и не даёшь ответа, как же эту часть реализовать
_>При использование исключений не для критических ошибок, а для обычной логики нет никакого распространения ошибок — всё обрабатывается на том же уровне. Максимум бывает перепаковка исключений (т.е. всё равно отлов на том же уровне), но это как раз вообще мрак с точки зрения удобства.
Ты понимаешь, что мы говорим про случай, когда ошибка и обработка на разных уровнях ?
I>>Типичный SaveDocument это очень длинная операция и имеет глубокий стек вызовов. Например потому, что у нас там скорее всего работает врайтер-форматтер который по сути своей или рекурсивен, например (обход графа-дерева и тд и тд) или имеет слоёную структуру. I>>И вот где то глубоко-глубоко произошло исключение. Опаньки ! Как доставить на самый верх ? I>>Вобщем пример в студию !
_>Тебе надо рассказать как вернуть булево значение из функции? )))
Да, покажи вот для этого случая, в контексте SaveDocument: "где то глубоко-глубоко произошло..."
Простой кейс — XML-форматтер нашел случай, который не может зарезолвить, например циклические ссылки, форматтер обычный рекурсивный.
Покажи начиная с ошибки и заканчивая SaveDocument.