Набор исключений метода
От: developer  
Дата: 14.06.18 17:19
Оценка:
Привет всем.

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

Более конкретно:
Стоит ли использовать исключения, которые не унаследованы от std::exception?
Стоит ли наследовать все исключения от какого-то одного класса, например, от std::runtime_error — чтобы иерархия исключений имела один единственный корень?
Стоит ли как-то сгруппировать исключения в группы (т.е. для каждого метода своя группа исключений)?
Иногда бывает одинаково уместно или вернуть false или кинуть исключение — что предпочесть?
Re: Набор исключений метода
От: kov_serg Россия  
Дата: 14.06.18 18:35
Оценка:
Здравствуйте, developer, Вы писали:

D>Привет всем.


D>У меня такой вопрос — в проекте становится до фига исключительных ситуаций. Например, есть сложный метод, который уже кидает кучу разных исключений. Конечно есго можно разбить на несколько методов и упростить, но вопрос не в этом. Как организовать все эти исключения различных методов в проекте?


D>Более конкретно:

D>Стоит ли использовать исключения, которые не унаследованы от std::exception?
D>Стоит ли наследовать все исключения от какого-то одного класса, например, от std::runtime_error — чтобы иерархия исключений имела один единственный корень?
D>Стоит ли как-то сгруппировать исключения в группы (т.е. для каждого метода своя группа исключений)?
D>Иногда бывает одинаково уместно или вернуть false или кинуть исключение — что предпочесть?
void panic();

void smart_fn() {
  try { timid_fn(); } catch(...) { panic(); }
}

void panic() {
  try { throw; }
  catch(A &a) { ... throw "A"; }
  catch(B &b) { ... throw "B"; }
  catch(C &c) { if (!ignore_c) throw; }
  catch(std::exception &e) { ... }
  ... // еще 100500 разных вариантов
  catch(...) { real_panic(); }
}
Re: Набор исключений метода
От: AlexGin Беларусь  
Дата: 14.06.18 20:25
Оценка: 4 (2)
Здравствуйте, developer, Вы писали:

D>Привет всем.


D>У меня такой вопрос — в проекте становится до фига исключительных ситуаций. Например, есть сложный метод, который уже кидает кучу разных исключений. Конечно есго можно разбить на несколько методов и упростить, но вопрос не в этом. Как организовать все эти исключения различных методов в проекте?


D>Более конкретно:

D>Стоит ли использовать исключения, которые не унаследованы от std::exception?
Не вижу особого смысла. Проще всё-таки наследовать от стандартных.

D>Стоит ли наследовать все исключения от какого-то одного класса, например, от std::runtime_error — чтобы иерархия исключений имела один единственный корень?

Да, вполне естественно — иметь один базовый класс исключений.
Вот подробнее:
https://isocpp.org/wiki/faq/exceptions
http://en.cppreference.com/w/cpp/error/exception
https://www.tutorialspoint.com/cplusplus/cpp_exceptions_handling.htm
http://www.cplusplus.com/doc/tutorial/exceptions

D>Стоит ли как-то сгруппировать исключения в группы (т.е. для каждого метода своя группа исключений)?

Стоит ли усложнять?
Делай как можно проще: https://en.wikipedia.org/wiki/KISS_principle

D>Иногда бывает одинаково уместно или вернуть false или кинуть исключение — что предпочесть?

В стиле C++ всё таки — кинуть исключение; В стиле C — вернуть значание
Приимущество исключения — меньше вероятности, что проблема останется незамеченной.

P.S. Вот ещё полезная информация:
https://stackoverflow.com/questions/12261915/howto-throw-stdexceptions-with-variable-messages
https://stackoverflow.com/questions/3074646/how-to-catch-unknown-exception-and-print-it

Применение std::exception_ptr:
http://en.cppreference.com/w/cpp/error/exception_ptr
https://stackoverflow.com/questions/14232814/how-do-i-make-a-call-to-what-on-stdexception-ptr
https://msdn.microsoft.com/en-us/library/dd293602.aspx
Отредактировано 14.06.2018 22:08 AlexGin . Предыдущая версия . Еще …
Отредактировано 14.06.2018 22:07 AlexGin . Предыдущая версия .
Отредактировано 14.06.2018 21:33 AlexGin . Предыдущая версия .
Отредактировано 14.06.2018 21:02 AlexGin . Предыдущая версия .
Re[2]: Набор исключений метода
От: kov_serg Россия  
Дата: 14.06.18 21:05
Оценка:
Здравствуйте, AlexGin, Вы писали:

AG>Да вот подробнее:

AG>https://www.tutorialspoint.com/cplusplus/cpp_exceptions_handling.htm
С делением примеры не очень http://tpcg.io/5Dl7Bh

D>>Иногда бывает одинаково уместно или вернуть false или кинуть исключение — что предпочесть?

AG>В стиле C++ всё таки — кинуть исключение; В стиле C — вернуть значание
В C код возврата 0-всё зашибись и !=0 код ошибки.
rc=func(); if (rc) log_error(rc);
Re[3]: Набор исключений метода
От: AlexGin Беларусь  
Дата: 14.06.18 21:40
Оценка:
Здравствуйте, kov_serg, Вы писали:

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


AG>>Да вот подробнее:

AG>>https://www.tutorialspoint.com/cplusplus/cpp_exceptions_handling.htm
_>С делением примеры не очень http://tpcg.io/5Dl7Bh

D>>>Иногда бывает одинаково уместно или вернуть false или кинуть исключение — что предпочесть?

AG>>В стиле C++ всё таки — кинуть исключение; В стиле C — вернуть значание
_>В C код возврата 0-всё зашибись и !=0 код ошибки.
_>
_>rc=func(); if (rc) log_error(rc);
_>

Да, но это не везде.
Так, WinAPI построен так, что для получения кода ошибки — нужно звать GetLastError():
https://msdn.microsoft.com/en-us/library/windows/desktop/ms679360(v=vs.85).aspx
Re[4]: Набор исключений метода
От: kov_serg Россия  
Дата: 15.06.18 06:26
Оценка: +1 :)
Здравствуйте, AlexGin, Вы писали:

D>>>>Иногда бывает одинаково уместно или вернуть false или кинуть исключение — что предпочесть?

AG>>>В стиле C++ всё таки — кинуть исключение; В стиле C — вернуть значание
_>>В C код возврата 0-всё зашибись и !=0 код ошибки.
_>>
_>>rc=func(); if (rc) log_error(rc);
_>>

AG>Да, но это не везде.
AG>Так, WinAPI построен так, что для получения кода ошибки — нужно звать GetLastError():
AG>https://msdn.microsoft.com/en-us/library/windows/desktop/ms679360(v=vs.85).aspx
Ага еще есть WSAGetLastError и просто BOOL, и HRESULT, и HANDLE ...
В winapi всегда стремились всё взять и сделать иначе и единообразие их заботило в последнюю очередь.
Единственное что у них было постоянно это любовь делать функции с огромным количеством бесполезных параметров и затем добавлять теже функции (но лучше) с еще большим количеством параметров.
Re: Набор исключений метода
От: Maniacal Россия  
Дата: 15.06.18 07:01
Оценка: 6 (2) :)
Здравствуйте, developer, Вы писали:

D>У меня такой вопрос — в проекте становится до фига исключительных ситуаций. Например, есть сложный метод, который уже кидает кучу разных исключений. Конечно есго можно разбить на несколько методов и упростить, но вопрос не в этом. Как организовать все эти исключения различных методов в проекте?


Советую забубенить макрос с чредой catch'ей. У меня самописный есть кросплатформенный (для винды ещё и системное сообщение об ошибке вытаскивает), он ещё и стек раскручивает. Но я только два вида исключений ловлю: std::runtime_error и (...). Макрос все исключения преобразует в std::runtime_error и пробрасывает дальше. В корневой функции ловлю уже обычным catch(std::runtime_error&)

В результате код выглядит как
try
{
   ...
}
RETHROW


По желанию можно FINALLY прикрутить

Ниже фрагмент описания макросов, задействован свой вспомогательный класс CError, хранящий стек в TLS, а так же truntime_error/tstring, наследники std::runtime_error/std::basic_string для поддержки UNICODE. STDERROR для бросания своих исключений, SYSERROR для дополнения сообщения системным описанием ошибки:
...
#ifdef linux 
#define __FUNCTION__ __func__
#endif

#define STACK_TRACE (CError::PopStackTrace() + _T("\n") + _T(__FUNCTION__))

