Джон Роббинс (John Robbins) [19] установил второе правило: "Утверждения, утверждения и еще раз утверждения". В соответствии с его книгой, он считает количество утверждений достаточным, если коллеги начинают жаловаться, что при вызове его кода они постоянно получают сообщения о проверках утверждений.
Таким образом, второе правило можно выразить так: используйте утверждения много и часто. Вставляйте утверждения при каждом удобном случае.
----
Правило № 2. Используйте утверждения много и часто.
----
К сожалению, некоторые программисты при использовании утверждений столкнутся с проблемами. Дело в том, что поддерживаемые компилятором утверждения появились только в версии Delphi 3. С тех пор утверждения можно применять безнаказанно. Компилятор позволяет указывать, вносить ли проверки в выполняемый файл или же игнорировать их. При тестировании и отладке компиляция выполняется с утверждениями. При создании окончательного выполняемого файла утверждения игнорируются.
В Delphi1 и Delphi 2 приходится применять другие способы. Существует два метода. Первый - написать метод Assert, реализация которого должна быть пустой при создании окончательной версии приложения и будет содержать проверку с вызовом Raise в противном случае. Пример такой процедуры утверждения приведен в листинге 1.7.
Листинг 1.7. Процедура утверждения для Delphi1 и Delphi 2
procedure Assert(aCondition : boolean; const aFailMsg : string);
begin
{$IFDEF UseAssert}
if not aCondition then
raise Exception.Create(aFailMsg);
{$ENDIF}
end;
Как видно из листинга, применяется директива $IFDEF компилятора. Несмотря на то что приведенная процедура проста в использовании и легко может быть вызвана из основного кода, она подразумевает, что в окончательной версии приложения будет присутствовать вызов пустой процедуры. Альтернативным вариантом организации проверки утверждений может быть использование оператора $IFDEF не в процедуре, а в основном коде при вызове процедуры. В таком случае блоки проверки будут выглядеть следующим образом:
...
{$IFDEF UseAssert}
Assert (MyPointer <> nil, "MyPointer should be allocated by now");
{$ENDIF}
MyPointer^.Field := 0;
...
Преимущество последнего метода заключается в том, что процедура в окончательном варианте выполняемого файла отсутствует совсем. Поскольку в настоящей книге все коды предназначены для компиляции на всех версиях Delphi, используется процедура Assert, код которой показан в листинге 1.7.
Проверка утверждений может применяться тремя способами: предусловие, постусловие и инвариант. Предусловие (pre-condition) - это утверждение, находящееся в начале функции. Оно однозначно указывает, какое условие, касающееся окружения и входных параметров, должно соблюдаться перед выполнением функции. Например, предположим, что создана функция, которой в качестве параметра передается объект. При этом разработчик решил, что функции не должен передаваться nil. Помимо доведения этой информации до всех разработчиков, он должен в начале функции вставить утверждение, которое будет проверять, передается ли функции nil. В таком случае, если сам разработчик функции или кто-то из его коллег забудет о существующем ограничении, предупреждение напомнит об этом.
Постусловие (post-condition) по назначению обратно предусловию - это утверждение, находящееся в конце функции и предназначенное для проверки того, что функция была выполнена правильно. Этот тип проверки можно считать менее полезным, нежели предыдущий. В конце концов, мы программируем, подразумевая, что код будет выполняться правильно. Если при проверке постусловия возникает исключение, оставшаяся часть функции пропускается.
И последний тип утверждений - инвариант (invariant). Он представляет собой нечто среднее между предусловием и постусловием. Это утверждение, которое находится в середине кода и гарантирует, что определенная часть функции выполняется корректно.
Единственной проблемой, связанной с утверждениями, является выбор случая, когда они более предпочтительны, чем "нормальные" исключения. Это малоисследованная область, но мы постараемся охватить и ее. В общем случае, ошибки, обнаруживаемые при тестировании, можно разбить на два типа: ошибки программиста и ошибки входных данных. Давайте попытаемся разобраться, в чем же состоит отличие между этими двумя типами.