Интуиция (Дзен) и искусство программной надежности: больше гарантий и меньше проверок
Интуиция (Дзен) и искусство программной надежности: больше гарантий и меньше проверок
Возможно, вы не заметили, что контракт противоречит мудрости, бытующей в программной инженерии. Поначалу это шокирует, но контракт - один из главных вкладов в надежность ПО.
Правило контракта говорит, что предусловие дает преимущество поставщику, если клиентская часть контракта не выполняется, то класс перестает быть связан постусловием. В этом случае программа может делать все что угодно, например зациклиться, не нарушая при этом контракт. Это тот самый случай, когда "заказчик виноват".
Первое преимущество от такого соглашения в том, что стиль программирования существенно упрощается. Разработчик класса при написании тела программы смело может предполагать, что все ограничения, заданные предусловием, выполняются; ему нет нужды проверять их в теле программы. Так для функции, вычисляющей квадратный корень:
sqrt (x: REAL): REAL is
-- Квадратный корень из x
require
x >= 0
do ... end
можно смело применять алгоритм, не учитывающий случай отрицательного x, поскольку это предусмотрено предусловием, и ответственность за его выполнение несут клиенты программы. С первого взгляда это может показаться опасным, но читайте дальше. Фактически метод Проектирования по Контракту идет дальше. Предположим, что мы написали в предложении do предыдущей программы следующий текст:
if x < 0 then
"Обработать ошибку как-нибудь"
else
"Выполнить нормальное вычисление квадратного корня"
end
Заметьте, в этом не только нет никакой необходимости, но это и неприемлемо! Этот факт можно отразить в следующем методологическом правиле:
Принцип Нет-Избыточности
Ни при каких обстоятельствах в теле программы не должно проверяться ее предусловие
Это правило противоречит тому, чему учат во многих учебниках по программированию, где необходимость проверок часто выступает под знаменами "защитного программирования" (defensive programming). Его идея в том, что для получения надежного ПО каждая программа должна защищать себя настолько, насколько это возможно. Лучше больше проверок, чем недостаточно; нельзя доверять незнакомцам; еще одна проверка может и не поможет, но и не навредит делу.
Проектирование по контракту утверждает противное: избыточные проверки могут нанести вред. Конечно, это кажется странным, на первый взгляд. Это естественная реакция, полагать, что дополнительная проверка в худшем случае может быть бесполезной, но не может быть причиной неполадок. Возьмем, например, программу sqrt, включившую проверку x<0, хотя ее клиенты были проинструктированы о необходимости обеспечения x>=0. Что в этом плохого? С микроскопической точки зрения, ограничив наше видение узким мирком sqrt, кажется, что включение проверки делает программу более устойчивой. Но мир системы не ограничивается одной программой - он содержит множество программ в множестве классов. Для получения надежной системы необходимо перейти к макроскопическому видению проблемы, обобщающему всю архитектуру.
С этой глобальной точки зрения простота становится критическим фактором. Сложность - главный враг качества. Когда в этот концерн привносятся излишние проверки, то это уже не покажется столь безобидным делом. Экстраполируйте на тысячи программ в системе среднего размера (или на десятки и сотни тысяч в большой системе) проверку (if x<0 then ...), столь безобидную с первого взгляда, - все это начнет выглядеть подобно монстру бесполезной сложности. Добавляя избыточные проверки, добавляете больше кода. Больше кода - больше сложности, отсюда и больше источников условий, приводящих к тому, что все пойдет не так, это приведет к дальнейшему разрастанию кода и так до бесконечности. Если пойти по этой дороге, то определенно можно сказать одно - мы никогда не достигнем надежности. Чем больше пишем, тем больше придется писать.
Этот бег с препятствиями не для нас, нас ждет другая дорога. Проектирование по Контракту приглашает идентифицировать согласованные условия, необходимые для правильного функционирования каждого контракта в кооперации клиенты - поставщики. Метод вынуждает для каждого соглашения установить, кто несет ответственность - клиент или поставщик. Ответ может быть разный, частично он определяется стилем проектирования; позже будет дан ответ, как это делать лучшим образом. Но когда решение принято, нужно его придерживаться. Если требования корректности появляются в предусловии, определяя тем самым ответственность клиента, то в программе не должно быть соответствующих проверок. Требования, не указанные в предусловии, должны проверяться и выполняться в программе.
Следует отметить, что избыточные проверки широко применяются в процессе функционирования компьютеров и другой электронной аппаратуры. Физическая система, нормально функционирующая, со временем может терять целостность; причины разные - износ, разрыв, внешние воздействия. Поэтому нормальной практикой является, когда получатель и отправитель сигнала оба проверяют его целостность. Для программных систем феномена износа не наблюдается, нет и необходимости в избыточных проверках.
Можно также заметить, что так называемая избыточная проверка аппаратуры, на самом деле таковой не является: это могут быть различные дополнительные тесты, например, проверка на четность, проверка разных устройств и т.д.
Еще одним недостатком защитного программирования является его стоимость. Потеря производительности - наказание за избыточные проверки. Иногда этого вполне достаточная причина для отказа от защитного программирования, что бы ни писалось в учебниках. Работа по удалению таких проверок может быть довольно утомительной. Приемы, рассматриваемые в этой лекции, оставляют место дополнительным проверкам, но они будут основываться на разработке такого окружения, которое возьмет на себя заботу о подобных проверках. После завершения отладки достаточно будет отключить соответствующий параметр компиляции, чтобы проверки исчезли; в самом программном продукте они не содержатся.
Не говоря уже о потере производительности, принципиальной причиной отказа от защитного программирования является наша цель - получение максимальной надежности. Для систем сколь либо существенных размеров недостаточно обеспечение качества отдельных элементов, - более важно гарантировать, что для каждого взаимодействия двух элементов задан явный список взаимных обязательств и преимуществ - контракт. В заключение сформулируем парадокс Дзен-стиля: меньше проверок - больше надежности.