Здравствуйте, alex_public, Вы писали:
_>Не о том речь. Просто у исключений есть очевидные преимущества. Но они проявляются как раз для случая проброса через длинный стек вызова. Т.е. в идеальной модели с исключениями мы имеет вообще один единственный блок с try/catch на всю программу.
В идеальной модели исключения нужны для передачи управления непосредтсвенно в то место, где есть вся информация для обработки.
С чего ты взял, что вся информация, депенденсы и тд и тд, будет именно в том самом месте, где этот единственный блок ?
_>Да, и тогда применять исключения не надо. Собственно о том и речь, что исключения хорошо подходят для решения только небольшой части общей задачи обработки ошибок. В отличие от тех же кодов возврата, которые подходят везде.
Коды возврата это полностью ручное раскручивание каждой ошибочной ситуации. Ты наверное хотел намекнуть, что вручную можно сделать всё что угодно ?
Здравствуйте, alex_public, Вы писали:
_>Я же говорю, проблемы у исключений не в теории, а на практике. Конкретно на практике мне не приходилось писать код с последовательным открытием 4-х сокетов (причём так что проблемы с одним отменяют попытки с остальными) в одной функции. Но зато вполне частенько встречался код вида _>
_>auto f1 = open_file(get_profile(username));
_>if(!f1) f1=open_file(get_default_profile());
_>s1_ = open_socket(f1); // читаем из конфига
_>
_>Понимаешь что получится, в случае переписывания такого варианта твоего кода на исключениях? )
Это не самый распространенный случай. Наиболее общий случай это линейный код, без каких либо ветвлений. Он есть вообще везде:
Представь себе простую задачу — отсылка емейла. На входе у тебя просто конфиг, фактически, указано где что взять, ничего конкретного. Здесь всё делается обычным линейным кодом.
Самый лучший способ — избавиться вообще от любых ветвлений. В твоем случае надо обкладывать линейный код условными операторами. То есть, на ровном месте вводить ветвления.
Единственная вещь, где мешают исключения — это восстановления после ошибок. Собтсвенно, мешают не потому ,что исключения, а потому, что исключения в общей массе не поддерживают восстановление. Собтсвенно, это не очень большая проблема, ибо линейного кода на порядки больше.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, vsb, Вы писали:
_>Когда-то этот вопрос уже здесь обсуждался, причём именно для случая C++ (в контексте запрета исключений в гугловских руководствах). Я тогда подробно (с подтверждающими примерами) изложил свою точку зрения. Могу повторить тезисно в этой темке.
_>Главное преимущество исключений в том, что они позволяют без напряжения для программиста и оптимально по быстродействию (как раз тесты из данной темки это подтверждают) передавать ошибку через множество уровней стека вызова. Так вот, на мой взгляд на практике нормальная обработка ошибок всегда происходит практически на следующем уровне вложенности, а не где-то далеко наверху.
У меня как раз обратное впечатление.
Недавно рефакторил код написанный, в сишном стиле без всяких исключений.
Программа для работы с девайсом. Девайсовый протокол, представляет из себя целый стек протоколов, точнее их несколько, т.к. в стеке одни протоколы могут заменятся на другие. И вот представляешь, в одном из протоколов скажем есть авторизация, и вот в процессе исполнения какой то команды обнаружилось, что пароль то неправильный. Ошибка фатальная, никакие перезапросы, тут не помогут. Нужно наверх пользователю засветить окошко с ошибкой, что дескать пароль то ты неправильно указал, ну или даже вывалить диалог ввода пароля. А от места где это возникло до инициатора команды еще чудовищный стек вызова, три конечных автомата. И как же эту ошибку протащить? Там был лютый ад. На каждом уровне были гиганские кейсы, с обработкой всевозможных ошибок, перекодирование ошибок одного уровня в ошибки другого уровня.
Просто руки опускались разгребать эти авгиевы конюшни. Я выкинул 80% кода, не дописав почти ничего, только генерацию исключений вместо возврата ошибок.
Конечно программу нужно проектировать с учетом использования исключений. Нужно обеспечивать грамотную финализацию, что бы объекты оставались в валидном состоянии и не текла память. Но всё это в большинстве случаем решается шаблонно. Нужно к исключением относиться без фанатизма. Не нужно по любой ошибке бросаться исключениями.
Вообще, на мой взгляд, общеиспользуемые библиотеки не должны наружу кидать исключение, за исключением случаев, которые возникают в результате их некорректного использования. Т.е. по сути эти исключения нужны только для отладки.
Меня тоже например раздражает, когда функция открытия файла кидает исключение. Ошибку открытия файла почти всегда удобнее обработать без исключений.
Даже самую простую задачу можно сделать невыполнимой, если провести достаточное количество совещаний
Здравствуйте, smeeld, Вы писали:
S>Вы немного не в ту степь завернули. Тут говорилось не про hardware exceptions.
Исключения, как вызванные проблемами в процессоре (деление на 0, page fault), так и программно,в нативном коде идут через обработку в ядре.
S>Не надо было за эталон брать то, что в С++, академики праздные, накорябали своими белыми ручонками.
С++ тут совершенно ни при чем. Исключения, которые в C++ выбрасываются с помощью throw, в Windows реализуются с помощью вызова RaiseException, а далее в ядро.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>С++ тут совершенно ни при чем. Исключения, которые в C++ выбрасываются с помощью throw, в Windows реализуются с помощью вызова RaiseException, а далее в ядро.
Что? throw в windows переключает в ядро? Зачем?
Hardware exceptions-ы предназначены для контроля
фундаметнальных ошибок исполнения инструкций, и для
аппаратной поддержки фундаментального функционала ОС.
Если же в юзерспайсе Вася решил откатится вверх по стеку,
зачем для этого в ядро нырять-то?
Здравствуйте, Ikemefula, Вы писали:
I>Это не самый распространенный случай. Наиболее общий случай это линейный код, без каких либо ветвлений. Он есть вообще везде:
Ты не понял какую идею демонстрирует данный пример. И кстати говоря это как раз очень распространённый случай.
Смотри, допустим у нас есть низкоуровневая функция, вызываемая где-то в самой глубине стека вызова и возможно завершающаяся с ошибкой. Теперь рассмотрим 2 случая:
1. В случае ошибки в данной функции приложение должно корректно завершить свою работу. Здесь исключения подходят просто идеально. Имеем один блок try/catch где-то на самому верху стека вызовов приложения, выводящий соответствующее сообщение об ошибке и завершающее приложение. Ну разве что если исключение слишком низкоуровневое и в разных модулях может иметь разный смысл для пользователя — тогда надо его ещё переупаковать где-то в промежуточном коде. Но обычно это не проблема.
2. В случае ошибки в данной функции приложение продолжает свою работу по альтернативному сценарию, прямо в коде после вызова нашей функции. В этом случае исключения очень плохо подходят для решения этой задачи, т.к. тогда приходятся оборачивать в try/catch каждый вызов этой самой низкоуровневой функции. Код становится намного более многословным (в сравнение с вариантом без исключений) и замусоренным.
Кто-то скажет, что типа да, всё правильно, во втором случае исключения и не надо применять, т.к. это не исключительная ситуация. Но не всё так просто. Во-первых наша функция может иметь различный смысл в разных приложениях и если она при этом реализована в библиотеке не с двойным интерфейсом (типа Boost.Asio), то получаем гарантированное неудобство в каком-то из случаев. А во-вторых некоторые "адепты исключений" (вроде как и у нас на форуме такие имеются) готовы использовать исключения не только для обработки критических ошибок, но и вообще для всех случаев обработки ошибок...
Да, и кстати, ещё отдельный вопрос. Попробуй глянуть в своём последнем приложение, сколько там имелось случаев обработки критических ошибок и сколько случаев обработки обычных. Вот лично у меня критические вообще очень редко попадаются (только что-то из области нехватки оперативной памяти или переполнения диска), а обычные естественно не редкость... )
Здравствуйте, tyomchick, Вы писали:
T>Вообще, на мой взгляд, общеиспользуемые библиотеки не должны наружу кидать исключение, за исключением случаев, которые возникают в результате их некорректного использования. Т.е. по сути эти исключения нужны только для отладки.
T>Меня тоже например раздражает, когда функция открытия файла кидает исключение. Ошибку открытия файла почти всегда удобнее обработать без исключений.
Ну можно ещё предоставлять оба интерфейса. ) Что местами и наблюдается. Но очень редко. )
Кстати, C++ библиотека вполне может кидаться исключениями, даже если автор библиотеки их и не использует сам. К примеру достаточно использовать обычную (а не nothrow) версию new. ))) Я такое видел. Правда это даже и не плохо, т.к. подобная ошибка в 99% случаев относится к критическим.
Здравствуйте, uncommon, Вы писали:
U>Ну, и будут каждую функцию заворачивать в try! или с монадами извращаться. Зато принцип!
Ну вообще-то, на Rust уже достаточно много кода написано. try! не так уж и много получается. И принципиальность — это как раз круто.
Я уж не говорю про то, что исключения не используются и в огромном количестве проектов на С++. При полном отсутствии сахара типа try!.
Здравствуйте, alex_public, Вы писали:
I>>Это не самый распространенный случай. Наиболее общий случай это линейный код, без каких либо ветвлений. Он есть вообще везде:
_>Ты не понял какую идею демонстрирует данный пример. И кстати говоря это как раз очень распространённый случай.
Цитирую себя:
Единственная вещь, где мешают исключения — это восстановления после ошибок. Собтсвенно, мешают не потому ,что исключения, а потому, что исключения в общей массе не поддерживают восстановление. Собтсвенно, это не очень большая проблема, ибо линейного кода на порядки больше.
_>Да, и кстати, ещё отдельный вопрос. Попробуй глянуть в своём последнем приложение, сколько там имелось случаев обработки критических ошибок и сколько случаев обработки обычных. Вот лично у меня критические вообще очень редко попадаются (только что-то из области нехватки оперативной памяти или переполнения диска), а обычные естественно не редкость... )
Линейная логика никуда не девается. Возьми какой юзерский сценарий и опиши подробно. Практически всегда будет минимум одна линейная цепочка, ошибка в которой означает фейл всего сценария.
Здравствуйте, koodeer, Вы писали:
C>>Ещё в качестве средства изоляции — при броске fail'а все разделяемые ресурсы (в Rust они есть в виде RWArc), которые в тот момент использовала задача, помечаются как "испорченные". K>Можно в двух словах для тех, кто не знаком с Rust'ом, что такое задача? Это процесс, поток или что-то ещё?
Это легковесный процесс. Общение с ним идёт через отсылку сообщений, которые при этом копируются — ровно как и в Erlang'е. В отличие от Erlang'а, ещё можно отсылать данные без копирования с помощью Arc и изменяемые данные с помощью RWArc.
K>Меня интересует, если ресурс, например, файл, будет помечен как испорченный, то другой процесс, работающий с ним же, продолжит свою работу?
Конкретно файл в процессе unwinding'а будет просто закрыт, если не делать какую-то дополнительную обратоку в деструкторе (т.е. в реализации трэйта Drop).
Здравствуйте, smeeld, Вы писали:
S>Что? throw в windows переключает в ядро? Зачем? S>Hardware exceptions-ы предназначены для контроля S>фундаметнальных ошибок исполнения инструкций, и для S>аппаратной поддержки фундаментального функционала ОС. S>Если же в юзерспайсе Вася решил откатится вверх по стеку, S>зачем для этого в ядро нырять-то?
Исключения C++ могут быть реализованы очень разными способами. Это и sjlj и dward и seh. А ещё есть исключения самой винды (которые могут быть в том числе и аппаратными), про которые и писал Павел. Так вот в исключениях винды используется механизм seh и при соответствующей поддержке компилятора эти исключения можно так же перехватывать через обычный C++ catch.
Здравствуйте, tyomchick, Вы писали:
T>Меня тоже например раздражает, когда функция открытия файла кидает исключение. Ошибку открытия файла почти всегда удобнее обработать без исключений.
Если есть восстановление после сбоя, то ситуация никакая не исключительная, потому исключения не нужны.
Надо отделять исключения, сбои, восстановления.
Сбой — это локальная проблема, ошибка. Исключение — это отсутствие возможность продолжать после сбоя. Восстановление — это наличие возможности продолжать после сбоя. Исключение, как правило, означает, что информации обработать ошибку в локальном скопе просто нет. Восстановление, наоборот, означает что есть вся информация обработать ошибку.
Отсюда ясно, что один и тот же сбой в зависимости от приложений может потребовать или исключения или восстановления.
alex_public говорит, что восстановление лучше, кошернее, но никак не хочет объяснить наиболее типичный сценарий — N последовательных шагов, сбой в любом прерывает весь сценарий.
Например — сохранить данные пользователя в конкретный файл. Открыть, получить данные, серилизовать, записать, закрыть. В любом месте если возникает ошибка — весь сценарий фейлится.
Другой сценарий — сохранить критические данные приложения во что бы то ни стало, даже вне контроля пользователя. Все почти тож самое, но есть восстановление — не открылся один файл, откроется другой, нет доступных носителей для файлов — есть БД, сеть, почта и тд и тд тд.
Здесь исключение возникает если все способы восстановления ошибок исчерпаны.
При этом, фокус, линейная логика так же присутствует — фейл при получении данных приводит к исключению. фейл при серилизации так же приводит к исключению. Не нашли куда можно сохранить — так же исключение.
Итого — линейная логика есть всегда. Восстановление есть иногда. Наличие резервных путей никак не отменяет наличия линейной логики.
Теоретически, можно сделать навроде
var x = null;
try{
x = sourceFromFile('a');
fallback x = sourceFromFile('b');
fallback x = sourceFromFile('z');
} catch() {
...
}
Но вообще уже здесь всякие монады смотрятся гораздо интереснее
Здравствуйте, Ikemefula, Вы писали:
I>Линейная логика никуда не девается. Возьми какой юзерский сценарий и опиши подробно. Практически всегда будет минимум одна линейная цепочка, ошибка в которой означает фейл всего сценария.
Наличие линейной цепочки ничего не меняет, т.к. если обработка ошибки будет происходить прямо на следующем уровне стека, то банальный return ничем не отличается по удобству от throw. Т.е. ключевой вопрос именно в точке обработки ошибок. Если это где-то наверху стека (как для критических ошибок), то исключения великолепны. Если же прямо на следующем уровне (а это абсолютно нормальный сценарий, например если взамен одной твоей цепочки надо запустить альтернативную), то исключения только портят картину.
Здравствуйте, alex_public, Вы писали:
I>>Линейная логика никуда не девается. Возьми какой юзерский сценарий и опиши подробно. Практически всегда будет минимум одна линейная цепочка, ошибка в которой означает фейл всего сценария.
_>Наличие линейной цепочки ничего не меняет,
Сценарий
"по требованию сохранить данные пользователя в конкретный файл. Открыть, получить данные, серилизовать, записать, закрыть."
Полагаю, ты можешь продемонстрировать заявленое чудо, а именно "запись в файл который невозможно открыть".
>т.к. если обработка ошибки будет происходить прямо на следующем уровне стека,
Намекаешь, что вызываемый код должен обладать знаниями о вызывающем ?
>Т.е. ключевой вопрос именно в точке обработки ошибок. Если это где-то наверху стека (как для критических ошибок), то исключения великолепны.
Ты путаешь три вещи — ошибка, исключение и восстановление. Второе и третье это стратегии обработки. Возможность использовать конкретную стретегию зависит не от твоего желания, а от самой логики.
>Если же прямо на следующем уровне (а это абсолютно нормальный сценарий, например если взамен одной твоей цепочки надо запустить альтернативную), то исключения только портят картину.
То есть, ты предлагаешь переписывать весь слой, если мы начнем вызывать его немного другим способом. Правильно тебя понимаю ?
PD>Затем, что таким образом организована обработка исключений в Windows. PD>что, как понимаешь, без участия ядра системы невозможно.
Для отладки оно, может, и удобно, хотя в юниксах и без таких SEH-удобств, проблем с отладкой нет.
И для продакшен версии такое ныряние в ядро на throw-ах, опциями компилятора, никак не отключается?
Здравствуйте, smeeld, Вы писали:
S>Для отладки оно, может, и удобно, хотя в юниксах и без таких SEH-удобств, проблем с отладкой нет. S>И для продакшен версии такое ныряние в ядро на throw-ах, опциями компилятора, никак не отключается?
Тут дело не в отладке. Передача управления отладчику, если он есть (а отладчик в Win32 это не абы кто, а тот, кто запустил этот процесс с флагом DEBUG_PROCESS) — это лишь один из моментов обработки исключений. Прочти еще раз то, что я процитировал в своем первом сообщении в этом треде, чтобы понять, как вообще исключения в нативных процессах Windows работают. Это переход в ядро и там обработка через стандартный механизм обработки исключений в Windows.
Debug или Release — тут не важно, потому что RaiseException находится в kernel32.dll, а далее идет через ntdll.dll и sysenter, что никакого отношения к Debug/Release не имеет.
Я не знаю, можно ли это как-то отключить или изменить. Если кто-то знает — буду рад услышать.
А вот в других языках исключения могут быть реализованы и без этого механизма. У меня нет сейчас под рукой исходников java — машины, но когда-то я в них копался (openJDK), так вот, вроде как там исключения обрабатываются внутри процесса java машины (jvm.exe) и дальше не идут. Не ручаюсь на 100%, но вроде это так. В принципе так вполне и должно быть : за пределы jvm этим исключениям моей java-программы ходу нет (а нативным есть!), поэтому она и может сама все сделать. В частности, ЕМНИП, NullPointerException в java ловится не как в нативном коде (попробуем *p, при p == NULL получим исключение и далее как сказано выше), а просто проверкой перед каждой операцией значения ссылки на null. Иными словами, никакого исключения Windows там не происходит, а просто jvm процесс в ходе своей работы обнаружил, что его данные (значение java-переменной) нехорошие, ну и выполнил некую обработку
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, uncommon, Вы писали:
ARK>>>А в моей присутствует. Кто из нас прав? U>>Покажи свой код. Дьявол, как обычно, в деталях.
ARK>Код принадлежит не мне, поэтому показать его я не могу. ARK>Но могу ответить на любые уточняющие вопросы.
Нет, товарищ. Это тот случай, когда надо иметь перед глазами код. Чтобы точно тебе показать, как можно и нужно делать по-другому. Уточняющими вопросами здесь не обойдёшься.
ARK>Хотя, честно говоря, не вижу смысла в этом. То, что многие (а из мною лично виденных — все без исключения) крупные системы содержат кучу мусора с try/catch/finally — это факт.
Если ты живёшь на помойке, то всё что ты видишь каждый день это куча говна. Это факт для тебя. Но твой личный опыт ничего не доказывает, особенно учитывая тот факт, что у тебя, мягко говоря, неверные представления об исключениях.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, uncommon, Вы писали:
C>>>Что рассказали: C>>>1) Исключения можно сделать хоть сейчас, это технически не проблема вообще. Тем более, что уже есть механизм unwinding'а. C>>>2) Но их делать не будут из принципа.
U>>Ну, и будут каждую функцию заворачивать в try! или с монадами извращаться. Зато принцип!
ARK>Маразм с каждой функцией нужен только в языках, не имеющих контроля за исключениями, в которых исключение может вылететь где угодно (то есть во всех менйстримовых ЯП).
Здравствуйте, uncommon, Вы писали:
U>Если ты живёшь на помойке, то всё что ты видишь каждый день это куча говна. Это факт для тебя. Но твой личный опыт ничего не доказывает
Безусловно, мой опыт ничего не доказывает. И ваш тоже.
U>особенно учитывая тот факт, что у тебя, мягко говоря, неверные представления об исключениях.
Во-первых, учтите тот факт, что обсуждение личности собеседника на этом форуме запрещено.
Во-вторых, мои представления об исключениях вполне исчерпывающи. Тем не менее, я не считаю их хорошим средством, даже при условии "правильного" применения. Да, и коды возврата панацеей я тоже не считаю.