Основы объектно-ориентированного программирования
Шрифт:
Рис. 11.1. Использование модулей - фильтров
При получении информации извне нельзя опираться на предусловия. Задача модулей ввода - гарантировать, что никакая информация не будет передана обрабатывающим модулям, пока она не будет удовлетворять условиям, требуемым для корректной обработки. При таком подходе утверждения будут широко использоваться в коммуникациях программа - программа. Постусловия модулей ввода должны соответствовать или превосходить предусловия, продиктованные обрабатывающими модулями. Фильтры играют охраняющую роль, обеспечивая корректность входных данных.
Утверждения это не управляющие структуры
Еще одно типичное заблуждение - рассматривать утверждения как управляющую структуру, реализующую разбор случаев. К этому моменту должно быть ясно, что не в этом их роль. Если написать программу sqrt, в которой отрицательные значения будут обрабатываться одним способом, а положительные - другим, то писать предусловие - предложение require не следует. В этом случае используется обычный разбор случаев: оператор if - then - else, или оператор case языка Pascal, или оператор inspect, введенный в этой книге как раз для таких целей.
Утверждения выражают нечто иное. Они говорят о корректности условий. Если sqrt имеет предусловие, то вызов, в котором x<0, это "жучок" (bug).
Правило нарушения утверждения (1)
Нарушение утверждения в период выполнения является проявлением "жучка" в ПО.
Слово "жучок" не принадлежит к научному лексикону, но этот термин понятен всем программистам. Учитывая контракты, это правило можно уточнить:
Правило нарушения утверждения (2)
Нарушение предусловия является проявлением "жучка" у клиента.
Нарушение постусловия является проявлением "жучка" у поставщика.
Нарушение предусловия означает, что вызывающая программа нарушила контракт - "виноват заказчик". С позиций внешнего наблюдателя можно, конечно, критиковать сам контракт, но коль скоро контракт заключен, его следует выполнять. Если есть программа, осуществляющая мониторинг утверждений, то запускать на выполнение программу, чье предусловие не выполняется, не имеет смысла.
Нарушение постусловия означает, что программа, предположительно вызванная в корректных условиях, не выполнила свою часть работы, предусмотренную контрактом. Здесь тоже ясно, кто виноват, а кто нет: "жучок" в программе, клиент не виновен.
Ошибки, дефекты и другие насекомые
Появление слова "жучок" в предыдущем анализе нарушений утверждений - хороший повод прояснить терминологию. Э. Дейкстра полагал, что появление термина "жучок" связано с жалкой попыткой программистов обвинить кого-то в том, что ошибка "закралась" в программу со стороны, пока программисты занимались делом, - как будто не разработчики повинны в ошибках. И все же термин прижился, возможно, из-за эмоциональной окраски и понятности. И в этой книге он свободно используется, но следует дополнить его более специфическими (и более нудными) терминами для случаев, когда необходима более строгая классификация ошибок.
Термины, обозначающие бедствия ПО
Ошибка (Error) - неверное решение, принятое при разработке программной системы.
Дефект (Defect) - свойство программной системы, которое может стать причиной отклонения системы от намеченного поведения.
Неисправность (Fault) - событие в программной системе, приведшее к отклонению от нормального поведения в процессе одного из запусков системы.
Причинные связи понятны: неисправности порождаются дефектами, являющиеся, в свою очередь, результатом ошибок.
"Жучок" обычно имеет смысл дефекта ("а вы уверены, что в вашей программе не осталось жучков"?). Такова его интерпретация в этой книге. Но в неформальных обсуждениях он может появляться и как ошибка и как неисправность.
Работа с утверждениями
Давайте займемся дальнейшим исследованием предусловий и постусловий, рассматривая понятные элементарные примеры. Утверждения, некоторые простые, другие более детальные, будут проникать во все примеры в последующих лекциях.
Класс стек
Поставляемый с утверждениями класс STACK был оставлен пока в схематичной форме (STACK1). Теперь на суд предстанет полная версия, включающая реализацию.
Для написания эффективного класса необходимо задать реализацию. В качестве таковой выберем реализацию стека на базе массива, уже обсуждавшаяся при рассмотрении АТД в шестой лекции.
Рис. 11.2. Реализация стека на базе массива
Массив, названный representation, имеет границы 1 и capacity: реализация использует также целочисленный атрибут count, отмечающий вершину стека. Заметьте, после того, как мы откроем для себя наследование, появится возможность писать классы с отложенной реализацией, что позволит покрывать несколько возможных реализаций, а не одну конкретную. Даже для класса c фиксированной реализацией, например, как здесь на базе массива, мы будем иметь возможность строить его как потомка родительского класса Array. В данный момент никакие методы наследования применяться не будут.
Вот он класс. Остается напомнить, что для массива a операция, присваивающая значение x его i– му элементу, записывается так: a.put(x,i). Получить i– й элемент можно так: a.item(i) или a @ i. Если, как здесь, границы заданы, то i во всех случаях лежит между этими границами: 1<= i <= capacity.