Какие у исключений проблемы?
От: vsb Казахстан  
Дата: 04.11.14 17:19
Оценка: +2 :))) :)
В некоторых новых языках программирования (Go, Swift) уходят от механизма исключений и подавляющее число ошибок обрабатывается через явный возврат ошибки из вызываемой функции. В качестве аргумента часто показывают пальцем на язык С++. Наличие там исключений заставляет писать весь код так, как будто из любого вызова функции может вылететь исключение и размотать стек.

На мой взгляд это проблема исключительно С++, а точнее его двух особенностей — отсутствие GC и запрет исключений в деструкторах.

Собственно все проблемы с исключениями решаются тремя простыми методиками:
1. Наличие GC позволяет не беспокоиться об утёкшей памяти из-за исключения.
2. Другие ресурсы практически всегда закрываются там же, где открываются, поэтому defer/finally-подобные конструкции позволяют очень легко устранить утечки этих ресурсов.
3. Исключение должно бросаться откуда угодно и программа должна себя вести корректно при этом (это я про исключения в десктрукторе/finally-блоке и тд).

Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.

Я перечитал много разных статей, но нигде не видел чётких аргументов в пользу отказа от исключений. Go вообще забавный язык, panic()/recover() это исключений как они есть, один в один. Любой код с исключениями тривиальными заменами преобразовывается в код с panic()/recover(). Но при этом они утверждают, что от исключений они избавились.
Re: Какие у исключений проблемы?
От: Pzz Россия https://github.com/alexpevzner
Дата: 04.11.14 17:27
Оценка: +1 -6 :)
Здравствуйте, vsb, Вы писали:

vsb>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.


Этим исключениям хорошо бы как-то законодательно синтаксически ограничить дальность полета. Потому что когда в ваш высокоуровневый код, работающий в терминах высокоуровневых абстракций, откуда-нибудь с самого нижнего уровня прилетит исключение про то, чего вы в своем высокоуровневом коде сказать-то и не можете, то что вы с ним будете делать? Выдадите пользователю ошибку с шешнадцетиричным номером и невнятной диагностикой? То-то пользователь будет счастлив.
Re[2]: Какие у исключений проблемы?
От: dimgel Россия https://github.com/dimgel
Дата: 04.11.14 17:31
Оценка: +4
Здравствуйте, Pzz, Вы писали:

Pzz>Этим исключениям хорошо бы как-то законодательно синтаксически ограничить дальность полета. Потому что когда в ваш высокоуровневый код, работающий в терминах высокоуровневых абстракций, откуда-нибудь с самого нижнего уровня прилетит исключение про то, чего вы в своем высокоуровневом коде сказать-то и не можете, то что вы с ним будете делать? Выдадите пользователю ошибку с шешнадцетиричным номером и невнятной диагностикой? То-то пользователь будет счастлив.


Ограничить дальность полёта можно только одним способом: ловить на каком-нибудь промежуточном слое и (опционально) заворачивать в нечто более понятное слою верхнему. Не проглатывать же его молча? Раз вылетело, значит неучтённая ошибка. Игнорирование её в конечном счёте приведёт к ещё большему пользовательскому щастью.
Re[3]: Какие у исключений проблемы?
От: Pzz Россия https://github.com/alexpevzner
Дата: 04.11.14 17:34
Оценка: -4
Здравствуйте, dimgel, Вы писали:

D>Ограничить дальность полёта можно только одним способом: ловить на каком-нибудь промежуточном слое и (опционально) заворачивать в нечто более понятное слою верхнему. Не проглатывать же его молча? Раз вылетело, значит неучтённая ошибка. Игнорирование её в конечном счёте приведёт к ещё большему пользовательскому щастью.


Можно, разумеется, но народ забывает. Это невозможно не забыть. Вот и вылетают непойманные на своем уровне исключения наверх, где их никто не ждет.
Re[4]: Какие у исключений проблемы?
От: dimgel Россия https://github.com/dimgel
Дата: 04.11.14 17:38
Оценка: 2 (1) +5
Здравствуйте, Pzz, Вы писали:

