Тестируйте при написании кода
Чем раньше обнаружена проблема, тем лучше. Если вы будете постоянно задумываться о том, что вы пишете, еще когда вы пишете, то сможете проконтролировать простейшие свойства программы прямо на этапе ее создания. В результате ваш код как бы пройдет один круг тестирования еще до того, как будет скомпилирован. Ошибки определенных видов даже никогда не появятся.
Тестируйте граничные условия кода.
Одним из важнейших методов тестирования является тестирование граничных условий: каждый раз, написав небольшой кусок кода, например цикл или условное выражение, проверьте, что тело цикла повторится нужное количество раз, а условное выражение правильно разветвляет вычисление. Этот процесс называется тестированием граничных условий потому, что вы проверяете крайние, экстремальные значения алгоритма или данных, такие как пустой ввод, единственный введенный элемент, полностью заполненный массив и т. п. Основная идея состоит в том, что большинство ошибок возникает как раз на границах – при каких-то экстремальных значениях. Если какой-то блок кода содержит ошибку, то, скорее всего, эта ошибка происходит на границе, и наоборот – если при экстремальных значениях код работает корректно, то он практически наверняка будет работать корректно и повсюду.
Приводимый фрагмент кода, моделирующий fgets, считывает символы, пока не найдет символ перевода строки или не заполнит буфер:
Представьте себе, что вы только что написали этот цикл. Теперь мысленно выполните за него обработку строки. Первое граничное условие, которое надо проверить, очевидно: пустая строка. Если представить строку, содержащую единственный символ перевода строки, то нетрудно убедиться, что цикл остановится на первой итерации со значением i, равным 0, так что в последней строке i будет уменьшено до -1 и, следовательно, запишет нулевой байт в элемент s[-1 ], который находится вне границ массива. Итак, проверка первого же граничного условия обнаружила ошибку.
Если переписать цикл так, чтобы он использовал идиоматическую форму заполнения массива вводимыми символами, он будет выглядеть следующим образом:
Повторив в уме первый reef, мы удостоверимся, что теперь строка, содержащая только символ перевода строки, обрабатывается корректно: i равно 0, первый же введенный символ прерывает работу цикла, а '\0 – сохраняется в s[0]. Проверив схожим образом варианты с вводом одного и двух символов, замыкаемых символом перевода строки, мы убедимся, что цикл работает корректно вблизи нижней границы ввода.
Теперь надо проверить и другие граничные условия. Ситуации, когда во вводе содержится очень длинная строка или не содержится символов перевода строки, предусмотрены кодом – на этот случай существует ограничение i значением МАХ-1. Однако что будет, если ввод абсолютно пуст (в нем нет вообще ни одного символа) и первый же вызов getchar возвратит значение EOF? Надо добавить проверку и для такого случая:
Проверка граничных условий может обнаружить много ошибок, но, конечно, не все. Мы еще вернемся к рассмотренному примеру в главе 8, где покажем, что в нем осталась еще ошибка переносимости.