Литмир - Электронная Библиотека
A
A

  return res;

}

• Вставьте инструкции для проверки инвариантов (т.е. условий, которые всегда должны выполняться; см. раздел 9.4.3) в подозрительные разделы.

Рассмотрим пример.

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

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

{

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

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

  // ...

}

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

assert
.

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Интересно, что существует несколько эффективных способов программирования. Разные люди совершенно по-разному программируют. Многие различия между методами отладки объясняются разнообразием программ, а другие проистекают из разных образов мышления. Следует знать, что наилучшего способа отладки не существует. Просто надо помнить, что запутанный код чаще содержит ошибки. Старайтесь писать программы просто и логично, форматируйте их, и вы сэкономите время за счет отладки.

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

 

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

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");

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

 

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

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

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

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

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

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

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

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

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!
Помимо всего прочего, следует указать, что именно функция будет возвращать; иначе говоря, если мы возвращаем из функции какое-то значение, то всегда обещаем вернуть что-то конкретное (а как иначе вызывающая функция будет знать, чего ей ждать?).

71
{"b":"847443","o":1}