Pzz>Можно, разумеется, но народ забывает. Это невозможно не забыть. Вот и вылетают непойманные на своем уровне исключения наверх, где их никто не ждет.


Да и нехай значит. Всё лучше, повторюсь, чем молча глотать. А на верхнем уровне можно журналировать всё непойманное, потихоньку фикся ранее забытое.
Re[2]: Какие у исключений проблемы?
От: vsb Казахстан  
Дата: 04.11.14 17:40
Оценка: +1
Здравствуйте, Pzz, Вы писали:

vsb>>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.


Pzz>Этим исключениям хорошо бы как-то законодательно синтаксически ограничить дальность полета. Потому что когда в ваш высокоуровневый код, работающий в терминах высокоуровневых абстракций, откуда-нибудь с самого нижнего уровня прилетит исключение про то, чего вы в своем высокоуровневом коде сказать-то и не можете, то что вы с ним будете делать? Выдадите пользователю ошибку с шешнадцетиричным номером и невнятной диагностикой? То-то пользователь будет счастлив.


Тут два варианта. Либо это проблема в коде приложения, либо это проблема в окружении. Типичный пример проблемы в коде приложения — разыменование нулевого указателя. Типичный пример проблемы в окружении — I/O ошибка при записи на диск (диск сломался).

Если это Web-сервер, мы выдаём 500 ошибку и логгируем всё, что можем. Возможно отправляем SMS администратору, это уже не суть важно. Пользователю показываем красивую страничку "Упс, у нас что-то сломалось".

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

А что ещё может сделать нижний уровень? Ну да, он может сказать "Production database unavailable" вместо "Socket disconnected", но кому от этого станет лучше? Пользователю лучше не станет. Администратор/программист из стектрейса и так поймёт, что проблема в БД-коде.

По сути я вижу отличие исключений от кодов возврата только в одном use-case. Если мы хотим проглотить ошибку. Вот такое у нас приложение — работаем до последнего и плевать, что там на улице. Тогда с кодами возврата отработает больше кода. С исключениями — выполнение оборвётся сразу и исключение улетит далеко наверх. Ну или ставить try { ... } catch {} везде. Но это ведь в любом случае плохая практика и даже хорошо, что исключения её усложняют.
Re[5]: Какие у исключений проблемы?
От: Pzz Россия https://github.com/alexpevzner
Дата: 04.11.14 17:44
Оценка: 1 (1) -6 :)
Здравствуйте, dimgel, Вы писали:

Pzz>>Можно, разумеется, но народ забывает. Это невозможно не забыть. Вот и вылетают непойманные на своем уровне исключения наверх, где их никто не ждет.


D>Да и нехай значит. Всё лучше, повторюсь, чем молча глотать. А на верхнем уровне можно журналировать всё непойманное, потихоньку фикся ранее забытое.


Тогда их на верхнем уровне будут молча глотать. С журналированием в /dev/null, ага.

P.S. Наверное, было бы легче, если бы исключения, которые могут прилететь от методов класса, должны были бы быть описаны в самом классе. И если метод класса зовет швыряющуюся чужеродными исключениями функцию, то компилятор бы требовал обложить ее соответствующими try/catch, чтобы незаявленные исключения наружу бе вылетали.
Re[6]: Какие у исключений проблемы?
От: vsb Казахстан  
Дата: 04.11.14 17:52
Оценка: 3 (1) +6
Здравствуйте, 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, мало ли, вдруг кинется, в сигнатуре написано же.
Отредактировано 04.11.2014 17:55 vsb . Предыдущая версия .
Re[6]: Какие у исключений проблемы?
От: dimgel Россия https://github.com/dimgel
Дата: 04.11.14 17:55
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Тогда их на верхнем уровне будут молча глотать. С журналированием в /dev/null, ага.


