Читать интересную книгу Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 52 53 54 55 56 57 58 59 60 ... 337
различия между методами отладки объясняются разнообразием программ, а другие проистекают из разных образов мышления. Следует знать, что наилучшего способа отладки не существует. Просто надо помнить, что запутанный код чаще содержит ошибки. Старайтесь писать программы просто и логично, форматируйте их, и вы сэкономите время за счет отладки.

5.10. Пред- и постусловия

 

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

int my_complicated_function(int a, int b, int c)

// Аргументы являются положительными и a < b < c

{

  if (!(0<a && a<b && b<c)) // ! значит НЕ, а && значит И

    error("Неверные аргументы функции mcf");

  // ...

}

Во-первых, в комментарии утверждается, какие аргументы ожидает функция, а затем происходит проверка этого условия (и генерирование исключения, если это условие нарушается). Это правильная стратегия. Требования, которые функция предъявляет к своим аргументам, часто называют предусловиями (pre-condition): они должны выполняться, чтобы функция работала правильно. Вопрос заключается в том, что делать, если предусловия нарушаются. У нас есть две возможности.

1. Игнорировать это (надеясь или предполагая, что все вызывающие функции передают правильные аргументы).

2. Проверить их (и каким-то образом сообщить об ошибке).

С этой точки зрения типы аргументов — это лишь способ проверки простейших предусловий на этапе компиляции. Рассмотрим пример.

int x = my_complicated_function(1, 2, "horsefeathers");

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

 

 Мы предполагаем, что предусловия всегда зафиксированы в комментариях (так что программист, вызывающий функцию, может видеть, что ожидает вызываемая функция). Если функция не содержит комментарии, в которых указаны условия, накладываемые на аргументы, будем считать, что он может принимать любые аргументы. Но стоит ли надеяться, что программист, вызывающий функцию, станет читать эти аргументы и придерживаться установленных правил? Иногда это можно делать, но, как правило, все же следует проверить выполнение предусловий. Это следует делать всегда, если нет веской причины этого не делать. К таким причинам относятся следующие.

• Никто не может передать неправильные аргументы.

• Проверка слишком сильно замедлит выполнение программы.

• Проверка является слишком сложной.

Первую причину можно признать уважительной, только если вы знаете, кто будет вызывать вашу функцию. В реальном мире это практически невозможно.

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

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

Написав предусловия (даже в виде комментариев), вы значительно повысите качество программы: это заставит вас задуматься о том, какие аргументы требует функция. Если вы не можете просто и ясно сформулировать эти требования в виде комментария, то, вероятно, вы плохо продумали свою программу. Опыт показывает, что такие предусловия и их проверки помогают избежать многих ошибок. Мы уже указывали, что ненавидим отладку; ясно сформулированные предусловия позволяют избежать конструктивных ошибок, а также устранить неправильное использование функций на ранних стадиях разработки программы. Вариант

int my_complicated_function(int a, int b, int c)

// Аргументы являются положительными и a < b < c

{

if (!(0<a && a<b && b<c)) // ! значит НЕ, а && значит И

  error("Неверные аргументы функции mcf");

  // ...

}

сэкономит ваше время и силы по сравнению с более простым вариантом:

int my_complicated_function(int a, int b, int c)

{

  // ...

}

5.10.1. Постусловия

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

Вернемся к нашей функции, вычисляющей площадь прямоугольника (см. раздел 5.6.1).

// Вычисляет площадь прямоугольника;

// если аргументы неправильные, генерирует исключение Bad_area

int area(int length, int width)

{

  if (length<=0 || width <=0) throw Bad_area();

    return length*width;

}

Эта функция проверяет предусловия, но они не сформулированы в виде комментариев (для такой короткой функции это вполне допустимо), и считается, что все вычисления проводятся корректно (для таких простых вычислений это также вполне приемлемо). И тем не менее эту функцию можно было написать намного яснее.

int area(int length, int width)

// Вычисляет площадь прямоугольника;

// предусловия: аргументы length и width являются положительными

// постусловия: возвращает положительное значение, являющееся

// площадью

{

  if (length<=0 || width <=0) error("area() pre-condition");

  int a = length*width;

  if (a<=0) error("area() post-condition");

  return a;

}

Мы не можем проверить выполнение всех постусловий, но можем проверить их часть, убедившись, что возвращаемое число является положительным.

ПОПРОБУЙТЕ

Найдите пару значений, при которых предусловие выполняется, а постусловие — нет.

Пред- и постусловия обеспечивают проверку логичности кода. Они тесно связаны с понятиями инвариантов (раздел 9.4.3), корректности (разделы 4.2 и 5.2), а также с тестированием (глава 26).

5.11. Тестирование

Как определить, когда следует остановить отладку? Ясно, что отладка должна идти до тех пор, пока не будут выявлены все ошибки, —

1 ... 52 53 54 55 56 57 58 59 60 ... 337
На этом сайте Вы можете читать книги онлайн бесплатно русская версия Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп.
Книги, аналогичгные Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп

Оставить комментарий