Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Модульность

.pdf
Скачиваний:
8
Добавлен:
18.03.2015
Размер:
422.56 Кб
Скачать

Главным достижением последних нескольких лет явилось появление стандартов качества ПО. Разработаны сертификаты ISO,

стандарт "2167" и его преемники, Модель Полноты Потенциала

(Capability Maturity Model), предложенная Институтом программной инженерии (Software Engineering Institute). Но поскольку они брали начало из моделей, используемых в других отраслях знания, они наделены обширным "хвостом" бумажной документации. Некоторые из этих стандартов могли бы оказать значительно большее влияние на качество ПО, (помимо того, что они дают администраторам программного продукта средство для оправданий в случае последующих эксплуатационных неполадок) если бы они включали принцип Самодокументирования.

В этом курсе следствием принципа Самодокументирования является метод документирования классов - модулей при ОО-

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

Просто модуль должен содержать свою документацию. (См. "Использование утверждений класса (assertions) для документирования" в лекции 11. См. также лекция 5 курса "Основы объектно-ориентированного проектирования" и последние два упражнения в ней.)

При таком подходе ПО превращается в единственный программный продукт, обеспечивающий его различные представления или облики (views). Один облик, пригодный для компиляции и выполнения, - полный исходный текст модуля. Другой - документация,

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

не знакомясь с содержанием исходного модуля, что соответствует правилу Скрытия Информации. Возможны и другие представления.

Унифицированный Доступ

Хотя вначале может показаться, что принцип Унифицированного Доступа направлен лишь на решение проблем, связанных с принятой нотацией, в действительности он задает правило проектирования,

влияющее на многие аспекты ОО-разработки ПО. Принцип следует из критерия Непрерывности; его можно рассматривать и как частный случай правила Скрытия Информации.6)

Пусть x - имя, используемое для доступа к некоторому элементу данных, который в последующем будем называть объектом. Пусть f -

имя компонента (feature), применимого к x. Под компонентом понимается некоторая операция; далее этот термин будет определен подробнее. Например, x может быть переменной, представляющей счет в банке, а f - компонент, задающий текущий баланс этого счета