#define RETHROW \
    catch(truntime_error &e) {\
        if (CError::GetStackTrace().empty()) CError::GetStackTrace() = e.what();\
        CError::GetStackTrace() += tstring(_T("\n")) +  _T(__FUNCTION__);\
        throw; }\
    catch(...) {\
        if (CError::GetStackTrace().empty()) CError::GetStackTrace() = _T("Unhandled exception");\
        CError::GetStackTrace() += tstring(_T("\n")) + _T(__FUNCTION__);\
        throw; }

...
#define STDERROR(message) CError::ThrowStdError(_T(__FILE__), __LINE__, _T(message))
#define SYSERROR(message) CError::ThrowSysError(_T(__FILE__), __LINE__, _T(message))
...
Re[2]: Набор исключений метода
От: Ops Россия  
Дата: 15.06.18 07:25
Оценка:
Здравствуйте, AlexGin, Вы писали:

D>>Иногда бывает одинаково уместно или вернуть false или кинуть исключение — что предпочесть?

AG>В стиле C++ всё таки — кинуть исключение; В стиле C — вернуть значание
AG>Приимущество исключения — меньше вероятности, что проблема останется незамеченной.

Ну вот есть dynamic_cast, с двояким поведением: в одном случае null, в другом — исключение
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Re[3]: Набор исключений метода
От: AlexGin Беларусь  
Дата: 15.06.18 10:30
Оценка:
Здравствуйте, Ops, Вы писали:

Ops>Ну вот есть dynamic_cast, с двояким поведением: в одном случае null, в другом — исключение


dynamic_cast — с преобразованием указателя вернёт null (указатели есть в ANSI-C),
dynamic_cast — с преобразованием ссылки кинет исключение (ссылки появились в C++) — в общем логично.
Re[4]: Набор исключений метода
От: Ops Россия  
Дата: 15.06.18 11:11
Оценка:
Здравствуйте, AlexGin, Вы писали:

AG>dynamic_cast — с преобразованием указателя вернёт null (указатели есть в ANSI-C),

Только вот ничего подобного dynamic_cast в C нет, так что можно было бы и бросить. Так сделано скорее всего, чтобы можно было разруливать это без исключений. Но это противоречит "В стиле C++ всё таки — кинуть исключение"

AG>dynamic_cast — с преобразованием ссылки кинет исключение (ссылки появились в C++) — в общем логично.

Это конечно, со ссылками я других вариантов вообще не вижу.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Re[5]: Набор исключений метода
От: AlexGin Беларусь  
Дата: 15.06.18 12:08
Оценка:
Здравствуйте, Ops, Вы писали:

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


AG>>dynamic_cast — с преобразованием указателя вернёт null (указатели есть в ANSI-C),

Ops>Только вот ничего подобного dynamic_cast в C нет, так что можно было бы и бросить. Так сделано скорее всего, чтобы можно было разруливать это без исключений. Но это противоречит "В стиле C++ всё таки — кинуть исключение".

Да, dynamic_cast в C, естественно, нет. Но так как он возвращает pointer, то null-pointer здесь как бы логичен.

AG>>dynamic_cast — с преобразованием ссылки кинет исключение (ссылки появились в C++) — в общем логично.

Ops>Это конечно, со ссылками я других вариантов вообще не вижу.

На сегодняшний день, IMHO наиболее востребованный вариант вообще НЕ dynamic_cast, а std::dynamic_pointer_cast<...>(...) — вызов его в случае неудачи возвращает nullptr.

http://en.cppreference.com/w/cpp/memory/shared_ptr/pointer_cast
http://www.cplusplus.com/reference/memory/dynamic_pointer_cast
Отредактировано 15.06.2018 12:38 AlexGin . Предыдущая версия .
Re[6]: Набор исключений метода
От: Ops Россия  
Дата: 15.06.18 12:27
Оценка:
Здравствуйте, AlexGin, Вы писали:

AG>Да, dynamic_cast в C, естественно, нет. Но так как он возвращает pointer, то null-pointer здесь как бы логичен.


Почему? Это своего рода код ошибки. Может, в C++ все же используются разные подходы, и однозначно утверждать, что в его стиле бросать — неверно?
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Re[7]: Набор исключений метода
От: AlexGin Беларусь  
Дата: 15.06.18 12:48
Оценка:
Здравствуйте, Ops, Вы писали:

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


AG>>Да, dynamic_cast в C, естественно, нет. Но так как он возвращает pointer, то null-pointer здесь как бы логичен.


