Эндрю Хант - Программист-прагматик. Путь от подмастерья к мастеру
Вы можете использовать семантические инварианты для выражения неизменных требований при составлении своего рода "философского контракта".
Однажды авторы книги написали программу обработки транзакций для дебетовых банковских карт. Главное требование заключалось в том, что пользователь дебетовой карты не должен проводить на своем счете одну и ту же транзакцию. Другими словами, ошибка скорее повлечет за собой отмену обработки транзакции, чем инициирует обработку дублированной транзакции – независимо от характера сбоя в системе.
Это простое правило, исходящее непосредственно из требований, доказало свою полезность при отсеивании сложных сценариев исправления ошибок и является руководством при детальном проектировании и реализации во многих областях.
Но убедитесь в том, что вы не смешиваете требования, представляющие собой жесткие, неизменные законы с теми, что являются не более чем политикой, которая может измениться вместе с правящим режимом. Именно поэтому мы используем термин "семантические инварианты" – он должен занимать главенствующее место при определении сути предмета и не подчиняться прихотям политики (для которой предназначаются более динамичные правила ведения бизнеса).
Если вы обнаруживаете подходящее требование, убедитесь, что оно становится неотъемлемой частью любой создаваемой вами документации – будь то маркированный список в требованиях, которые подписываются в трех экземплярах, или большое объявление на обычной лекционной доске, которое не заметит разве что слепой. Постарайтесь сформулировать его четко и однозначно. Например, в случае с дебетовой картой можно было бы записать:
ERR IN FAVOR OF THE CONSUMER (ОШИБКА В ПОЛЬЗУ КЛИЕНТА)
Это и есть четкая, сжатая, однозначная формулировка, которая применима к различным областям системы. Это наш контракт со всеми пользователями системы, наша гарантия ее поведения.
Динамические контракты и агенты
До сих пор мы говорили о контрактах как о неких фиксированных, раз и навсегда установленных спецификациях. Но в случае с автономными агентами этого быть не должно. Из определения автономных агентов следует, что они могут отвергать запросы, которые не хотят выполнять. Они могут обговаривать условия контракта – "я не могу предоставить то-то и то-то, но если вы дадите мне вот это, тогда я смогу предоставить что-то другое".
Конечно, любая система, которая полагается на технологию агентов, обладает критической зависимостью от положений контракта, даже если они генерируются динамически.
Только представьте себе: при достаточном количестве элементов и агентов, которые для достижения конечной цели могут обговаривать свои собственные контракты между собой, можно было бы просто выйти из кризисной ситуации, связанной с производительностью, позволив программам решать проблемы за нас.
Но если мы не можем использовать контракты «вручную», то мы не сможем использовать их и автоматически. Поэтому в следующий раз, когда вы будете проектировать фрагмент программы, проектируйте и его контракт.
Другие разделы, относящиеся к данной теме:• Ортогональность
• Мертвые программы не лгут
• Программирование утверждений
• Балансировка ресурсов
• Несвязанность и закон Деметера
• Временное связывание
• Программирование в расчете на совпадение
• Программа, которую легко тестировать
• Команды прагматиков
Вопросы для обсуждения• Информация к размышлению: Если принцип ППК является столь мощным, почему бы не применять его более широко? Насколько сложно выйти на контракт? Заставляет ли он вас думать о вещах, которые вы бы в данный момент проигнорировали? Заставляет ли он вас ДУМАТЬ? Это явно небезопасный принцип!
Упражнения14. Из чего получается удачный контракт? Можно добавлять любые предусловия и постусловия, но есть ли от них толк? Не могут ли они принести больше вреда, чем пользы? Определите, какими являются контракты в примере ниже и упражнениях 15 и 16: удачными, неудачными, уродливыми, и объясните, почему.
Рассмотрим вначале пример, написанный на языке Eiffel. Имеется программа для добавления STRING к двунаправленному циклическому списку (следует помнить, что предусловия обозначены require, а постусловия – ensure).
-- Добавляем элемент в двунаправленный список,
-- и возвращаем вновь созданный узел (NODE).
add_tem (item: STRING): NODE is
require
item /= Void -- /= означает 'не равно'.
deferred -- Абстрактный базовый класс
ensure
result.next.previous = result -- Проверка связей вновь
result.previous.next = result -- вновь добавленного узла.
find_item(item) = result -- Должен найти его.
end
15. Теперь рассмотрим пример на языке Java – нечто подобное примеру, из упражнения 14. Оператор InsertNumber вставляет целое число в упорядоченный список. Предусловия и постусловия обозначены в соответствии с сайтом iContract (см. [URL 17]). (Ответ см. в Приложении В.)
private int data[];
/**
* @post data[index-1] < data[index] &&
* data[index] == aValue
*/
public Node insertNumber (final int aValue)
{
int index = findPlaceTolnsert(aValue);
...
16. Фрагмент стекового класса на языке Java. Можно ли назвать этот контракт удачным? (Ответ см. в Приложении В.)
/**
* @рге anltem != null // Требует реальных данных
* @post рор() == anltem // Проверяет их наличие
* // в стеке
*/
public void рush(final String anltem)
17. В классических примерах использования принципа ППК (см. упражнения 14–16) реализуется абстрактный тип данных – обычно это стек, или очередь. Но немногие действительно создают подобные разновидности низкоуровневых классов.
В данном упражнении требуется спроектировать интерфейс блендера для коктейлей. Он должен основываться на web-технологии, включаться по сети Интернет и использовать технологию CORBA, но в данный момент необходим лишь интерфейс управления. Блендер имеет десять скоростей (0 означает отключение); он не должен работать вхолостую а его скорость может единовременно изменяться на одну ступень (т. е. с 0 до 1, или с 1 до 2, но не сразу с 0 до 2).
Методы указаны ниже. Добавьте соответствующие предусловия и постусловия, а также инвариант. (Ответ см. в Приложении В.)
int getSpeed()
void setSpeed(int x)
booolean isFull()
void fill()
void empty()
18. Сколько чисел содержится в ряду 0, 5, 10, 15…, 100? (Ответ см. в Приложении В.)
22
Мертвые программы не лгут
Приходилось ли вам замечать, что иногда, еще до того как вы осознаете проблему, ее признаки обнаруживают посторонние люди? То же самое применимо и к программам других разработчиков. Если в одной из наших программ что-то начинает идти не так, как надо, в ряде случаев первой это «заметит» библиотечная подпрограмма. Возможно, паразитный указатель заставил нас записать в дескриптор файла какие-то бессмысленные символы. При следующем обращении к read это будет обнаружено. Возможно, что переполнение буфера привело к уничтожению счетчика, который мы собирались использовать для определения объема назначаемой памяти. Возможно, причиной сбоя окажется malloc. Логическая ошибка в одном из нескольких миллионов операторов, находящихся в тексте перед оператором выбора, означает, что его селектор больше не примет значение 1, 2 или 3. Мы берем случай default (который является одной из причин того, почему любой оператор выбора должен иметь значение по умолчанию), мы хотим знать, в какой момент произошло невозможное).
Легко поддаться умонастроению "этого не может быть, потому что не может быть никогда". Большинство из нас создавало программы, которые не проверяют, успешно ли завершилась операция закрытия файла и правильно ли записан оператор трассировки. И все сводилось к одному (к тому, что мы и так знали) – рассматриваемая программа не откажет, если будет работать в нормальных условиях. Но мы пишем программы с осторожностью. Мы ищем инородные указатели в других частях нашей программы, очищая стек. Мы выясняем, какие версии библиотек совместного пользования загружались в действительности.
Все ошибки дают вам информацию. Вы могли внушить себе, что ошибка произойти не может, и проигнорировать эту информацию. В отличие от вас, прагматики говорят себе, что если ошибка имеет место, то произошло что-то очень скверное.
Подсказка 32: Пусть аварийное завершение работы программы произойдет как можно раньше
Аварийное завершение не означает "отправить в корзину для мусора"
Одним из преимуществ скорейшего обнаружения проблем является то, что аварийное завершение происходит как можно раньше. И во многих случаях такое завершение программы – это лучший выход из положения. Альтернативой может быть продолжение работы, запись поврежденных данных в жизненно важную базу данных или команда стиральной машине на проведение двадцатого по счету цикла отжима.