Программирование >>  Разработка устойчивых систем 

1 ... 11 12 13 [ 14 ] 15 16 17 ... 196


Защитное программирование

Возможно, идеальная программа -- ие более чем абстрактное понятие для разработчиков. И все же некоторые приемы защитного программирования, применяемые в повседневной работе, существенно повысят качество ващего кода.

Сложность большинства программных проектов гарантирует, что специалисты по тестированию никогда не останутся без работы. И все же хочется верить, что вы будете стремиться создавать программы без дефектов. Объектно-ориентированные приемы проектирования помогают держать под контролем сложность больших проектов, но в конечном счете вам все равно придется писать циклы и функции. Эти мелочи повседневного программирования превращаются в строительные блоки для более крупных компонентов ваших разработок. Если счетчик цикла принимает лишнее значение или функции возвращают правильное значение в большинстве случаев , дело кончится плохо даже при самой хитроумной методологии. В этой главе рассматриваются приемы, которые повышают надежность кода независимо от сложности проекта.

Среди прочего, ваша программа является выражением вашего подхода к решению некоторой задачи. Читателю программы (в том числе и вам) должно быть абсолютно ясно, о чем вы думали при написании того или иного цикла. В некоторых точках программы вы должны быть способны уверенно заявить об истинности тех или иных предположений (а если нет, значит, задача еще не решена). Такие утверждения называются инвариантами, потому что они неизменно должны быть истинными в конкретной точке программы. Невыполнение инварианта говорит либо о плохой архитектуре проекта, либо о неточном отражении ее программой.

Рассмотрим программу для игры угадай число . Один игрок задумывает число от 1 до 100, а другой пытается угадать его (пусть этим занимается компилятор). Игрок, загадавший число, сообщает угадывающему, как связана его догадка с загаданным числом - меньше, больше или равна ему. Оптимальная стратегия угадывания основана на бинарном поиске, при котором выбирается средняя точка в интервале искомых чисел. По ответу больше-меньше угадывающий определяет.



в какой половине интервала находится искомое число, после чего процесс повторяется. При каждой итерации размер интервала поиска уменьшается вдвое. Как же должен выглядеть правильный цикл угадывания числа? Поскольку зловредный пользователь может солгать, и дальнейшее угадывание затянется до бесконечности, недостаточно просто написать:

bool guessed = false: whi1е(!guessed) {

Какое предположение, каким бы простым оно ни было, делается при каждой итерации? Иначе говоря, какое условие благодаря архитектуре приложения должно автоматически выполняться при каждой итерации цикла?

Это простое предположение заключается в следующем: загаданное число принадлежит текущему активному интервалу непроверенных чисел. Обозначим конечные точки этого интервала переменными low и high. Если в начале каждой итерации число принадлежало интервалу [low, high], то в конце ее мы вычисляем новый интервал так, чтобы он по-прежнему содержа.?! загаданное число.

Наша цель - выразить инвариант цикла в программе, чтобы его нарушения обнаруживались во время ее выполнения. Компилятор не знает загаданное число, поэтому прямая проверка невозможна, но для начала можно вставить обычный комментарий:

whi1е(!guessed) { ИНВАРИАНТ: число находится в интервале [low. high]

Что произойдет, если ответ пользователя больше или меньше не соответствует действительности? Загаданное число будет исключено из нового интервала. Поскольку одна ложь всегда влечет за собой другую, со временем мы придем к пустому интервалу (интервал каждый раз уменьшается вдвое, а загаданное число в нем отсутствует). Это условие выражается в следующей программе:

: C02:HiLo.cpp {RunByHand}

Демонстрация инварианта цикла на примере игры угадай число #1 nclude <cstdlib> #i nclude <iostream> #1 ncl ude <stnng> using namespace std:

int mainO {

cout Think of a number between 1 and 100\n : cout I will make a guess: : cout tell me if Im (H)igh or (L)ow\n : int low = 1. high = 100: bool guessed = false: while (Iguessed) { Инвариант: число находится в интервале [low. high] if (low > high) { Нарушение инварианта

cout You cheated! I quit\n :

return EXITJAILURE:

int guess = (low + high) / 2:

cout My guess is guess . :

cout (H)igh. (Dow. or (E)qual? :



Утверждения

Критерий проверки в программе угадай число зависит от пользовательского ввода, поэтому предотвратить его нарушение невозможно. Однако чаще инварианты зависят только от кода, написанного вами, и при правильной реализации исходной архитектуры всегда остаются истинными. В таких случаях лучше воспользоваться положительным утверждением, в котором отражено решение, принятое в процессе проектирования.

Допустим, вы реализуете целочисленный вектор - массив, размеры которого изменяются по мере необходимости. Функция добавления элемента в вектор должна сначала убедиться в том, что в базовом массиве имеется свободная позиция; в противном случае она должна запросить дополнительную память в куче и скопировать в нее существующие элементы (а также удалить старый массив). Такая функция выглядит примерно так:

void MyVector::push back(int х) { if (nextSlot = capacity) grow():

assertCnextSlot < capacity); data[nextSlot++] = x:

string response: cin response: switch(toupper(response[0])) { case H:

high = guess - 1:

break: case L:

low = guess + 1:

break: case E:

guessed = true:

break: default:

cout Invalid responseXn :

continue:

cout I got it!\n : return EXIT SUCCESS: } /:-

Нарушение инварианта обнаруживается проверкоР! условия if(low>high). Если пользователь всегда говорит правду, то загаданное число рано или поздно будет найдено.

В этой программе также используется стандартный для языка С способ передачи информации о состоянии программы в контекст вызова: возврат разных значений функцией main(). Команда return 0; на любой платформе обозначает успешное завершение, однако не существует переносимого кода неудачного завершения. По этой причине мы используем макрос EXn FAILURE, объявленный для этой цели в файле <cstdlib>. Ради единства стиля также используется макрос EXn SUCCESS, хотя он всегда определяется как 0.



1 ... 11 12 13 [ 14 ] 15 16 17 ... 196

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика