Национальный исследовательский ядерный университет «МИФИ»
Опубликован: 28.11.2007 | Доступ: свободный | Студентов: 5153 / 804 | Оценка: 4.53 / 3.65 | Длительность: 22:18:00
ISBN: 978-5-94774-825-3
Специальности: Программист, Тестировщик
Лекция 15:

Методы разработки устойчивого кода

< Лекция 14 || Лекция 15: 1234 || Лекция 16 >

25.2. Методы разработки устойчивого кода

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

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

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

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

25.2.1. Критические точки и допущения (assertions)

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

string_vector surname;
string_vector phone;
…

int index = surname.find("Петров");
foundPhone = phone.at(index);

Однако, в случае рассинхронизации двух массивов, индекс может оказаться неверным, например, выходить за границы массива. Вообще, при использовании индекса мы делаем неявное допущение о его корректности. В большинстве современных языков программирования (C, C++, C#, Java, Eiffel) существуют средства для явного задания таких допущений.

Например, в C существует функция assert(), определенная в заголовочном файле <assert.h>. Аргументом этой функции может выступать любое булево выражение. В случае, если оно равно false, функция прерывает работу программы. Таким образом, при помощи булевых выражений могут быть описаны допущения в критических точках программы. Предыдущий пример при использовании функции assert() будет выглядеть как

#include <assert.h>
…

string_vector surname;
string_vector phone;
…

int index = surname.find("Петров");
assert( (index > 0) && (index < phone.size()) );
foundPhone = phone.at(index);

Часто программисты определяют свою собственную функцию assert(), например, следующим образом:

#ifdef NODEBUG
#define assert(ignore) 0
#else
#define assert(ex) \
((ex) ? 1 : \
( printf("Assertion failed "), \
abort(), 0))
#endif // NODEBUG

Эта функция отличается от стандартной тем, что в финальной сборке системы с установленным макросом NODEBUG, выдача предупреждений функцией assert() отключается. Такая организация функции assert() связана с широко распространенным заблуждением касательно того, что частые вызовы функции assert() значительно замедляют выполнение программы. Поэтому многие программисты используют допущения только на стадии отладки, считая, что они не могут сработать в конечном продукте. Однако достаточно небольшой проигрыш в скорости окупается дополнительной гарантией надежности системы.

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

Существует три причины использовать допущения в критических точках:

  • упрощение процесса создания корректных программ - явно задавая условия, необходимые для корректной работы программы в критических точках, мы защищаем себя от выдачи программой неверных данных;
  • помощь в сопровождении документации и документировании - явно заданные условия позволяют проще определить, синхронизирован ли программный код с проектной документацией, которая зачастую задает корректные и некорректные диапазоны значений обрабатываемых данных;
  • помощь в отладке - неверные допущения проявятся в процессе отладки, в результате упростится уточнение допущений.

В зависимости от того, в какой критической точке проверяется допущение, выделяют следующие их типы:

  • предусловия - такие допущения помещаются в начале функций или процессов обработки данных и предназначены для проверки того, все ли необходимые данные корректны;
  • постусловия - такие допущения помещаются в конце функций или процессов обработки данных и предназначены для проверки полученного результата на корректность до того, как передать его дальше;
  • инварианты классов - такие допущения предназначены для периодической проверки состояния данных объектов классов, которые не должны меняться в течение жизненного цикла объекта или должны меняться строго определенным образом;
  • инварианты циклов - такие допущения предназначены для проверки условий, которые должны быть всегда истинны во время выполнения цикла. Примером такого условия может служить допущение о том, что значение итератора цикла не должно превышать количества элементов массива, по которому организован цикл.

Применение допущений является основной для контрактного программирования, при котором в каждом классе явно определяются предусловия для использования методов этого класса. Таким образом, между объектами этого класса и другими объектами заключается своего рода "контракт" в котором четко прописан интерфейс взаимодействия не только по форматам вызова методов, но и по пересылаемым данным.

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

< Лекция 14 || Лекция 15: 1234 || Лекция 16 >
Илья Макаренко
Илья Макаренко

Добрый день.

Вопрос №1

Какова стоимость получения диплома о мини-МБА по данному курсу? Или ориентироваться на указанную на сайте?

Вопрос №2

Возможно ли начать обучение без потери результатов, не отправив документы на зачисление, а отправку выполнить позже?

Александр Медов
Александр Медов

Здравствуйте, какова полная сумма предоставленной услуги с печатью документа и отправкой по почте?