пятница, 5 августа 2011 г.

Использование исключений

Я очень люблю исключения. Но в последнее время я начал понимать тех, кто в своих стандартах кодирования запрещает исключения. Просто поразительно насколько часто, даже грамотные люди не могут оценить исключительность ситуации..

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

Теперь давайте я приведу несколько примеров, которые меня бесят. Все совпадения с реальным кодом - реальны, имена только изменены, и код немного подчищен...
type Client::receive()
{
struct special_case { };
type data;

try {
if (...) {
throw runtime_error("...");
}
... skipped many throws ...
if (...) {
throw special_case();
}
} catch (exception &) {
data = type();
} catch (special_case &) {
throw runtime_error("...");
}
return data;
}

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

Очень костыльно выглядит особый случай, который вполне легко решается одним throw, когда мы убираем все "goto"...
string Informer::get()
{
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock < 0) {
throw runtime_error("...");
}
... skip more code with throws ...

Конечно, мы же на C++ пишем, зачем мы будем связываться с кодами ошибок и тд...
С кодами ошибок связываться нет необходимости, проблема не в этом. Проблема в том, что функция приватная. В публичной функции мы видим следующее:
try {
string r = get(components[i].comp);
... do something ...
} catch (const runtime_error &e) {
}


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

Следующий пример больше не по генерации, а по перехвату исключений. Так уж сложилось, что наш интерфейс базы данных бросает исключения, если выборка пуста. С этим ничего не поделаешь, приходится мириться, хотя много кода, похожего на этот:
try {
scoped_ptr<Set> cursor(db->all_records());
... do something ...
} catch (EmptySet &) {
// ничего не надо делать
}

Костыльно конечно, но что делать..
И тут я увидел это:
void convert_records(shared_ptr<RecordSet> &in, list &records) {
do {
try {
shared_ptr<RecordSet> cur_rec((*in)->get_records());
convert_records(cur_rec, records);
} catch (EmptySet) {
Record rec;
convert_record(**in, rec);
records.push_back(rec);
}
} while (!in->next());
}

Смысл в том, что там добавилась древовидность записей, одна запись получила возможность ссылаться на другие. И записи отныне делятся на листовые и групповые.

Я сперва не мог сообразить, почему две одинаковые функции используют совершенно разные параметры, буква s в конце имени функции очень плохо видна.. а за такую перегрузку - вообще бы убил.

Пришлось трясти автора, и тогда я узнал всю страшную правду. Эта функция вызывается для корневых элементов, и каждый элемент пытается проходить вглубь. Для негрупповых элементов происходит исключение, где, в ветке catch, и происходит натуральная работа - наполнение списка записей...

Условие нельзя было поставить? чтобы одним легким движением вообще отказаться от исключений в этом месте? Исключительность ошибок - я еще могу оправдать. Но здесь через catch проходит основная логика программы, основная ветвь выполнения.

Вот после этого я и подумал - наверное в Google все таки не дураки сидят, что запретили исключения?

9 коммент.:

Tier комментирует...

Скажем так: тот факт, что гуглеры не способны понять, что исключения потому и исключения, что используются в исключительных местах, вызывает большие сомнения, что они такие уж умные.

Andrey Valyaev комментирует...

Все программисты разные... и не все имеют за плечами 20летний опыт, как я например :)

Молодеж может этого не осознавать. И поэтому, чтобы не отравлять жизнь остальным, лучше запретить.

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

airmax комментирует...

Насколько я помню, гугл отказался от исключений в пользу лучшей переносимости кода.

Andrey Valyaev комментирует...

Погодите, погодите...

А какая проблема с исключениями в плане переносимости? Помоему они стандартные. Везде, где есть c++, исключения работают так, как положено.

Единственное, что написано в ихнем стиле - есть проблемы при использовании кода с иключениями в exception-free проектах. Дык это и так всем понятно.

ИМХО исключения - это хорошо. Но злоупотребить можно чем угодно.

airmax комментирует...

Суть в том что не все платформы поддерживают исключения (embedded например).
Я тоже считаю что исключения это хорошо.

Potapov Anton комментирует...

Отсюда:
https://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Exceptions

"On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. If exceptions can be propagated beyond a new project, it also becomes problematic to integrate the new project into existing exception-free code. Because most existing C++ code at Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code that generates exceptions. "

Andrey Valyaev комментирует...

airmax: Это какие? Имхо, если есть g++, то и исключения тоже есть.

Potapov Anton: Мог бы и перевести. копипастить мы все умеем...

Насколько я понимаю, в Google тоже считают, что исключения - это хорошо.

Но поскольку раньше они их не ценили, то сейчас испытывают проблемы при использовании исключений в новых проектах. :)

eao197 комментирует...

Исключения в C++ -- это too big gun. Не такой большой, конечно, как шаблоны, но все-таки. Поэтому поначалу от предоставляемых исключениями возможностей крышу сносит и их пробуешь засовывать направо и налево, пока сам не нахлебаешься и не начнешь прислушиваться к здравому смыслу. Так что, если у разработчика голова на плечах есть, то с опытом и нормальное использование исключений приходит.

По поводу style guide-ов, в которых намеренно запрещают использовать исключения -- тут нужно смотреть на две вещи. Во-первых, на возраст самих guide-ов. Возможно, их писали еще когда поддержка исключений в ряде компиляторов оставляла желать лучшего. Во-вторых, в каких условиях идет разработка. Если в компании работают сотни C++ников, то имеет смысл сильно ограничить им рамки дозволенного (исключения, шаблоны, STL- и Boost-стиль). Это разумно, т.к. слабых программистов в таких командах явно больше и им не дают лишних возможностей наломать дров.

С другой стороны, сейчас все равно все это выглядит диковато. C++ -- это содержащий огромное количество возможностей язык. Поэтому неправильно запрещать пользоваться наиболее мощными из них.

Mike комментирует...

Исключения - это довольно тяжелая штука, даже когда исключительных ситуаций нет (прологи/эпилоги функций). В одной конторе писали С++ проект (3d движок), где было много небольших функций-членов класса. После запрета на использование исключений размер кода значительно уменьшился, плюс к этому заметно увеличилась общая производительность.

Исключения необходимы в том случае, когда в конструкторе надо завершить создание объекта с ошибкой. Но это тоже обходится достаточно просто через простой конструктор-инициализатор + create/init метод.

Тоже самое и с шаблонами. Шаблоны, без сомнения, исключительно мощная и полезная возможность. Мне их очень не хватает, когда я пишу на С. Однако широкое использование шаблонов вызывает создание каждый раз нового класса с набором методов, что вызывает рост объема кода, забивает кэш, в общем, те же проблемы, что и при бездумном использовании inline/__force_inline.

С++ - это настолько мощный язык, что человек может очень легко выстрелить себе в ногу, совершенно спокойно написав крайне неэффективный код, который выглядеть будет красиво.