(account's current balance). Унифицированный Доступ направлен на решение вопроса о том, какой должна быть нотация, задающая применение f к x, не содержащая каких-либо преждевременных обязательств по способу реализации f.

Во многих языках проектирования и программирования выражение, описывающее применение f к x, зависит от реализации f,

выбранной разработчиком. Это может быть свойство, хранимое вместе с x, или метод, вызываемый всякий раз, когда это требуется. В примере

с банковскими счетами и остатками на счетах возможно использование обоих подходов:

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

должна предусматривать корректировку соответствующего поля.

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

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

В общепринятой нотации таких языков, как Pascal, Ada, C, C++ и Java используется обозначение x.f для случая A1 и f(x) для случая A2.

Два представления банковского счета

Выбор между представлениями A1 и A2 это компромисс между

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

непрерывности желательно иметь нотацию для доступа к компоненту,

не зависящую от выбора одного из двух представлений. Если способ реализации x'ов на некотором этапе разработки проекта будет изменен,

то это не потребует изменений в модулях, использующих вызов f.

Мы рассмотрели пример принципа Унифицированного Доступа. В

общем виде принцип можно сформулировать так:

Принцип Унифицированного Доступа Все службы, предоставляемые модулем, должны быть

доступны в унифицированной нотации, которая не подведет вне зависимости от реализации, использующей память или вычисления.

Этому принципу удовлетворяют немногие языки. Старейшим из них был Algol W, в котором как вызов функции, так и доступ к полю записывались в виде a(x). Первым из ОО-языков, удовлетворяющих Принципу Унифицированного Доступа, был язык Simula 67,

использовавший обозначение x.f в обоих случаях. Нотация,

предлагаемая в лекциях 7-18 этого курса, будет поддерживать такое соглашение.

Открыт-Закрыт

Любой метод модульной декомпозиции должен удовлетворять принципу семафора: Открыт-Закрыт:

Принцип Открыт-Закрыт

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

и закрытыми.

Противоречие является лишь кажущимся, поскольку термины

соответствуют разным целевым установкам:

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

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

(project baseline), и его интерфейс опубликован в интересах авторов других модулей.

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

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

Поэтому разработчики стараются сохранять гибкость ПО,

допускающую последующие изменения и дополнения. Но необходимо,

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

системе, состоящей из многих модулей, большинство модулей зависимы. Например, модуль интерфейса пользователя может зависеть от модуля синтаксического разбора (parsing module) - синтаксического

анализатора и от модуля графики. Синтаксический анализатор может зависеть от модуля лексического анализа, и так далее. Если не закрывать модуль до тех пор, пока не будет уверенности, что он уже содержит все необходимые компоненты, то невозможно будет завершить разработку многомодульной программы: каждый из разработчиков будет вынужден ожидать, когда же завершат свою работу все остальные.

При использовании традиционной методики, две рассмотренные целевые установки оказываются несовместимыми. Либо модуль остается открытым, что не позволяет пользоваться им всем остальным,

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

Два рисунка, приведенные ниже, иллюстрируют ситуацию, в

которой трудно согласовать потребности в открытых и закрытых состояниях модуля. На первом рисунке модуль A используется модулями-клиентами B, С, D, которые сами могут иметь своих клиентов - E, F и так далее.

Рис. 3.12. Модуль А и его клиенты

В процессе течения времени ситуация изменяется и появляются новые клиенты - F и другие, которым требуется расширенная или

приспособленная к новым условиям версия модуля A, которую можно назвать A':

Рис. 3.13. Старые и новые клиенты

При использовании не ОО-методов, возможны лишь два решения этой проблемы, в равной степени неудовлетворительные:

N1 Можно переделать модуль A так, чтобы он обеспечивал расширенную или видоизмененную функциональность,

требуемую новым клиентам.

N2 Можно сохранить A в прежнем виде, сделать его копию,

изменить имя копии модуля на A', и выполнить все необходимые переделки в новом модуле. При таком подходе новый модуль A'

никак не будет связан со старым модулем A.

Возможные катастрофические последствия решения N1 очевидны.

Модуль A мог использоваться длительное время и иметь многих клиентов, таких как B, С и D. Переделки, необходимые для удовлетворения потребностей новых клиентов, могут нарушить предположения, на основе которых старые клиенты использовали модуль A; в этом случае изменения в A могут "запустить"

катастрофическую цепочку изменений у клиентов, у клиентов этих клиентов, и так далее. Для руководителя проекта это будет настоящим

кошмаром: внезапно целые части ПО, считавшегося давным-давно завершенным и сданным в эксплуатацию, окажутся заново открытыми,

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

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

Экстраполируем воздействие этого решения на множество модулей, -

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

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

хотя и не вполне идентичны.

Для многих организаций по разработке ПО такое изобилие модулей, не согласующееся с количеством выполняемых функций

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

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

рассмотренных решений. Ведь лучше избежать избыточности, чем создавать ее.

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

упражнении У3.6 предлагается выяснить, какова будет необходимость управления конфигурацией в объектно-ориентированной среде программирования.)

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

A и всех его клиентов в верхней части рисунка, и в то же время предоставить модуль A' клиентам в нижней части, избегая дублирования программных средств? Благодаря механизму наследования (inheritance), ОО-подход обеспечивает особенно изящный вклад в решение этой проблемы.

Механизм наследования подробно рассматривается в последующих лекциях, а здесь дается лишь общее представление об этом. Для разрешения дилеммы, - изменять или повторно выполнять -

наследование позволяет определить новый модуль A' на основе существующего модуля A, констатируя лишь различия между ними.

Опишем A' как class A' inherit

A

redefine f, g, ... end

feature

f is ...

g is ...

...

u is ...

...

end

где предложение feature содержит как определение новых компонент,

характерных для A', например u, так и переопределение тех компонент

(таких как f, g,:), представление которых в A' отличается от того,

которое они имели в A.

Для графической иллюстрации наследования используется стрелка от "наследника" (heir) (нового класса A') к "родителю" (parent) (классу

A):

Рис. 3.14. Адаптация модуля к новым клиентам

Благодаря механизму наследования ОО, разработчики могут осуществлять гораздо более последовательный подход к разработке ПО, чем это было возможно при использовании прежних методов.

Один из способов описания принципа Открыт-Закрыт и следующих из него ОО-методов состоит в рассмотрении их как организованного хакерства. Под "хакерством" здесь понимается небрежный (slipshod)

подход к компоновке и модификации программы (а вовсе не