Файл: Руководство по стилю программирования и конструированию по.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 30.11.2023
Просмотров: 798
Скачиваний: 2
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
182
ЧАСТЬ II Высококачественный код
Г Л А В А 8
Защитное
программирование
Содержание
쐽
8.1. Защита программы от неправильных входных данных
쐽
8.2. Утверждения
쐽
8.3. Способы обработки ошибок
쐽
8.4. Исключения
쐽
8.5. Изоляция повреждений, вызванных ошибками
쐽
8.6. Отладочные средства
쐽
8.7. Доля защитного кода в промышленной версии
쐽
8.8. Защита от защитного программирования
Связанные темы
쐽
Сокрытие информации: подраздел «Скрывайте секреты (к вопросу о сокрытии информации)» раздела 5.3
쐽
Дизайн изменений: подраздел «Определите области вероятных изменений» раз- дела 5.3
쐽
Архитектура программного обеспечения: раздел 3.5
쐽
Дизайн в проектировании: глава 5
쐽
Отладка: глава 23
Защитное программирование не означает защиту своего кода словами:
«Это так работает!» Его идея совпадает с идеей внимательного вождения,
при котором вы готовы к любым выходкам других водителей: вы не по- страдаете, даже если они совершат что-то опасное. Вы берете на себя ответствен- ность за собственную защиту и в тех случаях, когда виноват другой водитель.
В защитном программировании главная идея в том, что если методу передаются некорректные данные, то его работа не нарушится, даже если эти данные испор- чены по вине другой программы. Обобщая, можно сказать, что в программах всегда будут проблемы, программы будут модифицироваться и разумный программист будет учитывать это при разработке кода.
http://cc2e.com/0861
ГЛАВА 8 Защитное программирование
183
Эта глава рассказывает, как защититься от беспощадного мира неверных данных,
событий, которые «никогда» не могут случиться, и других программистских ошибок.
Если вы опытный программист, можете пропустить следующий раздел про обра- ботку входных данных и перейти к разделу 8.2, который рассказывает об утверж- дениях.
8.1. Защита программы от неправильных
входных данных
Вы, возможно, слышали в школе выражение: «Мусор на входе — мусор на выхо- де»
1
. Это вариант предостережения потребителю от разработчиков ПО: пусть пользователь остерегается.
Для промышленного ПО принцип «мусор на входе — мусор на выходе»
не слишком подходит. Хорошая программа никогда не выдает мусор не- зависимо от того, что у нее было на входе. Вместо этого она использует принципы: «мусор на входе — ничего на выходе», «мусор на входе — сообщение об ошибке на выходе» или «мусор на входе не допускается». По сегодняшним стан- дартам «мусор на входе — мусор на выходе» — признак небрежного, небезопас- ного кода.
Существует три основных способа обработки входных мусорных данных, пере- численные далее.
Проверяйте все данные из внешних источников Получив данные из фай- ла, от пользователя, из сети или любого другого внешнего интерфейса, удосто- верьтесь, что все значения попадают в допустимый интервал. Проверьте, что чис- ловые данные имеют разрешенные значения, а строки достаточно коротки, что- бы их можно было обработать. Если строка должна содержать определенный набор значений (скажем, идентификатор финансовой транзакции или что-либо подоб- ное), проконтролируйте, что это значение допустимо в данном случае, если же нет — отклоните его. Если вы работаете над приложением, требующим соблюде- ния безопасности, будьте особенно осмотрительны с данными, которые могут атаковать вашу систему: попыткам переполнения буфера, внедренным SQL-коман- дам, внедренному HTML- или XML-коду, переполнениям целых чисел, данным,
передаваемым системным вызовам и т. п.
Проверяйте значения всех входных параметров метода Проверка значе- ний входных параметров метода практически то же самое, что и проверка дан- ных из внешнего источника, за исключением того, что данные поступают из дру- гого метода, а не из внешнего интерфейса. В разделе 8.5 вы узнаете, как опреде- лить, какие методы должны проверять свои входные данные.
1
«Garbage in, garbage out». Возможно, эту их школьную поговорку следует перевести нашей сту- денческой: «Каков стол, таков и стул». —
Прим. перев.
184
ЧАСТЬ II Высококачественный код
Решите, как обрабатывать неправильные входные данные Что делать, если вы обнаружили неверный параметр? В зависимости от ситуации вы можете выб- рать один из дюжины подходов, подробно описанных в разделе 8.3.
Защитное программирование — это полезное дополнение к другим способам улучшения качества программ, описанным в этой книге. Лучший способ защит- ного кодирования — изначально не плодить ошибок. Итеративное проектирова- ние, написание псевдокода и тестов до начала кодирования и низкоуровневая проверка соответствия проекту — это все, что помогает избежать добавления де- фектов. Поэтому этим технологиям должен быть дан более высокий приоритет,
чем защитному программированию. К счастью, вы можете использовать защит- ное программирование в сочетании с ними.
Защита от проблем, кажущихся несущественными, может иметь большее значе- ние, чем можно подумать (рис. 8-1). В оставшейся части этой главы я расскажу о проверке данных из внешних источников, проверке входных параметров и об- работке неправильных входных данных.
Рис. 8-1. Часть плавучего моста Interstate-90 в Сиэтле затонула во время шторма,
потому что резервуары были оставлены открытыми. Они наполнились водой,
и мост стал слишком тяжел, чтобы держаться на плаву. Обеспечение защиты
от мелочей во время проектирования может значить больше, чем кажется
8.2. Утверждения
Утверждение (assertion) — это код (обычно метод или макрос), используемый во время разработки, с помощью которого программа проверяет правильность сво- его выполнения. Если утверждение истинно, то все работает так, как ожидалось.
Если ложно — значит, в коде обнаружена ошибка. Например, если система пред- полагает, что длина файла с информацией о заказчиках никогда не будет превы- шать 50 000 записей, программа могла бы содержать утверждение, что число за- писей меньше или равно 50 000. Пока это число меньше или равно 50 000, утвер-
1 ... 20 21 22 23 24 25 26 27 ... 104
ГЛАВА 8 Защитное программирование
185
ждение будет хранить молчание. Но как только записей станет больше 50 000, оно громко провозгласит об ошибке в программе.
Утверждения особенно полезны в больших и сложных программах, а также в программах, требующих высокой надежности. Они позволяют нам быстрее выявить несоответствия в интерфейсах, ошибки, вкравшиеся при изменении кода и т. п.
Обычно утверждение принимает два аргумента: логическое выражение, описыва- ющее предположение, которое должно быть истинным, и сообщение, выводимое в противном случае. Вот как будет выглядеть утверждение на языке Java, если пе- ременная
denominator должна быть ненулевой:
Пример утверждения (Java)
assert denominator != 0 : ”denominator is unexpectedly equal to 0.”;
В этом утверждении объявляется, что
denominator не должен быть равен 0. Пер- вый аргумент —
denominator != 0 — логическое выражение, принимающее зна- чение
true или false. Второй — это сообщение, выводимое, когда первый аргумент равен
false (т. е. утверждение ложно).
Используйте утверждения, чтобы документировать допущения, сделанные в коде,
и чтобы выявить непредвиденные обстоятельства. Например, утверждения мож- но применять при проверке таких условий:
쐽
значение входного (выходного) параметра попадает в ожидаемый интервал;
쐽
файл или поток открыт (закрыт), когда метод начинает (заканчивает) выпол- няться;
쐽
указатель файла или потока находится в начале (конце), когда метод начина- ет (заканчивает) выполняться;
쐽
файл или поток открыт только для чтения, только для записи или для чтения и записи;
쐽
значение входной переменной не изменяется в методе;
쐽
указатель ненулевой;
쐽
массив или другой контейнер, передаваемый в метод, может вместить по край- ней мере X элементов;
쐽
таблица инициализирована для помещения реальных значений;
쐽
контейнер пуст (заполнен), когда метод начинает (заканчивает) выполняться;
쐽
результаты работы сложного, хорошо оптимизированного метода совпадают с результатами метода более медленного, но написанного яснее.
Разумеется, это только основы, и ваши методы будут содержать много более спе- цифических допущений, которые вы сможете документировать, используя утвер- ждения.
Утверждения не предназначены для показа сообщений в промышленной версии
— они в основном применяются при разработке и поддержке. Обычно их добав- ляют при компиляции кода во время разработки и удаляют при компиляции про- мышленной версии. В период разработки утверждения выявляют противоречи- вые допущения, непредвиденные условия, некорректные значения, переданные
186
ЧАСТЬ II Высококачественный код методам, и т. п. При компиляции промышленной версии они могут быть удалены и, таким образом, не повлияют на производительность системы.
Создание собственного механизма утверждений
Многие языки программирования, включая C++, Java, и
Microsoft Visual Basic, имеют встроенную поддержку утвер- ждений. Если ваш язык не поддерживает процедуры утвер- ждений напрямую, их легко написать. Стандартный макрос
assert языка C++ не предусматривает вывода текстового со- общения. Вот пример улучшенного макроса
ASSERT на C++:
Пример макроса утверждения (C++)
#define ASSERT( condition, message ) {
\
if ( !(condition) ) {
\
LogError( ” Assertion failed: ”,
\
#condition, message );
\
exit( EXIT_FAILURE );
\
}
\
}
Общие принципы использования утверждений
Далее перечислены общие положения по применению утверждений.
Используйте процедуры обработки ошибок для ожидаемых событий и
утверждения для событий, которые происходить не должны Утверждения проверяют условия событий, которые
никогда не должны происходить. Обработчик ошибок проверяет внештатные события, которые могут и не происходить слишком часто, но были предусмотрены писавшим код программистом и должны обрабаты- ваться и в промышленной версии. Обработчик ошибок обычно проверяет некоррек- тные входные данные, утверждения — ошибки в программе.
Если для обработки аномальной ситуации служит обработчик ошибок, он позво- лит программе адекватно отреагировать на ошибку. Если же в случае аномальной ситуации сработало утверждение, для исправления просто отреагировать на ошибку мало — необходимо изменить исходный код программы, перекомпилировать и выпустить новую версию ПО.
Будет правильно рассматривать утверждения как выполняемую документацию —
работать программу с их помощью вы не заставите, но вы можете документиро- вать допущения в коде более активно, чем это делают комментарии языка про- граммирования.
Старайтесь не помещать выполняемый код в утверждения Если в утвер- ждении содержится код, возникает возможность удаления этого кода компилято- ром при отключении утверждений. Допустим, у вас есть следующее утверждение:
Перекрестная ссылка Создание собственной процедуры утверж- дений — хороший пример про- граммирования «с использова- нием языка», а не просто про- граммирования «на языке». Под- робнее об этих различиях см.
раздел 34.4.
ГЛАВА 8 Защитное программирование
187
Пример опасного использования утверждения (Visual Basic)
Debug.Assert( PerformAction() ) ’ Невозможно выполнить действие.
Проблема здесь в том, что, если вы не компилируете утвер- ждения, вы не компилируете и код, который выполняет ука- занное действие. Вместо этого поместите выполняемые выражения в отдельных строках, присвойте результаты ста- тусным переменным и проверяйте значения этих перемен- ных. Вот пример безопасного использования утверждения:
Пример безопасного использования утверждения (Visual Basic)
actionPerformed = PerformAction()
Debug.Assert( actionPerformed ) ’ Невозможно выполнить действие.
Используйте утверждения для документирования и
проверки предусловий и постусловий Предусловия и постусловия — это часть подхода к проектированию и раз- работке программ, известному как «проектирование по кон- тракту» (Meyer, 1997). При использовании пред- и постусло- вий каждый метод или класс заключает контракт с остальной частью программы.
Предусловия — это соглашения, которые клиентский код, вызывающий метод или класс,
обещает выполнить до вызова метода или создания экземпляра объекта. Предусло- вия — это обязательства клиентского кода перед кодом, который он вызывает.
Постусловия — это соглашения, которые метод или класс обещает выполнить при завершении своей работы. Постусловия — это обязательства метода или класса перед кодом, который их использует.
Утверждения — удобный инструмент для документирования пред- и постусловий.
С этой целью можно использовать и комментарии, но в отличие от них утверж- дения могут динамически проверять, выполняются ли пред- и постусловия.
В следующем примере утверждения документируют пред- и постусловия в функ- ции
Velocity:
Пример использования утверждений для документирования
пред- и постусловий (Visual Basic)
Private Function Velocity ( _
ByVal latitude As Single, _
ByVal longitude As Single, _
ByVal 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 )
Перекрестная ссылка Можете рассматривать этот случай как одну из многих проблем, связан- ных с размещением нескольких операторов на одной строке.
Другие примеры см. в подразде- ле «Размещение одного опера- тора на строке» раздела 31.5.
Дополнительные сведения О пре- дусловиях и постусловиях см.
«Object-Oriented Software Const- ruction» (Meyer, 1997).
188
ЧАСТЬ II Высококачественный код
’ Постусловия
Debug.Assert ( 0 <= returnVelocity And returnVelocity <= 600 )
’ Возвращаемое значение
Velocity = returnVelocity
End Function
Если бы переменные
latitude, longitude и elevation поступили из внешнего источ- ника, корректность их значений должна была быть проверена и обработана в коде обработчика ошибок, а не с помощью утверждений. Но если эти переменные поступили из доверенного внутреннего источника, а метод спроектирован в пред- положении, что их значения будут в разрешенном интервале, то применение утверждений допустимо.
Для большей устойчивости кода проверяйте утвер-
ждения, а затем все равно обработайте возможные
ошибки Каждая потенциально ошибочная ситуация обыч- но проверяется или утверждением, или кодом обработчи- ка ошибок, но не тем и другим вместе. Некоторые эксперты утверждают, что не- обходим только один тип проверки (Meyer, 1997).
Однако реальные программы и проекты бывают слишком запутанными, чтобы можно было полагаться на одни лишь утверждения. В больших, долгоживущих системах различные части могут разрабатываться несколькими проектировщиками
5–10 лет и более. Разработка будет производиться в разное время и в разных вер- сиях продукта. Эти проекты будут основаны на разных технологиях и сосредото- чены на различных вопросах разработки системы. Проектировщики могут быть удалены друг от друга географически, особенно если элементы системы приоб- ретались у независимых компаний. Программисты будут использовать различные стандарты кодирования в разное время жизни системы. В большой команде раз- работчиков некоторые неминуемо будут добросовестнее других, поэтому часть кода будет проверяться более тщательно, чем остальная. В любом случае, когда тестовые команды работают в нескольких географических регионах, а требова- ния бизнеса приводят к изменению тестового покрытия от версии к версии, рас- считывать на всестороннее низкоуровневое тестирование системы нельзя.
В этих обстоятельствах одна и та же ошибка может быть проверена и с помощью утверждения, и обработчиком ошибок. Так, в исходном коде Microsoft Word усло- вия, которые должны быть истинными, сперва помещаются в утверждения, а за- тем и в коде обработки ошибок рассматривается ситуация, когда утверждение ложно. В столь сложных и долгоживущих приложениях, как Word, утверждения служат для выявления как можно большего числа ошибок периода разработки. Но поскольку приложение очень сложное (миллионы строк кода) и прошло через столько изменений, неразумно ожидать обнаружения и исправления всех мысли- мых ошибок до начала поставки приложения пользователям. Поэтому ошибки должны обрабатываться и в промышленной версии системы.
Вот как это можно сделать на примере функции
Velocity:
Перекрестная ссылка Об устой- чивости см. «Устойчивость про- тив корректности» раздел 8.3.