05 апреля 2007

How to reuse try-catch blocks

Повторное использование кода - вещь очень полезная во многих отношениях, не мне вам объяснять. Как повторно использовать последовательность операций понятно - выделить их в виде функции. Как быть с обработкой исключений?

Цель такова: есть некий код, который может генерировать исключения. Есть набор catch блоков, каждый из них знает как обработать свой тип исключений, причем исключения могут не являться классами одной иерархии. Совершенно разные исключения. Как повторно использовать этот набор catch-блоков? Вот небольшой трюк: обернем наш код в функтор boost::function и скормим функции, которая будет знать как обрабатывать исключения, например такой:
void HandleExceptions(const boost::function<void()>& Job)
{
try
{
Job();
}
catch (std::exception& Exception)
{
ReportException(Exception.what());
}
catch (const char* Exception)
{
ReportException(Exception);
}
catch (_com_error& Exception)
{
ReportException(Exception.Description());
}
catch (SomeXMLException&)
{
ReportException("Error parsing XML file.");
}
catch (...)
{
ReportException("Unknown exception");
}
}

Использование тривиально: оборачиваем нужный код в функцию или метод и передаем в качестве параметра в HandleExceptions:
HandleExceptions(boost::bind(Function, Parameter1, Parameter2));


Я использовал такой подход при выводе сообщений пользователю о необработанных исключениях. В моем приложении есть слой бизнес-логики, который постоянно меняется , пишется очень быстро и можно сказать в полевых условиях. Такая обстановка не способствует детальному тестированию и где-то легко может вылететь необработанное исключение, которое и ловится на границе бизнес-логики и ядра системы (бизнес-логика вызывается из ядра). Поймав исключение сообщаем пользователю об ошибке и продолжаем работать (ядро продолжает работать). Однако подобный глобальный обработчик для ядра тоже не помешает, мало ли что случится - сообщим пользователю ("программа выполнила некорректную операцию ;)) и завершим программу. Как раз тот try-catch блок по преобразованию исключений в сообщения об ошибках.

В примере использован вызов функции ReportException(), но можно вставить туда генерацию какого-нибудь исключения определенного типа, например преобразовать все пойманные исключения к std::exception. Или даже вернуть сообщение об ошибке из HandleException() по значению через return.