![]() |
![]() |
![]() |
Анимация
JavaScript
|
Главная Библионтека Задача 11. Попробуй поймай" Сложность: 3 Заключается ли безопасность исключений в том, чтобы написать try и catch в нужном месте? Если нет, то в чем? О чем не следует забывать при разработке стратегии безопасности исключений в ваших программах? Вопрос для новичка 1. Что такое try-блок? Вопрос для профессионала 2. "Написание безопасного с точки зрения исключений кода сводится в целом к размещению try и catch в правильных местах". Обсудите это утверждение. 3. Когда следует использовать try и catch? Когда их не следует использовать? Изложите ваш ответ в виде рекомендации стандарта кодирования. Решение 1. Что такое try-блок? try-блок (в некоторых книгах - "блок с контролем") представляет собой блок кода (составную инструкцию), выполнение которого может быть неудачным (приводящим к генерации исключения), за которым следует один или несколько блоков обработки исключений, которые выполняются при генерации в основном блоке исключения определенного типа, например: Пример 11-1: пример try-блока try { if( не которое условие ) throw stringC "Строка" ); else if(некоторое иное условие ) throw 42; catch( const string* ) { Выполняется при генерации исключения, имеющего тип stri ng catchC ... ) { выполняется при генерации любого другого исключения В примере 11-1 код в основном блоке может сгенерировать исключение типа string, int или не сгенерировать никакого исключения. 2. "Написание безопасного с точки зрения исключений кода сводится в целом к размещению try и catch в правильных местах". Обсудите это утверждение. Честно говоря, такое утверждение отражает фундаментальное непонимание безопасности исключений. Исключения представляют собой один из видов сообщения об ошибках, и мы знаем, что написание устойчивого к ошибкам исходного текста не сводится к проверке кода возврата и обработке ошибки. В оригинале - ифа слов, основанная на переводе ключевых слов try - пробовать, и catch - ловить. - Прим. перев. в действительности, безопасность исключений редко сводится к написанию ключевых слов (и чем меньше вы их пишете, тем лучше). Никогда нельзя забывать о том, что безопасность исключений влияет на проектирование. Вопросы безопасности надо учитывать заранее, еще на стадии проектирования програм.мы, а не просто расставлять ключевые слова в подходящих местах. Вот три основных момента, которые надо учитывать при написании безопасного в смысле исключений кода. 1. Где и когда следует генерировать исключения? Это вопрос о том, где именно следует размещать инструкции throw. В частности, мы должны ответить на следующие вопросы. • Какие именно исключения должен генерировать код? То есть о каких ошибках мы будем сообщать при помощи механизма исключений, а не при помощи возврата кода ошибки или какого-либо иного метода? • Какой код не должен генерировать исключений? В частности, какой код гарантирует отсутствие исключений? (См. задачу 12 и [Sutter99].) 2. Где и когда следует обрабатывать исключения? Эт единственный юпрос, связанный с выбором правильного размещения try и catch, и в большинстве случаев эта проблема решается автоматически. Начнем с вопросов, на которые мы должны ответить. • Какая часть исходного текста должна отвечать за обработку исключений? То есть у какого кода оказывается достаточно контекста и информации для обработки ошибки, о которой сообщается посредством исключения (возможно, путем преобразования исключения к другому виду)? В частности, заметим, что код обработчика должен обладать информацией, достаточной для того, чтобы выполнить все необходимые действия по освобождению распределенных ресурсов. • Какой код должен обрабатывать исключения? То есть каким образом выбрать среди всех возможных вариантов размещения кода обработчика наиболее подходящий? После того как мы ответим на эти вопросы, отметим, что использование идиомы "распределение ресурса есть инициализация" зачастую позволяет избежать использования ряда try-блоков путем автоматизации работы по освобождению ресурсов. Если вы "обернете" динамически распределяемые ресурсы в объект-владелец, его деструктор обычно в состоянии выполнить аетоматическое освобождение распределенного ресурса в необходимый момент времени, без явного использования try и catch, не говоря о том, что код с использованием этого метода проще писать, а позже - читать и понимать. > Рекомендация Использование автоматического освобождения с помощью деструкторов предпочтительнее применения для этой цели try-блоков. 3. Будет ли поведение моего кода безопасным, если произойдет генерация исключения в какой-либо из вызываемых функций? Это - вопрос правильного управления ресурсами, позволяющего избежать утечек, содержания классов и инвариантов программы в должном порядке и прочих показателей корректности программы. Иначе говоря, надо, чтобы в случае генерации исключения до его перехвата и обработки программа оставалась работоспособной, в согласованном состоянии, и могла продолжить работу после обработки исключения. Для большинства программистов именно этот аспект безопасности исключений представляет наибольшую сложность и требует наибольших усилий при написании кода. Заметим, что среди трех перечисленных моментов только один непосредственно связан с размещением ключевых слов try и catch, да и тех часто можно избежать при разумном использовании деструкторов для автоматического освобождения распределенных ресурсов. 3. Когда следует использовать try и catch? Когда их не следует использовать? Изложите ваш ответ в виде рекомендации стандарта кодирования. Вот один из вариантов ответа на этот вопрос. /. Определите общую стратегию обработки ошибок и сообщений о них на уровне приложения или подсистемы и строго следуйте ей. В частности, стратегия должна охватывать, как минимум, следующие основные аспекты (а в действительности включать и многие другие вопросы). • Сообщения об ошибках. Определите, о каких именно ошибках будет сообщаться и каким образом. Среди всех прочих методов сообщения об ошибках следует отдавать предпочтение исключениям. Вообще говоря, для каждой ситуации следует подобрать наиболее подходящий, удобный и легко сопровождаемый метод. Так, исключения наиболее подходят для конструкторов и операторов, которые не в состоянии вернуть значение, указывающее на происшедшую ошибку, или когда место ошибки и ее обработчик оказываются далеко друг от друга. • Распространение ошибок. Помимо прочего, следует определить границы, которые не должно пересекать сгенерированное исключение. Обычно в роли таких границ выступает модуль или границы API. • Обработка ошибок. Там, где это возможно, предоставьте возможность освобождения распределенных ресурсов посредством деструкторов владеющих ресурсами объектов вместо того, чтобы использовать механизм try и catch. 2. Генерируйте исключение в месте обнаружения ошибки и не пытайтесь обработать его самостоятельно. (Понятно, что если код в состоянии сам справиться с возникшей ошибкой, то он не должен сообщать о ней!) Документируйте каждую операцию -- какие исключения может сгенерировать операция и почему. Такое описание должно быть частью документации каждой функции и каждого модуля. От вас не требуется писать спецификацию исключений для каждой функции (более того, как вы увидите в задаче 13, вы и не должны это делать), по вы должны ясно и точно документировать, чего следует ожидать вызывающей функции, поскольку семантика ошибок является частью интерфейса функции или модуля. 3. Используйте ключевые слова try и catch там, где у вас есть достаточно инфор-мации для обработки ошибки, ее преобразования или обеспечения границы, определенной стратегией обработки ошибок. В частности, я обнаружил, что для использования try и catch имеются три основные причины. • Для обработки ошибки. Это самый простой случай; произошла ошибка, мы знаем, что делать в такой ситуации, и мы выполняем эти действия. Жизнь продолжается (уже без исходного исключения, которое приказало долго жить). Если можно, то все необходимые действия лучше выполнять в деструкторе; если нет - использовать try/catch. • Для преобразования исключения. Это означает перехват одного исключения, которое сообщает о низкоуровневой ошибке, и генерацию другого исключения, в контексте своей собственной высокоуровневой семантики. Кроме того, исходное исключение может быть преобразовано в другое представление, например, код ошибки. Рассмотрим, например, класс, представляющий коммуникационное соединение, работающее с различными типами узлов и протоколов. Попытка установить соединение между двумя узлами может окончиться неуспешно из-за множества причин - например, из-за физического повреждения сети или ошибки 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [ 22 ] 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
![]() |