Если мы исходим из предположения, что в программном обеспечении будут ошибки, то, очевидно, в первую очередь следует принять меры для их обнаружения. Более того, если необходимо принимать дополнительные меры (например, исправлять ошибки или их последствия), то все равно сначала нужно уметь обнаруживать ошибки.
Меры по обнаружению ошибок можно разбить на две подгруппы: пассивные попытки обнаружить симптомы ошибки в процессе «обычной» работы программного обеспечения и активные попытки программной системы периодически обследовать свое состояние в поисках признаков ошибок. Пассивное обнаружение рассматривается в этом разделе, активное – в следующем.
Меры по обнаружению ошибок могут быть приняты на нескольких структурных уровнях программной системы. В этом разделе мы будем работать с уровнем подсистем, или компонент, т.е. нас будут интересовать меры по обнаружению симптомов ошибок, предпринимаемые при переходе от одной компоненты к другой, а также внутри компоненты. Все это, конечно, применимо также к отдельным модулям внутри компоненты.
Разрабатывая эти меры, мы будем опираться на следующие положения:
1. Взаимное недоверие. Каждая из компонент должна предполагать, что все другие содержат ошибки. Когда она получает какие-нибудь данные от другой компоненты или из источника вне системы, она должна предполагать, что данные могут быть неправильными, и пытаться найти в них ошибки.
2. Немедленное обнаружение. Ошибки необходимо обнаружить как можно раньше. Это не только ограничивает наносимый ими ущерб, но и значительно упрощает задачу отладки.
3. Избыточность. Все средства обнаружения ошибок основаны на некоторой форме избыточности (явной или неявной).
Конкретные меры обнаружения в большой степени зависят от специфики прикладной области. Однако некоторые идеи можно почерпнуть из следующего списка:
1. Проверяйте атрибуты любого элемента входных данных. Если входные данные должны быть числовыми или буквенными, проверьте это. Если число на входе должно быть положительным, проверьте его значение. Если известно, какой должна быть длина входных данных, проверьте ее.
2. Применяйте «тэги» [4] в таблицах, записях и управляющих блоках и проверяйте с их помощью допустимость входных данных. Тэг – это поле записи, явно указывающее на ее назначение.
3. Проверяйте, находится ли входное значение в установленных пределах. Например, если входной элемент – адрес в основной памяти, проверяйте его допустимость. Всегда проверяйте поле адреса или указателя на нуль и считайте, что оно неверно, если равно нулю. Если входные данные – таблица вероятностей, проверьте, находятся ли все значения между нулем и единицей.
4. Проверяйте допустимость всех вариантов значений. Если входное поле – код, обозначающий один из десяти районов, никогда не предполагайте, что если это не код ни одного из районов 1, 2,..., 9, то это обязательно код района 10.
5. Если во входных данных есть какая-либо явная избыточность, воспользуйтесь ею для проверки данных.
6. Там, где во входных данных нет явной избыточности, введите ее. Если ваша система использует крайне важную таблицу, подумайте о включении в нее контрольной суммы. Всякий раз, когда таблица обновляется, следует просуммировать (по некоторому модулю) ее поля и результат поместить в специальное поле контрольной суммы. Подсистема, использующая таблицу, сможет теперь проверить, не была ли таблица случайно испорчена, – для этого только нужно выполнить контрольное суммирование.
7. Сравнивайте, согласуются ли входные данные с какими-либо внутренними данными. Если на входе операционной системы возникает требование освободить некоторый блок памяти, она должна убедиться, что этот блок в данный момент действительно занят.
Когда разрабатываются меры по обнаружению ошибок, важно принять согласованную стратегию для всей системы (т.е. применить идею концептуальной целостности к обнаружению ошибок). Действия, предпринимаемые после обнаружения ошибки в программном обеспечении (например, возврат кода ошибки), должны быть единообразными для всех компонент системы. Это ставит вопрос о том, какие именно действия следует предпринять, когда ошибка обнаружена. Наилучшее решение – немедленно завершить выполнение программы или (в случае операционной системы) перевести центральный процессор в состояние ожидания. С точки зрения предоставления человеку, отлаживающему программу, например системному программисту, самых благоприятных условий для диагностики ошибок немедленное завершение представляется наилучшей стратегией. Конечно, во многих системах подобная стратегия бывает нецелесообразной (например, может оказаться, что приостанавливать работу системы нельзя). В таком случае используется метод регистрации ошибок. Описание симптомов ошибки и «моментальный снимок» состояния системы сохраняется во внешнем файле, после чего система может продолжать работу. Этот файл позднее будет изучен обслуживающим персоналом.
Всегда, когда это возможно, лучше приостановить выполнение программы, чем регистрировать ошибки (либо обеспечить как дополнительную возможность работу системы в любом из этих режимов). Различие между этими методами проиллюстрируем на способах выявления причин возникающего иногда скрежета автомобиля. Если автомеханик находится на заднем сиденье, то он может обследовать состояние машины в тот момент, когда скрежет возникает. Если же вы выбираете метод регистрации ошибок (записывая скрежет на магнитофон), задача диагностики будет значительно сложнее.