#c #исключения
Тема непростая и обширная, и я очень хочу как следует в ней разобраться, но в книгах информации мало, да и часто оказывается, что реальный опыт мало пересекается с теорией. В языке С существует огромное количество способов сообщать о результатах действий и возникших ошибках. Сейчас в меня кто-то кинет тухлым овощем, возражая, что в C имеются только лишь примитивные коды возврата. Да, только коды возврата... Но они используются очень по-разному. Конечно, можно реализовать исключения, используя setjmp/longjmp. Но, как сказал Д. Ритчи: Если тебе нужен PL/1, ты знаешь, где его взять... Вот варианты обработки результатов выполнения, которые мне встречаются чаще всего: Функция возвращает код, который является отчетом о ее работе: const int r_code = some_func(a, b, c); Или, например, функция возвращает указатель на созданный объект, а в случае, если объект создать не удалось, функция возвращает NULL, не предоставляя никакой дополнительной информации о причине неудачи: object *obj = object_create(a, b, c); Так же, функция может возвращать указатель на созданный объект, записывая в заданное расположение детальный отчет об операции (а не просто дополнительную информацию об ошибке): information i; object *obj = object_create(a, b, c, &i); Еще функция может использовать глобальную переменную текущего потока для предоставления информации об ошибке: FILE *f = fopen(a, b); if (f == NULL) { // Обработка errno() И еще примерно бесконечное количество вариантов... При написании кода часто приходится жонглировать различными способами обработки ошибок и информирования о результатах действий. Мои вопросы: Есть ли в C универсальный способ для всего этого? А если нет, то в чем причина? Было бы интересно узнать, какие еще существуют подходы, в чем их плюсы и минусы? Есть ли большая разница в сложности разработки/поддержки кода, который использует/не использует исключения? Речь идет не про исключения в C++, а про исключения в языках с полноценной сборкой мусора - аля Java. Например, я некоторое время писал на Java, так вот, когда я увидел реальный промышленный код, в котором ловились и проверялись все исключения, какие только возможно, то я вообще не понимал, как это можно поддерживать и развивать, когда на 5 строчек библиотечных вызовов приходится писать сотни строк try/catch/finally... И при любой малейшей правке приходится перекапывать весь код, чтобы где-то в 100500 ветке обработки ошибки добавить поддержку обработки исключения, которого раньше не существовало.
Ответы
Ответ 1
Попробую ответить, хотя это и очень сложно, учитывая, кто спрашивает и как. Есть ли в C универсальный способ для всего этого? А если нет, то в чем причина? Универсального способа нет. Рекомендованного к использованию нет. Почему так? все очень просто - это чистый си. Тут программист сам ответственный за свой код, никто не будет ему вытирать ручки и рот. Было бы интересно узнать, какие еще существуют подходы, в чем их плюсы и минусы? В принципе были перечислены все обычные способы. Еще есть несколько методов забить на ошибки (очень часто применяется) использовать сторонний фреймворк (да, для си такое есть. К примеру cello). Есть ли большая разница в сложности разработки/поддержки кода, который использует/не использует исключения? Речь идет не про исключения в C++, а про исключения в языках с полноценной сборкой мусора - аля Java. Сборка мусора имеет весьма относительное отношение к исключениям. И делать какие то умозаключения с изначально неверного предположения чревато странными выводами. Поэтому, даже не будут обсуждать где лучше исключения или сборка мусора. Оставим это холивар-экспертам. Но в жава с спецификацией исключений действительно беда. Нужно либо пробрасывать постоянно весь список допустимых исключений, либо писать базовый и не переживать. В с++ это также пытались сделать, но вовремя отказались (и сделали noexcept, который более понятный). При разработке с исключениями самое главное, что сложно допустить ситуацию, когда программа пошла в разнос. К примеру, одна функция инициализирует объект, а вторая его использует. Если первая может вернуть NULL, а вторая этого не ожидает... то ошибку можно долго и нудно искать. С исключениями это придется обработать и дальше код не пойдет (но все равно, программист это должен закодить, само оно редко так бывает сразу и красиво).Ответ 2
Механизм исключений очень удобен. Он позволяет обработать исключение именно в том месте, где известно, как его обработать. Пример: есть у вас функция записи в лог Как вы ее пишете на кодах ошибок int log(char* msg, int len) { FILE * f = fopen(.....) if (f == NULL) return FILE_NOT_OPEN; if (fwrite(f, msg, len) != len) return MESSAGE_NOT_WRITE; fclose(f); return SUCCESS; } Уже в таком коде вы теряете исходную ошибку. Файл не открыт. А почему? Не все байты записались. По какой причине? Если вы результат функции log не обработаете сразу после вызова, то опять потеряете информацию. И получите, что-то типа int doProcess(...) { if (log(...) != SUCCESS) return LOG_NOT_WRITE; } Чтобы не потерять код ошибки нужно иметь единую систему кодов во всей системе. Включая все используемые библиотеки. Теперь как этот же код будет выглядеть на исключениях void log(char* msg, int len) { try { FILE * f = fopen(.....); try { fwrite(f, msg, len); } finally { fclose(f); } } catch (Exception e) { throw new LogWriteException(e); } } void doProcess(...) { ...... log(....) ..... } try { doProcess(); } catch (LogWriteException e) { processLogFailed(e); } резко упало количество кода и повысилась читабельность Мы обрабатываем ошибки на том уровне, где знаем и можем их обработать Мы не теряем информацию об оригинальном исключении. В своих программах я все апишные вызовы делаю так HANDLE Win32Check(HANDLE res) { if (!res) throw new OSError(GetLastError()); return res; } HANDLE h = Win32Check(CreateEvent(....)); try { ..... } finally { CloseHandle(h); } И напоследок - наличие или отсутствие сборки мусора никак не влияет на возможность использования исключений.
Комментариев нет:
Отправить комментарий