Шаблон Посетителя Для Обработки Иерархии Исключений

Исключения в C++ — один из самых серьезных механизмов языка.

Предоставляет довольно мощные возможности для анализа и обработки ошибок.

Но работать с исключениями не всегда так удобно.

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

Думаю, самые умные люди уже поняли, в чем моя идея.

Кому еще интересно, предлагаю разобраться в этом подробнее.

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

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

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

У нас принято, что исключения не хранят никаких сообщений о произошедшей ошибке (так как для их поиска придется обращаться к ресурсам).

В лучшем случае через What() можно узнать файл и строку, откуда было выброшено исключение.

Каждый тип ошибки должен иметь свой тип исключения.

Класс исключений должен хранить только те значения, которые необходимы для анализа ошибки.

Наша система относительно большая, и при таком подходе каждый плагин имеет иерархию от 7 до 20 исключений.

Первой проблемой, с которой мы столкнулись, было дублирование кода.

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

выполненный.

С добавлением новых исключений становится еще веселее; программист должен пропустить хотя бы одну точку перехвата исключения, и появится потенциальная ошибка.

Конечно, такие ситуации сразу покрываются юнит- или компонентными тестами и быстро выявляются, но, как говорится: «лучшее исправление ошибки — исключить возможность ее возникновения».

Идея в том, что исключения каждого модуля имеют свой базовый класс с методами работы механизма посетителя:

  
  
  
  
   

namespace Exceptions { struct IBase : public std::exception { virtual void accept( IVisitor & _visitor ) const throw()= 0; }; } // namespace Exceptions

Также создается гостевой интерфейс, в котором объявляются методы для «посещения» всех типов исключений модуля:

namespace Exceptions { struct IVisitor { virtual ~IVisitor() {} virtual void visit( CannotOpenFile const & _exception ) = 0; virtual void visit( NotHaveSpace const & _exception ) = 0; }; } // namespace Exceptions

Реализация исключений будет выглядеть примерно так:

namespace Exceptions { class CannotOpenFile : public IBase { public: CannotOpenFile( std::string const & _filePath ) throw () : m_path( _filePath ) { } std::string const & getPath() const throw() { return m_path; } /*virtual*/ void accept( IVisitor & _visitor ) const throw() { _visitor.visit( *this ); } private: const std::string m_path; }; class NotHaveSpace : public IBase { public: /*virtual*/ void accept( IVisitor & _visitor ) const throw() { _visitor.visit( *this ); } }; } // namespace Exceptions

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

int main( int /*_argc*/, char * /*_argv[]*/ ) { int result = EXIT_SUCCESS; try { MyFile file; file.open( "file.my" ); } catch( Exceptions::IBase const & _exception ) { Exceptions::Visitors::Messenger visitor( std::cerr ); _exception.accept( visitor ); result = EXIT_FAILURE; } return result; }

Посетитель Exceptions::Visitors::Messenger будет выглядеть примерно так:

namespace Exceptions { namespace Visitors { class Messenger : public IVisitor { public: Messenger( std::ostream & _outputStream ) : m_outputStream( outputStream ) {} /*virtual*/ void visit( CannotOpenFile const & _exception ) { m_outputStream << "Cannot open file: " << _exception.getPath() << std::endl; } /*virtual*/ void visit( NotHaveSpace const & /*_exception*/ ) { m_outputStream << "Not have space for saving file" << std::endl; } private: std::ostream & m_outputStream; }; } // namespace Visitors } // namespace Exceptions

Список ловушек исчезает, как и дублирование кода.

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

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

реализует обработку, необходимую для конкретного случая.



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



Положительные стороны
  • Позволяет писать более гибкие обработчики исключений;
  • позволяет использовать один обработчик в нескольких местах;
  • практически исключает возможность «забыть» добавить обработку новых исключений во всех местах, где это необходимо.



Отрицательные стороны
  • Создание каждый раз дополнительных объектов (классов посетителей) для обработки исключений;
  • Каждое исключение должно иметь свой уникальный тип исключения.



Послесловие
Этот подход не является чем-то новым, и я не пытаюсь открыть новый шаблон проектирования.

В этой статье я постарался поделиться решением, которое показалось мне оптимальным для решения моих задач.

Надеюсь, этот опыт будет полезен и вам.

Теги: #c plus plus #исключения #шаблоны #шаблоны #visitor #C++

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.