Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 811
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
ГЛАВА 8 Защитное программирование
189
Пример использования утверждений для документирования
пред- и постусловий (Visual Basic)
Private Function Velocity ( _
ByRef latitude As Single, _
ByRef longitude As Single, _
ByRef elevation As Single _
) As Single
’ Предусловия
Так выглядит код утверждения.
Debug.Assert ( -90 <= latitude And latitude <= 90 )
Debug.Assert ( 0 <= longitude And longitude < 360 )
Debug.Assert ( -500 <= elevation And elevation <= 75000 )
’ Откорректируйте входные данные. Значения должны попадать
’ в интервалы, указанные в вышестоящих утверждениях. Иначе
’ они будут заменены ближайшими допустимыми значениями.
Таким может быть код, обрабатывающий неверные входные данные во время выполнения программы.
If ( latitude < -90 ) Then latitude = -90
ElseIf ( latitude > 90 ) Then latitude = 90
End If
If ( longitude < 0 ) Then longitude = 0
ElseIf ( longitude > 360 ) Then
8.3. Способы обработки ошибок
Утверждения применяют для обработки ошибок, которые никогда не должны происходить. А что делать с возможными ошибками? В зависимости от обстоя- тельств вы можете вернуть некое нейтральное значение, заменить следующим корректным блоком данных, вернуть тот же результат, что и в предыдущий раз,
подставить ближайшее допустимое значение, записать предупреждающее сооб- щение в файл, вернуть код ошибки, вызвать метод или объект — обработчик ошибки или прекратить выполнение. Вы также можете использовать несколько способов одновременно.
Рассмотрим эти приемы подробней.
Вернуть нейтральное значение Иногда наилучшей реакцией на неправиль- ные данные будет продолжение выполнения и возврат заведомо безопасного зна- чения. Численные расчеты могут возвращать 0. Операция со строкой может вер- нуть пустую строку, а операция с указателем — пустой указатель. Метод рисова- ния в видеоигре, получивший неправильное исходное значение цвета, может по
>
>
190
ЧАСТЬ II Высококачественный код умолчанию использовать цвет фона или изображения. Однако в методе рисова- ния рентгеновского снимка ракового больного вряд ли стоит применять «нейт- ральное значение». В таких случаях лучше прекратить выполнение программы, чем показать пациенту неправильные результаты.
Заменить следующим корректным блоком данных Условия обработки по- тока данных иногда таковы, что следует просто вернуть следующие допустимые данные. Если при чтении информации из базы данных встречена испорченная запись, можно просто продолжить считывание, пока не будут найдены коррект- ные данные. Если вы считываете показания термометра 100 раз в секунду и один раз не получили достоверного измерения, можно просто подождать 1/100 секун- ды и обратиться к следующему показанию.
Вернуть тот же результат, что и в предыдущий раз Если программа счи- тывания показаний термометра один раз не получила измерение, она может просто вернуть то же значение, что и в предыдущий раз. В зависимости от приложения температура скорее всего не сильно изменится за 1/100 секунды. Если в видеоиг- ре запросу на прорисовку части экрана передано неверное значение цвета, вы можете просто вернуть тот же цвет, что и раньше. Но, авторизуя транзакции в банкомате, вы, пожалуй, не захотите использовать «то же значение, что и в пре- дыдущий раз» — ведь это будет номер счета предыдущего клиента!
Подставить ближайшее допустимое значение В некоторых случаях вы мо- жете вернуть ближайшее допустимое значение, как выше в примере функции
Velocity. Часто это обоснованный подход для получения показаний откалиброван- ных инструментов. Так, термометр мог бы быть откалиброван от 0 до 100 граду- сов по Цельсию. Если вы получаете значение меньше 0, можно заменить его на 0,
как ближайшее допустимое значение. Если же значение больше 100, можно под- ставить 100. Если в операции со строкой ее длина заявлена меньшей 0, можно принять ее за 0. Мой автомобиль использует этот подход к обработке ошибок, когда я двигаюсь задним ходом. Так как спидометр не показывает отрицательную ско- рость, то при езде задним ходом, скорость просто равна 0 — ближайшему допус- тимому значению.
Записать предупреждающее сообщение в файл Обнаружив неверные дан- ные, вы можете решить записать предупреждение в файл журнала и продолжить работу. Этот подход можно сочетать с другими способами, такими как подстановка ближайшего допустимого значения или замена следующим корректным блоком данных. Используя такой журнальный файл, задумайтесь, можно ли его безопас- но сделать общедоступным или же его надо зашифровывать либо защищать ка- ким-либо иначе.
Вернуть код ошибки Вы можете решить, что только определенные части сис- темы будут обрабатывать ошибки. Другие же не будут обрабатывать ошибки локально,
а будут просто сообщать, что обнаружена ошибка, и надеяться, что какой-либо другой вышестоящая в иерархии вызовов метод эту ошибку обработает. Конкретный ме- ханизм оповещения остальной системы об ошибке может быть следующим:
쐽
установить значение статусной переменной;
쐽
вернуть статус в качестве возвращаемого значения функции;
ГЛАВА 8 Защитное программирование
191
쐽
сгенерировать исключение, используя встроенный в язык программирования механизм обработки исключений.
В этом случае не столь важно выбрать механизм обработки ошибок, как решить,
какая часть системы будет обрабатывать ошибки напрямую, а какая — только со- общать об их возникновении. Если система должна быть безопасной, убедитесь,
что вызывающие методы всегда проверяют коды возврата.
Вызвать процедуру или объект — обработчик ошибок Другим подходом к централизованной обработке ошибок является создание глобальной специали- зированной процедуры или объекта. Преимущество его в том, что контроль над обработкой ошибок сосредоточен в одном месте, что облегчает отладку. С дру- гой стороны, вся программа целиком будет зависеть от этого кода. Если же вы захотите повторно использовать какую-то часть программы в другой системе,
придется перетаскивать туда и весь механизм обработки ошибок.
Этот подход может очень серьезно повлиять на безопасность. Если в программе возникнет переполнение буфера, злоумышленник сможет узнать адрес метода
(объекта)-обработчика. Таким образом, при переполнении буфера во время ра- боты приложения использовать этот способ небезопасно.
Показать сообщение об ошибке, где бы она ни случилась Этот подход ми- нимизирует накладные расходы на обработку ошибок. Однако он приводит к расползанию сообщений пользовательского интерфейса по коду приложения. Это может создавать сложности, если вы хотите реализовать целостный интерфейс пользователя, отделить этот интерфейс от остальной части системы или локали- зовать ваше ПО. Остерегайтесь также сообщить потенциальным злоумышленни- кам слишком многое — они часто используют сообщения об ошибках для поиска способа проникновения в систему.
Обработать ошибку в месте возникновения наиболее подходящим спосо-
бом В некоторых проектах предлагается обрабатывать ошибки локально, а вы- бор используемого метода остается за программистом, реализующим ту часть си- стемы, где происходит ошибка.
Такой подход предоставляет разработчикам большую гибкость. Однако он таит в себе опасность, что система в целом не будет удовлетворять требованиям коррек- тности и устойчивости (см. ниже). А в зависимости от того, какой в конечном итоге будет реакция на ошибку, этот метод может привести к потенциальному распол- занию кода пользовательского интерфейса по системе. Это приведет к тем же проблемам, что и в случае с выводом сообщений об ошибках.
Прекратить выполнение Некоторые системы прекращают работу при возник- новении любой ошибки. Этот подход оправдан в приложениях, критичных к безопасности. Например, какая реакция на ошибку будет наилучшей, если ПО, кон- тролирующее радиационное оборудование для лечения рака, получит некоррек- тное значение радиационной дозы? Надо ли использовать то же значение, что и в предыдущий раз? А может, ближайшее допустимое или нейтральное значение?
В этом случае остановка работы — наилучший вариант. Мы охотнее предпочтем перезагрузить машину, чем рискнуть применить неправильную дозу.
Похожий подход применим и для повышения безопасности Microsoft Windows.
По умолчанию Windows продолжает работать, даже если журнал безопасности
192
ЧАСТЬ II Высококачественный код переполнен. Но вы можете изменить конфигурацию Windows так, что при запол- нении журнала сервер будет прекращать работу. Это может быть полезно для систем повышенной секретности.
Устойчивость против корректности
Как нам показали примеры с видеоигрой и рентгеновской установкой, выбор под- ходящего метода обработки ошибки зависит от приложения, в котором эта ошиб- ка происходит. Кроме того, обработка ошибок в общем случае может стремиться либо к большей корректности, либо к большей устойчивости кода. Разработчики привыкли применять эти термины неформально, но, строго говоря, эти термины находятся на разных концах шкалы.
Корректность предполагает, что нельзя воз- вращать неточный результат; лучше не вернуть ничего, чем неточное значение.
Устойчивость требует всегда пытаться сделать что-то, что позволит программе продолжить работу, даже если это приведет к частично неверным результатам.
Приложения, требовательные к безопасности, часто предпочитают корректность устойчивости. Лучше не вернуть никакого результата, чем неправильный резуль- тат. Радиационная машина — хороший пример применения такого принципа.
В потребительских приложениях устойчивость, напротив, предпочтительнее кор- ректности. Какой-то результат всегда лучше, чем прекращение работы. Текстовый редактор, которым я пользуюсь, временами показывает последнюю на экране строку лишь частично. Хочу ли я, чтобы при обнаружении этой ситуации редак- тор завершал выполнение? Нет: когда я в следующий раз нажму Page Up или Page
Down, экран обновится, и изображение исправится.
Влияние выбора метода обработки ошибок
на проектирование высокого уровня
При наличии такого широкого выбора надо стараться реагировать на не- правильные значения параметров одинаково во всей программе. Способ обработки ошибок влияет на соответствие ПО требованиям корректно- сти, устойчивости и другим атрибутам, не относящимся к функциональности.
Выбор общего подхода к работе с некорректными данными — это вопрос архи- тектуры или высокоуровневого проектирования, и он должен быть рассмотрен на одном из этих этапов разработки системы.
Выбрав подход, придерживайтесь его неукоснительно. Если вы решили обраба- тывать ошибки на высоком уровне, а в низкоуровневом коде просто сообщать о них, удостоверьтесь, что высокоуровневый код действительно их обрабатывает!
Некоторые языки позволяют игнорировать возвращаемое функцией значение (в
C++ вы не обязаны что-то то делать с возвращенным результатом), но не игнори- руйте информацию об ошибке! Проверяйте значение, возвращаемое из функции.
Даже если вы считаете, что ошибка в функции возникнуть не может, все равно проверяйте. Весь смысл защитного программирования в защите от ошибок, ко- торых вы не ожидаете.
Эти принципы относятся к системным функциям, так же как и вашим собствен- ным. Если только архитектура ПО не предусматривает игнорирования сбоев сис- темных вызовов, проверяйте коды ошибок после каждого такого вызова. При обнаружении ошибки укажите ее номер и описание.
1 ... 21 22 23 24 25 26 27 28 ... 104
ГЛАВА 8 Защитное программирование
193
8.4. Исключения
Исключения — это специальное средство, позволяющее передать в вызывающий код возникшие ошибки или исключительные ситуации. Если код в некотором методе встречает неожиданную ситуацию и не знает, как ее обработать, то он генерирует исключение, т. е. фактически умывает руки со словами: «Я не знаю, что с этим делать, надеюсь, кто-нибудь другой знает, как на это реагировать!» Код, не имеющий понятия о контексте ошибки, может вернуть управление другой части системы, которая, возможно, лучше знает, как интерпретировать ошибку и сде- лать с ней что-то осмысленное.
Кроме того, исключения могут быть полезны для упрощения запутанной логики участка кода, как в примере «Переписатьс помощью
try-finally» в разделе 17.3. Вот принцип действия исключений: метод, применяя оператор
throw, создает объект- исключение. Код какого-либо другого метода, стоящего выше в иерархии вызо- вов, перехватит это исключение в блоке
try-catch.
Популярные языки программирования по-разному реализуют исключения (табл. 8.1):
Табл. 8-1. Поддержка исключений в популярных языках программирования
Параметры
обработки
исключений
C++
Java
Visual Basic
Поддержка
try-catch Да.
Да.
Да.
Поддержка
Нет.
Да.
Да.
try-catch-finally
Что генерируется
Объект класса
Excep-
Объект класса
Объект класса
tion или производно-
Exception или про-
Exception или про- го от него, указатель изводного от него.
изводного от него.
на объект, объектная ссылка, другие типы данных, например,
строка или целое число.
Эффект при
Вызывается функция
Если это «проверяе- Программа не перехваченном
std::unexpected(), кото- мое исключение»,
завершает работу.
исключении рая по умолчанию вы- то прекращается зывает
std::terminate(),
работа потока, в ко- в свою очередь по умол- тором оно возникло.
чанию вызывающая
Если это «исключе- функцию
abort().
ние периода выпол- нения», то оно игнорируется.
Генерируемые
Нет.
Да.
Нет.
исключения должны быть определены в интерфейсе класса
Перехватываемые
Нет.
Да.
Нет.
исключения должны быть определены в интерфейсе класса
194
ЧАСТЬ II Высококачественный код
Исключения и наследование имеют общее свойство: исполь- зуемые разумно, они могут уменьшить сложность. Исполь- зуемые чрезмерно, они могут сделать код абсолютно нечи- таемым. Этот раздел содержит предложения по реализации преимуществ исключений и способы избежать трудностей,
которые часто с ними связаны.
Используйте исключения для оповещения других час-
тей программы об ошибках, которые нельзя игно-
рировать Основное преимущество исключений состоит в их способности сигнализировать об ошибке так, что ее нельзя проигнориро- вать (Meyers, 1996). При других подходах к обработке ошибок есть вероятность,
что сбойная ситуация останется незамеченной. Исключения устраняют такую возможность.
Генерируйте исключения только для действительно исключительных
ситуаций Применение исключений должно быть зарезервировано только для действительно исключительных случаев — иначе говоря, для ситуаций, которые нельзя реализовать другими методами кодирования. Исключения используются в таких же обстоятельствах, как и утверждения: для событий, которые не просто редко происходят, а которые
никогда не должны случаться.
Исключения представляют собой компромисс между возможностью обработки непредвиденных ситуаций, с одной стороны, и повышением сложности — с дру- гой. Исключения ухудшают инкапсуляцию, требуя от кода, вызывающего метод,
знать, какие исключения могут быть сгенерированы внутри него. Это усложняет код, что противоречит Главному Техническому Императиву ПО (см. главу 5), суть которого в снижении сложности.
Не используйте исключения по мелочам Если ошибка может быть обрабо- тана локально, там ее и обрабатывайте. Не генерируйте в коде неперехватывае- мое исключение, если ошибка может быть исправлена на месте.
Избегайте генерировать исключения в конструкторах и деструкторах,
если только вы не перехватываете их позднее Правила обработки исклю- чений очень быстро усложняются, когда исключения генерируются в конструк- торах и деструкторах. Так, в C++ деструктор не вызывается, пока объект не создан полностью. Это значит, что, если код в конструкторе сгенерировал исключение,
деструктор вызван не будет, что приведет к возможной утечке ресурсов (Meyers,
1996; Stroustrup, 1997). Аналогичные замысловатые правила относятся и к исклю- чениям внутри деструкторов.
Приверженцы языка могут сказать, что запомнить эти правила очень легко. Но все же программисты — простые смертные, у них могут возникнуть трудности с за- поминанием правил. Наилучшая программистская практика — избегать излишней сложности, которая создается при написании такого кода.
Генерируйте исключения на правильном уровне аб-
стракции Интерфейс метода и класса должен представ- лять собой целостную абстракцию. Генерируемые исключе- ния — такая же часть интерфейса, как и специальные типы данных.
Программы, использующие ис- ключения как часть нормальной работы алгоритма, страдают от всех проблем с читабельностью и удобством сопровождения так же, как и классический спагет- ти-код.
Энди Хант и Дэйв Томас
(Andy Hunt and Dave Thomas)
Перекрестная ссылка О поддер- жании целостных абстракций интерфейса см. подраздел «Хо- рошая абстракция» раздела 6.2.