Это как правило безопаснее, чем глотать на нижнем: при ошибке прерывается весь текущий сценарий целиком, а не продолжает выполняться с непонятного места с непонятными последствиями.
Re: Какие у исключений проблемы?
От: jazzer Россия Skype: enerjazzer
Дата: 04.11.14 18:47
Оценка: +2
Здравствуйте, vsb, Вы писали:

vsb>На мой взгляд это проблема исключительно С++, а точнее его двух особенностей — отсутствие GC и запрет исключений в деструкторах.


Нет такого запрета.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[2]: Про исключения в деструкторах
От: dimgel Россия https://github.com/dimgel
Дата: 04.11.14 18:52
Оценка:
Здравствуйте, jazzer, Вы писали:

vsb>>На мой взгляд это проблема исключительно С++, а точнее его двух особенностей — отсутствие GC и запрет исключений в деструкторах.


J>Нет такого запрета.


На вопрос "вызывать ли деструктор, если конструктор бросил исключение" разные языки, помнится, отвечали по-разному, поэтому я старался до такого не опускаться и под инициализацию, способную бросить исключение, заводил отдельный от конструктора метод. Но по крайней мере освобождение памяти из-под собственно объекта в том же C++ наверняка в любом случае происходит. А вот как программа будет дальше жить, если исключение бросает деструктор, я вообще ХЗ. Memory leak, не?
Re[6]: Какие у исключений проблемы?
От: MTD https://github.com/mtrempoltsev
Дата: 04.11.14 19:22
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>P.S. Наверное, было бы легче, если бы исключения, которые могут прилететь от методов класса, должны были бы быть описаны в самом классе. И если метод класса зовет швыряющуюся чужеродными исключениями функцию, то компилятор бы требовал обложить ее соответствующими try/catch, чтобы незаявленные исключения наружу бе вылетали.


В Java, как уже сказали это есть, причем с самого начала. Привело только к тому, что ИДЕ генерируют сопли глотающие исключения автоматически. В самом сообществе ява-разработчиков, сейчас преобладает мнение не использовать checked exceptions вообще.
Re[3]: Про исключения в деструкторах
От: jazzer Россия Skype: enerjazzer
Дата: 04.11.14 19:24
Оценка: 8 (1)
Здравствуйте, dimgel, Вы писали:

D>Здравствуйте, jazzer, Вы писали:


vsb>>>На мой взгляд это проблема исключительно С++, а точнее его двух особенностей — отсутствие GC и запрет исключений в деструкторах.


J>>Нет такого запрета.


D>На вопрос "вызывать ли деструктор, если конструктор бросил исключение" разные языки, помнится, отвечали по-разному, поэтому я старался до такого не опускаться и под инициализацию, способную бросить исключение, заводил отдельный от конструктора метод.


Ну в С++-то все вполне однозначно — деструктор вызывается только для полностью сконструированного (под)объекта (что имеет смысл, согласись — ведь пока не отработал конструктор, ничего о состоянии объекта в общем случае сказать нельзя — там может быть банально мусор в еще не инициализированных членах).

D>Но по крайней мере освобождение памяти из-под собственно объекта в том же C++ наверняка в любом случае происходит. А вот как программа будет дальше жить, если исключение бросает деструктор, я вообще ХЗ. Memory leak, не?


Не. Память под объектом будет освобождена независимо от успешности работы деструктора.
Так что просто надо аккуратно.
Вот есть, например, такая библиотека SOCI: http://soci.sourceforge.net/
Так в ней вся полезная работа совершается как раз в деструкторе, и исключения вылетают из него же, если что-то идет не так.

Так что исключения из деструктора просто исключают (каламбур) некоторые сценарии деструкции. Но не все (вернее, запрещен ровно один сценарий — если твой объект может разрушиться в результате размотки стека). И за этим надо очень внимательно следить (чтобы между созданием и разрушением твоего объекта не было кода, который может бросить исключение, и ты его при этом не ловишь), так что, естественно, общее правило — избегайте по возможности вылет исключений из деструктора.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[7]: Какие у исключений проблемы?
От: dimgel Россия https://github.com/dimgel
Дата: 04.11.14 19:25
Оценка:
Здравствуйте, MTD, Вы писали:

MTD>В Java, как уже сказали это есть, причем с самого начала. Привело только к тому, что ИДЕ генерируют сопли глотающие исключения автоматически. В самом сообществе ява-разработчиков, сейчас преобладает мнение не использовать checked exceptions вообще.


Добавлю, что в скале от них отказались по этой же причине: вреда больше чем пользы.
Re: Какие у исключений проблемы?
От: AlexRK  
Дата: 04.11.14 19:40
Оценка: +6
Здравствуйте, vsb, Вы писали:

vsb>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.


Главная проблема в том, что исключения создают дополнительный скрытый поток управления, который никак в коде не виден.

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

Таким образом, исключения предлагают избавление от лапши проверок, давая взамен потенциально некорректное состояние программы.
Коды возврата гарантируют видимость всех путей исполнения и отсутствие неожиданных нарушений инвариантов, но генерируют много бойлерплейта.
Что лучше — хрен знает.
Re[2]: Какие у исключений проблемы?
От: jazzer Россия Skype: enerjazzer
Дата: 04.11.14 19:57
Оценка: +2 :)
Здравствуйте, AlexRK, Вы писали:

ARK>Таким образом, исключения предлагают избавление от лапши проверок, давая взамен потенциально некорректное состояние программы.

ARK>Коды возврата гарантируют видимость всех путей исполнения и отсутствие неожиданных нарушений инвариантов, но генерируют много бойлерплейта.
ARK>Что лучше — хрен знает.

Ну вот монады бойлерплейт и почистят
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[2]: Какие у исключений проблемы?
От: x-code  
Дата: 04.11.14 20:11
Оценка:
Здравствуйте, AlexRK, Вы писали:

ARK>Главная проблема в том, что исключения создают дополнительный скрытый поток управления, который никак в коде не виден.


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

Например, сделать какой-то особый оператор (вместо throw) который был бы чем-то средним между throw и return: он мог бы бросать исключение, если ранее в какой-то глобальной таблице блок try выставил флаг, что данный тип исключения обрабатывается; а если флага нет, то возвращал бы свой аргумент как код возврата.
Re: Какие у исключений проблемы?
От: smeeld  
Дата: 04.11.14 20:21
Оценка: +1 -8 :))) :)))
Здравствуйте, vsb, Вы писали:

vsb>Какие есть серьёзные аргументы против исключений и за (извиняюсь за каламбур) возврат к кодам возврата? Ведь это же ужасный код, когда после вызова каждой функции мы тут же проверяем err и если он не null, просто передаём его наверх. Это то, от чего избавляют исключения.


Исключения это очень медленно и размашисто, по сравнению с if(func()!=0) err();
Оправдывает себя только для случаев, когда исключением можно обработать глобальные
сбой или ошибку, с или остановкой приложения, или его, в некотором смысле, полным перезапуском,
с какими другими входными данными и иным способом.
Короче, это как тяжёлая артиллерия, и применять это средство необдуманно, нерационально-чревато,
потерей производительности.
Re[2]: Какие у исключений проблемы?
От: WolfHound  
Дата: 04.11.14 20:27
Оценка: -1
Здравствуйте, smeeld, Вы писали:

S>Исключения это очень медленно и размашисто, по сравнению с if(func()!=0) err();

Это не правда. Правильно реализованные исключения работают быстрее кодов возврата.
Остальное тоже к реальности не относится.
... << RSDN@Home 1.2.0 alpha 5 rev. 62>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[3]: Какие у исключений проблемы?
От: smeeld  
Дата: 04.11.14 20:31
Оценка: +2
Здравствуйте, WolfHound, Вы писали:

WH>Это не правда. Правильно реализованные исключения работают быстрее кодов возврата.

WH>Остальное тоже к реальности не относится.

"Правильно реализованные", это как? Смотрел как они реализованы в ELF+gcc/clang/solarisstudio.
Простой if(err) return -1; сработает на порядок быстрее, чем throw x; }catch(){...}
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.