Ops>Почему? Это своего рода код ошибки. Может, в C++ все же используются разные подходы, и однозначно утверждать, что в его стиле бросать — неверно?


Да, на C++ используются разные подходы. Но ИМХО преобладает всё же подход с выбрасыванием exception.
Если смотреть в сторону STL, то всё таки exceptions — основной метод генерации/обработки ошибок.

Тем не менее — иногда действительно придерживаются применения кода ошибки: https://news.ycombinator.com/item?id=4563914
Re[8]: Набор исключений метода
От: so5team https://stiffstream.com
Дата: 16.06.18 06:33
Оценка:
Здравствуйте, AlexGin, Вы писали:

AG>Да, на C++ используются разные подходы. Но ИМХО преобладает всё же подход с выбрасыванием exception.


Ваше ИМХО в данном случае, как и во многих других случаях, касающихся C++, оказывается весьма далеким от действительности.
По результатам недавнего C++ Developers Survey только ~48% С++ разработчиков имеют возможность свободно использовать исключения, а в 20% случаев исключения вообще находятся под полным запретом. И гораздо чаще используются коды ошибок и специальные классы, вроде folly::Expected или outcome::result.
Re: Набор исключений метода
От: so5team https://stiffstream.com
Дата: 16.06.18 07:04
Оценка: 30 (3) +2
Здравствуйте, developer, Вы писали:

D>Стоит ли использовать исключения, которые не унаследованы от std::exception?


Если вы делаете библиотеку, то, скорее всего, без наследования от std::exception вы доставите больше хлопот пользователям своей библиотеки.
Если же код не выйдет за пределы вашей команды, то можно делать все, что угодно. Но вам же будет проще, если по catch(const std::exception &) вы сможете ловить и свои собственные исключения.

D>Стоит ли наследовать все исключения от какого-то одного класса, например, от std::runtime_error — чтобы иерархия исключений имела один единственный корень?


Да. Например, делаете my_exception наследником std::exception (или std::runtime_error, если хотите), а уже от my_exception наследуете остальные свои классы исключений.

D>Стоит ли как-то сгруппировать исключения в группы (т.е. для каждого метода своя группа исключений)?


Зависит, как минимум, от двух вещей:

1. Объем вашей кодовой базы и ее разделение на более-менее независимые модули. Если у вас небольшая кодовая база и ярко выраженных модулей нет, то можно обойтись всего одним классом исключения. Если кодовая база большая (скажем от 50-100KLOC и больше) и есть модули, отвечающие за разную функциональность (скажем, модуль net для работы с сетью, db для работы с СУБД, crypto для криптографии и т.д.), то для каждого модуля можно сделать свой базовый класс исключения. Тогда будет проще фильтровать проблемы, относящиеся к конкретному модулю. Что-то вроде:
try {
  auto raw_data = net::recv_msg(channel);
  auto data = crypto::decrypt_msg(raw_data);
  ...
}
catch(const crypto::exception & x) {
  ... // Специальным образом обрабатываем ошибки дешифрации.
}

