Программист-прагматик. Путь от подмастерья к мастеру
Шрифт:
Проблему намного проще найти и диагностировать "не сходя с места", при аварийном завершении работы программы.
Другие случаи применения инвариантов
До этого момента мы обсуждали предусловия и постусловия, применимые к отдельным методам и инварианты, которые, в свою очередь, применимы ко всем методам в пределах класса, но есть и другие полезные способы применения инвариантов.
Понимание граничных условий для нетривиального цикла может оказаться проблематичным. Циклы испытывают воздействие "проблемы банана" (я
В подобных ситуациях инварианты могут быть полезными: инвариант цикла представляет собой оператор возможной цели цикла, но он обобщен таким образом, что также истинен перед тем, как цикл выполняется, и при каждой итерации, осуществляемой с помощью цикла. Его можно считать контрактом в миниатюре. Классическим примером является подпрограмма поиска максимального элемента в массиве.
int m = arr[0]; // пример предполагает, что длина массива > 0
int i = 1;
// Инвариант цикла: m = max(arr[0:i-1])
while (i < arr.length) {
m = Math.max(m, arr[i]);
i = i + 1;
}
(arr [m:n] – принятое обозначение фрагмента массива, элементы которого имеют индексы от m до n). Инвариант должен быть истинным до начала выполнения цикла, а тело цикла должно гарантировать, что инвариант будет оставаться истинным во время выполнения цикла. Таким образом, нам известно, что инвариант истинен после выполнения цикла, и следовательно наш результат является достоверным. Инварианты цикла могут быть запрограммированы в явном виде (как утверждения); они также полезны при проектировании и документировании.
Вы можете использовать семантические инварианты для выражения неизменных требований при составлении своего рода "философского контракта".
Однажды авторы книги написали программу обработки транзакций для дебетовых банковских карт. Главное требование заключалось в том, что пользователь дебетовой карты не должен проводить на своем счете одну и ту же транзакцию. Другими словами, ошибка скорее повлечет за собой отмену обработки транзакции, чем инициирует обработку дублированной транзакции – независимо от характера сбоя в системе.
Это простое правило, исходящее непосредственно из требований, доказало свою полезность при отсеивании сложных сценариев исправления ошибок и является руководством при детальном проектировании и реализации во многих областях.
Но убедитесь в том, что вы не смешиваете требования, представляющие собой жесткие, неизменные законы с теми, что являются не более чем политикой, которая может измениться вместе с правящим режимом. Именно поэтому мы используем термин "семантические инварианты" – он должен занимать главенствующее место при определении сути предмета и не подчиняться прихотям политики (для которой предназначаются более динамичные правила ведения бизнеса).
Если вы обнаруживаете подходящее требование, убедитесь, что оно становится неотъемлемой частью любой создаваемой вами документации – будь то маркированный список в требованиях, которые подписываются в трех экземплярах, или большое объявление на обычной лекционной доске, которое не заметит разве что слепой. Постарайтесь сформулировать его четко и однозначно. Например, в случае с дебетовой картой можно было бы записать:
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]). (Ответ см. в Приложении В.)