Здравствуйте, vsb, Вы писали:
vsb>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.
Исключения это очень медленно и размашисто, по сравнению с if(func()!=0) err();
Оправдывает себя только для случаев, когда исключением можно обработать глобальные
сбой или ошибку, с или остановкой приложения, или его, в некотором смысле, полным перезапуском,
с какими другими входными данными и иным способом.
Короче, это как тяжёлая артиллерия, и применять это средство необдуманно, нерационально-чревато,
потерей производительности.
Здравствуйте, smeeld, Вы писали:
S>"Правильно реализованные", это как? Смотрел как они реализованы в ELF+gcc/clang/solarisstudio.
Вот так:
The second scheme, and the one implemented in many production-quality C++ compilers, is a table-driven approach. This creates static tables at compile time and link time that relate ranges of the program counter to the program state with respect to exception handling.[18] Then, if an exception is thrown, the runtime system looks up the current instruction location in the tables and determines what handlers are in play and what needs to be done. This approach minimizes executive overhead for the case where an exception is not thrown. This happens at the cost of some space, but this space can be allocated into read-only, special-purpose data sections that are not loaded or relocated until an exception is actually thrown.
S>Простой if(err) return -1; сработает на порядок быстрее, чем throw x; }catch(){...}
Ты что при каждом вызове функции исключение выбрасываешь?
Обычно они летают редко. И когда не летают, вообще никаких дополнительных расходов нет.
При этом проверка кодов возврата тормозит всегда.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, dimgel, Вы писали:
Pzz>>Можно, разумеется, но народ забывает. Это невозможно не забыть. Вот и вылетают непойманные на своем уровне исключения наверх, где их никто не ждет.
D>Да и нехай значит. Всё лучше, повторюсь, чем молча глотать. А на верхнем уровне можно журналировать всё непойманное, потихоньку фикся ранее забытое.
Тогда их на верхнем уровне будут молча глотать. С журналированием в /dev/null, ага.
P.S. Наверное, было бы легче, если бы исключения, которые могут прилететь от методов класса, должны были бы быть описаны в самом классе. И если метод класса зовет швыряющуюся чужеродными исключениями функцию, то компилятор бы требовал обложить ее соответствующими try/catch, чтобы незаявленные исключения наружу бе вылетали.
Здравствуйте, vsb, Вы писали:
vsb>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.
Этим исключениям хорошо бы как-то законодательно синтаксически ограничить дальность полета. Потому что когда в ваш высокоуровневый код, работающий в терминах высокоуровневых абстракций, откуда-нибудь с самого нижнего уровня прилетит исключение про то, чего вы в своем высокоуровневом коде сказать-то и не можете, то что вы с ним будете делать? Выдадите пользователю ошибку с шешнадцетиричным номером и невнятной диагностикой? То-то пользователь будет счастлив.
Здравствуйте, Pzz, Вы писали:
Pzz>>>Можно, разумеется, но народ забывает. Это невозможно не забыть. Вот и вылетают непойманные на своем уровне исключения наверх, где их никто не ждет.
D>>Да и нехай значит. Всё лучше, повторюсь, чем молча глотать. А на верхнем уровне можно журналировать всё непойманное, потихоньку фикся ранее забытое.
Pzz>Тогда их на верхнем уровне будут молча глотать. С журналированием в /dev/null, ага.
Pzz>P.S. Наверное, было бы легче, если бы исключения, которые могут прилететь от методов класса, должны были бы быть описаны в самом классе. И если метод класса зовет швыряющуюся чужеродными исключениями функцию, то компилятор бы требовал обложить ее соответствующими try/catch, чтобы незаявленные исключения наружу бе вылетали.
Это Checked Exceptions в Java.
Проблема в том, что во-первых есть очень много исключений, которые вылетают очень редко. Например ошибка связи с БД. У нас БД надёжная и работает надёжно и с ней нет ошибок связи. Но теоретически то вылететь может! А значит придётся обкладывать try-catch каждую операцию с БД.
Во-вторых ряд исключений не вылетит вообще никогда. Например Integer.parseInt("1"). Никогда тут не вылетит исключение NumberFormatException. А компилятор попросит обложить try-catch-ем.
В-третьих ряд исключений могут вылететь в любом месте. В разных языках по-разному. Например NullPointerException теоретически может вылететь на любой строчке, где есть ссылки или вызывается функций. StackOverflowError может вылететь опять же везде, где вызывается любая функция. Что с ними делать?
Первая и третья проблема решаются выделением NonChecked Exceptions. Опять же возникает исходная проблема, что хотя исключение и вылетает раз в год или "по идее" не должно вылетать, но оно вылетит. NullPointerException особенно. И это надо помнить и обрабатывать.
Вторая проблема вообще никак не решается. Вот получил я userId от API, вызываю userService.getUserInformation(userId). А он мне говорит обрабатывай UserNotFoundException. Не может такого быть, я только что этот userId из базы считал, он там 100% есть. Как я буду обрабатывать? Только выкину UnexpectedSituationException, а вы там выше логгируйте и разбирайтесь. И таких catch-ей на каждом шагу очень много будет. Синтаксический хлам.
Кстати ещё одна интересная проблема с наследованием. Вот описали мы
interface Closeable {
void close();
}
теперь примеряем его на FileInputStream, а оказыавется, что close там хочет кидать IOException. Чего нам делать? Либо добавляем в throws IOException, либо перекидыаем как непроверяемое исключение. И имеем опять что имеем.
Если добваляем throws IOException, то куча классов этот IOException никогда кидать не будет. StringWriter, например, пишет во внутренний буффер-строку, никакого там IO нет. А вот при закрытии — изволь, обработай IOException, мало ли, вдруг кинется, в сигнатуре написано же.
Здравствуйте, smeeld, Вы писали:
S>А тестами можете размахивать на праздных академических симпозиумах и конференциях. S>При разработки кода для дикого и ответственного продакшена, ориентироваться надо S>на тонкое знание реализации тех или иных используемых систем, по которому определять характер S>использования этих систем.
Главное при этом не забыть, что в 99.999% случаев код возврата содержит значение "всё хорошо".
А значит на практике всё то чем ты тут размахиваешь просто тает на фоне постоянных if (errorcode != ok)...
Что бы получить реалистичный тест сделай пять функций, которые друг друга вызывают. Запрети компилятору их инлайнить.
Вызови их 100000 раз. А на 100001 скажи, что всё плохо.
И сравни время выполнения.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, Pzz, Вы писали:
Pzz>Можно, разумеется, но народ забывает. Это невозможно не забыть. Вот и вылетают непойманные на своем уровне исключения наверх, где их никто не ждет.
Да и нехай значит. Всё лучше, повторюсь, чем молча глотать. А на верхнем уровне можно журналировать всё непойманное, потихоньку фикся ранее забытое.
В некоторых новых языках программирования (Go, Swift) уходят от механизма исключений и подавляющее число ошибок обрабатывается через явный возврат ошибки из вызываемой функции. В качестве аргумента часто показывают пальцем на язык С++. Наличие там исключений заставляет писать весь код так, как будто из любого вызова функции может вылететь исключение и размотать стек.
На мой взгляд это проблема исключительно С++, а точнее его двух особенностей — отсутствие GC и запрет исключений в деструкторах.
Собственно все проблемы с исключениями решаются тремя простыми методиками:
1. Наличие GC позволяет не беспокоиться об утёкшей памяти из-за исключения.
2. Другие ресурсы практически всегда закрываются там же, где открываются, поэтому defer/finally-подобные конструкции позволяют очень легко устранить утечки этих ресурсов.
3. Исключение должно бросаться откуда угодно и программа должна себя вести корректно при этом (это я про исключения в десктрукторе/finally-блоке и тд).
Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.
Я перечитал много разных статей, но нигде не видел чётких аргументов в пользу отказа от исключений. Go вообще забавный язык, panic()/recover() это исключений как они есть, один в один. Любой код с исключениями тривиальными заменами преобразовывается в код с panic()/recover(). Но при этом они утверждают, что от исключений они избавились.
Здравствуйте, vsb, Вы писали:
vsb>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.
Главная проблема в том, что исключения создают дополнительный скрытый поток управления, который никак в коде не виден.
Это не было бы проблемой, если бы гарантировалась атомарность всех изменений на некотором участке, на котором предположительно может вылететь исключение. То есть, если исключения нет — коммит, если есть — роллбек. При таком раскладе программа всегда остается в валидном состоянии и все инварианты сохраняются в любой момент времени. Увы, у STM есть фундаментальная проблема — ввод-вывод (на данный момент приемлемого решения этой проблемы не найдено).
Таким образом, исключения предлагают избавление от лапши проверок, давая взамен потенциально некорректное состояние программы.
Коды возврата гарантируют видимость всех путей исполнения и отсутствие неожиданных нарушений инвариантов, но генерируют много бойлерплейта.
Что лучше — хрен знает.
WH>The second scheme, and the one implemented in many production-quality C++ compilers, is a table-driven approach. This creates static tables at compile time and link time that relate ranges of the program counter to the program state with respect to exception handling.[18] Then, if an exception is thrown, the runtime system looks up the current instruction location in the tables and determines what handlers are in play and what needs to be done. This approach minimizes executive overhead for the case where an exception is not thrown. This happens at the cost of some space, but this space can be allocated into read-only, special-purpose data sections that are not loaded or relocated until an exception is actually thrown.
Всякие статейки в инетах читаем? Похвально, но посоветовл бы заглянуть в реальные
исходники, и узреть как оно на самом деле там наворочено.
Здравствуйте, vsb, Вы писали:
vsb>А что ещё может сделать нижний уровень? Ну да, он может сказать "Production database unavailable" вместо "Socket disconnected", но кому от этого станет лучше? Пользователю лучше не станет. Администратор/программист из стектрейса и так поймёт, что проблема в БД-коде.
У тебя может быть универсальная функция генерирующая исключение, например, string.Parse(). Далее может быть компонент читающий файл. Ну, и некий потребитель который может вывести сообщение пользователю. Логично, если компонент читающий файл поймает исключение и перезапакует его в свое, в котором говорится, что не шсмогла прочесть файл, так как возникла ошибка "...". Тогда конечный код выведет разумное сообщение об ошибке, мол "Не смогли прочитать файл, так как "№;%" не является числом".
vsb>По сути я вижу отличие исключений от кодов возврата только в одном use-case. Если мы хотим проглотить ошибку.
А тут вообще никакой связи нет, а тебе нужно учиться правильно формулировать вопросы.
Поглотить мы ее может всегда и везде.
Проблема кодов возвратов в том, что скрупулезно их выписывать, обрабатывать и протаскивать вниз довольно муторно. 90% кода будет связано не с обнаружением ошибки и не с ее обработкой, а с протаскиванием этих ошибок вниз по стеку и с тем, что этот код мешается в основном коде.
Люди не приняли даже спецификации исключений для метода в Яве. А уж прописывать все возможные виды ошибок в виде возвращаемых значений могут не только лишь все (ц).
vsb>Вот такое у нас приложение — работаем до последнего и плевать, что там на улице. Тогда с кодами возврата отработает больше кода. С исключениями — выполнение оборвётся сразу и исключение улетит далеко наверх. Ну или ставить try { ... } catch {} везде. Но это ведь в любом случае плохая практика и даже хорошо, что исключения её усложняют.
Это в корне не верное утверждение. Ставить try везде нет смысла. Кроме того в нормальных языках кроме catch есть еще и finally, который используется в разы чаще (не всегда явно, иногда через using или foreach).
Так вот единственная верная стратегия обработки исключений — не ловить их нигде кроме тех мест где мы можем восстановиться и в этом есть смысл. Таких мест обычно очень не много. Так что не удивительно, что, например, в GUI-приложении может быть ровно одно такое место.
Еще один случай — добавление информации в исключении (я его описал выше). Скажем есть слишком универсальная ошибка и мы перепаковываем ее в более конкретную добавляя информацию (например, о файле, как в примере выше).
А вот код возврата заставляют обрабатывать не только всегда и везде сами ошибки, но еще и портят основной код разбавляя его этой логикой. Кстати, самое название уже говорящее, не информация об ошибке, а какие-то не внятные коды. На практике это часто сводится к возврату чиселок. Кто работал с API VS знает, что порою понять по HResult-у, что же пошло не так практически невозможно.
Сахар, как в Расте, это лучше чем ничего. Но полноценные исключения в намного лучше. Если нужна замена исключениям — нужно изобретать новый подход. Коды возврата их не заменят.
В новых языках (в которых по факту мало нового) основная проблема не в иделогии исключений, а в принятых в них технических решениях. Так автоматический подсчет ссылок проблематично сделать вместе с исключениями, так как при раскрутке стеке (во время исключения) нужно отменить все увеличения ссылок, или объекты не умрут когда положено. У стековых деструкторов тоже не все ОК с исключениями. Вызвать то их можно, но раскрута стека, при этом, становится существенно дороже.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, jazzer, Вы писали:
J>>Все эти промежуточные try/catch не добавляют ничего, если исключение не было брошено. J>>Суть в том, чтоб оптимизировать сценарий, когда исключения не летят. J>>А если летят — то пофиг, насколько медленно это делается, что там как переупаковывается и т.д.
_>Ну вообще говоря это зависит от реализации, sjlj/seh/dwarf и т.п. Но в данном случае я говорил не об оптимизации, а об удобстве программиста. Ведь если ловить исключения непосредственно над функцией кидающей их, это получается заметно более громоздко и неудобно.
А можно продемонстрировать громоздкость и неудобность на примере?
Вот чтобы передавалась наверх одна и та же информация, только один раз — кодом возврата, а другой раз — исключением.
Ну чтоб было видно, что исключения сливают, на примере, так сказать.
Скажем, нужно открыть 4 сокета, прочитав параметры из разных конфигов. Мы делаем вперемежку 4 вызова функции (или конструктора обертки) open_file, которая может бросить исключение EFileOpen(filename,errno), и 4 вызова функции open_socket, которая может бросить исключение ESocketOpen(sourceIP,sourcePort,targetIP,targetPort,errno).
И вылетевшее исключение переупаковываются в, скажем, EEnvSetup(username).
Как ты понимаешь, c исключениями и переупаковкой код будет примерно таким:
try {
auto f1 = open_file(get_profile_dir(username)+"file1");
s1_ = open_socket(f1); // читаем из конфигаauto f2 = open_file(get_profile_dir(username)+"file2");
s2_ = open_socket(f2); // читаем из конфигаauto f3 = open_file(get_profile_dir(username)+"file3");
s3_ = open_socket(f3); // читаем из конфигаauto f4 = open_file(get_profile_dir(username)+"file4");
s4_ = open_socket(f4); // читаем из конфига
}
catch(...) {
std::throw_with_nested( EEnvSetup(username) );
}
_>Т.е. сама теоретическая идея исключений очень хороша, но на практике весьма редко получается использовать её преимущества (более того, при указанном выше раскладе, она скорее во вред уже идёт).
Ну, моя практика от твоей отличается, видимо.
Я вижу сплошные удобства в своем коде, и каждый вызов каждой функции в отдельный try/catch не заворачиваю.
Когда-то этот вопрос уже здесь обсуждался, причём именно для случая C++ (в контексте запрета исключений в гугловских руководствах). Я тогда подробно (с подтверждающими примерами) изложил свою точку зрения. Могу повторить тезисно в этой темке.
Главное преимущество исключений в том, что они позволяют без напряжения для программиста и оптимально по быстродействию (как раз тесты из данной темки это подтверждают) передавать ошибку через множество уровней стека вызова. Так вот, на мой взгляд на практике нормальная обработка ошибок всегда происходит практически на следующем уровне вложенности, а не где-то далеко наверху. Происходит это по уже упоминаемой тут причине — иначе не получится выдавать адекватные сообщения об ошибках. Т.е. даже если мы сделаем идеальный для системы исключений случай с единственной на всю программу точкой реакции на ошибки (грубо говоря один try/catch на всю программу), то нам всё равно придётся по несколько раз переупаковывать их по ходу дела (хотя бы потому, что одинаковые низкоуровневые исключения в разных модулях означают разные вещи). Т.е. в итоге мы всё равно приходим к кучей блоков try/catch раскиданных по всей программе, причём как показывает практика, чаще всего они будут прямо вокруг функции кидающей исключение. А это полностью убирает все преимущества исключений. Более того, при таком раскладе они становятся более многословным и при этом менее явным способом работы.
Здравствуйте, Pzz, Вы писали:
Pzz>Этим исключениям хорошо бы как-то законодательно синтаксически ограничить дальность полета. Потому что когда в ваш высокоуровневый код, работающий в терминах высокоуровневых абстракций, откуда-нибудь с самого нижнего уровня прилетит исключение про то, чего вы в своем высокоуровневом коде сказать-то и не можете, то что вы с ним будете делать? Выдадите пользователю ошибку с шешнадцетиричным номером и невнятной диагностикой? То-то пользователь будет счастлив.
Ограничить дальность полёта можно только одним способом: ловить на каком-нибудь промежуточном слое и (опционально) заворачивать в нечто более понятное слою верхнему. Не проглатывать же его молча? Раз вылетело, значит неучтённая ошибка. Игнорирование её в конечном счёте приведёт к ещё большему пользовательскому щастью.
Здравствуйте, dimgel, Вы писали:
D>Ограничить дальность полёта можно только одним способом: ловить на каком-нибудь промежуточном слое и (опционально) заворачивать в нечто более понятное слою верхнему. Не проглатывать же его молча? Раз вылетело, значит неучтённая ошибка. Игнорирование её в конечном счёте приведёт к ещё большему пользовательскому щастью.
Можно, разумеется, но народ забывает. Это невозможно не забыть. Вот и вылетают непойманные на своем уровне исключения наверх, где их никто не ждет.
Здравствуйте, alex_public, Вы писали:
_>Прелесть, это когда ничего дополнительного самому писать не надо, но при этом всё ясно видно.
Всё ясно видно: обработка ошибок делегируется на уровень выше. Абсолютно одинаково с кодом на расте, только там делегирование надо производить явно. С исключениями надо привыкнуть к одному лишь правилу, что если нет обработки на данном уровне, то она делегируется на уровень выше.
Это как с деструкторами. Слышал аргумент сишников о том, что деструкторы это плохо, потому что их не видно в коде? С деструкторами нужно просто запомнить одно правило: они вызываются при выходе из области видимости.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, vsb, Вы писали:
_>Когда-то этот вопрос уже здесь обсуждался, причём именно для случая C++ (в контексте запрета исключений в гугловских руководствах). Я тогда подробно (с подтверждающими примерами) изложил свою точку зрения. Могу повторить тезисно в этой темке.
_>Главное преимущество исключений в том, что они позволяют без напряжения для программиста и оптимально по быстродействию (как раз тесты из данной темки это подтверждают) передавать ошибку через множество уровней стека вызова. Так вот, на мой взгляд на практике нормальная обработка ошибок всегда происходит практически на следующем уровне вложенности, а не где-то далеко наверху.
У меня как раз обратное впечатление.
Недавно рефакторил код написанный, в сишном стиле без всяких исключений.
Программа для работы с девайсом. Девайсовый протокол, представляет из себя целый стек протоколов, точнее их несколько, т.к. в стеке одни протоколы могут заменятся на другие. И вот представляешь, в одном из протоколов скажем есть авторизация, и вот в процессе исполнения какой то команды обнаружилось, что пароль то неправильный. Ошибка фатальная, никакие перезапросы, тут не помогут. Нужно наверх пользователю засветить окошко с ошибкой, что дескать пароль то ты неправильно указал, ну или даже вывалить диалог ввода пароля. А от места где это возникло до инициатора команды еще чудовищный стек вызова, три конечных автомата. И как же эту ошибку протащить? Там был лютый ад. На каждом уровне были гиганские кейсы, с обработкой всевозможных ошибок, перекодирование ошибок одного уровня в ошибки другого уровня.
Просто руки опускались разгребать эти авгиевы конюшни. Я выкинул 80% кода, не дописав почти ничего, только генерацию исключений вместо возврата ошибок.
Конечно программу нужно проектировать с учетом использования исключений. Нужно обеспечивать грамотную финализацию, что бы объекты оставались в валидном состоянии и не текла память. Но всё это в большинстве случаем решается шаблонно. Нужно к исключением относиться без фанатизма. Не нужно по любой ошибке бросаться исключениями.
Вообще, на мой взгляд, общеиспользуемые библиотеки не должны наружу кидать исключение, за исключением случаев, которые возникают в результате их некорректного использования. Т.е. по сути эти исключения нужны только для отладки.
Меня тоже например раздражает, когда функция открытия файла кидает исключение. Ошибку открытия файла почти всегда удобнее обработать без исключений.
Даже самую простую задачу можно сделать невыполнимой, если провести достаточное количество совещаний
Здравствуйте, AlexRK, Вы писали:
ARK>Таким образом, исключения предлагают избавление от лапши проверок, давая взамен потенциально некорректное состояние программы. ARK>Коды возврата гарантируют видимость всех путей исполнения и отсутствие неожиданных нарушений инвариантов, но генерируют много бойлерплейта. ARK>Что лучше — хрен знает.
Здравствуйте, vsb, Вы писали:
vsb>Насколько медленней? Насколько это может сказаться в работе программы? Я вот не могу себе представить сценария, где программа будет пытаться миллион раз открыть несуществующий файл и её производительность будет страдать от медленных исключений.
Тут как всегда — кто о чем, а сиплюсплюсник о скорости. Корректность ПО, производительность разработки? Не, не слышал.
Насчет замедления я где-то читал постмортем о разработке игры для PS3, там чувак приводил цифры насчет исключений — по его словам, замедление может быть до 30% (поэтому они от исключений отказались).
Но это, понятное дело, на слабоватом железе.
Здравствуйте, WolfHound, Вы писали:
WH>Покажи класс. Я скажу, где у тебя ошибка.
Ок. Код, обрабатывающий ошибку возвратом из функции:
Такой кодик
#include <iostream>
int func(int a){
int b=0;
if(a) b=-1;
return b;
};
int main(){
int a=10;
if( func(a)!=0) std::cout<<"BAD \n";
else std::cout<<" Ok \n";
};
Между двумя листингами есть только одно существенное отличие- вызов функции __cxa_throw(), в коде с исключениями,
который происходит после подготовки работы функцией __cxa_allocate_exception. Эти функции
принадлежат libstdc++. __cxa_throw -это просто кроличья нора, куда проваливаемся по самое не балуйся.
Дальше приведу ссылки на исходники GCC-С++, где расписаны основные функции, в которые, так или иначе, попадает управление после вызова __cxa_throw.
Кстати, из последней мы не возвращаемся, а, загрузкой контекста стекового фрейма, оказываемся на том месте, которые сответствуют
определённым catch. В какой псоледовательности происходит вся работа можно отследить отладчиком, стартуя c break point __cxa_throw.
Ссыли
Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.
где он явно говорит о использовании исключений вместо проверок корректности исполнения
функций значениями возврата из функций, что подразумевает таки использование исключений
в каждой функции, требующей проверки.
Во вторых, процетрирую свой стартовый посыл, в котором как раз говорил про Ваше "не из каждой функции"
Оправдывает себя только для случаев, когда исключением можно обработать глобальные
сбой или ошибку, с или остановкой приложения, или его, в некотором смысле, полным перезапуском,
с какими другими входными данными и иным способом.
WH>Ты не первый и даже не десятый кто пытается доказать что-то не корректными тестами.
А тестами можете размахивать на праздных академических симпозиумах и конференциях.
При разработки кода для дикого и ответственного продакшена, ориентироваться надо
на тонкое знание реализации тех или иных используемых систем, по которому определять характер
использования этих систем.
Здравствуйте, WolfHound, Вы писали:
WH>Здравствуйте, smeeld, Вы писали:
S>>А тестами можете размахивать на праздных академических симпозиумах и конференциях. S>>При разработки кода для дикого и ответственного продакшена, ориентироваться надо S>>на тонкое знание реализации тех или иных используемых систем, по которому определять характер S>>использования этих систем. WH>Главное при этом не забыть, что в 99.999% случаев код возврата содержит значение "всё хорошо". WH>А значит на практике всё то чем ты тут размахиваешь просто тает на фоне постоянных if (errorcode != ok)... WH>Что бы получить реалистичный тест сделай пять функций, которые друг друга вызывают. Запрети компилятору их инлайнить. WH>Вызови их 100000 раз. А на 100001 скажи, что всё плохо. WH>И сравни время выполнения.
int func1_1(int a)
{
if (a > 1000000)
return -1;
else
return a + 1;
}
int func1_2(int a)
{
if (a > 1000001)
return -1;
else
return func1_1(a);
}
int func1_3(int a)
{
if (a > 1000002)
return -1;
else
return func1_2(a);
}
int func1_4(int a)
{
if (a > 1000003)
return -1;
else
return func1_3(a);
}
int func1_5(int a)
{
if (a > 1000004)
return -1;
else
return func1_4(a);
}
int func2_1(int a)
{
if (a > 1000000)
throw new Exception("qq");
return a + 1;
}
int func2_2(int a)
{
return func2_1(a);
}
int func2_3(int a)
{
return func2_2(a);
}
int func2_4(int a)
{
return func2_3(a);
}
int func2_5(int a)
{
return func2_4(a);
}
private string Run(string title, int max)
{
var start1 = DateTime.Now;
int res1 = 0;
for (int i = 0; i < max; i++)
{
res1 += func1_5(i);
}
var end1 = DateTime.Now;
var start2 = DateTime.Now;
int res2 = 0;
for (int i = 0; i < max; i++)
{
int val;
try
{
val = func2_5(i);
}
catch
{
val = -1;
}
res2 += val;
}
var end2 = DateTime.Now;
return String.Format("{0}: {1} ({2}), {3} ({4})", title, end1 - start1, res1, end2 - start2, res2);
}
private void button1_Click(object sender, EventArgs e)
{
Run("test run", 1000010);
var line1 = Run("Single exception", 1000002);
var line2 = Run("Two exceptions", 1000003);
System.IO.File.WriteAllLines("D:\\temp.txt", new[] { line1, line2 });
}
Компиляция — "Release".
Результат:
Single exception: 00:00:00.0311996 (1785293664), 00:00:00.0467994 (1785293664)
Two exceptions: 00:00:00.0155998 (1785293663), 00:00:00.0623992 (1785293663)
Вот это коды возврата слили так слили — аж в 6 раз быстрее в варианте с двумя исключениями.
И даже с одним исключением на миллион вызовов все равно быстрее.
Здравствуйте, Cyberax, Вы писали:
C>DWARF-исключения заметно раздувают код для обработки ошибочных путей, а таблицы исключений заметно давят на кэш процессора. Так что использовать исключения для нормального flow-control'а — очень неэффективно.
А для нормального их никто и не использует.
Они нужны для того чтобы обработать ситуацию когда что-то пошло не так.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, uncommon, Вы писали:
U>Коды ошибок всегда проверять нужно, произошла ошибка или нет. А исключения задействуются, только когда произошла ошибка. Когда никаких ошибок не происходит, в коде с использованием исключений нет никаких проверок вообще. Исключения оптимизируют тот случай, когда ошибок не происходит. U>В случае-же ошибки и выбрасывания исключения, нам плевать на производительность, потому что это очень редкая (исключительная) ситуация.
Гладко было на бумаге, да забыли про овраги. В реальности при наличии исключений быдлокодят будь здоров. Ну может разве что в софте Curiosity все хорошо. Ускорение с исключениями — чистая теория. На практике в том же дотнете исключений в API хоть одним местом жуй.
Здравствуйте, LaptevVV, Вы писали:
LVV>Одна из серьезных проблем, которую практически не поминают — это парадигма обработки исключений. LVV>И вот получается, что в одной проге смешаны как минимум две, а то и три парадигмы: ООП+событийное (и очень часто процедурное).
О! Ребята начали чего-то подозревать! Я, помнится, спрашивал тут когда-то: "а как вы собираетесь реагировать на исключения, где перехватывать (если собираетесь перехватывать)". Исключальщики меня только об[какали], типа, у нас все тип-топ, транзакционная семантика, и вообще ты не в теме, учи матчасть.
LVV>И если нормальную работу худо-бедно проектируют, то аварии не проектируют совсем.
Да ладно не проектируют, их и не тестируют совсем — вот беда! За исключением (пардон за каламбур) тривиальных случаев: неправильный аргумент -> функция кидает исключение. И я даже с трудом себе представляю, а как можно написать юнит-тесты для кода, обрабатывающего аварии.
LVV>Надо отметить, что в новом стандарте управляемость исключений СУЩЕСТВЕННО улучшилась. LVV>Текущее исключение можно явным образом сохранить, передать в нужную функцию, заново кинуть в нужном месте.
Но вот спецификации исключений, вместо того, чтобы довести до ума (== до уровня Java) предпочли выпилить, чтобы не париться Уж тут либо полноценные исключения со спецификациями и (может быть) с ограничением распространения по стеку вызовов, по области видимости (например, исключение является внутренним для данного класса), либо ну их нафиг.
Нет, когда Страуструп в D&E написал, что исключения в C++ — это первый опыт комитетской разработки, я не удивился ничуть. "Верблюд — это скаковая лошадь, спроектированная комитетом по стандартизации"(c)
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, jazzer, Вы писали:
J>>Ну а теперь изобрази свой замечательный вариант на кодах возврата. С донесением всей необходимой информации на верхний уровень: если не открылся файл, то какой и почему, если сокет, то какой и почему. J>>Жду-пожду.
ARK>
ARK> Log.Write(GetLastErrorInfo());
ARK>
Ну то есть вместо многообразия кодов возврата есть просто булевская логика, а информация об ошибке не передается наверх ни в каком виде. Желающие могут пройти в лог. Про автоматический вызов деструкторов для того, что успело сработать, я уж и не заикаюсь. Супер, чо. Полный эквивалент.
ARK>Изображать GOTO через все слои не вижу смысла, потому что и при наличии исключений на практике (а не в теории) бойлерплейт тоже присутствует в нехилом объеме.
А в моей практике никаких нехилых объемов не присутствует. ЧЯДНТ?
Здравствуйте, tyomchick, Вы писали:
T>Меня тоже например раздражает, когда функция открытия файла кидает исключение. Ошибку открытия файла почти всегда удобнее обработать без исключений.
Если есть восстановление после сбоя, то ситуация никакая не исключительная, потому исключения не нужны.
Надо отделять исключения, сбои, восстановления.
Сбой — это локальная проблема, ошибка. Исключение — это отсутствие возможность продолжать после сбоя. Восстановление — это наличие возможности продолжать после сбоя. Исключение, как правило, означает, что информации обработать ошибку в локальном скопе просто нет. Восстановление, наоборот, означает что есть вся информация обработать ошибку.
Отсюда ясно, что один и тот же сбой в зависимости от приложений может потребовать или исключения или восстановления.
alex_public говорит, что восстановление лучше, кошернее, но никак не хочет объяснить наиболее типичный сценарий — N последовательных шагов, сбой в любом прерывает весь сценарий.
Например — сохранить данные пользователя в конкретный файл. Открыть, получить данные, серилизовать, записать, закрыть. В любом месте если возникает ошибка — весь сценарий фейлится.
Другой сценарий — сохранить критические данные приложения во что бы то ни стало, даже вне контроля пользователя. Все почти тож самое, но есть восстановление — не открылся один файл, откроется другой, нет доступных носителей для файлов — есть БД, сеть, почта и тд и тд тд.
Здесь исключение возникает если все способы восстановления ошибок исчерпаны.
При этом, фокус, линейная логика так же присутствует — фейл при получении данных приводит к исключению. фейл при серилизации так же приводит к исключению. Не нашли куда можно сохранить — так же исключение.
Итого — линейная логика есть всегда. Восстановление есть иногда. Наличие резервных путей никак не отменяет наличия линейной логики.
Теоретически, можно сделать навроде
var x = null;
try{
x = sourceFromFile('a');
fallback x = sourceFromFile('b');
fallback x = sourceFromFile('z');
} catch() {
...
}
Но вообще уже здесь всякие монады смотрятся гораздо интереснее
Здравствуйте, Ikemefula, Вы писали:
I>Сценарий I>"по требованию сохранить данные пользователя в конкретный файл. Открыть, получить данные, серилизовать, записать, закрыть." I>Полагаю, ты можешь продемонстрировать заявленое чудо, а именно "запись в файл который невозможно открыть".
Я говорю тебе, что всё зависит от точки обработки ошибок, а не от наличия/отсутствия цепочек. Если у нас точка обработки идёт на следующем уровне, то разницы между исключениями и кодами возврата не будет. Ну или даже исключения похуже выглядят:
void Save()
{
...
if(...) throw MyException("не могу открыть файл");
...
if(...) throw MyException("не могу сериализовать данные");
...
if(...) throw MyException("не могу записать данные в файл");
...
}
...
try{
Save();
}catch(MyException& e){
ShowError("Не могу сохранить данные.");
}
int Save()
{
...
if(...) return ERROR_OPEN;
...
if(...) return ERROR_SERIAL;
...
if(...) return ERROR_WRITE;
...
return OK;
}
...
if(Save()!=OK) ShowError("Не могу сохранить данные.");
Преимущества исключений появляются, только если обработка ошибки идёт где-то намного выше (по стеку вызова, не по линейному коду) вызова Save.
I>То есть, ты предлагаешь переписывать весь слой, если мы начнем вызывать его немного другим способом. Правильно тебя понимаю ?
При проектирование приложения такие вещи (что критично, а что нет) сразу понятны, так что по нормальному никаких переписываний не должно быть. А вот в случае библиотек оптимальным было бы наличие обоих видов интерфейсов...
Здравствуйте, AlexRK, Вы писали:
ARK>А как же RAII?
А что такое RAII? Это сказал кто-то, modern programer из каких-то гуглов или фейсбуков, а может вообще академик с бадуна,
что "Resource Acquisition Is Initialization"-это модно и есть признак развития. И стадо обезьян загудело. Все так и кинулись внедрять
себе в код это развитие. Только то, что для того, чтоб это без проблем работало, нужно или переписывать всю ОС с самого загрузчика по новому,
включая все системные либы, или нужно формировать мощную прослойку, на уровне интерпретатора (достаточно хорошо реализовано во всех существующих),
или стандартной библиотеки языка компилируемого (Да в C++ RAII названо, но не релизовано как надо, полноценная реализация требует добавления
специализированных средств в stdc++) между ОС, и приложением использующим RAII, чтоб она и взяла на себя все нестыковки RAII с существующими
низкоуровневыми системами.
Здравствуйте, alex_public, Вы писали:
_>Я говорю тебе, что всё зависит от точки обработки ошибок, а не от наличия/отсутствия цепочек. Если у нас точка обработки идёт на следующем уровне, то разницы между исключениями и кодами возврата не будет. Ну или даже исключения похуже выглядят:
ой ли?
_>
_>void Save()
_>{
_> ...
_> if(...) throw MyException("не могу открыть файл");
_> ...
_> if(...) throw MyException("не могу сериализовать данные");
_> ...
_> if(...) throw MyException("не могу записать данные в файл");
_> ...
_>}
_>...
_>try{
_> Save();
_>}catch(MyException& e){
_> ShowError("Не могу сохранить данные , случилось страшное: " + e.what());
_>}
_>
_>
_>
_>const int OK = 0, ERROR_OPEN = 1, ERROR_SERIAL = 2, ... ; // да, надо вести список кодов ошибок. Кстати это глобальные ошибки или ошибки функции Save() ?
_>string getSaveErrorString(int);// и поддерживать в согласованном состоянии соотв функцию
_>int Save()
_>{
_> ...
_> if(...) return ERROR_OPEN;
_> ...
_> if(...) return ERROR_SERIAL;
_> ...
_> if(...) return ERROR_WRITE;
_> doSomething();// ой, забыли проверить код
_> switch(doSomethingElse()) { // ой, надо перекодировать
_> case ELSEERR_OK: break;
_> case ELSEERR_SOME_WRONG: return ERROR_SERIALIZE;
_> default: return ERROR_UNKNOWN;
_> }
_> ...
_> return OK;
_>}
_>...
_>auto err = Save()
_>if(err!=OK) ShowError("Не могу сохранить данные. Случилось страшное: " + getSaveErrorString(err)); // А как получить вложенный код ошибки? - упс, int мало, надо городить что-то свое...
_>
Здравствуйте, Vain, Вы писали:
V>Здравствуйте, uncommon, Вы писали:
V>>>непонятно только, как исключения в этом случае будут быстрее кодов ошибок? U>>Коды ошибок всегда проверять нужно, произошла ошибка или нет. А исключения задействуются, только когда произошла ошибка. V>Ну это не совсем так, при входе в try блок ведь чтото должно делаться, не так ли? Единственный вопрос, быстрее ли оно обычного условия в случае проверки кода возврата.
Нет, ничего не надо делать, это обычный scope с толпой деструкторов в конце. И исключение — это по сути табличное goto куда-то в середину этой пачки деструкторов, в зависимости от того, в какой строчке вылетело исключение (чтоб убить только то, что успело создаться к этому моменту). И место goto вычисляется только при размотке стека в случае исключения.
Здравствуйте, vsb, Вы писали:
vsb>В некоторых новых языках программирования (Go, Swift) уходят от механизма исключений и подавляющее число ошибок обрабатывается через явный возврат ошибки из вызываемой функции. В качестве аргумента часто показывают пальцем на язык С++. Наличие там исключений заставляет писать весь код так, как будто из любого вызова функции может вылететь исключение и размотать стек.
Я вчера разговаривал с Rust-овцами на Bay Area Rust Meetup
Что рассказали:
1) Исключения можно сделать хоть сейчас, это технически не проблема вообще. Тем более, что уже есть механизм unwinding'а.
2) Но их делать не будут из принципа.
3) Возможно, что в будущем принципы поменяют, если появится что-нибудь типа полноценной транзакционной памяти.
Аргумент такой, что исключения в надёжных системах возможно нормально использовать только при полной транзакционности кода ("strong exception guarantee"). Оказывается, что статически проверить обеспечение такой гарантии очень сложно (они пытались) и даже в академических языках её нет.
Потому они приняли решение сделать более мягкое решение, аналогичное "weak exception guarantee". В Rust при броске fail'а они убивают всю задачу, в которой код исполняется. Это обеспечивает вполне естественную границу для изоляции ошибки. Похожий подход и в Erlang'е применён, если что.
Ещё в качестве средства изоляции — при броске fail'а все разделяемые ресурсы (в Rust они есть в виде RWArc), которые в тот момент использовала задача, помечаются как "испорченные".
На вопрос про промежуточные слои они ответили, что у них тут вам не Java, и на практике промежуточных слоёв очень мало и почти всегда нужно делать семантическую трансляцию ошибок.
Здравствуйте, vsb, Вы писали:
vsb>На мой взгляд это проблема исключительно С++, а точнее его двух особенностей — отсутствие GC и запрет исключений в деструкторах.
Здравствуйте, WolfHound, Вы писали:
WH>Это не правда. Правильно реализованные исключения работают быстрее кодов возврата. WH>Остальное тоже к реальности не относится.
"Правильно реализованные", это как? Смотрел как они реализованы в ELF+gcc/clang/solarisstudio.
Простой if(err) return -1; сработает на порядок быстрее, чем throw x; }catch(){...}
Здравствуйте, smeeld, Вы писали:
S>Всякие статейки в инетах читаем? Похвально, но посоветовл бы заглянуть в реальные S>исходники, и узреть как оно на самом деле там наворочено.
Я реально измерял производительность.
Коды возврата слили.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, smeeld, Вы писали:
S>Ок. Код, обрабатывающий ошибку возвратом из функции:
Зевая. Перечитай вторую половину сообщения 10 раз. Re[4]: Какие у исключений проблемы?
Здравствуйте, WolfHound, Вы писали:
WH>Здравствуйте, smeeld, Вы писали:
S>>А тестами можете размахивать на праздных академических симпозиумах и конференциях. S>>При разработки кода для дикого и ответственного продакшена, ориентироваться надо S>>на тонкое знание реализации тех или иных используемых систем, по которому определять характер S>>использования этих систем. WH>Главное при этом не забыть, что в 99.999% случаев код возврата содержит значение "всё хорошо". WH>А значит на практике всё то чем ты тут размахиваешь просто тает на фоне постоянных if (errorcode != ok)... WH>Что бы получить реалистичный тест сделай пять функций, которые друг друга вызывают. Запрети компилятору их инлайнить. WH>Вызови их 100000 раз. А на 100001 скажи, что всё плохо. WH>И сравни время выполнения.
Это бред, потому что в любом случае в машинном коде ифы будут — исключения это будут, коды возврата или что угодно еще. Проверки либо есть, либо их нет (тогда просто будет UB).
Здравствуйте, AlexRK, Вы писали:
ARK>Это бред, потому что в любом случае в машинном коде ифы будут — исключения это будут, коды возврата или что угодно еще. Проверки либо есть, либо их нет (тогда просто будет UB).
откуда ифы при работе с исключениями? Вся машинерия исключений запускается только при вызове throw.
Здравствуйте, AlexRK, Вы писали:
ARK>Исправил, прогнал — см. мой исправленный пост. ARK>Ничего не изменилось, исключения медленнее.
Что смотреть?
ARK>Совсем без ошибки — да, медленнее. Но в реальном приложении на это рассчитывать не стоит, ИМХО.
Это 99.999% случаев.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, Cyberax, Вы писали:
WH>>А для нормального их никто и не использует. C>А хочется.
Не-а.
WH>>Они нужны для того чтобы обработать ситуацию когда что-то пошло не так. C>Классика жанра — должен ли File.open() кидать исключение, если файл не найден?
Голосовалку замути. Очередное ---или--- вслед за моей вчерашней. Я за исключение проголосую, потому что имена файлов, которые открывать собираюсь, не с бухты-барахты выдумываю и имею право ожидать, что они должны открыться успешно. Десктопники тоже — либо свои собственные файлы открывают, либо выбранное юзером в диалоге OpenFile — тоже обычно существующие. А вот когда приходилось на PHP низкоуровнево с файлами работать, то вся эта C-style муть с трёхстрочными проверками на ошибку на каждую строку собственно логики бесила.
Здравствуйте, WolfHound, Вы писали:
WH>Исправил. Коды возврата всё равно сливают.
В случае с одним исключением результат практически одинаковый. Без исключений вообще — да, коды возврата слегка медленнее.
С двумя исключениями у exception-based code был бы слив, просто в тесте после вылета первого исключения замер прекращается.
ARK>>Кстати, чем это от моего последнего "некорректного" теста отличается? WH>А в цикле код возврата проверять, кто будет? Пушкин?
Это не имеет отношения к корректности теста. Можно было и не писать с многозначительным видом напыщенные фразы про некорректность.
Просто вместо 5 миллионов ифов стало 6. Можно и 7 сделать, если добавить еще один уровень вложенности.
Ну и самое главное осталось без комментариев.
А именно:
1) реальное приложение выполняет реальную работу, и проверки на ее фоне будут не видны в микроскоп;
2) не предоставлено обоснования, что exception-based code быстрее, чем retval-based code. Тесты показывают только одно — для дотнета одно исключение по времени соответствует 5 миллионам проверок.
Здравствуйте, LaptevVV, Вы писали:
LVV>Одна из серьезных проблем, которую практически не поминают — это парадигма обработки исключений. LVV>По сути это событийное программирование. LVV>И вот получается, что в одной проге смешаны как минимум две, а то и три парадигмы: ООП+событийное (и очень часто процедурное).
Да хоть 10 парадигм в одной проге — ничего страшного. Главное парадигмы применять правильно и тогда, когда это имеет смысл. А проблемы с исключениями возникают тогда, когда с их помощью начинают хреначить логику, и они становятся не исключениями, а нормальным потоком выполнения программы.
А типичная обработка исключений — это вывести пользователю сообщение об ошибке или матернуться в лог о том, что случилась какая то хрень. 99 процентов всех обработок исключений. В ряде случаев приходится некоторую логику навешивать. Например для вебсервисов внезапно могут куки авторизации стать невалидными, например по причине перезапуска сервиса. В этом случае нужно перелогиниться и повторить попытку. Соответствуем декорируем вызов метода, и обработчик исключения сделает перелогин и вызовет метод еще раз.
Здравствуйте, enji, Вы писали:
E>Здравствуйте, dimgel, Вы писали:
D>>есть одиночные разыменования obj_?.get после сложных проверок прямо строчкой выше, где я в т.ч. убеждаюсь, что obj_? не пуст; а чаще встречается obj_?.getOrElse(throw new Exception("понятное сообщение об ошибке")).
E>А в скале появился "?." ? Когда я пару лет назад на нее смотрел, в ней такого не было...
Здравствуйте, Vain, Вы писали:
V>непонятно только, как исключения в этом случае будут быстрее кодов ошибок?
Коды ошибок всегда проверять нужно, произошла ошибка или нет. А исключения задействуются, только когда произошла ошибка. Когда никаких ошибок не происходит, в коде с использованием исключений нет никаких проверок вообще. Исключения оптимизируют тот случай, когда ошибок не происходит.
В случае-же ошибки и выбрасывания исключения, нам плевать на производительность, потому что это очень редкая (исключительная) ситуация.
Здравствуйте, Ikemefula, Вы писали:
I>Это не самый распространенный случай. Наиболее общий случай это линейный код, без каких либо ветвлений. Он есть вообще везде:
Ты не понял какую идею демонстрирует данный пример. И кстати говоря это как раз очень распространённый случай.
Смотри, допустим у нас есть низкоуровневая функция, вызываемая где-то в самой глубине стека вызова и возможно завершающаяся с ошибкой. Теперь рассмотрим 2 случая:
1. В случае ошибки в данной функции приложение должно корректно завершить свою работу. Здесь исключения подходят просто идеально. Имеем один блок try/catch где-то на самому верху стека вызовов приложения, выводящий соответствующее сообщение об ошибке и завершающее приложение. Ну разве что если исключение слишком низкоуровневое и в разных модулях может иметь разный смысл для пользователя — тогда надо его ещё переупаковать где-то в промежуточном коде. Но обычно это не проблема.
2. В случае ошибки в данной функции приложение продолжает свою работу по альтернативному сценарию, прямо в коде после вызова нашей функции. В этом случае исключения очень плохо подходят для решения этой задачи, т.к. тогда приходятся оборачивать в try/catch каждый вызов этой самой низкоуровневой функции. Код становится намного более многословным (в сравнение с вариантом без исключений) и замусоренным.
Кто-то скажет, что типа да, всё правильно, во втором случае исключения и не надо применять, т.к. это не исключительная ситуация. Но не всё так просто. Во-первых наша функция может иметь различный смысл в разных приложениях и если она при этом реализована в библиотеке не с двойным интерфейсом (типа Boost.Asio), то получаем гарантированное неудобство в каком-то из случаев. А во-вторых некоторые "адепты исключений" (вроде как и у нас на форуме такие имеются) готовы использовать исключения не только для обработки критических ошибок, но и вообще для всех случаев обработки ошибок...
Да, и кстати, ещё отдельный вопрос. Попробуй глянуть в своём последнем приложение, сколько там имелось случаев обработки критических ошибок и сколько случаев обработки обычных. Вот лично у меня критические вообще очень редко попадаются (только что-то из области нехватки оперативной памяти или переполнения диска), а обычные естественно не редкость... )
Здравствуйте, uncommon, Вы писали:
U>Ну, и будут каждую функцию заворачивать в try! или с монадами извращаться. Зато принцип!
Ну вообще-то, на Rust уже достаточно много кода написано. try! не так уж и много получается. И принципиальность — это как раз круто.
Я уж не говорю про то, что исключения не используются и в огромном количестве проектов на С++. При полном отсутствии сахара типа try!.
Здравствуйте, Cyberax, Вы писали:
C>Здравствуйте, uncommon, Вы писали:
U>>Ну, и будут каждую функцию заворачивать в try! или с монадами извращаться. Зато принцип! C>Ну вообще-то, на Rust уже достаточно много кода написано. try! не так уж и много получается. И принципиальность — это как раз круто.
Чем круто? Опыт показывает, что в дизайне языков программирования прагматизм имеет больший успех чем принципиальность. Но насчёт раста, время покажет. Пока что они перелопачивают свой код на новый лад каждые несколько месяцев.
C>Я уж не говорю про то, что исключения не используются и в огромном количестве проектов на С++. При полном отсутствии сахара типа try!.
И о чём это говорит, кроме того, что есть много людей, которые не знают как использовать исключения.
На самом деле опыт С++ в отношении исключений не так интересен, потому что он как раз пестрит примерами того, как люди продолжают писать код в стиле С (с кодами возврата, плохим уровнем абстракции, макросами где попало, и другими прелестями С). Более интересен будет опыт таких языков как Python, где исключения с самого начала в языке и рантайме, а кодов возврата нет как таковых. Так вот в Питоне ни у кого почему-то не возникает с исключениями никаких проблем. Ещё Брюс Эккель в своё время писал, как он попробовал Питон, и все проблемы с исключениями, которые до этого у него были в других языках, исчезли. А на С++ (как показывает практика особенно в последнее время на С++11/14) можно писать код практически как на Питоне.
Здравствуйте, alex_public, Вы писали:
_>Ничего ясно не видно. Если функцию, бросающую исключения, писали не мы, то надо лезть в документацию (если она есть) или вообще в исходники, чтобы понять кидаются ли какие-то исключения или нет. В случае же кодов возврата (к примеру) всё сразу видно по заголовочному файлу.
Давай проверим
BOOL X();
BOOL Y();
Какой из двух методов возвращает ошибку а какой просто значение ?
Здравствуйте, alex_public, Вы писали:
E>>ой ли?
_>Ну во-первых если делать всё всё по правильному, то не забудь ещё реализацию своего класса исключений для данной функции... )
_>А во-вторых я на практике обычно не использую даже коды возврата — только булево значение, т.к. обычно пользователю вообще не нужно знать низкоуровневые детали. А в данном примере я написал так, чтобы никто не придрался (мол с исключениями передаётся больше информации).
Постоянно нужно получать детали — не просто невозможно открыть файл, а еще и сообщать причину — файл используется, отсутствует, не хватает прав и тд и тд. Опаньки — нужны разные коды.
Очень часто нужно перекодировать ошибки, потому что коды в каждой библиотеке свои.
Очень часто нужно сохранять вложеные ошибки.
Итого — ты просто не утруждаешь себя обработкой ошибок.
Здравствуйте, jazzer, Вы писали:
J>Я тоже так пишу. Саттер/Абрахамс в свое время прочистили мозги на этот счет. J>Надо только помнить, что есть 3 уровня безопасности по исключениям.
Вот именно, что нужно помнить, понимать... То, что при использовании кодов возврата или паттерна "null object" вылезает явно, здесь припрятано — как я люблю повторять, тот же бег, по тем же граблям, только их еще заботливо присыпали сенцом, чтобы не смущать бегущих.
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Здравствуйте, smeeld, Вы писали:
S>Здравствуйте, AlexRK, Вы писали:
ARK>>А как же RAII?
S>А что такое RAII? Это сказал кто-то, modern programer из каких-то гуглов или фейсбуков, а может вообще академик с бадуна,
Т.е., по твоим сведениям, это не профессор Страуструп придумал 30 лет назад?
S>что "Resource Acquisition Is Initialization"-это модно и есть признак развития. И стадо обезьян загудело. Все так и кинулись внедрять S>себе в код это развитие. Только то, что для того, чтоб это без проблем работало,
Без каких именно проблем?
S>нужно или переписывать всю ОС с самого загрузчика по новому,
Зачем же переписывать? Вот было в ОС malloc/free — сверху сделали надстройку — std::vector. Над fopen/fclose — std::fstream. Что переписывать-то?
S>включая все системные либы, или нужно формировать мощную прослойку, на уровне интерпретатора (достаточно хорошо реализовано во всех существующих), S>или стандартной библиотеки языка компилируемого (Да в C++ RAII названо, но не релизовано как надо,
Как надо?
S>полноценная реализация требует добавления специализированных средств в stdc++)
Каких средств?
S>между ОС, и приложением использующим RAII, чтоб она и взяла на себя все нестыковки RAII с существующими S>низкоуровневыми системами.
Что за нестыковки?
Если не поможет, будем действовать током... 600 Вольт (C)
1. Скорость обработки. Впрочем, от среды исполнения зависит. В нативных средах исключения реализуются через переход в кольцо 0 и обратным возвратом. Обработка исключений далеко не так проста, как кое-кому тут кажется.
Позволю себе процитировать достаточно большой кусок из Соломона-Руссиновича. Выделения мои — PD
/////////////////////////////////
All exceptions, except those simple enough to be resolved by the trap handler, are serviced by a kernel module called the exception dispatcher. The exception dispatcher's job is to find an exception handler that can "dispose of" the exception. Examples of architecture-independent exceptions that the kernel defines include memory access violations, integer divide-by-zero, integer overflow, floating-point exceptions, and debugger breakpoints. For a complete list of architecture-independent exceptions, consult the Win32 API reference documentation.
...
A few exceptions are allowed to filter back, untouched, to user mode. For example, a memory access violation or an arithmetic overflow generates an exception that the operating system doesn't handle. An environment subsystem can establish frame-based exception handlers to deal with these exceptions. The term frame-based refers to an exception handler's association with a particular procedure activation. When a procedure is invoked, a stack frame representing that activation of the procedure is pushed onto the stack. A stack frame can have one or more exception handlers associated with it, each of which protects a particular block of code in the source program. When an exception occurs, the kernel searches for an exception handler associated with the current stack frame. If none exists, the kernel searches for an exception handler associated with the previous stack frame, and so on, until it finds a frame-based exception handler. If no exception handler is found, the kernel calls its own default exception handlers.
When an exception occurs, whether it is explicitly raised by software or implicitly raised by hardware, a chain of events begins in the kernel. The CPU hardware transfers control to the kernel trap handler, which creates a trap frame (as it does when an interrupt occurs). The trap frame allows the system to resume where it left off if the exception is resolved. The trap handler also creates an exception record that contains the reason for the exception and other pertinent information.
...
If the exception occurred in user mode, the exception dispatcher does something more elaborate. As you'll see in Chapter 6, the Win32 subsystem has a debugger port and an exception port to receive notification of user-mode exceptions in Win32 processes. The kernel uses these in its default exception handling, as illustrated in Figure 3-6.
Debugger breakpoints are common sources of exceptions. Therefore, the first action the exception dispatcher takes is to see whether the process that incurred the exception has an associated debugger process. If so, it sends the first-chance debug message (via an LPC port) to the debugger port associated with the process that incurred the exception. (The message is sent to the session manager process, which then dispatches it to the appropriate debugger process.)
If the process has no debugger process attached, or if the debugger doesn't handle the exception, the exception dispatcher switches into user mode and calls a routine to find a frame-based exception handler. If none is found, or if none handles the exception, the exception dispatcher switches back into kernel mode and calls the debugger again to allow the user to do more debugging. (This is called the second-chance notification.)
All Win32 threads have an exception handler declared at the top of the stack that processes unhandled exceptions. This exception handler is declared in the internal Win32 start-of-process or start-of-thread function. The start-of-process function runs when the first thread in a process begins execution. It calls the main entry point in the image. The start-of-thread function runs when a user creates additional threads. It calls the user-supplied thread start routine specified in the CreateThread call.
///////////////////////////////
2. Невозможность не обрабатывать исключения. Коды возврата можно учитывать или игнорировать, то есть их можно классифицировать по обычной схеме как ошибки, предупреждения или информационные сообщения; второе и третье при желании можно игнорировать.
Здравствуйте, dimgel, Вы писали:
D>Здравствуйте, jazzer, Вы писали:
vsb>>>На мой взгляд это проблема исключительно С++, а точнее его двух особенностей — отсутствие GC и запрет исключений в деструкторах.
J>>Нет такого запрета.
D>На вопрос "вызывать ли деструктор, если конструктор бросил исключение" разные языки, помнится, отвечали по-разному, поэтому я старался до такого не опускаться и под инициализацию, способную бросить исключение, заводил отдельный от конструктора метод.
Ну в С++-то все вполне однозначно — деструктор вызывается только для полностью сконструированного (под)объекта (что имеет смысл, согласись — ведь пока не отработал конструктор, ничего о состоянии объекта в общем случае сказать нельзя — там может быть банально мусор в еще не инициализированных членах).
D>Но по крайней мере освобождение памяти из-под собственно объекта в том же C++ наверняка в любом случае происходит. А вот как программа будет дальше жить, если исключение бросает деструктор, я вообще ХЗ. Memory leak, не?
Не. Память под объектом будет освобождена независимо от успешности работы деструктора.
Так что просто надо аккуратно.
Вот есть, например, такая библиотека SOCI: http://soci.sourceforge.net/
Так в ней вся полезная работа совершается как раз в деструкторе, и исключения вылетают из него же, если что-то идет не так.
Так что исключения из деструктора просто исключают (каламбур) некоторые сценарии деструкции. Но не все (вернее, запрещен ровно один сценарий — если твой объект может разрушиться в результате размотки стека). И за этим надо очень внимательно следить (чтобы между созданием и разрушением твоего объекта не было кода, который может бросить исключение, и ты его при этом не ловишь), так что, естественно, общее правило — избегайте по возможности вылет исключений из деструктора.
Здравствуйте, dimgel, Вы писали:
D>Напомни плиз, как это DSP расшифровывается. А то идею помню (что-то там типа не более двух ссылок в цепочке, иначе заворачивать в методы) и юзаю давно на автомате, а нагуглить не могу.
Здравствуйте, dimgel, Вы писали:
D>Напомни плиз, как это DSP расшифровывается. А то идею помню (что-то там типа не более двух ссылок в цепочке, иначе заворачивать в методы) и юзаю давно на автомате, а нагуглить не могу.
Здравствуйте, vsb, Вы писали:
vsb>Это Checked Exceptions в Java.
Для концептуальной законченности им не хватает нескольких вещей:
1. Полного запрета бросать руками RuntimeException
2. Перевода всех RuntimeException в либо в checked либо в error.
3. Синтаксического сахара позволяющего конвертировать необработанные исключения при выбрасывании их вверх по стеку
Что-то типа:
class UserDao {
exception SQLException translate to DaoException;
User getUser(long id) throws DaoException {
//code can cause SQL exception
}
}
vsb>Во-вторых ряд исключений не вылетит вообще никогда. Например Integer.parseInt("1"). Никогда тут не вылетит исключение NumberFormatException. А компилятор попросит обложить try-catch-ем.
Never say "never". Завтра пользователь поставит локаль где все числа римские, а программист поправит parseInt чтобы он использовал локаль и здравствуй исключение. Большинство таких ситуация вызваны кривым дизайном языка, а не фундаментальными проблемами.
vsb>В-третьих ряд исключений могут вылететь в любом месте. В разных языках по-разному. Например NullPointerException теоретически может вылететь на любой строчке, где есть ссылки или вызывается функций. StackOverflowError может вылететь опять же везде, где вызывается любая функция. Что с ними делать?
Запретить null как класс. Вместо него использовать либо Option, либо вообще ничего не использовать. Как в Scala:
[code=scala]
val userOption:Option[User] = userDao.getUser(userId);
for (user <- userOption) {
doSmth(user);
}
[/code]
vsb>теперь примеряем его на FileInputStream, а оказыавется, что close там хочет кидать IOException. Чего нам делать? Либо добавляем в throws IOException, либо перекидыаем как непроверяемое исключение. И имеем опять что имеем.
Но разработчики спрева не подумали, а потом у них не хватило яиц поломать обратную несовместимость.
vsb>Если добваляем throws IOException, то куча классов этот IOException никогда кидать не будет. StringWriter, например, пишет во внутренний буффер-строку, никакого там IO нет. А вот при закрытии — изволь, обработай IOException, мало ли, вдруг кинется, в сигнатуре написано же.
LSP никто не отменял. Завтра StringWriter заменят на BufferedFileWriter а вызывающий код должен работать как прежде. Единственный способ достичь этого это корректно обработать исключенние даже если сейчас оно не бросается.
Здравствуйте, uncommon, Вы писали:
U>Чтобы освободить буфер, его не надо флашнуть в деструкторе. Флашать уже поздно.
Это, как мне кажется, последствия конкретных решений в дизайне деструкторов и исключений, а не фундаментальное свойство математики. U>Мы здесь говорим о двух принципиально разных операциях: 1) освобождение ресурсов, 2) операции завершения/коммита, которые могут завершиться неудачно. Эти завершающие операции надо делать явно, хочешь ты этого или нет.
С моей точки зрения, мне важна семантика некоего "возврата", т.е. гарантии, что в момент выхода из scope у меня некий объект восстанавливает своё состояние к исходному.
Частный случай — освобождение ресурсов. Т.е. меня интересует пара антонимов типа push/pop, acquire/release, change/revert.
Типичный пример — смена курсора мышки на часики. Я хочу иметь гарантию того, что часики вернутся в стрелочку, независимо от того, каким способом я вышел из scope — return, break, или throw. U>Приемлимого решения делать их в деструкторе нет. Эта проблема есть и в стандартной библиотеке в классе std::thread. Если thread является joinable, но join() не был позван явно перед деструктором, то деструктор вызывает std::terminate().
Вызывает интерес поступательный процесс: можно ли изобрести другую технологию, в которой можно будет описывать то, чего я хочу?
Может быть, два "деструктора" у классов — commit-деструктор и rollback-деструктор?
Или другую семантику раскрутки стека, которая позволяет спасти больше информации об ошибке? Типа "мы читали из одного файла и писали в другой, потом при чтении у нас возникла ошибка формата, а при экстренном выходе мы и предыдущие данные в выходной файл записать не смогли".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, DarkEld3r, Вы писали:
DE>Я всё-таки "спорил" в контексте этой темы. В смысле "исключения против кодов возврата". В таком случае, не вижу разницы.
Да, если в контексте темы, то никакой разницы нет.
ARK>>Но причем тут стратегия работы с ошибками в самом низу? DE>При том, что нет смысла в "идеальном" "сообщении об ошибках" если обработка их будет сложной и неудобной. И как удобно сделать обработку сразу кучи возникших ошибок я плохо представляю.
И с этим согласен. Интересно найти "правильный" способ обработки ошибок, при этом не заставляющий писать очень уж много кода (если такой метод вообще существует).
на ту же тему. Ничего интересного не обнаружил. Доставил пользователь с ником _FRED_, отказывающийся верить, что в дотнете многие методы Dispose кидают исключения.
Здравствуйте, Pzz, Вы писали:
vsb>>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.
Pzz>Этим исключениям хорошо бы как-то законодательно синтаксически ограничить дальность полета. Потому что когда в ваш высокоуровневый код, работающий в терминах высокоуровневых абстракций, откуда-нибудь с самого нижнего уровня прилетит исключение про то, чего вы в своем высокоуровневом коде сказать-то и не можете, то что вы с ним будете делать? Выдадите пользователю ошибку с шешнадцетиричным номером и невнятной диагностикой? То-то пользователь будет счастлив.
Тут два варианта. Либо это проблема в коде приложения, либо это проблема в окружении. Типичный пример проблемы в коде приложения — разыменование нулевого указателя. Типичный пример проблемы в окружении — I/O ошибка при записи на диск (диск сломался).
Если это Web-сервер, мы выдаём 500 ошибку и логгируем всё, что можем. Возможно отправляем SMS администратору, это уже не суть важно. Пользователю показываем красивую страничку "Упс, у нас что-то сломалось".
Если это десктопное приложение, мы сохраняем всё, что можем, в логи, возможно отправляем логи разработчику, пользователю показываем окошко с ошибкой.
А что ещё может сделать нижний уровень? Ну да, он может сказать "Production database unavailable" вместо "Socket disconnected", но кому от этого станет лучше? Пользователю лучше не станет. Администратор/программист из стектрейса и так поймёт, что проблема в БД-коде.
По сути я вижу отличие исключений от кодов возврата только в одном use-case. Если мы хотим проглотить ошибку. Вот такое у нас приложение — работаем до последнего и плевать, что там на улице. Тогда с кодами возврата отработает больше кода. С исключениями — выполнение оборвётся сразу и исключение улетит далеко наверх. Ну или ставить try { ... } catch {} везде. Но это ведь в любом случае плохая практика и даже хорошо, что исключения её усложняют.
Здравствуйте, smeeld, Вы писали:
S>Исключения это очень медленно и размашисто, по сравнению с if(func()!=0) err();
Это не правда. Правильно реализованные исключения работают быстрее кодов возврата.
Остальное тоже к реальности не относится.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, vsb, Вы писали:
vsb>По-моему невалидное состояние после исключения это достаточно редкая штука. Можете привести какие то часто встречающиеся случаи таких состояний в managed языке (Java/C# например)? Я только искусственные примеры могу придумать, в реальности я такого не видел (может не замечал).
Я бы сказал, что это не только не редкая штука, а наоборот — повсеместная.
Любой метод, изменивший некоторое поле класса и после этого выкинувший исключение, уже оставил объект в промежуточном состоянии.
Другой вопрос — всегда ли это приводит к катастрофическим последствиям? Понятное дело, что нет.
А это уже зависит от стечения обстоятельств.
C медленней на порядок это, конечно, перебульонил малость,
но в остальном происходит поиск по нескольким массивам в секциях,
отмаппенного в память процесса, объектника: в .eh_frame ищутся CIE для
нахождения FDE, из FDE попадаем в секцию расширение (.gcc_except_table для gcc)
с массивом LSDA структур, по ним находим пойнтеры к type_info вызовом функции personality,
которая своя для каждого языка. type_info находятся в другой секции, при совпадении, находим
в массивах call site-ов из текущего LSDA пойнтеры для landing pad.
Соответствующими значениями из call site-ов заполняется структура Unwind_Context, далее происходит
вторая фаза, при которой всё повторяется сначала, только не для поиска совпадений,
а очищения фреймов стека вызовом той же personality. После чего управление передаётся на обработку загрузкой
оформленного контекста. В инете полно инфы, описал тут так для того, чтоб масштаб событий, происходящих после throw, был заметен.
Здравствуйте, AlexRK, Вы писали:
vsb>>По-моему невалидное состояние после исключения это достаточно редкая штука. Можете привести какие то часто встречающиеся случаи таких состояний в managed языке (Java/C# например)? Я только искусственные примеры могу придумать, в реальности я такого не видел (может не замечал).
ARK>Я бы сказал, что это не только не редкая штука, а наоборот — повсеместная. ARK>Любой метод, изменивший некоторое поле класса и после этого выкинувший исключение, уже оставил объект в промежуточном состоянии. ARK>Другой вопрос — всегда ли это приводит к катастрофическим последствиям? Понятное дело, что нет.
Интересно, я неявно стараюсь все изменения записывать в локальные переменные и присваивать в поля скопом, уже когда вся работа выполнена, а присваивания уж точно не выкинут исключение. Да, есть такая проблема и нужно писать exception-safe код. Вопрос — сложно ли это?
Здравствуйте, vsb, Вы писали:
vsb>Интересно, я неявно стараюсь все изменения записывать в локальные переменные и присваивать в поля скопом, уже когда вся работа выполнена, а присваивания уж точно не выкинут исключение. Да, есть такая проблема и нужно писать exception-safe код. Вопрос — сложно ли это?
vsb>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата?
Недавно как раз видел:
From the caller's perspective, language support for unchecked exceptions means something like:
"On any function call, instead of returning with a result with the expected type, a function may have the effect of running any destructors in scope, and then exiting the function to repeat the process, skipping all the actual instructions you wanted to execute or returning the type you were specified to return. Were you doing anything but calling functions in a linear sequence? I hope not, because the order in which they were called is going to affect your program's behavior now! Don't like it? Too bad!
So anyway, you never know whether this will happen or not. It's totally nondeterministic. You have no idea how often it happens, whether it's supposed to happen, if it can happen at all, anything. It's okay, though, because you can totally plan ahead and wrap it in a try block, and then if you have a "catch" block that accepts a type T, it might be called with data of that type, instead of returning from the function!
It might not, though. You don't really know. Make sure your code works if it does or doesn't.
But wait, if you have a finally clause after your try, that always runs, whether an exception was thrown or not! I mean, even if you return in the middle of the function, this block still runs! So that's pretty useful, no gotchas there! It's really nice, because it's the only part of this system where you can just write straightforward code!
(Just don't, like, call any functions or anything, because if you do that you might overwrite the exception type. Well, I guess you don't know if there's an exception type in scope or not. I guess just be, like, extra careful? Oh, and don't return in the finally block, that's bad news, that swallows the type too. Actually, don't do any nontrivial control flow at all. Okay, maybe it's not totally like normal code.)
Anyway, any time you call a function, make sure you keep all that in mind! And make sure that none of the above listed ways you can exit from your function can ever leave any of your state inconsistent in a way that violates the caller's expected invariants, because it would be pretty embarrassing if the caller decided to catch the exception at an inopportune moment! Oh, and make sure any destructors in scope also can't operate on their types while they're in an inconsistent state, or that could cause extreme terribleness!"
You have to think about this for every function call. And that's just the tip of the iceberg. I didn't even go into the various ways that exceptions screw you over in practice. Let's take Java, for example (which does not have destructors and does have checked exceptions, which are actually somewhat tolerable):
* Unchecked exceptions can actually happen basically anywhere. Not just inside functions. Because the Java specification raises exceptions for things like stack overflows, VM internal errors, allocation failure, etc. And Java also allocates everywhere.
* By the way, implicit conversions from unboxed to boxed primitives are function calls in this context and can thus trigger these errors.
* Thanks to the magic of inheritance, you generally can't actually enumerate all the possible error types (unless you are the that one Java developer who hates OOP and makes every class final. To that Java developer: I like you!). But that's okay, because most people just create a single error type and put 30 different exceptions under it, and just throw that from all their functions. If you're lucky.
* Also, thanks to Thread.stop, checked exceptions of any type can happen anywhere.
* Also, if you catch Throwable, you screw over anyone who was listening for a different type of exception.
* ALSO, InterruptedException has special semantics where when it's caught, it unsets the isInterrupted flag. Wisely, most people just make everything a RuntimeError or an IOException and wrap the old exception, so now you lose the flag and can't see the exception!
* Did I mention that the above is what Oracle recommends you do to use exceptions with try-with-resources?
* If you call a reflection API, you just get one exception type and get to unwrap it at runtime!
Every function call.
The "elegance" of (non-typesafe) exceptions is a myth. They're awful. You use them when you don't have an alternative.
Здравствуйте, WolfHound, Вы писали:
WH>1)А где у тебя тут коды возврата? WH>Не вижу. То как ты реализовал функции func1_* к кодам возврата отношения не имеет.
Да? То есть проверка результата malloc — это не код возврата?
WH>try/catch в реальном приложении стоит снаружи цикла.
Когда снаружи — для одного исключения ничего не меняется, я проверял.
WH>2)Я что-то с ходу не нашел как исключения в .NET реализованы.
Так сойдет?
int func1_1(int a, out int result)
{
result = a + 1;
if (a > 1000000)
return -1;
else
return 0;
}
int func1_2(int a, out int result)
{
if (func1_1(a, out result) == 0)
return 0;
else
return -1;
}
int func1_3(int a, out int result)
{
if (func1_2(a, out result) == 0)
return 0;
else
return -1;
}
int func1_4(int a, out int result)
{
if (func1_3(a, out result) == 0)
return 0;
else
return -1;
}
int func1_5(int a, out int result)
{
if (func1_4(a, out result) == 0)
return 0;
else
return -1;
}
int func2_1(int a)
{
if (a > 1000000)
throw new Exception("qq");
return a + 1;
}
int func2_2(int a)
{
return func2_1(a);
}
int func2_3(int a)
{
return func2_2(a);
}
int func2_4(int a)
{
return func2_3(a);
}
int func2_5(int a)
{
return func2_4(a);
}
private string Run(string title, int max)
{
var start1 = DateTime.Now;
int res1 = 0;
for (int i = 0; i < max; i++)
{
int val;
func1_5(i, out val);
res1 += val;
}
var end1 = DateTime.Now;
var start2 = DateTime.Now;
int res2 = 0;
try
{
for (int i = 0; i < max; i++)
{
res2 += func2_5(i);
}
}
catch
{
// do nothing
}
var end2 = DateTime.Now;
return String.Format("{0}: {1} ({2}), {3} ({4})", title, end1 - start1, res1, end2 - start2, res2);
}
private void button1_Click(object sender, EventArgs e)
{
Run("test run", 1000010);
var line1 = Run("Single exception", 1000002);
var line2 = Run("Two exceptions", 1000003);
System.IO.File.WriteAllLines("D:\\temp.txt", new[] { line1, line2 });
}
Результат (Debug):
Single exception: 00:00:00.0890089 (1786293667), 00:00:00.1050105 (1785293665)
Two exceptions: 00:00:00.0950095 (1787293670), 00:00:00.1080108 (1785293665)
Результат (Release):
Single exception: 00:00:00.0430043 (1786293667), 00:00:00.0680068 (1785293665)
Two exceptions: 00:00:00.0320032 (1787293670), 00:00:00.0420042 (1785293665)
Здравствуйте, WolfHound, Вы писали:
WH>Ты сначала тест исправь. WH>А потом прогони два варианта.
Исправил, прогнал — см. мой исправленный пост.
Ничего не изменилось, исключения медленнее.
WH>С ошибкой и без. WH>Вариант без ошибки будет в подавляющем большинстве случаев.
Совсем без ошибки — да, медленнее. Но в реальном приложении на это рассчитывать не стоит, ИМХО.
Там как не было кодов возврата, так и нет.
ARK>Мой тест показывает, что одно исключение уже медленнее, чем миллион проверок.
Так ты даже корректный тест написать не можешь...
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
WH>Здравствуйте, artelk, Вы писали:
A>>Убери лишний вызов и перезапусти плз. WH>Копипаста. Но код с исключениями всёравно быстрее в 1.5 раза.
Не думаю, что в .net сильно заморачивались с оптимизацией исключений. Но если их использовать по назначению (т.е. для исключительных ситуаций), то это не проблема — обычно после них что-то где-то логгируется, что делает тормоза исключений пренебрежимо малой величиной.
Здравствуйте, enji, Вы писали:
M>>Запретить null как класс. Вместо него использовать либо Option, либо вообще ничего не использовать. E>И кстати — как его запретить? Вообще запретить простые ссылки? Т.е. везде только Option? E>И во что тогда превратится такое: obj1.calc().doIt().getSome() ?
Если каждый из уровней может потенциально вернуть Nothing или его аналог — то придется выписывать руками проверки. Ну или засахарить как-то типа obj1?.calc()?.doIt()?.getSome().
Но фишка в том, что обычно такие вызовы не будут возвращать Nothing. А там, где все-таки возвращают — как раз в коде и будет хорошо видно.
Здравствуйте, enji, Вы писали:
M>>[code=scala] M>>for (user <- userOption) { M>> doSmth(user); M>>} M>>[/code]
E>и в чем тут профит? Вместо эксепшена в строке user->doSmth(), doSmth молча не будет выполнено, что выстрелит где-то позже и в другом месте...
Пожалуйста: doSmth(userOption.get). Здесь get может вылетить NoSuchElementException.
E>опять же, сравни с
E>
E>val user = userDao.getUser(userId);
E>if (user)
user->>doSmth();
E>
E>по строчкам — тоже самое, по символам — короче
E>Если добавить чуток сахара, можно было бы писать user?.doSmth(), как в груви. И тут кстати user — обычная ссылка, а не Option<User>
Здравствуйте, enji, Вы писали:
M>>Запретить null как класс. Вместо него использовать либо Option, либо вообще ничего не использовать.
E>И кстати — как его запретить? Вообще запретить простые ссылки? Т.е. везде только Option?
E>И во что тогда превратится такое: obj1.calc().doIt().getSome() ?
Ни во что не превратится, т.к. подавляющее большинтво этих obj — not null.
А где nullable, там превратится в явные разыменования: obj1.get.calc().get.doIt().getSome(). При условии, что программист понимает, что он делает, и уверен, что все эти объекты в данный момент действительно существуют. Хотя в моём коде таких многоэтажных конструкций нет; есть одиночные разыменования obj_?.get после сложных проверок прямо строчкой выше, где я в т.ч. убеждаюсь, что obj_? не пуст; а чаще встречается obj_?.getOrElse(throw new Exception("понятное сообщение об ошибке")).
Здравствуйте, Pzz, Вы писали:
Pzz>Этим исключениям хорошо бы как-то законодательно синтаксически ограничить дальность полета. Потому что когда в ваш высокоуровневый код, работающий в терминах высокоуровневых абстракций, откуда-нибудь с самого нижнего уровня прилетит исключение про то, чего вы в своем высокоуровневом коде сказать-то и не можете, то что вы с ним будете делать? Выдадите пользователю ошибку с шешнадцетиричным номером и невнятной диагностикой? То-то пользователь будет счастлив.
Если ты не знаешь что делать с ошибкой, прибивай программу. Это намного лучше чем считать погоду в Африке.
Низкоуровневое исключение — это почти всегда открытые открытые файлы, захваченные мьютексы/критические секции, выделенная память и т.д, но самое главное — инконсистентное состояние программы.
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, Miroff, Вы писали:
M>Здравствуйте, dimgel, Вы писали:
D>>Напомни плиз, как это DSP расшифровывается. А то идею помню (что-то там типа не более двух ссылок в цепочке, иначе заворачивать в методы) и юзаю давно на автомате, а нагуглить не могу.
M> Извиняюсь, LoD конечно.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, jazzer, Вы писали:
J>>А можно продемонстрировать громоздкость и неудобность на примере? J>>Вот чтобы передавалась наверх одна и та же информация, только один раз — кодом возврата, а другой раз — исключением. J>>Ну чтоб было видно, что исключения сливают, на примере, так сказать.
_>Обсуждалось это всё уже давным давно) Причём и вместо с тобой. ))) Например здесь http://rsdn.ru/forum/cpp/4623107?tree=tree
(и там дальше по ветке основное будет) можно глянуть.
Ну глянул. Моя позиция не изменилась. Ты там, вроде, мне не возражал.
J>>Скажем, нужно открыть 4 сокета, прочитав параметры из разных конфигов. Мы делаем вперемежку 4 вызова функции (или конструктора обертки) open_file, которая может бросить исключение EFileOpen(filename,errno), и 4 вызова функции open_socket, которая может бросить исключение ESocketOpen(sourceIP,sourcePort,targetIP,targetPort,errno). J>>И вылетевшее исключение переупаковываются в, скажем, EEnvSetup(username).
_>Я же говорю, проблемы у исключений не в теории, а на практике. Конкретно на практике мне не приходилось писать код с последовательным открытием 4-х сокетов (причём так что проблемы с одним отменяют попытки с остальными) в одной функции.
А мне вот приходится регулярно. Там сокеты, правда, упрятаны внутрь соответствующих транспортов/протоколов, но это роли не играет — они все создаются разом и все должны работать.
_>Но зато вполне частенько встречался код вида _>
_>auto f1 = open_file(get_profile(username));
_>if(!f1) f1=open_file(get_default_profile());
_>s1_ = open_socket(f1); // читаем из конфига
_>
_>Понимаешь что получится, в случае переписывания такого варианта твоего кода на исключениях? )
Во-первых, в таких случаях используются функции типа can_open_file(filename), но даже в твоей формулировке:
try {
try {
f1 = open_file(get_profile(username));
} catch (...) {
f1 = open_file(get_default_profile());
}
s1_ = open_socket(f1); // читаем из конфига
}
catch(...) { std::throw_with_nested( EEnvSetup(username) ); }
а в реальном коде, даже если у нас в API только исключения, будет отдельная функция
try {
f1 = get_profile_or_default(username);
s1_ = open_socket(f1); // читаем из конфига
}
catch(...) { std::throw_with_nested( EEnvSetup(username) ); }
Ну а теперь изобрази свой замечательный вариант на кодах возврата. С донесением всей необходимой информации на верхний уровень: если не открылся файл, то какой и почему, если сокет, то какой и почему.
Жду-пожду.
J>>Ну, моя практика от твоей отличается, видимо. J>>Я вижу сплошные удобства в своем коде, и каждый вызов каждой функции в отдельный try/catch не заворачиваю.
_>Я говорил не про оборачивание каждого вызова, а про перехват исключений на следующем уровне (а не где-то высоко). Что ты как раз и продемонстрировал в своём примере.
Да, продемонстрировал. И как, это было так ужасно, что ты демонстрацию стер с глаз долой? Целая одна строчка с std::throw_with_nested?
_>Но и оборачивание отдельных функций тоже возможно, если требуется продолжить нормальное исполнение программы после неудачного вызова функции.
Да, на верхнем уровне, скажем, в обработчике кнопок меню: прилетело исключение — рассказал пользователю, что облом (с показом по требованию всей собранной исключениями через throw_with_nested информации), и работаешь себе дальше.
Потому что если можно продолжить нормальное исполнение программы после неудачного вызова функции на низком уровне, то это не исключительная ситуация по определению, правда?
Здравствуйте, jazzer, Вы писали:
J>Ну а теперь изобрази свой замечательный вариант на кодах возврата. С донесением всей необходимой информации на верхний уровень: если не открылся файл, то какой и почему, если сокет, то какой и почему. J>Жду-пожду.
bool OpenSocket(string fileName, out int handle)
{
handle = -1;
var f = open_file(get_profile_dir(username) + fileName);
if (f < 0)
{
Log.Write(GetLastErrorInfo());
return false;
}
handle = open_socket(f);
if (handle < 0)
{
Log.Write(GetLastErrorInfo());
return false;
}
return true;
}
bool OpenAll()
{
return
OpenSocket("file1", out s1_) &&
OpenSocket("file2", out s2_) &&
OpenSocket("file3", out s3_) &&
OpenSocket("file4", out s4_);
}
Изображать GOTO через все слои не вижу смысла, потому что и при наличии исключений на практике (а не в теории) бойлерплейт тоже присутствует в нехилом объеме.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, jazzer, Вы писали:
_>>>Обсуждалось это всё уже давным давно) Причём и вместо с тобой. ))) Например здесь http://rsdn.ru/forum/cpp/4623107?tree=tree
(и там дальше по ветке основное будет) можно глянуть. J>>Ну глянул. Моя позиция не изменилась. Ты там, вроде, мне не возражал.
_>Ну так консенсус (во всяком случае у меня с собеседникам) там сложился на формулировке "исключения хорошо подходят для обработки критических (т.е. после которых программа скорее всего прервёт исполнение) ошибок, а не всех видов ошибок вообще". Причём тут есть ещё хитрый нюанс — ошибки во многих API (типа того же открытия файла) могут быть как критическими, так и нет, в зависимости от контекста использования. Получается что по идее надо бы иметь по два вида API в таких библиотеках. Но Boost.Asio — это скорее исключение, чем правило...
_>В общем для большинства случаев (если не трогать таки вещи как скажем new) мне кажется, что лучше делать интерфейс без исключений. Ну или тогда уж оба варианта, как в Asio.
Ты прям мои слова повторяешь оттуда
Ну, кроме "большинства случаев".
Ты почему-то постоянно вставляешь это "большинство" (если речь о кодах возврата) и "очень специфические случаи" (если речь об исключениях), и во всех моих словах видишь этому своему тезису подтверждение, даже если я говорил об обратном. Очень странный разговор получается.
J>>а в реальном коде, даже если у нас в API только исключения, будет отдельная функция J>>
_>Ну так разве это всё не ужас, по сравнению с тем моим кодом в 3 строчки? )
Ну так твой код нифига не делает. Конечно, он будет короче, чем код, который делает
У тебя же там даже кодов возврата нет.
Вместо кодов ошибок у тебя бинарная логика — сработало/не сработало, без какой-либо детализации.
Я хотел бы увидеть твой код, который делает все то же самое, что и мой, но на кодах возврата, тогда и сравним.
J>>Ну а теперь изобрази свой замечательный вариант на кодах возврата. С донесением всей необходимой информации на верхний уровень: если не открылся файл, то какой и почему, если сокет, то какой и почему. J>>Жду-пожду.
_>Так а зачем выносить эту информацию на верхний уровень? ) Пользователю будешь это показывать? ))) Как раз это и обсуждалось в той старой темке...
Да, показывать, совершенно верно. "Файл такой-то не найден, восстановитесь из бэкапа или запустите программу починки профиля". "Не удалось подключиться к серверу такому-то, проверьте свое интернет-соединение". Какие-то проблемы с этим? Ты что, не видел никогда окошечек типа "операция обломалась" с дополнительной кнопочкой "показать техническую информацию"?
Так что давай, изобрази. Покажи мощь и всеподходящесть кодов возврата.
J>>Да, продемонстрировал. И как, это было так ужасно, что ты демонстрацию стер с глаз долой? Целая одна строчка с std::throw_with_nested?
_>Не о том речь. Просто у исключений есть очевидные преимущества. Но они проявляются как раз для случая проброса через длинный стек вызова. Т.е. в идеальной модели с исключениями мы имеет вообще один единственный блок с try/catch на всю программу.
Давай не доводить до абсурда, хорошо?
_>Так вот, я как раз и утверждаю, что на практике такая модель может получиться только в очень специфических случаях. И ты только подтвердил это своим примером.
Очень интересно, где ты увидел в моем примере подтверждение своего тезиса про "очень специфические случаи"
_>Да, и я не говорю, что этот код сильно ужасен. Просто при такой схеме все преимущества исключений пропадают.
Где они тут пропадают? Можно пальцем показать?
_>А недостатки (например неявность ошибок) остаются.
Это еще что за зверь? И как насчет невозможности игнорирования исключений, по сравнению с повсеместным игнорированием кодов ошибок?
_>>>Но и оборачивание отдельных функций тоже возможно, если требуется продолжить нормальное исполнение программы после неудачного вызова функции. J>>Да, на верхнем уровне, скажем, в обработчике кнопок меню: прилетело исключение — рассказал пользователю, что облом (с показом по требованию всей собранной исключениями через throw_with_nested информации), и работаешь себе дальше. J>>Потому что если можно продолжить нормальное исполнение программы после неудачного вызова функции на низком уровне, то это не исключительная ситуация по определению, правда?
_>Да, и тогда применять исключения не надо. Собственно о том и речь, что исключения хорошо подходят для решения только небольшой части общей задачи обработки ошибок.
Почему небольшой-то? У тебя в основном функциям позволено ломаться и ты в основном для каждого вызова каждой функции предоставляешь обходные пути? Не смешно.
Не говоря уже о том, что общая задача обработки ошибок, а именно невозможность игнорирования ошибки, кодами возврата не решается вообще.
_>В отличие от тех же кодов возврата, которые подходят везде.
Жаль, что так и не удалось послушать начальника транспортного цеха о том, как кодами возврата передается наверх информация об ошибке.
А то, может, я просто не знаю значения слова "везде"...
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, vsb, Вы писали:
_>Когда-то этот вопрос уже здесь обсуждался, причём именно для случая C++ (в контексте запрета исключений в гугловских руководствах).
В гугло-коде проблемы с исключениями по одной простой причине. Они не знают, что такое RAII, и выброс исключения порушит их код. В общем, они ССЗБ. Не их не жалко.
Здравствуйте, koodeer, Вы писали:
C>>Ещё в качестве средства изоляции — при броске fail'а все разделяемые ресурсы (в Rust они есть в виде RWArc), которые в тот момент использовала задача, помечаются как "испорченные".
K>Можно в двух словах для тех, кто не знаком с Rust'ом, что такое задача? Это процесс, поток или что-то ещё?
Вы немного не в ту степь завернули. Тут говорилось не про hardware exceptions.
Тут говорилось про нужный и качественный механизм обработки катастрофических для программ обстоятельств,
обеспечивающих невозможность нормального её исполнения, появивишийся однажды в Lisp, и впоследствии
раздербаненный стадом обезьян, и изуродованный, и растасканный по всем закоулкам, по самое не балуйся.
Обезьяны наигрались, и теперь, на некоторых деревьях, начинают терять интерес к игрушке, типа она такая
кошмарная как в С++. Не надо было за эталон брать то, что в С++, академики праздные, накорябали своими белыми ручонками.
Здравствуйте, alex_public, Вы писали:
_>Не о том речь. Просто у исключений есть очевидные преимущества. Но они проявляются как раз для случая проброса через длинный стек вызова. Т.е. в идеальной модели с исключениями мы имеет вообще один единственный блок с try/catch на всю программу.
В идеальной модели исключения нужны для передачи управления непосредтсвенно в то место, где есть вся информация для обработки.
С чего ты взял, что вся информация, депенденсы и тд и тд, будет именно в том самом месте, где этот единственный блок ?
_>Да, и тогда применять исключения не надо. Собственно о том и речь, что исключения хорошо подходят для решения только небольшой части общей задачи обработки ошибок. В отличие от тех же кодов возврата, которые подходят везде.
Коды возврата это полностью ручное раскручивание каждой ошибочной ситуации. Ты наверное хотел намекнуть, что вручную можно сделать всё что угодно ?
Здравствуйте, koodeer, Вы писали:
C>>Ещё в качестве средства изоляции — при броске fail'а все разделяемые ресурсы (в Rust они есть в виде RWArc), которые в тот момент использовала задача, помечаются как "испорченные". K>Можно в двух словах для тех, кто не знаком с Rust'ом, что такое задача? Это процесс, поток или что-то ещё?
Это легковесный процесс. Общение с ним идёт через отсылку сообщений, которые при этом копируются — ровно как и в Erlang'е. В отличие от Erlang'а, ещё можно отсылать данные без копирования с помощью Arc и изменяемые данные с помощью RWArc.
K>Меня интересует, если ресурс, например, файл, будет помечен как испорченный, то другой процесс, работающий с ним же, продолжит свою работу?
Конкретно файл в процессе unwinding'а будет просто закрыт, если не делать какую-то дополнительную обратоку в деструкторе (т.е. в реализации трэйта Drop).
Здравствуйте, Ikemefula, Вы писали:
I>Линейная логика никуда не девается. Возьми какой юзерский сценарий и опиши подробно. Практически всегда будет минимум одна линейная цепочка, ошибка в которой означает фейл всего сценария.
Наличие линейной цепочки ничего не меняет, т.к. если обработка ошибки будет происходить прямо на следующем уровне стека, то банальный return ничем не отличается по удобству от throw. Т.е. ключевой вопрос именно в точке обработки ошибок. Если это где-то наверху стека (как для критических ошибок), то исключения великолепны. Если же прямо на следующем уровне (а это абсолютно нормальный сценарий, например если взамен одной твоей цепочки надо запустить альтернативную), то исключения только портят картину.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, uncommon, Вы писали:
ARK>>>А в моей присутствует. Кто из нас прав? U>>Покажи свой код. Дьявол, как обычно, в деталях.
ARK>Код принадлежит не мне, поэтому показать его я не могу. ARK>Но могу ответить на любые уточняющие вопросы.
Нет, товарищ. Это тот случай, когда надо иметь перед глазами код. Чтобы точно тебе показать, как можно и нужно делать по-другому. Уточняющими вопросами здесь не обойдёшься.
ARK>Хотя, честно говоря, не вижу смысла в этом. То, что многие (а из мною лично виденных — все без исключения) крупные системы содержат кучу мусора с try/catch/finally — это факт.
Если ты живёшь на помойке, то всё что ты видишь каждый день это куча говна. Это факт для тебя. Но твой личный опыт ничего не доказывает, особенно учитывая тот факт, что у тебя, мягко говоря, неверные представления об исключениях.
Здравствуйте, uncommon, Вы писали:
U>Где здесь обработка ошибок? Вся прелесть в том, что её как бы нет, но она на самом деле есть.
Вот это как раз совсем не прелесть. Особенно для тех, кто будет потом читать этот код.
Прелесть, это когда ничего дополнительного самому писать не надо, но при этом всё ясно видно. В области обработки ошибок я к сожалению такого решения не знаю...
Здравствуйте, Cyberax, Вы писали:
C>Давно не хватает, на самом деле, принципиальных языков. В С++ был принцип "не платить за то, что не используется" — это стало сущесвенным стимулом в развитии.
Сравни принцип "не платить за то, что не используется" с "исключений не будет, потому что принцип". Какой-то один из них высосан из пальца.
C>>>Я уж не говорю про то, что исключения не используются и в огромном количестве проектов на С++. При полном отсутствии сахара типа try!. U>>И о чём это говорит, кроме того, что есть много людей, которые не знают как использовать исключения. C>Стоит задуматься почему.
Если честно, то мне нечего сказать людям, которые не хотят учиться. Говнокодеров полно, и ничего с этим не поделать. Пусть они лучше возятся с PHP, который идеально подходит для этих людей (по идее и реализации). А я буду работать отдельно от них.
U>> Более интересен будет опыт таких языков как Python, где исключения с самого начала в языке и рантайме, а кодов возврата нет как таковых. Так вот в Питоне ни у кого почему-то не возникает с исключениями никаких проблем. C>Вот уж не надо сказок. Я на досуге после миграции нашего проекта на PyPy сижу и переписываю код вида:
C>В обычном Python'е он "работает" из-за того, что файловый объект детерминированно уничтожается при выходе из scope'а. А вот в PyPy уже честный GC и ВНЕЗАПНО оно как-то не очень работает.
Это отношение к исключениям не имеет. Тут эффект одинаковый вылетит исключение или нет.
C>Но и это фигня. Например, надо поймать исключение о нарушении констрейнта внешнего ключа. С трёх раз догадайтесь как это правильно сделать.
Почитать документацию, и из IntegrityError извлечь pgcode? Какое отношение косяки каких-то API имеют к существу вопроса? Я могу какое угодно кривое API придумать, но это не значит, что используемый язык или идиомы кривы. Это всего лишь значит, что я придумал кривое API.
Здравствуйте, uncommon, Вы писали:
C>>Давно не хватает, на самом деле, принципиальных языков. В С++ был принцип "не платить за то, что не используется" — это стало сущесвенным стимулом в развитии. U>Сравни принцип "не платить за то, что не используется" с "исключений не будет, потому что принцип". Какой-то один из них высосан из пальца.
Принцип у них другой: "Минимум магии и максимальная надёжность".
U>Если честно, то мне нечего сказать людям, которые не хотят учиться. Говнокодеров полно, и ничего с этим не поделать.
Если инструмент крив до неюзабельности, то это проблемы уже инструмента.
C>>В обычном Python'е он "работает" из-за того, что файловый объект детерминированно уничтожается при выходе из scope'а. А вот в PyPy уже честный GC и ВНЕЗАПНО оно как-то не очень работает. U>Это отношение к исключениям не имеет. Тут эффект одинаковый вылетит исключение или нет.
Это к вопросу о деструкторах.
C>>Но и это фигня. Например, надо поймать исключение о нарушении констрейнта внешнего ключа. С трёх раз догадайтесь как это правильно сделать. U>Почитать документацию, и из IntegrityError извлечь pgcode?
Авотнатрибуквы. Оно кидает разные классы из разных драйверов БД, так что официальная рекомендация — делать regex match по строке с деталями ошибки.
U>Какое отношение косяки каких-то API имеют к существу вопроса? Я могу какое угодно кривое API придумать, но это не значит, что используемый язык или идиомы кривы. Это всего лишь значит, что я придумал кривое API.
Это к вопросу об удобстве исключений.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, uncommon, Вы писали: U>>Например? Что-то мне кажется, что такие деструкторы — результат плохого дизайна. Как может быть много логики в деструкторе RAII класса, где должны только освобождаться ресурсы?\ S>Ну, например ресурсы — это буфер. Чтобы его освободить, его надо флашнуть. Что делать? Требовать ручного flush перед выходом из каждого scope? Причём его отсутствие мы заметим только в рантайме, и то не каждый раз.
Чтобы освободить буфер, его не надо флашнуть в деструкторе. Флашать уже поздно. Мы здесь говорим о двух принципиально разных операциях: 1) освобождение ресурсов, 2) операции завершения/коммита, которые могут завершиться неудачно. Эти завершающие операции надо делать явно, хочешь ты этого или нет. Приемлимого решения делать их в деструкторе нет. Эта проблема есть и в стандартной библиотеке в классе std::thread. Если thread является joinable, но join() не был позван явно перед деструктором, то деструктор вызывает std::terminate().
Здравствуйте, Sinclair, Вы писали:
S>С моей точки зрения, мне важна семантика некоего "возврата", т.е. гарантии, что в момент выхода из scope у меня некий объект восстанавливает своё состояние к исходному. S>Частный случай — освобождение ресурсов. Т.е. меня интересует пара антонимов типа push/pop, acquire/release, change/revert. S>Типичный пример — смена курсора мышки на часики. Я хочу иметь гарантию того, что часики вернутся в стрелочку, независимо от того, каким способом я вышел из scope — return, break, или throw.
Ну так это обеспечивают обычные RAII guards. (Естественно, предполагается, что смена часиков на стрелочку не бросит исключения.)
S>Вызывает интерес поступательный процесс: можно ли изобрести другую технологию, в которой можно будет описывать то, чего я хочу? S>Может быть, два "деструктора" у классов — commit-деструктор и rollback-деструктор?
для этого у guard-а делается флажок, который взводится, если все хорошо (обычно метод называется commit), и тогда деструктор идет по сценарию commit, а иначе (по умолчанию) — по сценарию rollback.
S>Или другую семантику раскрутки стека, которая позволяет спасти больше информации об ошибке? Типа "мы читали из одного файла и писали в другой, потом при чтении у нас возникла ошибка формата, а при экстренном выходе мы и предыдущие данные в выходной файл записать не смогли".
Для этого, очевидно, нужно предпринять специальные усилия (т.е. какие-то параллельные структуры) по сохранению сути того, что мы собирались сделать и не смогли.
Самое простое — очередь задач, где обломившаяся задача не удаляется из очереди и ее можно попробовать запустить еще раз или что-то с ней сделать.
Другой вариант — сохранять сами исключения (у меня есть игрушечный вариант на С++11, если интересно, выложу куда-нть) и всю необходимую информацию прямо в них.
Но тут, понятно, нас всегда подстерегают ошибки выделения памяти.
Здравствуйте, Sinclair, Вы писали:
S>С моей точки зрения, мне важна семантика некоего "возврата", т.е. гарантии, что в момент выхода из scope у меня некий объект восстанавливает своё состояние к исходному. S>Частный случай — освобождение ресурсов. Т.е. меня интересует пара антонимов типа push/pop, acquire/release, change/revert.
Абсолютно верно. Это одна из очевидных вещей, которые почему-то нормально не реализованы в современных ЯП.
Кстати, пара "антонимов" — это всего лишь вырожденный случай некоторой цепочки вычислений, в которой должны быть гарантированно вызваны некоторые методы в некотором порядке.
S>Вызывает интерес поступательный процесс: можно ли изобрести другую технологию, в которой можно будет описывать то, чего я хочу? S>Может быть, два "деструктора" у классов — commit-деструктор и rollback-деструктор? S>Или другую семантику раскрутки стека, которая позволяет спасти больше информации об ошибке? Типа "мы читали из одного файла и писали в другой, потом при чтении у нас возникла ошибка формата, а при экстренном выходе мы и предыдущие данные в выходной файл записать не смогли".
Вот здесь вся "прелесть" исключений и вылезает. ИМХО, создание корректного кода, "рассуждения" о коде будут сильно затруднены в присутствии спагетти деструкторов и неочевидных с виду переходов.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, jazzer, Вы писали: J>>Ну так это обеспечивают обычные RAII guards. (Естественно, предполагается, что смена часиков на стрелочку не бросит исключения.) S>Не вижу в этом ничего естественного. Давайте попробуем придумать операцию, которая не бросит исключения вообще никак-никак, а не просто "ну, скорее всего вы вряд ли получите в ответ window station is shutting down". Мне что-то вообще ничего в голову не приходит.
А чего ты тогда хочешь? Что входит в твое понятие антонима? Просто попытка вызвать обратное действие, независимо от того, будет она успешной или нет?
Ну так это делается путем подавления ошибок в деструкторе (и то же самое было бы в любом другом случае).
А если нет, и ты хочешь получить сообщение об ошибке курсора, то что предлагается делать в случае цепочки антонимов?
Скажем, один, восстанавливает курсор, а второй гасит лампочку на девайсе, подключенном по COM-порту.
Плюс возврат ошибки в случае отвалившегося антонима — это очень сомнительная практика сама по себе.
Типа ты вызываешь функцию для долгого расчета интеграла, а в ответ получаешь ошибку "не смогли восстановить курсор-стрелочку".
J>>для этого у guard-а делается флажок, который взводится, если все хорошо (обычно метод называется commit), и тогда деструктор идет по сценарию commit, а иначе (по умолчанию) — по сценарию rollback. S>Это изоморфно ручному вызову commit перед каждым выходом из scope. Для минимально нелинейной логики отслеживание этого руками — тот ещё геморрой.
Ну еще есть scope(exit), scope(success), scope(failure) — ты их имеешь в виду?
S>Это частный случай. В том смысле, что если мы выбрасываем исключение, то у нас оно уже есть, и для упаковки его в nested exception доп.места не надо. А если память кончилась при попытке выкинуть исключение — то вместо него мы выбрасываем тот самый статический экземпляр out-of-memory, который мы заботливо прикопали при старте программы.
По Стандарту С++11 — никаких гарантий, увы, нет. Даже std::current_exception() может вернуть bad_exception, если не удалось создать копию текущего исключения (если таковая понадобилась).
Здравствуйте, jazzer, Вы писали:
J>А чего ты тогда хочешь? Что входит в твое понятие антонима? Просто попытка вызвать обратное действие, независимо от того, будет она успешной или нет?
Антоним — это то действие, которое я заложил в алгоритм.
Вот, например, банальная штука на воображаемом языке:
using(var stream = openSerializationStream())
{
stream.Write("Customer", customer);
if (customer.Status < Status.Ready)
return; // #1 - no data saved for the not ready customers
var orders = Orders.GetOrders(customer) ;
if (orders.Length==0)
return; #2 - nothing to save
stream.Write("OrdersTotal", orders.Total(o=>o.Amount));
foreach(var o in orders)
if (o.Status != Status.Ready)
break; # 3 - list all ready orders, then exit
else
stream.Write("Order #"+o.Id, o);
}
Я хочу двух вещей:
1. Чтобы у меня буфер сбрасывался на диск не после каждой операции, но при выходе из scope. То есть я не хочу ни добавлять принудительный flush к Write (страдает перформанс), ни руками дописывать stream.Flush() перед каждым return/break/} (чревато ошибками).
2. Чтобы о об ошибках записи я узнавал не из чтения побитого файла, а из вылетающего исключения.
В С++ сие невозможно, т.к. единственное, что гарантированно сработает при выходе — это деструктор, а в деструкторе бросать исключения нельзя.
J>Скажем, один, восстанавливает курсор, а второй гасит лампочку на девайсе, подключенном по COM-порту.
Это — вопрос, требующий проработки. У меня нет всех деталей задачи про курсор и лампочку; потому мне сложно сказать, какие гарантии тут нужны. Потому что по ком-порту может быть не лампочка подключена, а электронный замок, перекрывающий дверь шлюза. И подавление исключения означает "да хрен с ним, с этим шлюзом — нехай экипаж задохнётся".
J>Плюс возврат ошибки в случае отвалившегося антонима — это очень сомнительная практика сама по себе. J>Типа ты вызываешь функцию для долгого расчета интеграла, а в ответ получаешь ошибку "не смогли восстановить курсор-стрелочку".
Это потому, что вам не жалко курсор. А что, если функция для долгого расчёта не смогла результат в файл сохранить. Вы тоже сочтёте сомнительной практику как-то намекнуть об этом вызывающему коду?
J>Ну еще есть scope(exit), scope(success), scope(failure) — ты их имеешь в виду?
А что это? Может и их.
J>По Стандарту С++11 — никаких гарантий, увы, нет. Даже std::current_exception() может вернуть bad_exception, если не удалось создать копию текущего исключения (если таковая понадобилась).
Ну, я стандарты С++ не считаю откровением господним. Интересно не то, чего они не гарантируют, а то, что вообще можно получить — хотя бы в теории.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, jazzer, Вы писали:
J>Что ты собираешься получить, если и одна из операций сломалась, и автоматический Flush?
Ну, например в данном случае, если одна из операций сломалась, то я не хочу автоматический Flush(). J>Если очень хочется — то можно.
И такое исключение поймают?
J>А зачем тогда делать важные вещи невидимой автоматикой без внятной обработки ошибок, которые могут в этих важных вещах возникнуть?
Затем, чтобы минимизировать шансы на ошибку. Потому что "класс шлюза" проектирует один человек, а применяет — совсем другой.
И этот другой может вообще быть не в курсе, что добавленный им return открывает дыру с последствиями типа Чернобыля.
J>И что бы ты делал без исключений, если у тебя возникло несколько ошибок сразу?
Ну, во-первых, у меня не возникает несколько ошибок сразу. Всё начинается с одной ошибки. Дальше я буду как-то эту ошибку обрабатывать. Если у меня в процессе обработки этой ошибки возникла другая ошибка, то я хочу иметь возможность обработать и её тоже.
J>Суть кода с исключениями — прервать цепочку действий, почиститься и передать это исключение наверх.
Да-да, весь вопрос только в том, что означает "почиститься".
J>Именно это, а не то, которое возникнет в результате очистки. Ну или по крайней мере оно не должно быть главным, а быть упрятанным в какой-нть nested и проигнорированным в цепочке действий по очистке, чтоб остальные тоже могли почиститься. Хотя там могут быть свои взаимозависимости, так что все это — прогулки по граблям, вне зависимости от наличия или отсутствия исключений.
Ну да. Вообще всё программирование — это прогулки по граблям. Вы не знали? Весь прогресс связан только с тем, чтобы сократить количество этих граблей.
Введя, например, принудительную type safety.
J>Я сочту сомнительной практикой засовывать сохранение результатов в деструктор, вместо того, чтобы сделать это отдельной явной строчкой.
Я тоже. Но ведь кроме деструкторов у нас (в С++) ничего нету. А идея "сделать это отдельной явной строчкой" — регрессивна.
Зачем тогда нам деструкторы? Ведь нетрудно же "почиститься" отдельной явной строчкой при выходе из каждого scope. Тем более, что непустые деструкторы вообще редко нужны.
J>Это ведь можно до абсурда дойти — не писать функций вообще, весь код в деструкторах, а в функциях только создание объектов и тут же их автоматическое уничтожение.
Можно и в обратную сторону до абсурда дойти. Вот только зачем?
J>Встроено в D, есть реализация для С++: J>http://rsdn.ru/forum/cpp/4908728.1
Прикольно! Вот об этом я и говорил. J>В случае success исключение можно бросить, в принципе.
Вот, одна из возможных реализаций.
J>В теории это всё криво. Я не видел ни одной схемы вменяемой автоматизированной обработки ошибок обработки ошибок обработки ошибок.
Поэтому надо дать возможность работать с ними вручную. Напомню: исключения — тоже "неавтоматизированы". catch за вас никто не напишет. Зато дают возможность вам выразить свои намерения компактно и корректно.
Хочется ещё компактнее и корректнее.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, AlexRK, Вы писали:
ARK>Да блин, мне _вообще_ непонятно, какова должна быть грамотная стратегия обработки ошибок при финализации. ARK>Если у нас есть блок, внутри которого содержатся 10 автоматических переменных, то что должно произойти при выходе из блока, если при вызове первого деструктора вылетела ошибка? ARK>Если бы был полный STM с учетом IO, то все было бы супер. Но его нету. Что делать? Выполнять остальные деструкторы или нет? Все ошибки из всех деструкторов склеить в одно исключение и кидануть его наверх?
Пока что все выглядит так, что я открываю Америку — деструкторы не должны возвращать ошибку, а все действия, которые могут ее вернуть, должны быть вызваны явно. По сути мы просто перекладываем всю логику обработки на программиста.
Но в этом свете хваленый RAII оказывается просто пшиком, потому что безошибочно с гарантией освободить можно только память.
Не, можно конечно просто плюнуть на полную корректность, как в С++ и поступают. "Подумаешь, файл не закрылся, этого почти никогда и не бывает."
Но для меня такой вариант неприемлем.
Здравствуйте, AlexRK, Вы писали:
ARK>Не может ли уважаемый DarkEld3r пояснить, с чем он несогласен?
Не согласен с подачей, а именно с "RAII оказывается просто пшиком". Да, деструкторы не панацея и не решение всех проблем. Да, в сложных случаях, надо писать дополнительный "ручной" код, но в общем, с ними всё равно удобнее.
ARK>>Не, можно конечно просто плюнуть на полную корректность, как в С++ и поступают.
Ок, как должен выглядеть правильный вариант?
Я уж не говорю о том, что даже если мы будем всегда сохранять все возникшие ошибки, то это не гарантирует их последующую корректную обработку. Кто помешает их просто игнорировать? Ну и раз мы в этой теме, то у кодов возврата такая же фигня, если не хуже (nested exception выглядит всё-таки удобнее).
Разницы между исключениями и кодами возврата быть не должно, до тех пор, пока количество ошибок пренебрежимо мало по сравнению с количеством успешных вызовов. Это происходит потому что в процессоре есть branch predictor, который успешно предсказывает ветвления при проверках кодов ошибок в 99.(9)% случаев. Если количество ошибок выелико, исключения работают медленнее чем коды ошибок по понятным причинам.
WH>Debug: WH>No exceptions: 00:00:00.0254283 (1784293664), 00:00:00.0222134 (1784293664) WH>Single exception: 00:00:00.0245588 (1785293665), 00:00:00.0232391 (1785293665) WH>Two exceptions: 00:00:00.0242956 (1785293665), 00:00:00.0234590 (1785293665)
Забавно читать рассуждания о производительности, написаные любителями бенчмаркать дебажный(!sic) код
Здравствуйте, Pzz, Вы писали:
Pzz>Тогда их на верхнем уровне будут молча глотать. С журналированием в /dev/null, ага.
Это как правило безопаснее, чем глотать на нижнем: при ошибке прерывается весь текущий сценарий целиком, а не продолжает выполняться с непонятного места с непонятными последствиями.
Здравствуйте, jazzer, Вы писали:
vsb>>На мой взгляд это проблема исключительно С++, а точнее его двух особенностей — отсутствие GC и запрет исключений в деструкторах.
J>Нет такого запрета.
На вопрос "вызывать ли деструктор, если конструктор бросил исключение" разные языки, помнится, отвечали по-разному, поэтому я старался до такого не опускаться и под инициализацию, способную бросить исключение, заводил отдельный от конструктора метод. Но по крайней мере освобождение памяти из-под собственно объекта в том же C++ наверняка в любом случае происходит. А вот как программа будет дальше жить, если исключение бросает деструктор, я вообще ХЗ. Memory leak, не?
Здравствуйте, Pzz, Вы писали:
Pzz>P.S. Наверное, было бы легче, если бы исключения, которые могут прилететь от методов класса, должны были бы быть описаны в самом классе. И если метод класса зовет швыряющуюся чужеродными исключениями функцию, то компилятор бы требовал обложить ее соответствующими try/catch, чтобы незаявленные исключения наружу бе вылетали.
В Java, как уже сказали это есть, причем с самого начала. Привело только к тому, что ИДЕ генерируют сопли глотающие исключения автоматически. В самом сообществе ява-разработчиков, сейчас преобладает мнение не использовать checked exceptions вообще.
Здравствуйте, MTD, Вы писали:
MTD>В Java, как уже сказали это есть, причем с самого начала. Привело только к тому, что ИДЕ генерируют сопли глотающие исключения автоматически. В самом сообществе ява-разработчиков, сейчас преобладает мнение не использовать checked exceptions вообще.
Добавлю, что в скале от них отказались по этой же причине: вреда больше чем пользы.
Здравствуйте, AlexRK, Вы писали:
ARK>Главная проблема в том, что исключения создают дополнительный скрытый поток управления, который никак в коде не виден.
Да, согласен.
Вызываешь функцию, и не знаешь — возвратится ли она здесь или где-то вообще неизвестно где...
Интересный вопрос — что с этим делать? С одной стороны, и от исключений отказываться не хочется, с другой — как бы исхитриться сделать так чтобы исключение, брошенное из функции, превращалось в код возврата, если обработчик исключения для данного исключения не предусмотрен? Компилятор в принципе может это отследить?
Например, сделать какой-то особый оператор (вместо throw) который был бы чем-то средним между throw и return: он мог бы бросать исключение, если ранее в какой-то глобальной таблице блок try выставил флаг, что данный тип исключения обрабатывается; а если флага нет, то возвращал бы свой аргумент как код возврата.
Здравствуйте, AlexRK, Вы писали:
vsb>>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.
ARK>Главная проблема в том, что исключения создают дополнительный скрытый поток управления, который никак в коде не виден.
ARK>Это не было бы проблемой, если бы гарантировалась атомарность всех изменений на некотором участке, на котором предположительно может вылететь исключение. То есть, если исключения нет — коммит, если есть — роллбек. При таком раскладе программа всегда остается в валидном состоянии и все инварианты сохраняются в любой момент времени. Увы, у STM есть фундаментальная проблема — ввод-вывод (на данный момент приемлемого решения этой проблемы не найдено).
ARK>Таким образом, исключения предлагают избавление от лапши проверок, давая взамен потенциально некорректное состояние программы. ARK>Коды возврата гарантируют видимость всех путей исполнения и отсутствие неожиданных нарушений инвариантов, но генерируют много бойлерплейта. ARK>Что лучше — хрен знает.
По-моему невалидное состояние после исключения это достаточно редкая штука. Можете привести какие то часто встречающиеся случаи таких состояний в managed языке (Java/C# например)? Я только искусственные примеры могу придумать, в реальности я такого не видел (может не замечал).
Здравствуйте, smeeld, Вы писали:
vsb>>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.
S>Исключения это очень медленно и размашисто, по сравнению с if(func()!=0) err(); S>Оправдывает себя только для случаев, когда исключением можно обработать глобальные S>сбой или ошибку, с или остановкой приложения, или его, в некотором смысле, полным перезапуском, S>с какими другими входными данными и иным способом. S>Короче, это как тяжёлая артиллерия, и применять это средство необдуманно, нерационально-чревато, S>потерей производительности.
Насколько медленней? Насколько это может сказаться в работе программы? Я вот не могу себе представить сценария, где программа будет пытаться миллион раз открыть несуществующий файл и её производительность будет страдать от медленных исключений.
Здравствуйте, x-code, Вы писали:
XC>Интересный вопрос — что с этим делать? С одной стороны, и от исключений отказываться не хочется, с другой — как бы исхитриться сделать так чтобы исключение, брошенное из функции, превращалось в код возврата, если обработчик исключения для данного исключения не предусмотрен? Компилятор в принципе может это отследить?
Да, что делать — непонятно. Монады разве что действительно прикручивать.
XC>Например, сделать какой-то особый оператор (вместо throw) который был бы чем-то средним между throw и return: он мог бы бросать исключение, если ранее в какой-то глобальной таблице блок try выставил флаг, что данный тип исключения обрабатывается; а если флага нет, то возвращал бы свой аргумент как код возврата.
Молодец. Только у меня имеются противоположные результаты. Может
просто дизассемблером по бинарникам и по исходникам компиляторов прошвырнёмся,
и определим, что будет исполнятся медленней, а что быстрее.
Здравствуйте, smeeld, Вы писали:
S>Молодец. Только у меня имеются противоположные результаты. Может S>просто дизассемблером по бинарникам и по исходникам компиляторов прошвырнёмся, S>и определим, что будет исполнятся медленней, а что быстрее.
Покажи класс. Я скажу, где у тебя ошибка.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Сугубое ИМХО.
Одна из серьезных проблем, которую практически не поминают — это парадигма обработки исключений.
По сути это событийное программирование.
Ведь исключения могут генерироваться в проге где угодно.
И обработка их ведется совершенно в другом месте. Причем программист этим практически не управлял.
И вот получается, что в одной проге смешаны как минимум две, а то и три парадигмы: ООП+событийное (и очень часто процедурное).
И если нормальную работу худо-бедно проектируют, то аварии не проектируют совсем.
И обработка исключений часто появляются как заплатки над "правильным" кодом.
Надо отметить, что в новом стандарте управляемость исключений СУЩЕСТВЕННО улучшилась.
Текущее исключение можно явным образом сохранить, передать в нужную функцию, заново кинуть в нужном месте.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, vsb, Вы писали:
vsb>Интересно, я неявно стараюсь все изменения записывать в локальные переменные и присваивать в поля скопом, уже когда вся работа выполнена, а присваивания уж точно не выкинут исключение. Да, есть такая проблема и нужно писать exception-safe код. Вопрос — сложно ли это?
Я тоже так пишу. Саттер/Абрахамс в свое время прочистили мозги на этот счет.
Надо только помнить, что есть 3 уровня безопасности по исключениям.
Базовую гарантию (отсутствие утечек памяти, незакрытых хэндлов, неразлоченных мьютексов) обеспечить можно практически всегда.
Сильную — не всегда (если принимать во внимание сопутствующие накладные расходы, которые могут быть слишком большими), но и не всегда это так уж нужно.
Ну и у меня уровень exception safety, обеспечиваемый функцией — это отдельная строчка в Doxygen-комментарии перед ней, наряду с thread safety (даже макрос специальный для Doxygen есть).
Здравствуйте, x-code, Вы писали:
XC>Здравствуйте, AlexRK, Вы писали:
ARK>>Главная проблема в том, что исключения создают дополнительный скрытый поток управления, который никак в коде не виден.
XC>Да, согласен. XC>Вызываешь функцию, и не знаешь — возвратится ли она здесь или где-то вообще неизвестно где...
Ну конкретно в C++ (начиная с С++11) есть оператор nothrow(выражение), который позволяет определить, может ли выражение внутри него бросить исключение (основываясь на декларации nothrow).
К сожалению, нельзя им пометить блок кода и как-то заставить бросать ошибку компиляции, если встретилось выражение, которое может бросить, но и то хлеб — потенциально опасные функции можно проверить на nothrow заранее (полезно в шаблонах, когда не знаешь, что тебе подсунут).
Здравствуйте, D. Mon, Вы писали:
vsb>>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата?
DM>Недавно как раз видел: DM>
Let's take Java, for example (which does not have destructors and does have checked exceptions, which are actually somewhat tolerable):
DM>* Unchecked exceptions can actually happen basically anywhere. Not just inside functions. Because the Java specification raises exceptions for things like stack overflows, VM internal errors, allocation failure, etc. And Java also allocates everywhere.
DM>* By the way, implicit conversions from unboxed to boxed primitives are function calls in this context and can thus trigger these errors.
Так в С++ как раз совершенно другая картина, именно из-за того, что 1) исключения не летят откуда угодно, потому что нет рантайма, который бы их бросал, и 2) из-за наличия деструкторов, в которые ты кладешь код очистки/отката в случае неудачи — именно за счет деструкторов в С++ достигается автоматическая обработка исключений.
В D тем же самым занимается scope(failure).
А в Java этого нет, да. Нам их тоже жалко
Цитата относится больше к Java, чем не к исключениям вообще.
Здравствуйте, WolfHound, Вы писали:
S>>Исключения это очень медленно и размашисто, по сравнению с if(func()!=0) err(); WH>Это не правда. Правильно реализованные исключения работают быстрее кодов возврата.
"Не всё так однозначно" (c).
DWARF-исключения заметно раздувают код для обработки ошибочных путей, а таблицы исключений заметно давят на кэш процессора. Так что использовать исключения для нормального flow-control'а — очень неэффективно.
Здравствуйте, jazzer, Вы писали:
J>Так в С++ как раз совершенно другая картина, именно из-за того, что 1) исключения не летят откуда угодно, потому что нет рантайма, который бы их бросал, и 2) из-за наличия деструкторов, в которые ты кладешь код очистки/отката в случае неудачи — именно за счет деструкторов в С++ достигается автоматическая обработка исключений. J>В D тем же самым занимается scope(failure). J>А в Java этого нет, да. Нам их тоже жалко J>Цитата относится больше к Java, чем не к исключениям вообще.
В Java есть try-with-resources, он ничем не хуже вышеперечисленных вариантов. Аналог scope(failure) это try-finally. Разница только в том, что в try-finally код освобождения ресурса не находится рядом с объявлением и добавляется один отступ для внутреннего кода. Это менее удобно, но принципиально ничего не меняет.
Разве что деструктор невозможно забыть вызвать, но в принципе в Java есть статические анализаторы кода, которые тебе подскажут, если ты забыл закрыть ресурс. Проблемой при хорошей организации процесса я это не считаю.
Здравствуйте, vsb, Вы писали:
vsb>Здравствуйте, jazzer, Вы писали:
J>>Так в С++ как раз совершенно другая картина, именно из-за того, что 1) исключения не летят откуда угодно, потому что нет рантайма, который бы их бросал, и 2) из-за наличия деструкторов, в которые ты кладешь код очистки/отката в случае неудачи — именно за счет деструкторов в С++ достигается автоматическая обработка исключений. J>>В D тем же самым занимается scope(failure). J>>А в Java этого нет, да. Нам их тоже жалко J>>Цитата относится больше к Java, чем не к исключениям вообще.
vsb>В Java есть try-with-resources, он ничем не хуже вышеперечисленных вариантов.
Я, конечно, давно на это смотрел в последний раз, но, насколько я помню, try-with-resources работает только для совсем примитивных случаев.
Т.е. для одного файла он сработает, а вот для массива файлов — уже нет.
Плюс надо явно указывать все, что ты хочешь, чтоб освободилось в случае исключения, никакой автоматики, как в С++, нет.
Плюс там могут быть только декларации, если хочешь в промежутке между декларациями позвать еще какой-то код — придется писать еще один уровень try/catch.
Поправь, если не так.
vsb>Аналог scope(failure) это try-finally. Разница только в том, что в try-finally код освобождения ресурса не находится рядом с объявлением и добавляется один отступ для внутреннего кода. Это менее удобно, но принципиально ничего не меняет.
В Java можно сделать автоматическое Memento (в смысле, чтоб в случае исключения указанная переменная возвращалась к своему предыдущему значению автоматически)?
Ну и в С++ вообще try/catch очень редко появляется в коде.
Именно за счет автоматики деструкторов.
Совершенно нет никакой необходимости использовать try/catch, чтобы написать exception-safe код.
Большинство такого кода вообще try/catch не содержит, а пишется в стиле
Memento m1(a);
// что-то делаем (исключения OK)
File f;
// что-то делаем (исключения OK)
Memento m2(b);
// что-то делаем (исключения OK)
DBConnection db;
// что-то делаем (исключения OK)
// закончили делать
// раз мы пришли сюда, значит, никаих исключений не случилось, коммитим изменения
m1.commit();
m2.commit();
всё. Этот код exception-safe. Где бы ни появилось исключение — a и b корректно откатятся к старым значениям, файлы/сессии закроются, всё автоматически. И никаких try/catch, простой линейный код.
vsb>Разве что деструктор невозможно забыть вызвать, но в принципе в Java есть статические анализаторы кода, которые тебе подскажут, если ты забыл закрыть ресурс. Проблемой при хорошей организации процесса я это не считаю.
Хм. Ну это и про goto можно сказать — что недостаток языка компенсируется правильной организацией процесса и статическими анализаторами кода.
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, AlexRK, Вы писали:
ARK>>Это бред, потому что в любом случае в машинном коде ифы будут — исключения это будут, коды возврата или что угодно еще. Проверки либо есть, либо их нет (тогда просто будет UB).
J>откуда ифы при работе с исключениями? Вся машинерия исключений запускается только при вызове throw.
Да, наверно тут я не прав. Я имел в виду изначальные проверки в том месте, где может быть ошибка — от них никуда не деться. Переполнение там, деление на ноль или еще чего-нибудь. Выше по иерархии вызовов проверок уже не будет (впрочем, смотря как реализовать).
Здравствуйте, jazzer, Вы писали:
J>>>Так в С++ как раз совершенно другая картина, именно из-за того, что 1) исключения не летят откуда угодно, потому что нет рантайма, который бы их бросал, и 2) из-за наличия деструкторов, в которые ты кладешь код очистки/отката в случае неудачи — именно за счет деструкторов в С++ достигается автоматическая обработка исключений. J>>>В D тем же самым занимается scope(failure). J>>>А в Java этого нет, да. Нам их тоже жалко J>>>Цитата относится больше к Java, чем не к исключениям вообще.
vsb>>В Java есть try-with-resources, он ничем не хуже вышеперечисленных вариантов.
J>Я, конечно, давно на это смотрел в последний раз, но, насколько я помню, try-with-resources работает только для совсем примитивных случаев. J>Т.е. для одного файла он сработает, а вот для массива файлов — уже нет.
try-with-resources вызывает метод close(). Можно имплементировать интерфейс Closeable и делать там что угодно. Можно создать класс CloseableFiles, оборачивающий массив ресурсов и всё будет работать.
J>Плюс надо явно указывать все, что ты хочешь, чтоб освободилось в случае исключения, никакой автоматики, как в С++, нет. J>Плюс там могут быть только декларации, если хочешь в промежутке между декларациями позвать еще какой-то код — придется писать еще один уровень try/catch. J>Поправь, если не так.
Всё так, но это вопрос удобства. О забытом закрыть Closeable предупредит статический анализатор. Лишний уровень вложенности — заводим новую функцию. Деструкторы удобней, но существенной разницы нет.
vsb>>Аналог scope(failure) это try-finally. Разница только в том, что в try-finally код освобождения ресурса не находится рядом с объявлением и добавляется один отступ для внутреннего кода. Это менее удобно, но принципиально ничего не меняет.
J>В Java можно сделать автоматическое Memento (в смысле, чтоб в случае исключения указанная переменная возвращалась к своему предыдущему значению автоматически)?
Можно чего-нибудь придумать, но выглядеть будет не очень, главным образом потому, что нет возможности передать переменную по ссылке, чтобы иметь возможность изменять её значение. Если бы была такая возможность, можно было бы сделать так:
memento(a, {
...
});
void memento(T& var, Function body) {
T valueBefore = var;
try {
body();
} catch (Exception e) {
var = valueBefore;
}
});
к сожалению +1 уровень вложенности аналогично try-with-resources, ну и такое не работает, потому что нет передачи по ссылке/указателю, только передача по значению. В общем то можно сделать класс-указатель, class Ref<T> { T value; }, но это уже совсем некрасиво будет.
vsb>>Разве что деструктор невозможно забыть вызвать, но в принципе в Java есть статические анализаторы кода, которые тебе подскажут, если ты забыл закрыть ресурс. Проблемой при хорошей организации процесса я это не считаю.
J>Хм. Ну это и про goto можно сказать — что недостаток языка компенсируется правильной организацией процесса и статическими анализаторами кода.
Ну в goto в ограниченном количестве (break/continue/return) ничего плохого нет. А с безудержной фантазией можно что-угодно сделать нечитаемым, имхо. Хотя это вопрос на отдельный холивар.
Здравствуйте, AlexRK, Вы писали:
ARK>Вот это коды возврата слили так слили — аж в 6 раз быстрее в варианте с двумя исключениями. ARK>И даже с одним исключением на миллион вызовов все равно быстрее.
1)А где у тебя тут коды возврата?
Не вижу. То как ты реализовал функции func1_* к кодам возврата отношения не имеет.
try/catch в реальном приложении стоит снаружи цикла.
2)Я что-то с ходу не нашел как исключения в .NET реализованы.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, AlexRK, Вы писали:
ARK>Да? То есть проверка результата malloc — это не код возврата?
Случаев, когда можно совместить результат и код возврата, ничтожно мало.
ARK>Когда снаружи — для одного исключения ничего не меняется, я проверял.
Ты сначала тест исправь.
А потом прогони два варианта.
С ошибкой и без.
Вариант без ошибки будет в подавляющем большинстве случаев.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
ARK>>Совсем без ошибки — да, медленнее. Но в реальном приложении на это рассчитывать не стоит, ИМХО. WH>Это 99.999% случаев.
Во-первых, реальное приложение выполняет реальную работу, и проверки на ее фоне будут в микроскоп не видны.
Во-вторых, какой тезис вы хотите доказать? Что exception-based code быстрее, чем retval-based code? Это надо обосновать, тестами, примерами или еще чем-то, а не просто голословными заявлениями про 146%.
Мой тест показывает, что одно исключение уже медленнее, чем миллион проверок.
Внесу свою лепту
Процессор прогнозирует ветвления-переходы, поэтому лишняя ветка на производительность практически не сказывается. Кроме того, существует такая процессорная реализация, как параллельное исполнение кода по двум веткам сразу. В случае исключения это невозможно в принципе.
Здравствуйте, WolfHound, Вы писали:
C>>DWARF-исключения заметно раздувают код для обработки ошибочных путей, а таблицы исключений заметно давят на кэш процессора. Так что использовать исключения для нормального flow-control'а — очень неэффективно. WH>А для нормального их никто и не использует.
А хочется.
WH>Они нужны для того чтобы обработать ситуацию когда что-то пошло не так.
В этом и проблема. "Что-то пошло не так" может различаться с точки зрения автора кода и программиста, который этот код использует. Классика жанра — должен ли File.open() кидать исключение, если файл не найден?
Здравствуйте, WolfHound, Вы писали:
ARK>>Мой тест показывает, что одно исключение уже медленнее, чем миллион проверок. WH>Так ты даже корректный тест написать не можешь...
Да, кстати. А чем это мои тесты некорректны? 5 миллионов проверок есть? Есть. Что еще надо?
Напишите уже внятно, изложите свою позицию. Если, конечно, она у вас есть.
Здравствуйте, AlexRK, Вы писали: ARK>Ну так приведите корректный тест с правильными кодами возврата, а не сотрясайте воздух.
Код с исключениями в 4.7 раза быстрее.
Release:
No exceptions: 00:00:00.0079723 (1784293664), 00:00:00.0016705 (1784293664)
Single exception: 00:00:00.0082693 (1785293665), 00:00:00.0016904 (1785293665)
Two exceptions: 00:00:00.0077591 (1785293665), 00:00:00.0017407 (1785293665)
Debug:
No exceptions: 00:00:00.0449391 (1784293664), 00:00:00.0218418 (1784293664)
Single exception: 00:00:00.0438513 (1785293665), 00:00:00.0222914 (1785293665)
Two exceptions: 00:00:00.0436745 (1785293665), 00:00:00.0216939 (1785293665)
Скрытый текст
using System;
using System.Diagnostics;
class Prog
{
int func1_1(int a, out int result)
{
result = a + 1;
if (a > 1000000)
return -1;
else
return 0;
}
int func1_2(int a, out int result)
{
if (func1_1(a, out result) == 0)
return 0;
else
return -1;
}
int func1_3(int a, out int result)
{
if (func1_2(a, out result) == 0)
return 0;
else
return -1;
}
int func1_4(int a, out int result)
{
if (func1_3(a, out result) == 0)
return 0;
else
return -1;
}
int func1_5(int a, out int result)
{
if (func1_4(a, out result) == 0)
return 0;
else
return -1;
}
int func2_1(int a)
{
if (a > 1000000)
throw new Exception("qq");
return a + 1;
}
int func2_2(int a)
{
return func2_1(a);
}
int func2_3(int a)
{
return func2_2(a);
}
int func2_4(int a)
{
return func2_3(a);
}
int func2_5(int a)
{
return func2_4(a);
}
private string Run(string title, int max)
{
var start1 = Stopwatch.StartNew();
int res1 = 0;
for (int i = 0; i < max; i++)
{
int val;
if (func1_5(i, out val) == 0)
{
func1_5(i, out val);
res1 += val;
}
else
break;
}
var end1 = start1.Elapsed;
var start2 = Stopwatch.StartNew();
int res2 = 0;
try
{
for (int i = 0; i < max; i++)
{
res2 += func2_5(i);
}
}
catch
{
// do nothing
}
var end2 = start2.Elapsed;
return String.Format("{0}: {1} ({2}), {3} ({4})", title, end1, res1, end2, res2);
}
public static void Main()
{
var prog = new Prog();
prog.Run("test run", 1000010);
prog.Run("test run", 1000010);
prog.Run("test run", 1000010);
var line1 = prog.Run("No exceptions", 1000000);
var line2 = prog.Run("Single exception", 1000002);
var line3 = prog.Run("Two exceptions", 1000003);
System.IO.File.WriteAllLines("c:\\temp\\temp.txt", new[] { line1, line2, line3 });
}
}
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
ARK>>Ну так приведите корректный тест с правильными кодами возврата, а не сотрясайте воздух. WH>Код с исключениями в 4.7 раза быстрее.
У вас тут мелкое жульничество в виде двойного вызова func1_5.
Кстати, чем это от моего последнего "некорректного" теста отличается?
Здравствуйте, AlexRK, Вы писали:
ARK>У вас тут мелкое жульничество в виде двойного вызова func1_5.
Исправил. Коды возврата всё равно сливают.
ARK>Кстати, чем это от моего последнего "некорректного" теста отличается?
А в цикле код возврата проверять, кто будет? Пушкин?
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, dimgel, Вы писали:
D>Ограничить дальность полёта можно только одним способом: ловить на каком-нибудь промежуточном слое и (опционально) заворачивать в нечто более понятное слою верхнему.
Открой для себя Boost.Exception, который позволяет присоединять к исключению информацию о контексте. Ничего заворачивать и не нужно.
Здравствуйте, jazzer, Вы писали:
J>Здравствуйте, vsb, Вы писали:
vsb>>На мой взгляд это проблема исключительно С++, а точнее его двух особенностей — отсутствие GC и запрет исключений в деструкторах.
J>Нет такого запрета.
Запрета нет, но классы, выбрасывающие из деструктора, регулярными считаться не не могут. Они разве что могут играть роль утилит с очень специфичным поведением. Недаром в C++11 деструкторы являются noexcept(true) по умолчанию.
Здравствуйте, uncommon, Вы писали:
U>Здравствуйте, jazzer, Вы писали:
J>>Здравствуйте, vsb, Вы писали:
vsb>>>На мой взгляд это проблема исключительно С++, а точнее его двух особенностей — отсутствие GC и запрет исключений в деструкторах.
J>>Нет такого запрета.
U>Запрета нет, но классы, выбрасывающие из деструктора, регулярными считаться не не могут. Они разве что могут играть роль утилит с очень специфичным поведением. Недаром в C++11 деструкторы являются noexcept(true) по умолчанию.
Все так, просто я не хотел, чтоб у читающих не-С++-ников создалось ложное впечатление, что в С++ такой запрет есть.
Рекомендация — да, есть, даже в Стандарте записана, но не запрет (т.е. программа, содержащия выбрасывание исключения из деструктора, не считается содержащей ошибку).
А насчет регулярности типов — на этот счет есть очень разные мнения.
Например, Степанов в своей книге включает в определение регулярности типа наличие копирования, присваивания и конструктора по умолчанию (плюс, емнип, сравнение на равенство и больше-меньше — чтоб тип можно было использовать в качестве ключа ассоциативного контейнера. Ну и хэш тогда тоже, наверное, заодно).
Ну и, плюс, не факт, что регулярность так уж необходима.
Здравствуйте, Miroff, Вы писали:
M>Запретить null как класс. Вместо него использовать либо Option, либо вообще ничего не использовать. Как в Scala:
M>[code=scala] M>val userOption:Option[User] = userDao.getUser(userId);
M>for (user <- userOption) { M> doSmth(user); M>} M>[/code]
и в чем тут профит? Вместо эксепшена в строке user->doSmth(), doSmth молча не будет выполнено, что выстрелит где-то позже и в другом месте...
опять же, сравни с
val user = userDao.getUser(userId);
if (user)
user->doSmth();
по строчкам — тоже самое, по символам — короче
Если добавить чуток сахара, можно было бы писать user?.doSmth(), как в груви. И тут кстати user — обычная ссылка, а не Option<User>
Здравствуйте, enji, Вы писали:
M>>Запретить null как класс. Вместо него использовать либо Option, либо вообще ничего не использовать.
E>И кстати — как его запретить? Вообще запретить простые ссылки? Т.е. везде только Option?
E>И во что тогда превратится такое: obj1.calc().doIt().getSome() ?
Можно сделать два вида ссылок — nullable и nonnullable. Компилятор легко может проверить все места, где возможны проблемы, и заставить вставить проверки.
Здравствуйте, dimgel, Вы писали:
D>есть одиночные разыменования obj_?.get после сложных проверок прямо строчкой выше, где я в т.ч. убеждаюсь, что obj_? не пуст; а чаще встречается obj_?.getOrElse(throw new Exception("понятное сообщение об ошибке")).
А в скале появился "?." ? Когда я пару лет назад на нее смотрел, в ней такого не было...
Здравствуйте, enji, Вы писали:
D>>Пожалуйста: doSmth(userOption.get). Здесь get может вылетить NoSuchElementException.
E>ну ок, и в чем разница с user.doSmth()? Или здесь doSmth — это именно внешний метод, и надо гарантировать непопадание туда null?
doSmth — внешний метод. Будь он методом User, тогда надо было бы писать так: userOption.map(_.doSmth()).
UPD. Вернее, для безбожного разыменования по аналогии с doSmth(userOption.get), так: userOption.get.doSmth().
E>т.е. зачем нам доп сущность Option[User]?
Чтобы на уровне системы типов (т.е. на этапе компиляции) отличать nullable от not null и бить по рукам программиста, когда он пытается заюзать nullable как not null.
Здравствуйте, dimgel, Вы писали:
E>>т.е. зачем нам доп сущность Option[User]?
D>Чтобы на уровне системы типов (т.е. на этапе компиляции) отличать nullable от not null и бить по рукам программиста, когда он пытается заюзать nullable как not null.
так подожди. Обычная ссылка — это nullable. Option[T] — тоже nullable, просто имеет свои методы (упрощающие обработку null) и добавляет мусорок при вызове метода нижележащего объекта. А где not null?
Здравствуйте, enji, Вы писали:
E>так подожди. Обычная ссылка — это nullable. Option[T] — тоже nullable, просто имеет свои методы (упрощающие обработку null) и добавляет мусорок при вызове метода нижележащего объекта. А где not null?
Not null там, где ты решишь не использовать для nullable обычные ссылки, а только Option. Т.е., грубо говоря, пройдёшься по всему коду поиском "null" и отовсюду его вычистишь, заменив соответствующие типы T на Option[T].
Ну, кроме того, в скале есть маркер trait NotNull, из которого лично я все свои классы наследую, и ежели мне в бреду приспичит (хотя ни разу не случалось) передать null параметру отнаследованного от NotNull типа, компилятор ругнётся.
Здравствуйте, dimgel, мы писали:
D>Not null там, где ты решишь не использовать для nullable обычные ссылки, а только Option. Т.е., грубо говоря, пройдёшься по всему коду поиском "null" и отовсюду его вычистишь, заменив соответствующие типы T на Option[T].
D>Ну, кроме того, в скале есть маркер trait NotNull, из которого лично я все свои классы наследую, и ежели мне в бреду приспичит (хотя ни разу не случалось) передать null параметру отнаследованного от NotNull типа, компилятор ругнётся.
В котлине это красивше порешали (единственное, что мне там понравилось, правда уже забыл подробности — помню только, что там нету никаких Option / Maybe, и все ссылки по умолчанию not null), но увы. Где котлин, а где скала.
Здравствуйте, WolfHound, Вы писали:
S>>Всякие статейки в инетах читаем? Похвально, но посоветовл бы заглянуть в реальные S>>исходники, и узреть как оно на самом деле там наворочено. WH>Я реально измерял производительность. WH>Коды возврата слили.
Это каким это образом пара проверок может слить?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, WolfHound, Вы писали:
S>>"Правильно реализованные", это как? Смотрел как они реализованы в ELF+gcc/clang/solarisstudio. WH>Вот так: WH>
WH>The second scheme, and the one implemented in many production-quality C++ compilers, is a table-driven approach.
Это только в том случае будет быстрее если, к примеру, запрос по таблице работает быстрее запроса через код ветвлением, что к примеру, в risk платформах может быть с лёгкостью не так, так как там проц в разы быстрее памяти и быстрее результат ветвлением найти чем запросом по таблице.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, elmal, Вы писали:
E>Здравствуйте, LaptevVV, Вы писали:
LVV>>Одна из серьезных проблем, которую практически не поминают — это парадигма обработки исключений. LVV>>По сути это событийное программирование. LVV>>И вот получается, что в одной проге смешаны как минимум две, а то и три парадигмы: ООП+событийное (и очень часто процедурное). E>Да хоть 10 парадигм в одной проге — ничего страшного. Главное парадигмы применять правильно и тогда, когда это имеет смысл. А проблемы с исключениями возникают тогда, когда с их помощью начинают хреначить логику, и они становятся не исключениями, а нормальным потоком выполнения программы.
Вот именно! Правильно применять — это надо быть опытным и дисциплинированным.
Программисты хреначить начинают — именно потому, что <предоставляется возможность>.
E>А типичная обработка исключений — это вывести пользователю сообщение об ошибке или матернуться в лог о том, что случилась какая то хрень. 99 процентов всех обработок исключений. В ряде случаев приходится некоторую логику навешивать. Например для вебсервисов внезапно могут куки авторизации стать невалидными, например по причине перезапуска сервиса. В этом случае нужно перелогиниться и повторить попытку. Соответствуем декорируем вызов метода, и обработчик исключения сделает перелогин и вызовет метод еще раз.
Ну, с этим согласен.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Здравствуйте, Vain, Вы писали:
V>Здравствуйте, WolfHound, Вы писали:
S>>>"Правильно реализованные", это как? Смотрел как они реализованы в ELF+gcc/clang/solarisstudio. WH>>Вот так: WH>>
WH>>The second scheme, and the one implemented in many production-quality C++ compilers, is a table-driven approach.
V>Это только в том случае будет быстрее если, к примеру, запрос по таблице работает быстрее запроса через код ветвлением, что к примеру, в risk платформах может быть с лёгкостью не так, так как там проц в разы быстрее памяти и быстрее результат ветвлением найти чем запросом по таблице.
Мне кажется, ты не понимаешь какой случай оптимизируется в table-driven реализации исключений. Это тот случай, когда никаких исключений не выбрасывается. Т.е. код для обработки исключений есть, но он никогда не вызывается. Поэтому поиск по таблицам делать вообще никогда не надо. И вся сложность переносится именно в эти таблицы, которые достаточно медленные, но это не важно, потому чтоо исключения должны происходить только в исключительных случаях, а в таких случаях нам на производительность наплевать.
Здравствуйте, uncommon, Вы писали:
V>>Это только в том случае будет быстрее если, к примеру, запрос по таблице работает быстрее запроса через код ветвлением, что к примеру, в risk платформах может быть с лёгкостью не так, так как там проц в разы быстрее памяти и быстрее результат ветвлением найти чем запросом по таблице. U>Мне кажется, ты не понимаешь какой случай оптимизируется в table-driven реализации исключений. Это тот случай, когда никаких исключений не выбрасывается. Т.е. код для обработки исключений есть, но он никогда не вызывается. Поэтому поиск по таблицам делать вообще никогда не надо. И вся сложность переносится именно в эти таблицы, которые достаточно медленные, но это не важно, потому чтоо исключения должны происходить только в исключительных случаях, а в таких случаях нам на производительность наплевать.
непонятно только, как исключения в этом случае будут быстрее кодов ошибок?
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, uncommon, Вы писали:
WH>>>Коды возврата слили. V>>Это каким это образом пара проверок может слить? U>Пара проверок vs никаких проверок — вот таким образом.
ну так и с кодами возврата также — не вроверяют и нет проверок
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, uncommon, Вы писали:
V>>непонятно только, как исключения в этом случае будут быстрее кодов ошибок? U>Коды ошибок всегда проверять нужно, произошла ошибка или нет. А исключения задействуются, только когда произошла ошибка.
Ну это не совсем так, при входе в try блок ведь чтото должно делаться, не так ли? Единственный вопрос, быстрее ли оно обычного условия в случае проверки кода возврата.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, dimgel, Вы писали:
D>Да и нехай значит. Всё лучше, повторюсь, чем молча глотать. А на верхнем уровне можно журналировать всё непойманное, потихоньку фикся ранее забытое.
Тут проблема еще глубже. Корни этого вопроса растут из того, что в некоторых новых языках решили отказаться от искючений признав их бякой. Аргумент один "ИМХО".
Так что предлагается не глотать, а тупо возиться с кодами возврата. Так что глотать будут детали, а ездить будут какие-нить коды возврата вроде E_FAIL (без каких либо деталей). Ну, или — да, скрыть ошибку и сделать вид что ничего не случилось.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, alex_public, Вы писали:
_>Т.е. в итоге мы всё равно приходим к кучей блоков try/catch раскиданных по всей программе, причём как показывает практика, чаще всего они будут прямо вокруг функции кидающей исключение. А это полностью убирает все преимущества исключений. Более того, при таком раскладе они становятся более многословным и при этом менее явным способом работы.
+1
Во всех мало-мальски крупных системах, которые я видел (не миллионы строк, но сотни тысяч) — именно так и есть. Причем я говорю о софте разного типа (записи судебных заседаний, ГИС, программы учета), созданном в разных компаниях (в т.ч. занимающихся исключительно разработкой ПО).
Здравствуйте, enji, Вы писали:
E>Здравствуйте, Miroff, Вы писали:
M>>Запретить null как класс. Вместо него использовать либо Option, либо вообще ничего не использовать.
E>И кстати — как его запретить? Вообще запретить простые ссылки? Т.е. везде только Option?
Запретить неинициализированные ссылки и ключевое слово null.
Т.е.
User user; // error
return null; // error
foo(null); //error
E>И во что тогда превратится такое: obj1.calc().doIt().getSome() ?
За такой код в приличном обществе принято бить канделябром потому что это нарушение DSP.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, vsb, Вы писали:
_>Когда-то этот вопрос уже здесь обсуждался, причём именно для случая C++ (в контексте запрета исключений в гугловских руководствах). Я тогда подробно (с подтверждающими примерами) изложил свою точку зрения. Могу повторить тезисно в этой темке.
_>Главное преимущество исключений в том, что они позволяют без напряжения для программиста и оптимально по быстродействию (как раз тесты из данной темки это подтверждают) передавать ошибку через множество уровней стека вызова. Так вот, на мой взгляд на практике нормальная обработка ошибок всегда происходит практически на следующем уровне вложенности, а не где-то далеко наверху. Происходит это по уже упоминаемой тут причине — иначе не получится выдавать адекватные сообщения об ошибках. Т.е. даже если мы сделаем идеальный для системы исключений случай с единственной на всю программу точкой реакции на ошибки (грубо говоря один try/catch на всю программу), то нам всё равно придётся по несколько раз переупаковывать их по ходу дела (хотя бы потому, что одинаковые низкоуровневые исключения в разных модулях означают разные вещи). Т.е. в итоге мы всё равно приходим к кучей блоков try/catch раскиданных по всей программе, причём как показывает практика, чаще всего они будут прямо вокруг функции кидающей исключение. А это полностью убирает все преимущества исключений. Более того, при таком раскладе они становятся более многословным и при этом менее явным способом работы.
Все эти промежуточные try/catch не добавляют ничего, если исключение не было брошено.
Суть в том, чтоб оптимизировать сценарий, когда исключения не летят.
А если летят — то пофиг, насколько медленно это делается, что там как переупаковывается и т.д.
Здравствуйте, Miroff, Вы писали:
E>>И во что тогда превратится такое: obj1.calc().doIt().getSome() ?
M>За такой код в приличном обществе принято бить канделябром потому что это нарушение DSP.
Напомни плиз, как это DSP расшифровывается. А то идею помню (что-то там типа не более двух ссылок в цепочке, иначе заворачивать в методы) и юзаю давно на автомате, а нагуглить не могу.
Здравствуйте, jazzer, Вы писали:
V>>Ну это не совсем так, при входе в try блок ведь чтото должно делаться, не так ли? Единственный вопрос, быстрее ли оно обычного условия в случае проверки кода возврата. J>Нет, ничего не надо делать, это обычный scope с толпой деструкторов в конце.
ну значит расходы на вход в скоп:
Здравствуйте, jazzer, Вы писали:
J>Все эти промежуточные try/catch не добавляют ничего, если исключение не было брошено. J>Суть в том, чтоб оптимизировать сценарий, когда исключения не летят. J>А если летят — то пофиг, насколько медленно это делается, что там как переупаковывается и т.д.
Ну вообще говоря это зависит от реализации, sjlj/seh/dwarf и т.п. Но в данном случае я говорил не об оптимизации, а об удобстве программиста. Ведь если ловить исключения непосредственно над функцией кидающей их, это получается заметно более громоздко и неудобно. Т.е. сама теоретическая идея исключений очень хороша, но на практике весьма редко получается использовать её преимущества (более того, при указанном выше раскладе, она скорее во вред уже идёт).
(зевает) Я, оказывается, и идею не помнил: не лезть через голову контрагента, а длина цепочки — уже следствие. Хотя юзал на автопилоте всё равно правильно, в подсознание вбил однако.
Здравствуйте, jazzer, Вы писали:
J>А можно продемонстрировать громоздкость и неудобность на примере? J>Вот чтобы передавалась наверх одна и та же информация, только один раз — кодом возврата, а другой раз — исключением. J>Ну чтоб было видно, что исключения сливают, на примере, так сказать.
(и там дальше по ветке основное будет) можно глянуть.
J>Скажем, нужно открыть 4 сокета, прочитав параметры из разных конфигов. Мы делаем вперемежку 4 вызова функции (или конструктора обертки) open_file, которая может бросить исключение EFileOpen(filename,errno), и 4 вызова функции open_socket, которая может бросить исключение ESocketOpen(sourceIP,sourcePort,targetIP,targetPort,errno). J>И вылетевшее исключение переупаковываются в, скажем, EEnvSetup(username).
Я же говорю, проблемы у исключений не в теории, а на практике. Конкретно на практике мне не приходилось писать код с последовательным открытием 4-х сокетов (причём так что проблемы с одним отменяют попытки с остальными) в одной функции. Но зато вполне частенько встречался код вида
auto f1 = open_file(get_profile(username));
if(!f1) f1=open_file(get_default_profile());
s1_ = open_socket(f1); // читаем из конфига
Понимаешь что получится, в случае переписывания такого варианта твоего кода на исключениях? )
J>Ну, моя практика от твоей отличается, видимо. J>Я вижу сплошные удобства в своем коде, и каждый вызов каждой функции в отдельный try/catch не заворачиваю.
Я говорил не про оборачивание каждого вызова, а про перехват исключений на следующем уровне (а не где-то высоко). Что ты как раз и продемонстрировал в своём примере. Но и оборачивание отдельных функций тоже возможно, если требуется продолжить нормальное исполнение программы после неудачного вызова функции.
(и там дальше по ветке основное будет) можно глянуть. J>Ну глянул. Моя позиция не изменилась. Ты там, вроде, мне не возражал.
Ну так консенсус (во всяком случае у меня с собеседникам) там сложился на формулировке "исключения хорошо подходят для обработки критических (т.е. после которых программа скорее всего прервёт исполнение) ошибок, а не всех видов ошибок вообще". Причём тут есть ещё хитрый нюанс — ошибки во многих API (типа того же открытия файла) могут быть как критическими, так и нет, в зависимости от контекста использования. Получается что по идее надо бы иметь по два вида API в таких библиотеках. Но Boost.Asio — это скорее исключение, чем правило...
В общем для большинства случаев (если не трогать таки вещи как скажем new) мне кажется, что лучше делать интерфейс без исключений. Ну или тогда уж оба варианта, как в Asio.
J>Во-первых, в таких случаях используются функции типа can_open_file(filename), но даже в твоей формулировке: J>
Ну так разве это всё не ужас, по сравнению с тем моим кодом в 3 строчки? )
J>Ну а теперь изобрази свой замечательный вариант на кодах возврата. С донесением всей необходимой информации на верхний уровень: если не открылся файл, то какой и почему, если сокет, то какой и почему. J>Жду-пожду.
Так а зачем выносить эту информацию на верхний уровень? ) Пользователю будешь это показывать? ))) Как раз это и обсуждалось в той старой темке...
J>Да, продемонстрировал. И как, это было так ужасно, что ты демонстрацию стер с глаз долой? Целая одна строчка с std::throw_with_nested?
Не о том речь. Просто у исключений есть очевидные преимущества. Но они проявляются как раз для случая проброса через длинный стек вызова. Т.е. в идеальной модели с исключениями мы имеет вообще один единственный блок с try/catch на всю программу. Так вот, я как раз и утверждаю, что на практике такая модель может получиться только в очень специфических случаях. И ты только подтвердил это своим примером. Да, и я не говорю, что этот код сильно ужасен. Просто при такой схеме все преимущества исключений пропадают. А недостатки (например неявность ошибок) остаются.
_>>Но и оборачивание отдельных функций тоже возможно, если требуется продолжить нормальное исполнение программы после неудачного вызова функции. J>Да, на верхнем уровне, скажем, в обработчике кнопок меню: прилетело исключение — рассказал пользователю, что облом (с показом по требованию всей собранной исключениями через throw_with_nested информации), и работаешь себе дальше. J>Потому что если можно продолжить нормальное исполнение программы после неудачного вызова функции на низком уровне, то это не исключительная ситуация по определению, правда?
Да, и тогда применять исключения не надо. Собственно о том и речь, что исключения хорошо подходят для решения только небольшой части общей задачи обработки ошибок. В отличие от тех же кодов возврата, которые подходят везде.
Здравствуйте, jazzer, Вы писали:
J>Ну то есть вместо многообразия кодов возврата есть просто булевская логика, а информация об ошибке не передается наверх ни в каком виде. Желающие могут пройти в лог.
А зачем нужно многообразие кодов возврата? Когда сектор с диска прочитать не удается, там тоже тонна кодов возврата, только это обычно нафиг никому не нужно.
Текстовый лог просто как пример, информацию об ошибке можно писать в БД с нужной степенью детализации.
J>Про автоматический вызов деструкторов для того, что успело сработать, я уж и не заикаюсь. Супер, чо. Полный эквивалент.
Вызов деструкторов инкапсулируется в ту же самую функцию OpenSocket (точно так же, как у вас работа с исключениями инкапсулирована в отдельной функции).
J>А в моей практике никаких нехилых объемов не присутствует. ЧЯДНТ?
А в моей присутствует. Кто из нас прав?
То, что с исключениями можно писать хороший код — никто не спорит. Но можно писать и не очень хороший. Вопрос в том, каково процентное соотношение того и другого.
И да, с кодами возврата тоже можно писать хороший код, даже операционные системы пишут.
Здравствуйте, Cyberax, Вы писали:
C>Я вчера разговаривал с Rust-овцами на Bay Area Rust Meetup
Rust-овцы.
Смешно. Извините.
C>Что рассказали: C>1) Исключения можно сделать хоть сейчас, это технически не проблема вообще. Тем более, что уже есть механизм unwinding'а. C>2) Но их делать не будут из принципа. C>3) Возможно, что в будущем принципы поменяют, если появится что-нибудь типа полноценной транзакционной памяти.
C>Аргумент такой, что исключения в надёжных системах возможно нормально использовать только при полной транзакционности кода ("strong exception guarantee"). Оказывается, что статически проверить обеспечение такой гарантии очень сложно (они пытались) и даже в академических языках её нет.
Блин, радует, что разработчики хоть одного ЯП это понимают.
C>Потому они приняли решение сделать более мягкое решение, аналогичное "weak exception guarantee". В Rust при броске fail'а они убивают всю задачу, в которой код исполняется. Это обеспечивает вполне естественную границу для изоляции ошибки. Похожий подход и в Erlang'е применён, если что. C>Ещё в качестве средства изоляции — при броске fail'а все разделяемые ресурсы (в Rust они есть в виде RWArc), которые в тот момент использовала задача, помечаются как "испорченные".
По-хорошему, ИМХО, надо бы обязать все деструкторы ресурсов быть NoThrow и NoAllocate, и вызывать их при разрушении задачи в нужной последовательности.
C>На вопрос про промежуточные слои они ответили, что у них тут вам не Java, и на практике промежуточных слоёв очень мало и почти всегда нужно делать семантическую трансляцию ошибок.
Здравствуйте, Vain, Вы писали:
V>Ну это не совсем так, при входе в try блок ведь чтото должно делаться, не так ли? Единственный вопрос, быстрее ли оно обычного условия в случае проверки кода возврата.
Во-первых, try/catch не надо вставлять куда попало. try/catch-ей должно быть очень и очень мало и только там, где понятно, что делать с исключениями. (А проверки ошибок возврата, как ты понимаешь, повсеместно.) Это, кстати, очень частая ошибка людей, которые не знают, как правильно использовать исключения. Во-вторых, нужно научится использовать RAII.
Здравствуйте, Cyberax, Вы писали:
C>Я вчера разговаривал с Rust-овцами на Bay Area Rust Meetup
C>Что рассказали: C>1) Исключения можно сделать хоть сейчас, это технически не проблема вообще. Тем более, что уже есть механизм unwinding'а. C>2) Но их делать не будут из принципа.
Ну, и будут каждую функцию заворачивать в try! или с монадами извращаться. Зато принцип!
Здравствуйте, Cyberax, Вы писали:
C> В Rust при броске fail'а они убивают всю задачу, в которой код исполняется.
C>Ещё в качестве средства изоляции — при броске fail'а все разделяемые ресурсы (в Rust они есть в виде RWArc), которые в тот момент использовала задача, помечаются как "испорченные".
Можно в двух словах для тех, кто не знаком с Rust'ом, что такое задача? Это процесс, поток или что-то ещё?
Меня интересует, если ресурс, например, файл, будет помечен как испорченный, то другой процесс, работающий с ним же, продолжит свою работу?
Здравствуйте, uncommon, Вы писали:
U>Во-первых, try/catch не надо вставлять куда попало. try/catch-ей должно быть очень и очень мало и только там, где понятно, что делать с исключениями. (А проверки ошибок возврата, как ты понимаешь, повсеместно.) Это, кстати, очень частая ошибка людей, которые не знают, как правильно использовать исключения.
Справедливости ради, в той же жаве try/finally приходится юзать на каждый чих, к примеру:
val stmt = conn.prepareStatement("...")
val result = try {
...
} finally {
stmt.close()
}
UPD. Потому что деструкторов нету с авто-закрытием ресурсов при выходе из scope.
Здравствуйте, uncommon, Вы писали:
ARK>>А в моей присутствует. Кто из нас прав? U>Покажи свой код. Дьявол, как обычно, в деталях.
Код принадлежит не мне, поэтому показать его я не могу.
Но могу ответить на любые уточняющие вопросы.
Хотя, честно говоря, не вижу смысла в этом. То, что многие (а из мною лично виденных — все без исключения) крупные системы содержат кучу мусора с try/catch/finally — это факт. То, что это можно переписать лучшим образом — тоже факт. То, что это не переписывается — тоже факт. О чем спор?
Здравствуйте, uncommon, Вы писали:
C>>Что рассказали: C>>1) Исключения можно сделать хоть сейчас, это технически не проблема вообще. Тем более, что уже есть механизм unwinding'а. C>>2) Но их делать не будут из принципа.
U>Ну, и будут каждую функцию заворачивать в try! или с монадами извращаться. Зато принцип!
Маразм с каждой функцией нужен только в языках, не имеющих контроля за исключениями, в которых исключение может вылететь где угодно (то есть во всех менйстримовых ЯП).
Здравствуйте, uncommon, Вы писали:
V>>Ну это не совсем так, при входе в try блок ведь чтото должно делаться, не так ли? Единственный вопрос, быстрее ли оно обычного условия в случае проверки кода возврата. U>Во-первых, try/catch не надо вставлять куда попало.
Не выходит оно так, приходится вставлять везде где надо пробрасывать исключение, т.е. почти везде.
U>try/catch-ей должно быть очень и очень мало и только там, где понятно, что делать с исключениями. (А проверки ошибок возврата, как ты понимаешь, повсеместно.) Это, кстати, очень частая ошибка людей, которые не знают, как правильно использовать исключения. Во-вторых, нужно научится использовать RAII.
Наивняк.. Это ты ещё корбу и другие библы не использовал.
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, alex_public, Вы писали:
_>Я же говорю, проблемы у исключений не в теории, а на практике. Конкретно на практике мне не приходилось писать код с последовательным открытием 4-х сокетов (причём так что проблемы с одним отменяют попытки с остальными) в одной функции. Но зато вполне частенько встречался код вида _>
_>auto f1 = open_file(get_profile(username));
_>if(!f1) f1=open_file(get_default_profile());
_>s1_ = open_socket(f1); // читаем из конфига
_>
_>Понимаешь что получится, в случае переписывания такого варианта твоего кода на исключениях? )
Это не самый распространенный случай. Наиболее общий случай это линейный код, без каких либо ветвлений. Он есть вообще везде:
Представь себе простую задачу — отсылка емейла. На входе у тебя просто конфиг, фактически, указано где что взять, ничего конкретного. Здесь всё делается обычным линейным кодом.
Самый лучший способ — избавиться вообще от любых ветвлений. В твоем случае надо обкладывать линейный код условными операторами. То есть, на ровном месте вводить ветвления.
Единственная вещь, где мешают исключения — это восстановления после ошибок. Собтсвенно, мешают не потому ,что исключения, а потому, что исключения в общей массе не поддерживают восстановление. Собтсвенно, это не очень большая проблема, ибо линейного кода на порядки больше.
Здравствуйте, smeeld, Вы писали:
S>Вы немного не в ту степь завернули. Тут говорилось не про hardware exceptions.
Исключения, как вызванные проблемами в процессоре (деление на 0, page fault), так и программно,в нативном коде идут через обработку в ядре.
S>Не надо было за эталон брать то, что в С++, академики праздные, накорябали своими белыми ручонками.
С++ тут совершенно ни при чем. Исключения, которые в C++ выбрасываются с помощью throw, в Windows реализуются с помощью вызова RaiseException, а далее в ядро.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>С++ тут совершенно ни при чем. Исключения, которые в C++ выбрасываются с помощью throw, в Windows реализуются с помощью вызова RaiseException, а далее в ядро.
Что? throw в windows переключает в ядро? Зачем?
Hardware exceptions-ы предназначены для контроля
фундаметнальных ошибок исполнения инструкций, и для
аппаратной поддержки фундаментального функционала ОС.
Если же в юзерспайсе Вася решил откатится вверх по стеку,
зачем для этого в ядро нырять-то?
Здравствуйте, tyomchick, Вы писали:
T>Вообще, на мой взгляд, общеиспользуемые библиотеки не должны наружу кидать исключение, за исключением случаев, которые возникают в результате их некорректного использования. Т.е. по сути эти исключения нужны только для отладки.
T>Меня тоже например раздражает, когда функция открытия файла кидает исключение. Ошибку открытия файла почти всегда удобнее обработать без исключений.
Ну можно ещё предоставлять оба интерфейса. ) Что местами и наблюдается. Но очень редко. )
Кстати, C++ библиотека вполне может кидаться исключениями, даже если автор библиотеки их и не использует сам. К примеру достаточно использовать обычную (а не nothrow) версию new. ))) Я такое видел. Правда это даже и не плохо, т.к. подобная ошибка в 99% случаев относится к критическим.
Здравствуйте, alex_public, Вы писали:
I>>Это не самый распространенный случай. Наиболее общий случай это линейный код, без каких либо ветвлений. Он есть вообще везде:
_>Ты не понял какую идею демонстрирует данный пример. И кстати говоря это как раз очень распространённый случай.
Цитирую себя:
Единственная вещь, где мешают исключения — это восстановления после ошибок. Собтсвенно, мешают не потому ,что исключения, а потому, что исключения в общей массе не поддерживают восстановление. Собтсвенно, это не очень большая проблема, ибо линейного кода на порядки больше.
_>Да, и кстати, ещё отдельный вопрос. Попробуй глянуть в своём последнем приложение, сколько там имелось случаев обработки критических ошибок и сколько случаев обработки обычных. Вот лично у меня критические вообще очень редко попадаются (только что-то из области нехватки оперативной памяти или переполнения диска), а обычные естественно не редкость... )
Линейная логика никуда не девается. Возьми какой юзерский сценарий и опиши подробно. Практически всегда будет минимум одна линейная цепочка, ошибка в которой означает фейл всего сценария.
Здравствуйте, smeeld, Вы писали:
S>Что? throw в windows переключает в ядро? Зачем? S>Hardware exceptions-ы предназначены для контроля S>фундаметнальных ошибок исполнения инструкций, и для S>аппаратной поддержки фундаментального функционала ОС. S>Если же в юзерспайсе Вася решил откатится вверх по стеку, S>зачем для этого в ядро нырять-то?
Исключения C++ могут быть реализованы очень разными способами. Это и sjlj и dward и seh. А ещё есть исключения самой винды (которые могут быть в том числе и аппаратными), про которые и писал Павел. Так вот в исключениях винды используется механизм seh и при соответствующей поддержке компилятора эти исключения можно так же перехватывать через обычный C++ catch.
Здравствуйте, 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, Вы писали:
C>>>Что рассказали: C>>>1) Исключения можно сделать хоть сейчас, это технически не проблема вообще. Тем более, что уже есть механизм unwinding'а. C>>>2) Но их делать не будут из принципа.
U>>Ну, и будут каждую функцию заворачивать в try! или с монадами извращаться. Зато принцип!
ARK>Маразм с каждой функцией нужен только в языках, не имеющих контроля за исключениями, в которых исключение может вылететь где угодно (то есть во всех менйстримовых ЯП).
Здравствуйте, uncommon, Вы писали:
U>Если ты живёшь на помойке, то всё что ты видишь каждый день это куча говна. Это факт для тебя. Но твой личный опыт ничего не доказывает
Безусловно, мой опыт ничего не доказывает. И ваш тоже.
U>особенно учитывая тот факт, что у тебя, мягко говоря, неверные представления об исключениях.
Во-первых, учтите тот факт, что обсуждение личности собеседника на этом форуме запрещено.
Во-вторых, мои представления об исключениях вполне исчерпывающи. Тем не менее, я не считаю их хорошим средством, даже при условии "правильного" применения. Да, и коды возврата панацеей я тоже не считаю.
Здравствуйте, vsb, Вы писали:
vsb> Проблема в том, что во-первых есть очень много исключений, которые вылетают очень редко. Например ошибка связи с БД. У нас БД надёжная и работает надёжно и с ней нет ошибок связи. Но теоретически то вылететь может! А значит придётся обкладывать try-catch каждую операцию с БД.
Что значит теоретически? Проблемы связи очень даже реальны. А если программа рассчитывает на то, что эти проблемы теоретические, то работает обычно хреново.
vsb> Во-вторых ряд исключений не вылетит вообще никогда. Например Integer.parseInt("1"). Никогда тут не вылетит исключение NumberFormatException. А компилятор попросит обложить try-catch-ем.
Реально таких случаев не так уж много. Может пяток штук наберётся. Если, конечно, не учитывать случаи когда программист просто мамой клянётся, что не вылетит. А оно вдруг таки вылетает.
vsb> В-третьих ряд исключений могут вылететь в любом месте. В разных языках по-разному. Например NullPointerException теоретически может вылететь на любой строчке, где есть ссылки или вызывается функций. StackOverflowError может вылететь опять же везде, где вызывается любая функция. Что с ними делать?
Ловить и соответственно обрабатывать, что ж ещё. Ну или писать программы без багов. Хотя, думаю первое на порядки проще.
vsb> Первая и третья проблема решаются выделением NonChecked Exceptions. Опять же возникает исходная проблема, что хотя исключение и вылетает раз в год или "по идее" не должно вылетать, но оно вылетит. NullPointerException особенно. И это надо помнить и обрабатывать.
vsb> Вторая проблема вообще никак не решается. Вот получил я userId от API, вызываю userService.getUserInformation(userId). А он мне говорит обрабатывай UserNotFoundException. Не может такого быть, я только что этот userId из базы считал, он там 100% есть.
Ага, а 3 миллисекунды назад этого юзера удалили в другом треде.
vsb> Как я буду обрабатывать? Только выкину UnexpectedSituationException, а вы там выше логгируйте и разбирайтесь. И таких catch-ей на каждом шагу очень много будет. Синтаксический хлам.
Чаще такой результат — сигнал плохого дизайна кода. Если ты получил userId, то может сразу вернуть User, зачем эти бессмысленные однотипные ID? А раз у тебя уже есть User, то и UserNotFoundException станет не нужен.
vsb> Кстати ещё одна интересная проблема с наследованием. Вот описали мы
vsb>
vsb> теперь примеряем его на FileInputStream, а оказыавется, что close там хочет кидать IOException. Чего нам делать? Либо добавляем в throws IOException, либо перекидыаем как непроверяемое исключение. И имеем опять что имеем.
Вроде эту проблему решили, правда не помню с какой Явы, сделав ковариантные сигнатуры методов.
Можно посмотреть на примере java.io.StringWriter.flush(). Он не содержит throws, хотя Writer.flush() содержит. А значит, если у тебя объект типа StringWriter, то можно делать flush без try-блока, а если Writer, тогда блок обязателен. Интересно почему подобное не сделано для метода close, может какая-нибудь совместимость ломается?..
Хотя это в данном конкретном случае не важно, если ты знаешь, что у тебя StringWriter, то его можно и не закрывать.
Кстати, по поводу исключений в finally-блоках ("деструкторах"), в 1.7 ввели getSuppressed. По-моему, вполне хорошее решение.
Здравствуйте, uncommon, Вы писали:
U> Ещё Брюс Эккель в своё время писал, как он попробовал Питон, и все проблемы с исключениями, которые до этого у него были в других языках, исчезли.
Интересно, а какие у него были проблемы с исключениями и как их помог решить Python?
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Я не знаю, можно ли это как-то отключить или изменить. Если кто-то знает — буду рад услышать.
Можно изменить взяв компилятор C++ с другой реализацией исключений. К примеру mingw есть во всех трёх базовых разновидностях. )))
Но не вижу особого смысла это делать, т.к. SEH вполне хороший механизм с практически нулевыму накладными расходами (при работе без кидания исключение, что собственно главное, т.к. производительность исключительной ситуации никого не волнует).
А в опциях компилятора можно включить/выключить перехват этих исключений с помощью catch. В принципе это конечно приятный механизм (иногда полезно ловить аппаратные исключения), но к сожалению не кроссплатформенный, что в современном программирование весьма печально.
Здравствуйте, uncommon, Вы писали:
U>Чем круто? Опыт показывает, что в дизайне языков программирования прагматизм имеет больший успех чем принципиальность. Но насчёт раста, время покажет. Пока что они перелопачивают свой код на новый лад каждые несколько месяцев.
Давно не хватает, на самом деле, принципиальных языков. В С++ был принцип "не платить за то, что не используется" — это стало сущесвенным стимулом в развитии.
C>>Я уж не говорю про то, что исключения не используются и в огромном количестве проектов на С++. При полном отсутствии сахара типа try!. U>И о чём это говорит, кроме того, что есть много людей, которые не знают как использовать исключения.
Стоит задуматься почему.
U> Более интересен будет опыт таких языков как Python, где исключения с самого начала в языке и рантайме, а кодов возврата нет как таковых. Так вот в Питоне ни у кого почему-то не возникает с исключениями никаких проблем.
Вот уж не надо сказок. Я на досуге после миграции нашего проекта на PyPy сижу и переписываю код вида:
for ln in open("somefile"):
....
на
with open("somefile") as fl:
for ln in fl:
....
В обычном Python'е он "работает" из-за того, что файловый объект детерминированно уничтожается при выходе из scope'а. А вот в PyPy уже честный GC и ВНЕЗАПНО оно как-то не очень работает.
Но и это фигня. Например, надо поймать исключение о нарушении констрейнта внешнего ключа. С трёх раз догадайтесь как это правильно сделать.
Здравствуйте, Cyberax, Вы писали:
C>Вот теперь делаем так, чтобы write кидал исключение, а потом в деструкторе socket'а ещё раз исключение вылезало (вполне реальная ситуация!).
Вот так говоришь с человеком, вроде как всё нормально. А потом бабах! Он выдаёт тебе такое. И не знаешь, что думать.
Здравствуйте, Cyberax, Вы писали:
C>Уже не совсем последние. Почти решили сделать оператор вызова с передачей исключения и встроенный в компилятор сахар для Result: C>
Кстати, можно идти еще дальше: оставить только в сигнатуре "throws". Будет не видно, что именно возвращает Result<>, но в данном случае это не вредит, поскольку код тупо переписывается в цепочку ифов и можно статически проконтролировать все, что требуется (инварианты, разрушение ресурсов и т.п.).
Здравствуйте, Cyberax, Вы писали:
C>Уже не совсем последние. Почти решили сделать оператор вызова с передачей исключения и встроенный в компилятор сахар для Result: C>
Здравствуйте, uncommon, Вы писали:
U>Они ещё добавили сигилов? Я даже не знаю, что сказать. Творят, что хотят, управы на них нет.
Ну до этого они поубирали нафиг @ и #. Так что баланс пока всё ещё положительный.
U>Подожди, тут вроде как похоже на обработку Nullable<T> в C#. Или нет?
Это один из минусов именно оператора "?".
Кстати, "try!(open_socket(...))" тоже заменяется на "open_socket(...)?"
Мне офигительно не нравится предложение с Carrier-трейтом, но FromError выглядит очень правильно. Я подобный подход и в С++ использовал.
Здравствуйте, AlexRK, Вы писали:
ARK>Кстати, можно идти еще дальше: оставить только в сигнатуре "throws".
В Rust принципиально в сигнатуре функции указывают весь контракт для типов. Хотя можно в качестве сахара на уровне компилятора, конечно, сделать.
Здравствуйте, uncommon, Вы писали:
C>>Вот теперь делаем так, чтобы write кидал исключение, а потом в деструкторе socket'а ещё раз исключение вылезало (вполне реальная ситуация!). U>Вот так говоришь с человеком, вроде как всё нормально. А потом бабах! Он выдаёт тебе такое. И не знаешь, что думать.
Ну а что делать? В деструкторе сокета, очевидно, будет вызываться библиотечный close(). Который может возвращать ошибки: http://linux.die.net/man/2/close
В некоторых условиях эти ошибки будут важны, так как они могут означать, что буферизованные данные не были записаны в сокет/файл/пайп.
Здравствуйте, Cyberax, Вы писали:
C>Здравствуйте, AlexRK, Вы писали:
ARK>>Кстати, можно идти еще дальше: оставить только в сигнатуре "throws". C>В Rust принципиально в сигнатуре функции указывают весь контракт для типов. Хотя можно в качестве сахара на уровне компилятора, конечно, сделать.
Я не очень понятно выразился, я имел в виду — оставить в сигнатуре все, что есть, но убрать из тела "try" и "?".
Здравствуйте, AlexRK, Вы писали:
C>>В Rust принципиально в сигнатуре функции указывают весь контракт для типов. Хотя можно в качестве сахара на уровне компилятора, конечно, сделать. ARK>Я не очень понятно выразился, я имел в виду — оставить в сигнатуре все, что есть, но убрать из тела "try" и "?".
Можно, причём несложными изменениями в компиляторе. Остаётся вопрос не лучше ли оставить явные ? для улучшения читабельности.
Здравствуйте, alex_public, Вы писали:
_>Преимущества исключений появляются, только если обработка ошибки идёт где-то намного выше (по стеку вызова, не по линейному коду) вызова Save.
Я так и думал — у тебя вызываемый код знает детали реализации вызывающего кода. Браво !
I>>То есть, ты предлагаешь переписывать весь слой, если мы начнем вызывать его немного другим способом. Правильно тебя понимаю ?
_>При проектирование приложения такие вещи (что критично, а что нет) сразу понятны, так что по нормальному никаких переписываний не должно быть. А вот в случае библиотек оптимальным было бы наличие обоих видов интерфейсов...
Ну да, дизайн сразу очевиден на любой срок наперёд. В твоих проектах похоже треботвания никогда не менялись.
ребята послушали alex_public и пришли к тому, от чего хотели убежать. Собтсвенно демонстрация того, что возврат значения даёт хаос уже на самом первом уровне
Здравствуйте, Cyberax, Вы писали:
C>Здравствуйте, uncommon, Вы писали:
C>>>Вот теперь делаем так, чтобы write кидал исключение, а потом в деструкторе socket'а ещё раз исключение вылезало (вполне реальная ситуация!). U>>Вот так говоришь с человеком, вроде как всё нормально. А потом бабах! Он выдаёт тебе такое. И не знаешь, что думать. C>Ну а что делать? В деструкторе сокета, очевидно, будет вызываться библиотечный close(). Который может возвращать ошибки: http://linux.die.net/man/2/close
close() может возвратить ошибку, а деструктор socket'а исключение выбросить не может. Подумай сам, что этот деструктор делает. И посмотри реализацию твоей любимой библиотеки для работы с файлами или с сетью.
C>В некоторых условиях эти ошибки будут важны, так как они могут означать, что буферизованные данные не были записаны в сокет/файл/пайп.
Хочешь, чтобы была уверенность в том, что буферизованные данные были записаны, вызывай явно функцию в конце работы с socket-ом. На деструктор нельзя в этом случае надеяться.
Здравствуйте, Cyberax, Вы писали:
C>Ну а что делать? В деструкторе сокета, очевидно, будет вызываться библиотечный close(). Который может возвращать ошибки: http://linux.die.net/man/2/close
C>В некоторых условиях эти ошибки будут важны, так как они могут означать, что буферизованные данные не были записаны в сокет/файл/пайп.
ну вестимо должен быть явный метод close(), который можно позвать, если важна судьба буферизованных данных. А деструктор исключение глотает, возможно что-то записывая в лог...
А, кстати, как это разруливается в питоне/шарпе/яве (деструкторов там нет, но есть with)?
И что нам по этому поводу предлагают коды возврата?
Здравствуйте, enji, Вы писали:
C>>В некоторых условиях эти ошибки будут важны, так как они могут означать, что буферизованные данные не были записаны в сокет/файл/пайп. E>ну вестимо должен быть явный метод close(), который можно позвать, если важна судьба буферизованных данных. А деструктор исключение глотает, возможно что-то записывая в лог...
А как быть с библиотечным кодом, который может не знать в какой лог ему писать?
E>А, кстати, как это разруливается в питоне/шарпе/яве (деструкторов там нет, но есть with)? E>И что нам по этому поводу предлагают коды возврата?
Пользователя заставят явно обработать возможную ошибку.
Здравствуйте, uncommon, Вы писали:
C>>Я подобный подход и в С++ использовал. U>Подкинь ссылочку? Интересно посмотреть.
Лень искать, что-то типа такого:
Здравствуйте, uncommon, Вы писали:
C>>Ну а что делать? В деструкторе сокета, очевидно, будет вызываться библиотечный close(). Который может возвращать ошибки: http://linux.die.net/man/2/close U>close() может возвратить ошибку, а деструктор socket'а исключение выбросить не может. Подумай сам, что этот деструктор делает.
У деструктора есть магические силы, позволяющие ему избегать ошибок в close()?
U>И посмотри реализацию твоей любимой библиотеки для работы с файлами или с сетью.
Дефолтная реализация паникует задачу. При использовании явной обработки через вызов drop — на совести пользователя.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Здравствуйте, vsb, Вы писали:
PD>Добавлю свои три копейки.
PD>1. Скорость обработки. Впрочем, от среды исполнения зависит. В нативных средах Винде+MSVC исключения реализуются через переход в кольцо 0 и обратным возвратом. Обработка исключений далеко не так проста, как кое-кому тут кажется.
Я исправил у тебя ошибку там.
Это в чистом виде артефакут MSVC, все вопросы к нему. Мало того, что Винда — не единственная ось на рынке, так и MSVC — не единственный компилятор под винду. Ходить в ядро, потому что в пользовательском коде что-то не распарсилось — это бред, потому что мешает всем работающим процессам.
С другой стороны, даже если это и так, то и хрен бы с ним.
Во-первых, нам нужна скорость работы код в отсутствие исключений, а не когда они летят как лепестки сакуры; а во-вторых, Винда — это ось для домохозяек, кто там в ней походы в ядро считает, "косынка" работает — и ладно...
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, uncommon, Вы писали:
ARK>>>А в моей присутствует. Кто из нас прав? U>>Покажи свой код. Дьявол, как обычно, в деталях.
ARK>Код принадлежит не мне, поэтому показать его я не могу. ARK>Но могу ответить на любые уточняющие вопросы.
Это беспредметный разговор. Речь идет именно о коде.
Так что покажи код. Переименуй там все классы/переменные, чтоб не было ничего секретного.
ARK>Хотя, честно говоря, не вижу смысла в этом. То, что многие (а из мною лично виденных — все без исключения) крупные системы содержат кучу мусора с try/catch/finally — это факт. То, что это можно переписать лучшим образом — тоже факт. То, что это не переписывается — тоже факт. О чем спор?
Упоминание finally намекает, что код, о котором ты говоришь — не С++. Тогда да, вопросов нет — там ад и израиль.
Здравствуйте, Pzz, Вы писали:
Pzz>Этим исключениям хорошо бы как-то законодательно синтаксически ограничить дальность полета. Потому что когда в ваш высокоуровневый код, работающий в терминах высокоуровневых абстракций, откуда-нибудь с самого нижнего уровня прилетит исключение про то, чего вы в своем высокоуровневом коде сказать-то и не можете, то что вы с ним будете делать?
Мне нравится как в джаве сделано, все функции генерирующие исключения специально помечаются. И либо ты его обрабатываешь, либо тоже генерируешь исключение. И это все видно в коде.
PD>>1. Скорость обработки. Впрочем, от среды исполнения зависит. В нативных средах Винде+MSVC исключения реализуются через переход в кольцо 0 и обратным возвратом. Обработка исключений далеко не так проста, как кое-кому тут кажется.
J>Я исправил у тебя ошибку там.
Не понял. Где там исправил и как ? И что за ошибка ?
J>Это в чистом виде артефакут MSVC, все вопросы к нему. Мало того, что Винда — не единственная ось на рынке, так и MSVC — не единственный компилятор под винду.
Я про другие ОС и не говорил. Что же касается MSVC — не знаю, как там GNU C++ с ними обходится, если знаешь — расскажи.
>Ходить в ядро, потому что в пользовательском коде что-то не распарсилось — это бред, потому что мешает всем работающим процессам.
Тем не менее это так. Мешает — согласен.
J>С другой стороны, даже если это и так, то и хрен бы с ним. J>Во-первых, нам нужна скорость работы код в отсутствие исключений,
Это спорный вопрос. Исключения — не всегда ошибки, а порой и контракт.
До ошибок , действительно, лучше дело не доводить, вместо того, чтобы доводить и обрабатывать — насколько возможно. К примеру, выход индекса за границы массива лучше просто не допускать.
А вот контракт предполагает, что вполне законными являются действия по всем его вариантам : как с нормальным выходом, так и с исключением.
Вот тебе пример, позаимствованный у Рихтера.
Сильно разреженная очень большая матрица. Операция всего одна — занести элемент. Память надо экономить. Поэтому резервируем АП под всю матрицу (VirtualAlloc/MEM_RESERVE), при этом ни одного байта не выделяется, а потом, ничтоже сумняшеся, пишем по нужному адресу. Естественно, получаем exception (AV), коммитируем 4Кб, включающие нужный адрес (VirtualAlloc/MEM_COMMIT), повторяем операцию. Следующий раз мы либо попадем в эти 4 Кб, тогда доступ без AV, либо в другое место, тогда еще 4 Кб и т.д.
Как видишь, исключения здесь не есть что-то ненормальное, а есть вполне нормальный code flow. Да, они не при каждом обращении будут происходить. Но будут, и это нормально.
>а не когда они летят как лепестки сакуры; а во-вторых, Винда — это ось для домохозяек, кто там в ней походы в ядро считает, "косынка" работает — и ладно...
Устраивать холивар насчет Windows против других осей я не буду. И без того на RSDN мегабайты на этот счет написаны.
Здравствуйте, Cyberax, Вы писали:
E>>ну вестимо должен быть явный метод close(), который можно позвать, если важна судьба буферизованных данных. А деструктор исключение глотает, возможно что-то записывая в лог... C>А как быть с библиотечным кодом, который может не знать в какой лог ему писать?
Банально. Код получает колбэк, который и дергает в таком случае...
C>Пользователя заставят явно обработать возможную ошибку.
коды возврата заставят? Скорее — программист может разрулить ситуацию вручную, если не забудет об этом... Та же штука и с close в деструкторе — ты можешь позвать его явно и разрулить ошибку. А можешь не звать, тогда деструктор поступит умолчательным образом (ошибку проглотит)
Здравствуйте, uncommon, Вы писали:
U>Всё ясно видно: обработка ошибок делегируется на уровень выше. Абсолютно одинаково с кодом на расте, только там делегирование надо производить явно. С исключениями надо привыкнуть к одному лишь правилу, что если нет обработки на данном уровне, то она делегируется на уровень выше.
Ничего ясно не видно. Если функцию, бросающую исключения, писали не мы, то надо лезть в документацию (если она есть) или вообще в исходники, чтобы понять кидаются ли какие-то исключения или нет. В случае же кодов возврата (к примеру) всё сразу видно по заголовочному файлу.
Ну во-первых если делать всё всё по правильному, то не забудь ещё реализацию своего класса исключений для данной функции... )
А во-вторых я на практике обычно не использую даже коды возврата — только булево значение, т.к. обычно пользователю вообще не нужно знать низкоуровневые детали. А в данном примере я написал так, чтобы никто не придрался (мол с исключениями передаётся больше информации).
Здравствуйте, jazzer, Вы писали:
J>Я исправил у тебя ошибку там. J>Это в чистом виде артефакут MSVC, все вопросы к нему. Мало того, что Винда — не единственная ось на рынке, так и MSVC — не единственный компилятор под винду. Ходить в ядро, потому что в пользовательском коде что-то не распарсилось — это бред, потому что мешает всем работающим процессам.
J>С другой стороны, даже если это и так, то и хрен бы с ним. J>Во-первых, нам нужна скорость работы код в отсутствие исключений, а не когда они летят как лепестки сакуры; а во-вторых, Винда — это ось для домохозяек, кто там в ней походы в ядро считает, "косынка" работает — и ладно...
Вообще то seh как раз лучше других реализаций. dwarf имеет такие же нулевые накладные расходы, но имеет проблемы при работе со смешанным кодом. sjlj без проблем работает со смешанным кодом, но имеет приличные накладные расходы. Естественно здесь речь про режим работы без запуска исключений. А кого-то волнует быстродействие исключительной ситуации? ) Ну разве что тех, кто применяет исключения для обработки всех ошибок...
Кстати, seh не применяли в других компиляторах (в 32 битном коде) из-за патентных ограничений. Но как раз совсем недавно патент вышел и теперь его спешно реализуют и для 32-ых битного mingw (64 битная реализация есть давно).
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Вот тебе пример, позаимствованный у Рихтера.
PD>Сильно разреженная очень большая матрица. Операция всего одна — занести элемент. Память надо экономить. Поэтому резервируем АП под всю матрицу (VirtualAlloc/MEM_RESERVE), при этом ни одного байта не выделяется, а потом, ничтоже сумняшеся, пишем по нужному адресу. Естественно, получаем exception (AV), коммитируем 4Кб, включающие нужный адрес (VirtualAlloc/MEM_COMMIT), повторяем операцию. Следующий раз мы либо попадем в эти 4 Кб, тогда доступ без AV, либо в другое место, тогда еще 4 Кб и т.д.
PD>Как видишь, исключения здесь не есть что-то ненормальное, а есть вполне нормальный code flow. Да, они не при каждом обращении будут происходить. Но будут, и это нормально.
Ну это вообще немного не в тему нашего обсуждение. Это скорее пример специфической оптимизации на базе аппаратных исключений.
Здравствуйте, alex_public, Вы писали:
_>Ну это вообще немного не в тему нашего обсуждение. Это скорее пример специфической оптимизации на базе аппаратных исключений.
Согласен, немного не то. Но и речь-то в моем предыдущем ответе jazzer шла немного не о том — я говорил, что исключения могут быть частью контракта метода, а поэтому аппелировать к тому, что нам нужна хорошая скорость, когда их не будет — не совсем верно.
U>> Более интересен будет опыт таких языков как Python, где исключения с самого начала в языке и рантайме, а кодов возврата нет как таковых. Так вот в Питоне ни у кого почему-то не возникает с исключениями никаких проблем. C>Вот уж не надо сказок. Я на досуге после миграции нашего проекта на PyPy сижу и переписываю код вида:
PyPy уже не совсем Питон.
C>В обычном Python'е он "работает" из-за того, что файловый объект детерминированно уничтожается при выходе из scope'а. А вот в PyPy уже честный GC и ВНЕЗАПНО оно как-то не очень работает.
Здравствуйте, Ikemefula, Вы писали:
U>>> Более интересен будет опыт таких языков как Python, где исключения с самого начала в языке и рантайме, а кодов возврата нет как таковых. Так вот в Питоне ни у кого почему-то не возникает с исключениями никаких проблем. C>>Вот уж не надо сказок. Я на досуге после миграции нашего проекта на PyPy сижу и переписываю код вида: I>PyPy уже не совсем Питон.
"PyPy is a fast, compliant alternative implementation of the Python language (2.7.8 and 3.2.5). It has several advantages and distinct features..." (c) http://pypy.org/
Здравствуйте, alex_public, Вы писали:
I>>Итого — ты просто не утруждаешь себя обработкой ошибок.
_>Просто я её делаю в пределах не больших, чем то, что реально требуется для показа пользователю, а не в пределах всей доступной мне изнутри информации.
Да, я в курсе, и проектируешь все проекты на сто лет вперёд, учитывая все возможные и невозможные изменения требований, а так же учитываешь все случаи реюзания твоего кода всеми возможными и невозможными способами.
Здравствуйте, uncommon, Вы писали:
U>Если честно, то мне нечего сказать людям, которые не хотят учиться. Говнокодеров полно, и ничего с этим не поделать.
Есть мнение, что имеет смысл учиться не синтаксису, а более важным вещам — времени всего 24 часа.
Здравствуйте, Cyberax, Вы писали:
C>У деструктора есть магические силы, позволяющие ему избегать ошибок в close()?
Деструктор вынужден игнорировать ошибку в close. В результате из деструктора невозможно сообщить об ошибке. Поэтому все операции, которые могут закончиться с ошибкой надо делать до вызова деструктора. Я же говорю, посмотри в код практически любой библиотеки. Там так и будет.
Здравствуйте, Cyberax, Вы писали:
C>Здравствуйте, uncommon, Вы писали:
C>>>Я подобный подход и в С++ использовал. U>>Подкинь ссылочку? Интересно посмотреть. C>Лень искать, что-то типа такого: C>
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, uncommon, Вы писали:
U>>Всё ясно видно: обработка ошибок делегируется на уровень выше. Абсолютно одинаково с кодом на расте, только там делегирование надо производить явно. С исключениями надо привыкнуть к одному лишь правилу, что если нет обработки на данном уровне, то она делегируется на уровень выше.
_>Ничего ясно не видно. Если функцию, бросающую исключения, писали не мы, то надо лезть в документацию (если она есть) или вообще в исходники, чтобы понять кидаются ли какие-то исключения или нет. В случае же кодов возврата (к примеру) всё сразу видно по заголовочному файлу.
Ни в какие исходники лезть не надо. Это ещё одно твоё заблуждение. Ты можешь считать, что всегда возможен выброс исключения из функции, если она не объявлена noexcept(true), throw() или extern "C".
Здравствуйте, Ikemefula, Вы писали:
I>Здравствуйте, uncommon, Вы писали:
U>>Если честно, то мне нечего сказать людям, которые не хотят учиться. Говнокодеров полно, и ничего с этим не поделать.
I>Есть мнение, что имеет смысл учиться не синтаксису, а более важным вещам — времени всего 24 часа.
В обсуждаемом вопросе синтаксиса практически нет. Здесь дело в непонимании самой концепции. А синтаксис, какой бы он не был, стоит просто запомнить. Это часть профессии.
Здравствуйте, Cyberax, Вы писали:
U>>Сравни принцип "не платить за то, что не используется" с "исключений не будет, потому что принцип". Какой-то один из них высосан из пальца. C>Принцип у них другой: "Минимум магии и максимальная надёжность".
А в результате что мы получаем? Куча магии вроде try!, ?, throws (плюс монады на кривом уровне, это вам всё-таки не Хаскель). К этой магии прилагается ещё и инструкция как пользоваться на хрен знает сколько страниц. Ну и соответственно, чем сложнее механизм и чем больше магии он требует, тем меньше его надёжность. Так что я думаю, они здесь проиграли.
Я не очень хочу неявных исключений, но если всё же их делать, то модель с Result'ом, который автоматически может превращаться в исключение — мне больше всего нравится.
Здравствуйте, Ikemefula, Вы писали:
_>>Это в твоих проектах такие имена функций обычно, да? ))) I>Намекаешь, что не в состоянии провести заявленое различие ?
Очевидно, что метод вот такой: "fn Y() -> Result<BOOL, Error> "
Здравствуйте, uncommon, Вы писали:
U>Деструктор вынужден игнорировать ошибку в close. В результате из деструктора невозможно сообщить об ошибке. Поэтому все операции, которые могут закончиться с ошибкой надо делать до вызова деструктора.
Второй вопрос, как быть с RAII-кодом, в котором в деструкторах огромное количество логики?
Здравствуйте, uncommon, Вы писали:
U>Деструктор вынужден игнорировать ошибку в close. В результате из деструктора невозможно сообщить об ошибке. Поэтому все операции, которые могут закончиться с ошибкой надо делать до вызова деструктора.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, enji, Вы писали:
E>>ой ли?
_>Ну во-первых если делать всё всё по правильному, то не забудь ещё реализацию своего класса исключений для данной функции... )
не надо, есть boost::exception. Ну и класс нужен не для функции, а для подсистемы в целом — возможно, несколько классов.
_>А во-вторых я на практике обычно не использую даже коды возврата — только булево значение, т.к. обычно пользователю вообще не нужно знать низкоуровневые детали. А в данном примере я написал так, чтобы никто не придрался (мол с исключениями передаётся больше информации).
Дык и в самом деле больше С другой стороны можно сгородить свой собственный тип возврата — в который пихать доп инфу. Что-то вроде boost::error_code.
Основной плюс исключений, имхо — их нельзя проигнорировать.
Здравствуйте, uncommon, Вы писали:
I>>Есть мнение, что имеет смысл учиться не синтаксису, а более важным вещам — времени всего 24 часа.
U>В обсуждаемом вопросе синтаксиса практически нет. Здесь дело в непонимании самой концепции. А синтаксис, какой бы он не был, стоит просто запомнить. Это часть профессии.
Языковая фича == синтаксис. Исключения вызывают проблемы в основном в С++. В Джаве той же или джаваскрипте как то незаметно таких проблем.
Здравствуйте, Cyberax, Вы писали:
_>>>Это в твоих проектах такие имена функций обычно, да? ))) I>>Намекаешь, что не в состоянии провести заявленое различие ? C>Очевидно, что метод вот такой: "fn Y() -> Result<BOOL, Error> "
Какие методы — я указал. Они возвращают BOOL. В принципе, могут возвращать что угодно — строку, указатель, хендл.
Здравствуйте, enji, Вы писали:
E>Дык и в самом деле больше С другой стороны можно сгородить свой собственный тип возврата — в который пихать доп инфу. Что-то вроде boost::error_code.
E>Основной плюс исключений, имхо — их нельзя проигнорировать.
Основной плюс — изоляция от обработки. С кодами возврата, если внезапно между обработкой и местом ошибки вклинивается пару методов, надо начинать переписывание всего нижележащего слоя или начинать городить раскрутку ошибки вручную, чего на практике и наблюдаем.
Принципиально есть единственный способ всё забороть — нанять архитектора-всемогутора, который заглянет в будущее и создаст нерушимый дизайн на любой срок вперёд.
Без этого будет тяжело сделать любую более менее полезную логику. Скажем, есть отсылка емейла. В зависимости от приложения может быть достаточно и "невозможно отправить дизайн", а может и не хватит "аттачмент не доступен — не хватает прав на файловую систему". В первом случае хватит булевого кода возврата, а во втором надо пилить распространение информации об ошибках.
Что характерно, второй случай никак не вмещается в концепцию кодов возврата. На самом деле не вмещается уже простая линейная логика.
x = a();
if(x == null)
return null;
x = b(x);
if(x == null)
return null;
x = c(x);
if(x == null)
return null;
x = d(x);
if(x == null)
return null;
return e(x);
Здравствуйте, Ikemefula, Вы писали:
I>Что характерно, второй случай никак не вмещается в концепцию кодов возврата. На самом деле не вмещается уже простая линейная логика.
Да вмещается он, просто надо вместо целого кода возвращать объект. А там уже можно делать вложенные коды и т.п. Но от ручного распространения без какого-то сахара со стороны языка не уйти, да
Здравствуйте, Cyberax, Вы писали:
U>>Деструктор вынужден игнорировать ошибку в close. В результате из деструктора невозможно сообщить об ошибке. Поэтому все операции, которые могут закончиться с ошибкой надо делать до вызова деструктора. C>Второй вопрос, как быть с RAII-кодом, в котором в деструкторах огромное количество логики?
Здравствуйте, enji, Вы писали:
I>>Что характерно, второй случай никак не вмещается в концепцию кодов возврата. На самом деле не вмещается уже простая линейная логика.
E>Да вмещается он, просто надо вместо целого кода возвращать объект. А там уже можно делать вложенные коды и т.п. Но от ручного распространения без какого-то сахара со стороны языка не уйти, да
Наверное ирония была плохо заметна — `концепция кодов вовзрата` предусматривает только вещи навроде open('a') || open('b') || open('c');
Здравствуйте, Cyberax, Вы писали:
C>Здравствуйте, uncommon, Вы писали:
U>>Деструктор вынужден игнорировать ошибку в close. В результате из деструктора невозможно сообщить об ошибке. Поэтому все операции, которые могут закончиться с ошибкой надо делать до вызова деструктора. C>Второй вопрос, как быть с RAII-кодом, в котором в деструкторах огромное количество логики?
Например? Что-то мне кажется, что такие деструкторы — результат плохого дизайна. Как может быть много логики в деструкторе RAII класса, где должны только освобождаться ресурсы?
vsb>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения. vsb>Я перечитал много разных статей, но нигде не видел чётких аргументов в пользу отказа от исключений. Go вообще забавный язык, panic()/recover() это исключений как они есть, один в один. Любой код с исключениями тривиальными заменами преобразовывается в код с panic()/recover(). Но при этом они утверждают, что от исключений они избавились.
С точки зрения программиста, в коде могут случиться ожидаемые ошибки, которые он готов обработать, например нет файла. Файла, который хочет открыть пользователь. И неожиданные ошибки, которые он не хочет обрабатывать и хочет крешиться, например нет файла. Файла библиотки программы. Когда программист пишет код, он хочет для мест с ожидаемыми ошибками указать, какие исключение ловить. И не может — потому что во всех существующих реализациях нет возможности быстро понять, какие именно исключения может кинуть функция в той или иной ситуации. К примеру, использует разработчик функцию функцию работы с сетью, и хочет написать что потеря соединения — это ожидаемая ошибка, ее нужно ловить, по всем остальным нужно крешиться. Все работает два дня, а потом крешится на каком-нибудь OSError, которое "тоже потеря сетевого соединения, но на другом уровне абстракции". Еще через неделю там же крешится на SocketError, которое тоже потеря сетевого соединения, но в другом месте... И так может продолжаться бесконечно.
Здравствуйте, smeeld, Вы писали:
S>А что такое RAII? Это сказал кто-то, modern programer из каких-то гуглов или фейсбуков, а может вообще академик с бадуна, S>что "Resource Acquisition Is Initialization"-это модно и есть признак развития. И стадо обезьян загудело. Все так и кинулись внедрять S>себе в код это развитие. Только то, что для того, чтоб это без проблем работало, нужно или переписывать всю ОС с самого загрузчика по новому, S>включая все системные либы, или нужно формировать мощную прослойку, на уровне интерпретатора (достаточно хорошо реализовано во всех существующих), S>или стандартной библиотеки языка компилируемого (Да в C++ RAII названо, но не релизовано как надо, полноценная реализация требует добавления S>специализированных средств в stdc++) между ОС, и приложением использующим RAII, чтоб она и взяла на себя все нестыковки RAII с существующими S>низкоуровневыми системами.
Здравствуйте, Pzz, Вы писали:
Pzz>Этим исключениям хорошо бы как-то законодательно синтаксически ограничить дальность полета. Потому что когда в ваш высокоуровневый код, работающий в терминах высокоуровневых абстракций, откуда-нибудь с самого нижнего уровня прилетит исключение про то, чего вы в своем высокоуровневом коде сказать-то и не можете, то что вы с ним будете делать? Выдадите пользователю ошибку с шешнадцетиричным номером и невнятной диагностикой? То-то пользователь будет счастлив.
Так не нужно кидать исключения из низкоуровневого кода. Для низкого уровня — коды ошибок, для верхнего — исключения.
Здравствуйте, enji, Вы писали: E>Да вмещается он, просто надо вместо целого кода возвращать объект. А там уже можно делать вложенные коды и т.п. Но от ручного распространения без какого-то сахара со стороны языка не уйти, да
Стесняюсь спросить — а в чём преимущество-то у этого "объекта ошибки" по сравнению с "объектом-исключением"?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, uncommon, Вы писали: U>Например? Что-то мне кажется, что такие деструкторы — результат плохого дизайна. Как может быть много логики в деструкторе RAII класса, где должны только освобождаться ресурсы?\
Ну, например ресурсы — это буфер. Чтобы его освободить, его надо флашнуть. Что делать? Требовать ручного flush перед выходом из каждого scope? Причём его отсутствие мы заметим только в рантайме, и то не каждый раз.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, jazzer, Вы писали:
J>Ну так это обеспечивают обычные RAII guards. (Естественно, предполагается, что смена часиков на стрелочку не бросит исключения.)
Здравствуйте, jazzer, Вы писали: J>Ну так это обеспечивают обычные RAII guards. (Естественно, предполагается, что смена часиков на стрелочку не бросит исключения.)
Не вижу в этом ничего естественного. Давайте попробуем придумать операцию, которая не бросит исключения вообще никак-никак, а не просто "ну, скорее всего вы вряд ли получите в ответ window station is shutting down". Мне что-то вообще ничего в голову не приходит.
J>для этого у guard-а делается флажок, который взводится, если все хорошо (обычно метод называется commit), и тогда деструктор идет по сценарию commit, а иначе (по умолчанию) — по сценарию rollback.
Это изоморфно ручному вызову commit перед каждым выходом из scope. Для минимально нелинейной логики отслеживание этого руками — тот ещё геморрой.
J>Для этого, очевидно, нужно предпринять специальные усилия (т.е. какие-то параллельные структуры) по сохранению сути того, что мы собирались сделать и не смогли. J>Самое простое — очередь задач, где обломившаяся задача не удаляется из очереди и ее можно попробовать запустить еще раз или что-то с ней сделать.
J>Другой вариант — сохранять сами исключения (у меня есть игрушечный вариант на С++11, если интересно, выложу куда-нть) и всю необходимую информацию прямо в них. J>Но тут, понятно, нас всегда подстерегают ошибки выделения памяти.
Это частный случай. В том смысле, что если мы выбрасываем исключение, то у нас оно уже есть, и для упаковки его в nested exception доп.места не надо. А если память кончилась при попытке выкинуть исключение — то вместо него мы выбрасываем тот самый статический экземпляр out-of-memory, который мы заботливо прикопали при старте программы.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, AlexRK, Вы писали: ARK>Кстати, пара "антонимов" — это всего лишь вырожденный случай некоторой цепочки вычислений, в которой должны быть гарантированно вызваны некоторые методы в некотором порядке.
Мне одному между строк мерещится термин "монада"?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, AlexRK, Вы писали: ARK>>Кстати, пара "антонимов" — это всего лишь вырожденный случай некоторой цепочки вычислений, в которой должны быть гарантированно вызваны некоторые методы в некотором порядке. S>Мне одному между строк мерещится термин "монада"?
Я не большой знаток монад (сам недавно просил пояснить, что это такое ), но, насколько я понял, монады это не совсем то.
Я говорю о чем-то вроде контрактов на каналы в Singularity. Например, для работы с файлами контракт может быть навроде "Open[1] -> (Read | Write)[0..] -> Close[1]".
Для упрощенной модели ресурсов хватит только "открыть-закрыть", причем "закрыть" можно выполнять автоматом.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, Sinclair, Вы писали:
S>>Здравствуйте, AlexRK, Вы писали: ARK>>>Кстати, пара "антонимов" — это всего лишь вырожденный случай некоторой цепочки вычислений, в которой должны быть гарантированно вызваны некоторые методы в некотором порядке. S>>Мне одному между строк мерещится термин "монада"?
ARK>Я не большой знаток монад (сам недавно просил пояснить, что это такое ), но, насколько я понял, монады это не совсем то.
А по мне так как раз ровно оно: они же введены как раз для того, чтобы гарантировать "вызов некоторых методов в некотором порядке". Например, что для Future мы сначала дожидаемся окончания отложенного вычисления, а уже потом потребляем значение. ARK>Я говорю о чем-то вроде контрактов на каналы в Singularity. Например, для работы с файлами контракт может быть навроде "Open[1] -> (Read | Write)[0..] -> Close[1]". ARK>Для упрощенной модели ресурсов хватит только "открыть-закрыть", причем "закрыть" можно выполнять автоматом.
Эта модель уже реализована в С++, где "открыть" — это конструктор, а "закрыть" — деструктор.
Как мы только что обсудили, этой модели недостаточно для покрытия интересных нам сценариев, когда стадия финализации может обломиться.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, jazzer, Вы писали:
J>>Ну так это обеспечивают обычные RAII guards. (Естественно, предполагается, что смена часиков на стрелочку не бросит исключения.)
ARK>А если бросит?
Здравствуйте, Sinclair, Вы писали:
S>А по мне так как раз ровно оно: они же введены как раз для того, чтобы гарантировать "вызов некоторых методов в некотором порядке". Например, что для Future мы сначала дожидаемся окончания отложенного вычисления, а уже потом потребляем значение.
А можно через монады организовать, например, циклы? Чисто гипотетически, конечно, но все равно: сперва "открыть", потом произвольное количество раз сначала "читать", потом "писать", и в самом конце "закрыть".
S>Эта модель уже реализована в С++, где "открыть" — это конструктор, а "закрыть" — деструктор. S>Как мы только что обсудили, этой модели недостаточно для покрытия интересных нам сценариев, когда стадия финализации может обломиться.
Ну да, этого недостаточно. Я, кстати, не могу придумать нормального варианта, как быть в такой ситуации, то бишь если происходит крах при финализации. Похоже, единственное, что можно сделать — как-то передать ошибку наверх. Но при этом должны выполниться и все остальные деструкторы. Правда, ошибка в первом деструкторе может повлиять и на остальные... В общем, хз, как лучше. Конечно, лучше всего статически запретить любые ошибки в финализаторах, но это не сработает для внешних сущностей — баз данных, сетевых соединений и т.п.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, jazzer, Вы писали:
J>>а смысл в этом какой?
ARK>Смысл тот же, какой и при обработке любой другой ошибки. Например сообщить, что важное действие выполнить не удалось.
Здравствуйте, AlexRK, Вы писали:
ARK>А можно через монады организовать, например, циклы? Чисто гипотетически, конечно, но все равно: сперва "открыть", потом произвольное количество раз сначала "читать", потом "писать", и в самом конце "закрыть".
Ну, такое-то можно и без монад.
Достаточно сильная система типов отличает "новый сокет" от "открытого сокета" и от "закрытого сокета". Поэтому достаточно объявить правильные сигнатуры у функций открытия/чтения/закрытия.
ARK>Ну да, этого недостаточно. Я, кстати, не могу придумать нормального варианта, как быть в такой ситуации, то бишь если происходит крах при финализации. Похоже, единственное, что можно сделать — как-то передать ошибку наверх. Но при этом должны выполниться и все остальные деструкторы. Правда, ошибка в первом деструкторе может повлиять и на остальные... В общем, хз, как лучше. Конечно, лучше всего статически запретить любые ошибки в финализаторах, но это не сработает для внешних сущностей — баз данных, сетевых соединений и т.п.
Надо понять,
1. Что мы хотим делать в случае возникновения ошибки на таком этапе. Если вообще отвлечься от деструкторов, финализаторов, скоупов, и исключений. Вот мы писали-писали данные в буфер, вот стали сохранять — облом, кончилось место на диске. Что мы будем делать? Пытаться повторно? Откатывать назад к "предыдущему успешному чекпоинту"?
2. Отличается ли стратегия завершения для успеха и неудачи. Предположим, в процессе подготовки данных для записи в файл мы напоролись на деление на ноль. Имеет ли смысл вообще пытаться записывать кусок буфера? Или надо откатываться к "предыдущему успешному чекпоинту"? Или можно просто бросить как есть, т.к. упорно пытаться записывать мусор бессмысленно, а откатывать назад — дорого.
Пока что выглядит всё так, что даже пытаться делать commit стоит только при условии выхода из блока без исключений. При исключительном выходе нужно применять либо rollback, либо ignore.
Дальше начинаются нюансы — что, если у нас три файла, выходим успешно, один уже закоммитился, теперь коммитим второй — упс, ошибка. Очевидное решение (скорее всего неверное) — продолжать отмотку с учётом изменившейся ситуации. То есть для второго и третьего мы попробуем теперь вызвать "завершатель ошибки".
В таком подходе можно попытаться запретить (языком и верификацией) выброс исключений из fault completion handler, разрешая нетривиальную логику в success completion handler.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, jazzer, Вы писали:
J>>А чего ты тогда хочешь? Что входит в твое понятие антонима? Просто попытка вызвать обратное действие, независимо от того, будет она успешной или нет? S>Антоним — это то действие, которое я заложил в алгоритм. S>Вот, например, банальная штука на воображаемом языке: S>
S>using(var stream = openSerializationStream())
S>{
S> stream.Write("Customer", customer);
S> if (customer.Status < Status.Ready)
S> return; // #1 - no data saved for the not ready customers
S> var orders = Orders.GetOrders(customer) ;
S> if (orders.Length==0)
S> return; #2 - nothing to save
S> stream.Write("OrdersTotal", orders.Total(o=>o.Amount));
S> foreach(var o in orders)
S> if (o.Status != Status.Ready)
S> break; # 3 - list all ready orders, then exit
S> else
S> stream.Write("Order #"+o.Id, o);
S>}
S>
S>Я хочу двух вещей: S>1. Чтобы у меня буфер сбрасывался на диск не после каждой операции, но при выходе из scope. То есть я не хочу ни добавлять принудительный flush к Write (страдает перформанс), ни руками дописывать stream.Flush() перед каждым return/break/} (чревато ошибками). S>2. Чтобы о об ошибках записи я узнавал не из чтения побитого файла, а из вылетающего исключения.
Что ты собираешься получить, если и одна из операций сломалась, и автоматический Flush?
S>В С++ сие невозможно, т.к. единственное, что гарантированно сработает при выходе — это деструктор, а в деструкторе бросать исключения нельзя.
Если очень хочется — то можно.
J>>Скажем, один, восстанавливает курсор, а второй гасит лампочку на девайсе, подключенном по COM-порту. S>Это — вопрос, требующий проработки. У меня нет всех деталей задачи про курсор и лампочку; потому мне сложно сказать, какие гарантии тут нужны. Потому что по ком-порту может быть не лампочка подключена, а электронный замок, перекрывающий дверь шлюза. И подавление исключения означает "да хрен с ним, с этим шлюзом — нехай экипаж задохнётся".
А зачем тогда делать важные вещи невидимой автоматикой без внятной обработки ошибок, которые могут в этих важных вещах возникнуть?
И что бы ты делал без исключений, если у тебя возникло несколько ошибок сразу?
Суть кода с исключениями — прервать цепочку действий, почиститься и передать это исключение наверх. Именно это, а не то, которое возникнет в результате очистки. Ну или по крайней мере оно не должно быть главным, а быть упрятанным в какой-нть nested и проигнорированным в цепочке действий по очистке, чтоб остальные тоже могли почиститься. Хотя там могут быть свои взаимозависимости, так что все это — прогулки по граблям, вне зависимости от наличия или отсутствия исключений.
J>>Плюс возврат ошибки в случае отвалившегося антонима — это очень сомнительная практика сама по себе. J>>Типа ты вызываешь функцию для долгого расчета интеграла, а в ответ получаешь ошибку "не смогли восстановить курсор-стрелочку". S>Это потому, что вам не жалко курсор. А что, если функция для долгого расчёта не смогла результат в файл сохранить. Вы тоже сочтёте сомнительной практику как-то намекнуть об этом вызывающему коду?
Я сочту сомнительной практикой засовывать сохранение результатов в деструктор, вместо того, чтобы сделать это отдельной явной строчкой. Это ведь можно до абсурда дойти — не писать функций вообще, весь код в деструкторах, а в функциях только создание объектов и тут же их автоматическое уничтожение.
J>>Ну еще есть scope(exit), scope(success), scope(failure) — ты их имеешь в виду? S>А что это? Может и их.
Это способ задать действия в деструкторе в зависимости от того, в каких условиях вызвался деструктор — по-нормальному (success), в результате раскрутки стека из-за исключения (failure), пофиг что (exit).
Встроено в D, есть реализация для С++: http://rsdn.ru/forum/cpp/4908728.1
В случае success исключение можно бросить, в принципе.
J>>По Стандарту С++11 — никаких гарантий, увы, нет. Даже std::current_exception() может вернуть bad_exception, если не удалось создать копию текущего исключения (если таковая понадобилась). S>Ну, я стандарты С++ не считаю откровением господним. Интересно не то, чего они не гарантируют, а то, что вообще можно получить — хотя бы в теории.
В теории это всё криво. Я не видел ни одной схемы вменяемой автоматизированной обработки ошибок обработки ошибок обработки ошибок.
Здравствуйте, Sinclair, Вы писали:
E>>Да вмещается он, просто надо вместо целого кода возвращать объект. А там уже можно делать вложенные коды и т.п. Но от ручного распространения без какого-то сахара со стороны языка не уйти, да S>Стесняюсь спросить — а в чём преимущество-то у этого "объекта ошибки" по сравнению с "объектом-исключением"?
это ты не у меня спрашивай. Тут выше рассказывали, что коды ошибок более очевидны... Лично я использую исключения
Здравствуйте, Sinclair, Вы писали:
S>Может быть, два "деструктора" у классов — commit-деструктор и rollback-деструктор?
вот это кстати вполне в духе плюсов, странно что еще не сделали
S>Или другую семантику раскрутки стека, которая позволяет спасти больше информации об ошибке? Типа "мы читали из одного файла и писали в другой, потом при чтении у нас возникла ошибка формата, а при экстренном выходе мы и предыдущие данные в выходной файл записать не смогли".
Дык одно из исключений добавлять к основному. Проблема в плюсах в том, что можно выкинуть любой объект (очень интересно, зачем?).
Здравствуйте, enji, Вы писали:
E>Дык одно из исключений добавлять к основному. Проблема в плюсах в том, что можно выкинуть любой объект (очень интересно, зачем?).
Зачем как раз понятно — чтобы можно было "не платить за то, чего не используешь" — в случае чего прямо throw hr; catch(HRESULT).
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
J>>Что ты собираешься получить, если и одна из операций сломалась, и автоматический Flush? S>Ну, например в данном случае, если одна из операций сломалась, то я не хочу автоматический Flush().
Здравствуйте, Sinclair, Вы писали:
S>Пока что выглядит всё так, что даже пытаться делать commit стоит только при условии выхода из блока без исключений. При исключительном выходе нужно применять либо rollback, либо ignore. S>Дальше начинаются нюансы — что, если у нас три файла, выходим успешно, один уже закоммитился, теперь коммитим второй — упс, ошибка. Очевидное решение (скорее всего неверное) — продолжать отмотку с учётом изменившейся ситуации. То есть для второго и третьего мы попробуем теперь вызвать "завершатель ошибки". S>В таком подходе можно попытаться запретить (языком и верификацией) выброс исключений из fault completion handler, разрешая нетривиальную логику в success completion handler.
Да блин, мне _вообще_ непонятно, какова должна быть грамотная стратегия обработки ошибок при финализации.
Если у нас есть блок, внутри которого содержатся 10 автоматических переменных, то что должно произойти при выходе из блока, если при вызове первого деструктора вылетела ошибка?
Если бы был полный STM с учетом IO, то все было бы супер. Но его нету. Что делать? Выполнять остальные деструкторы или нет? Все ошибки из всех деструкторов склеить в одно исключение и кидануть его наверх?
Здравствуйте, Ikemefula, Вы писали:
I>Да, я в курсе, и проектируешь все проекты на сто лет вперёд, учитывая все возможные и невозможные изменения требований, а так же учитываешь все случаи реюзания твоего кода всеми возможными и невозможными способами.
Дело не в устройстве приложения, а в самой прикладной задаче.
Вот смотри, у тебя задача скачать файл из сети по данному url'у. Понятно что по такой вроде бы просто формулируемой задаче мы имеем наверное больше десятка низкоуровневых ошибок (начиная с открытия сокета и заканчивая записью на диск). Теперь простой вопрос: что ты планируешь выдавать пользователю в данном случае? )
Здравствуйте, AlexRK, Вы писали:
ARK>Пока что все выглядит так, что я открываю Америку — деструкторы не должны возвращать ошибку, а все действия, которые могут ее вернуть, должны быть вызваны явно. По сути мы просто перекладываем всю логику обработки на программиста.
ARK>Но в этом свете хваленый RAII оказывается просто пшиком, потому что безошибочно с гарантией освободить можно только память. ARK>Не, можно конечно просто плюнуть на полную корректность, как в С++ и поступают. "Подумаешь, файл не закрылся, этого почти никогда и не бывает." ARK>Но для меня такой вариант неприемлем.
Не может ли уважаемый DarkEld3r пояснить, с чем он несогласен?
С тем, что любая операция освобождения ресурса (и даже операция записи в лог) хоть редко, но может завершиться с ошибкой? Или с тем, что проглатывать эти ошибки нехорошо?
Не срача ради, а токмо истины для.
Здравствуйте, DarkEld3r, Вы писали:
DE>Да, в сложных случаях, надо писать дополнительный "ручной" код, но в общем, с ними всё равно удобнее.
Получается, что "простые случаи" — это когда мы просто игнорируем потенциальную ошибку.
ARK>>>Не, можно конечно просто плюнуть на полную корректность, как в С++ и поступают. DE>Ок, как должен выглядеть правильный вариант?
В том-то и дело, что нормального варианта я не знаю.
Пока что у меня такое мнение: с точки зрения корректности, правильно всегда вызывать вручную все методы типа close и, в случае возникновения сбоя, передавать ошибку дальше (исключениями, кодами возврата или еще чем — неважно). Плюс система типов должна гарантировать вызов этих методов, чтобы нельзя было забыть их вызвать (хотя в С++ гарантировать это невозможно). Но идеальным такой вариант я назвать не могу.
DE>Я уж не говорю о том, что даже если мы будем всегда сохранять все возникшие ошибки, то это не гарантирует их последующую корректную обработку. Кто помешает их просто игнорировать?
Это уже другой вопрос. Что там наверху, мы не знаем и не хотим знать. Главное, что свой код у нас написан корректно.
Здравствуйте, AlexRK, Вы писали:
ARK>Получается, что "простые случаи" — это когда мы просто игнорируем потенциальную ошибку.
Далеко не всегда это проблема.
ARK>Это уже другой вопрос. Что там наверху, мы не знаем и не хотим знать. Главное, что свой код у нас написан корректно.
А я-то думал, что главное — это корректность работы программы в целом.
ARK>Пока что у меня такое мнение: с точки зрения корректности, правильно всегда вызывать вручную все методы типа close и, в случае возникновения сбоя, передавать ошибку дальше (исключениями, кодами возврата или еще чем — неважно). Плюс система типов должна гарантировать вызов этих методов, чтобы нельзя было забыть их вызвать (хотя в С++ гарантировать это невозможно). Но идеальным такой вариант я назвать не могу.
Ну и получим при вызове функции кучу "ошибок" "не удалось записать в лог", например.
На мой взгляд, у ошибок всё-таки степень важности разная бывает. Некоторые можно и проигнорировать ("подумаешь файл не закрылся"). Вряд ли, возможна система типов которая правильно это будет "понимать" и заставлять обрабатывать.
Здравствуйте, DarkEld3r, Вы писали:
ARK>>Получается, что "простые случаи" — это когда мы просто игнорируем потенциальную ошибку. DE>Далеко не всегда это проблема. DE>На мой взгляд, у ошибок всё-таки степень важности разная бывает. Некоторые можно и проигнорировать ("подумаешь файл не закрылся").
Все-таки, RAII это библиотечный код, как ни крути. И он за меня сразу принимает решение, что ошибки при финализации надо игнорировать. При этом он, вообще говоря, не знает, что действительно можно проигнорировать, а что нельзя. А потом поверх этого кода пишется тонна другого кода. В результате, если мне все-таки _надо_ среагировать на ошибку закрытия файла, то иного варианта, кроме как переписать вообще все это, не существует.
ARK>>Это уже другой вопрос. Что там наверху, мы не знаем и не хотим знать. Главное, что свой код у нас написан корректно. DE>А я-то думал, что главное — это корректность работы программы в целом.
Это, конечно, главное.
Но причем тут стратегия работы с ошибками в самом низу? Наш код в большинстве случаев не знает, что делать с ошибкой. Он просто должен сообщить о ней наверх. А если мы ошибку всегда глотаем, то тем самым принимаем политическое решение, полномочий на которое у нас нет.
DE>Ну и получим при вызове функции кучу "ошибок" "не удалось записать в лог", например.
ИМХО, это лучше, чем не получить ничего и тихо-мирно работать дальше, не зная, что уже что-то пошло не так.
Хотя, конечно, это зависит от приложения.
Но сам подход "замалчивания" мне не нравится.
Я понимаю, что ошибки закрытия файлов или сетевых соединений довольно редкие и обычно их можно просто игнорировать. И что написана куча кода с RAII и он работает (?). Но сам фундамент этого подхода, ИМХО, ущербен (прошу прощения за эмоциональную "подачу" ).
Здравствуйте, AlexRK, Вы писали:
ARK>Все-таки, RAII это библиотечный код, как ни крути. И он за меня сразу принимает решение, что ошибки при финализации надо игнорировать. При этом он, вообще говоря, не знает, что действительно можно проигнорировать, а что нельзя. А потом поверх этого кода пишется тонна другого кода. В результате, если мне все-таки _надо_ среагировать на ошибку закрытия файла, то иного варианта, кроме как переписать вообще все это, не существует.
Я всё-таки "спорил" в контексте этой темы. В смысле "исключения против кодов возврата". В таком случае, не вижу разницы. Вот есть у нас функция возвращающая Result<T, Error>. В итоге или мы будем возвращать ошибки вида "не удалось записать в лог", даже если результат был успешно получен. Или точно так же будем игнорировать "мелкие" ошибки.
ARK>Это, конечно, главное. ARK>Но причем тут стратегия работы с ошибками в самом низу?
При том, что нет смысла в "идеальном" "сообщении об ошибках" если обработка их будет сложной и неудобной. И как удобно сделать обработку сразу кучи возникших ошибок я плохо представляю.
Здравствуйте, chaotic-good, Вы писали:
CG>Разницы между исключениями и кодами возврата быть не должно, до тех пор, пока количество ошибок пренебрежимо мало по сравнению с количеством успешных вызовов. Это происходит потому что в процессоре есть branch predictor, который успешно предсказывает ветвления при проверках кодов ошибок в 99.(9)% случаев. Если количество ошибок выелико, исключения работают медленнее чем коды ошибок по понятным причинам.
Иллюстрация:
#include <iostream>
#include <stdexcept>
#include <boost/timer.hpp>
volatile int K = 1000000000l - 1;
void check_value(int x) {
if (x == K)
throw std::runtime_error("invalid value");
}
bool check_value_ret(int x) {
return x == K;
}
int main()
{
boost::timer tm;
size_t value = 0u;
try {
while(true) {
check_value(value);
value++;
}
}
catch(const std::runtime_error& err) {
double secs = tm.elapsed();
std::cout << value << ", With exception: " << secs << std::endl;
}
value = 0u;
tm.restart();
while(true) {
if (check_value_ret(value)) break;
value++;
}
double secs = tm.elapsed();
std::cout << value << ", Without exception: " << secs << std::endl;
return 0;
}
999999999, With exception: 0.278302
999999999, Without exception: 0.271519
В примере выше, .NET компилятор просто фейлит оптимизацию более сложного кода с бранчами, поэтому для .NET возможно имеет смысл обратное.
Здравствуйте, Sinclair, Вы писали:
E>>Дык одно из исключений добавлять к основному. Проблема в плюсах в том, что можно выкинуть любой объект (очень интересно, зачем?). S>Зачем как раз понятно — чтобы можно было "не платить за то, чего не используешь" — в случае чего прямо throw hr; catch(HRESULT).
исключения сами по себе недешевы, опять же, на самом деле к исключению добавляется информация о типе. Неясно, для чего сделали альтернативный механизм, отданный на откуп компиляторам (к которому доступа без хаков нет), а не обычный dynamic_cast.
Здравствуйте, alex_public, Вы писали:
_>Вот смотри, у тебя задача скачать файл из сети по данному url'у. Понятно что по такой вроде бы просто формулируемой задаче мы имеем наверное больше десятка низкоуровневых ошибок (начиная с открытия сокета и заканчивая записью на диск). Теперь простой вопрос: что ты планируешь выдавать пользователю в данном случае? )
ну наверное описание ошибки — сервер не ответил, соединения разорвано, нет места на диске...