При этом у вас может быть вот такая иерархия исключений:
std::exception
`- my_exception
   `- net::exception
   |  `- ...
   `- crypto::exception
      `- ...
...


2. Если вам нужно сгруппировать в исключениях какие-то наборы данных, описывающих ошибку. Например, в каких-то случаях вам нужно передавать в исключении имя файла и код системной ошибки. В каких-то случаях имя пользователя и причину невозможности его аутентификации. В каких-то случаях недопустимый индекс и актуальный разрешенный диапазон индексов. Вот если у вас такие случаи есть, то тогда вы можете создавать отдельные ветки иерархии исключений:
std::exception
`- my_exception
   `- file_error (name, error_code)
   |  `- open_file_error
   |  `- read_file_error
   |  `- unlink_error
   |  ...
   `- user_auth_error (user_name, reason)
      `- unknown_user_error
      `- no_permission_error
      `- disabled_user_error
      `- ...


D>Иногда бывает одинаково уместно или вернуть false или кинуть исключение — что предпочесть?


В таких случаях ориентируются на три важнейших фактора:

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

2. Тяжесть последствий, если исключение не будет брошено, а пользователь забудет проверить код возврата. Например, вы делаете метод memory_arena::preallocate, который возвращает вам указатель на зарезервированный сегмент. Если пользователь забудет проверить возвращенный указатель и обратиться по нему, то программа рухнет в случае возврата нулевого указателя. Тогда как при выбросе исключения этого не произойдет.

Однако, в современном мире для таких случаев может быть выгоднее использовать специализированные классы вроде folly::Expected или outcome::result.

3. Цена выброса исключения. Если у вас исключения могут бросаться часто, то даже там, где исключения реализованы эффективно, вы все равно столкнетесь с заметной просадкой производительности. Вряд ли вас это устроит и в таких случаях вам лучше предпочесть возвращаемые значения исключениям.
Re[9]: Набор исключений метода
От: AlexGin Беларусь  
Дата: 16.06.18 09:42
Оценка:
Здравствуйте, so5team, Вы писали:
...
https://isocpp.org/files/papers/CppDevSurvey-2018-02-summary.pdf

Я бы трактовал данную статистику так:

В 20% пользоваться исключениями нельзя. То есть, в остальных 80%, ими пользоваться всё-таки можно.
Для кодов возврата ошибок — требования не такие строгие: там нельзя пользоваться в 11% (в остальных 89% можно).
Re[2]: Набор исключений метода
От: AlexGin Беларусь  
Дата: 16.06.18 10:11
Оценка: +1 -1
Здравствуйте, so5team, Вы писали:
...
Всё вполне логично и правильно (уважаемый so5team, я не занимаюсь критиканством).

Но, тем не менее, Вы довольно странно трактуете понятие "Тяжесть последствий":

S>2. Тяжесть последствий, если исключение не будет брошено, а пользователь забудет проверить код возврата. Например, вы делаете метод memory_arena::preallocate, который возвращает вам указатель на зарезервированный сегмент. Если пользователь забудет проверить возвращенный указатель и обратиться по нему, то программа рухнет в случае возврата нулевого указателя. Тогда как при выбросе исключения этого не произойдет.


Значит, если я, работая как Заказчик, выполняя важную работу на Вашем приложении, получил краш или зависание, это более тяжёлое последствие, чем если я тихо и спокойно получил неверный результат вычислений — который (возможно по недосмотру как моему, так и других лиц), передал далее — например для проектировщиков АЭС или космодрома...
Отредактировано 16.06.2018 10:30 AlexGin . Предыдущая версия . Еще …
Отредактировано 16.06.2018 10:16 AlexGin . Предыдущая версия .
Re[9]: Набор исключений метода
От: Ops Россия  
Дата: 16.06.18 11:04
Оценка: +1
Здравствуйте, so5team, Вы писали:

S>Ваше ИМХО в данном случае, как и во многих других случаях, касающихся C++, оказывается весьма далеким от действительности.

S>По результатам недавнего C++ Developers Survey только ~48% С++ разработчиков имеют возможность свободно использовать исключения, а в 20% случаев исключения вообще находятся под полным запретом. И гораздо чаще используются коды ошибок и специальные классы, вроде folly::Expected или outcome::result.

Ситуаций, когда исключения обоснованно запрещены, не так много, твоя статистика скорее говорит о вахтеризме у работодателей, нежели о языке. Один гугл со своим code style чего стоит, причем некоторые его выдают за "хорошую практику".
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Re: Набор исключений метода
От: Nikе Россия  
Дата: 16.06.18 11:12
Оценка: +1
Здравствуйте, developer, Вы писали:

D>Иногда бывает одинаково уместно или вернуть false или кинуть исключение — что предпочесть?


Общее правило: исключения для исключительных ситуаций.
А то видел я энтузиастов, которые исключения использовали в качестве кодов возврата: захотели — размер вернули, или точку, а захотели HRESULT. И это всё из одной функции.
Нужно разобрать угил.
Re[3]: Набор исключений метода
От: so5team https://stiffstream.com
Дата: 16.06.18 11:16
Оценка: 9 (1) +1 -1 :)
Здравствуйте, AlexGin, Вы писали:

AG>Значит, если я, работая как Заказчик, выполняя важную работу на Вашем приложении, получил краш или зависание, это более тяжёлое последствие, чем если я тихо и спокойно получил неверный результат вычислений — который (возможно по недосмотру как моему, так и других лиц), передал далее — например для проектировщиков АЭС или космодрома...


Простите, но в очередной раз складывается ощущение, что у вас просто недостаточно мозгов опыта в разработке софта, чтобы вы поняли о чем идет речь. Либо же вы пытаетесь троллить вместо того, чтобы дать себе труд разобраться с тем, что было сказано.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.