Главная страница

Совершенный код. Совершенный код. Мастер-класс. Стив Макконнелл. Руководство по стилю программирования и конструированию по


Скачать 5.88 Mb.
НазваниеРуководство по стилю программирования и конструированию по
АнкорСовершенный код
Дата31.03.2023
Размер5.88 Mb.
Формат файлаpdf
Имя файлаСовершенный код. Мастер-класс. Стив Макконнелл.pdf
ТипРуководство
#1028502
страница26 из 106
1   ...   22   23   24   25   26   27   28   29   ...   106
ГЛАВА 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.

ГЛАВА 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   ...   22   23   24   25   26   27   28   29   ...   106


написать администратору сайта