Александреску Андрей
Шрифт:
Как минимум, ваше приложение должно обеспечить наличие заглушек
• Вокруг
• Вокруг функций обратного вызова из кода, который находится вне вашего контроля. Операционные системы и библиотеки часто используют схему, при которой вы передаете указатель на функцию, которая будет вызвана позже (например, при некотором асинхронном событии). Не позволяйте исключениям распространиться за пределы вашей функции обратного вызова, поскольку вполне возможно, что код, вызывающий вашу функцию, использует иной механизм обработки исключений. Кстати говоря, он может вообще быть написан не на С++.
• Вокруг границ потока. В конечном итоге поток выполнения создается внутри операционной системы. Убедитесь, что ваша функция, представляющая поток, не преподнесет операционной системе сюрприз в виде исключения.
• Вокруг границ интерфейса модуля. Ваша подсистема предоставляет окружающему миру некоторый открытый интерфейс. Если подсистема представляет собой отдельную библиотеку, лучше, чтобы исключения оставались в ее границах, а для сообщения об ошибках использовались старые добрые коды ошибок (см. рекомендацию 72).
• Внутри деструкторов. Деструкторы не должны генерировать исключений (см. рекомендацию 51). Деструкторы, которые вызывают функции, способные генерировать исключения, должны защититься от возможной утечки этих исключений.
Убедитесь, что каждый модуль согласованно использует одну и ту же внутреннюю стратегию обработки ошибок (предпочтительно — исключения С++; см. рекомендацию 72) и единую стратегию обработки ошибок в интерфейсе (например, коды ошибок для API на языке С); обе эти стратегии могут быть одинаковы, но обычно это не так. Стратегии обработки ошибок могут изменяться только на границах модуля. Определите, как происходит связывание стратегий между модулями (например, как происходит взаимодействие с COM или CORBA, или что всегда следует перехватывать исключения на границе с API на языке С). Хорошим решением будет определить центральные функции, которые выполняют преобразования между исключениями и кодами ошибок, возвращаемых подсистемой. Так вы сможете легко транслировать входящие ошибки от других модулей в используемые внутренние исключения и тем самым упростить интеграцию.
Использование двух стратегий вместо одной выглядит избыточным, и вы можете поддаться соблазну отказаться от исключений и везде использовать только старые добрые коды ошибок. Но не забывайте, что обработка исключений имеет достоинства простоты использования и надежности, естественна для С++ и что избежать ее в нетривиальных программах на С++ невозможно (просто потому, что стандартный язык и библиотека генерируют исключения), так что вам следует предпочесть использовать исключения там, где это только возможно. Дополнительную информацию вы найдете в рекомендации 72.
Небольшое предостережение. Некоторые операционной системы используют механизм исключений С++ при обработке низкоуровневых системных ошибок, как, например, разыменование нулевого указателя. Следовательно, инструкция
Использование
[Stroustrup00] §3.7.2, §14.7 • [Sutter00] §8-17 • [Sutter02] §17-23 • [Sutter04] §11-13
63. Используйте достаточно переносимые типы в интерфейсах модулей
Не позволяйте типам появляться во внешнем интерфейсе модуля, если только вы не уверены в том, что все пользователи смогут корректно их понять и работать с ними. Используйте наивысший уровень абстракции, который в состоянии понять клиентский код.
Чем более широко распространяется ваша библиотека, тем меньше ваш контроль над средами программирования, используемыми вашими клиентами, и тем меньше множество типов, которые ваша библиотека может надежно использовать в своем внешнем интерфейсе. Взаимодействие между модулями включает обмен бинарными данными. Увы, С++ не определяет стандартные бинарные интерфейсы; широко распространенные библиотеки для взаимодействия со внешним миром могут полагаться на такие встроенные типы, как