ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 06.11.2023
Просмотров: 920
Скачиваний: 6
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
6.5. Слои программного продукта
Сложность и значительные размеры текста программы, предлагае- мой как решение реальной задачи, заставляют читателей этого текста искать возможности анализа программы на нескольких уровнях: снача- ла установить ее грубую функциональную структуру, затем выделить в ней функциональные единицы (компоненты, модули) и, наконец, про- анализировать каждый модуль отдельно. Сознательное систематиче-
282 ское применение некоторых методологий программирования позволяет создавать программы, которые легче поддаются анализу.
Большинство известных специалистов (9, 12, 20) еще в 70 – 80 годы прошлого века пришли к заключению, что разбиение на уровни являет- ся стандартным способом восприятия сложных текстов (не только про- грамм) независимо от того, помогает в этом автор текста или нет. На- сколько хорошо понимают это основное правило разработчики про- граммных систем, убедительно показывает пример выделения уровней в документации, сопровождающей большие программы: как правило, од- на и та же программа описывается в нескольких документах, отражаю- щих конкретные уровни абстракции (Руководство пользователя, Руко- водство оператора, Руководство системного программиста и т. п.). Раз- нообразие руководств указывает на возможность создания различных абстрактных описаний одной и той же программы. Эти описания могут
(но не обязательно) образовывать упорядоченное множество. Правда, на практике документация, предоставляемая разработчиками, во многих случаях вопреки декларируемых ими целям не является ни упорядочен- ной, ни полной.
Имеются весомые опытные данные в защиту того утверждения, что разбиение на уровни абстракции является стандартным методом разбо- ра сложных текстов, таких, как машинные программы. Синтаксический анализ (применяется ли он к предложению на русском языке или к опе- ратору программы), использование мнемонических имен, абзацев, от- ступов и других графических средств (таких, как блок-схемы, подчер- кивание строк, выделение фрагментов текста на распечатках и т. п.) – лишь немногие методы, используемые для этой цели.
Почти все машинные программы могут рассматриваться как конст- рукции с расслоенной структурой, вытекающей из общей структуры программного обеспечения. Программы, о которых идет речь, пишутся на языке, интерпретируемом компилятором, содержат библиотечные подпрограммы, выполняемые под управлением операционной системы, использующей различные служебные программы. Если рассмотреть теперь фактически выполняемую последовательность машинных ко- манд, то все упомянутые промежуточные средства будут отражать те или иные уровни абстракции. В сою очередь машинные команды можно интерпретировать как абстрактные описания микропрограмм, посредст- вом которых они реализуются.
Таким образом, мы получаем другое понятие расслоения программы, связанное со степенью отдаленности от оборудования компьютера. Ес- ли рассматривать переносимые программы, то, видимо, нужно выде- ляться другое множество слоев, определяемое разделением свойств про- граммы на машинно-независимые и присущие данной машине.
Наконец, можно обнаружить еще модульное расслоение программы: слои, соответствующие множествам равнодоступных модулей. Отме- тим, что (пока еще неточное) понятие равнодоступности модулей осво- бождает от языковых ограничений: языковые конструкции могут облег- чать модульное расслоение или даже обеспечивать синтаксическую
283 единицу, соответствующую модульному слою, но и при отсутствии та- ких языковых средств можно различать модульные слои в программе.
Дальнейшее изложение материала этого раздела следует В. Турскому
[37].
Изучение структуры программы или ее проектирование, поскольку оно почти неизбежно связано с разбиением программы на слои, должно начинаться с выбора принципа расслоения. Далее будем рассматривать программные системы одного определенного языкового уровня (с хо- рошо определенными синтаксическими единицами в соответствии с хорошо определенными синтаксическими правилами и хорошо опреде- ленной семантикой элементарных операторов и синтаксических конст- рукций). Из рассмотрения исключим все вопросы, относящиеся к дру- гим языковым уровням, таким, например, как интерпретация элемен- тарных операторов в терминах более примитивных составляющих.
Для полноты элементарные операторы данного языкового уровня будем рассматривать как модули базового (или нулевого) уровня, со- ставляющие базовый слой. Это можно сделать потому, что все элемен- тарные операторы повсеместно доступны: единственные возможные ограничения на использование элементарных операторов касаются вхо- дящих в них данных, т.е. фактических параметров активации модулей.
Затем, что здесь не учитываются привилегированные операторы ма- шинного языка, которые не могут использоваться в прикладных про- граммных системах.
Модули, построенные из модулей нулевого уровня, могут рассмат- риваться только как модули первого уровня. При этом не требуется, что- бы все модули первого уровня были равнодоступны: например, в про- граммах, написанных на языке, допускающем блочную структуру, неко- торые модули первого уровня могут быть локализованы в каком-либо блоке и, следовательно, доступны только внутри этого блока и его под- блоков.
С другой стороны, при конструировании модулей высших уровней в
некоторых языках можно использовать модули разных уровней и даже
того же самого уровня, что и конструируемый модуль (рекурсия, со- программы). Это заставляет отказаться от обманчиво простого опреде- ления слоев как модулей одного и того же уровня, где под уровнем мо- дуля должно пониматься нечто большее, чем наивысший уровень со- ставляющих его модулей. Поэтому уточним понятие равной доступно- сти.
Определение (семантическая спецификация) модуля, отличного от базового, зависит от спецификации других модулей. Будем предпола- гать, что в каком бы месте программы ни определялся модуль, специ- фикации всех требуемых модулей в том месте, где дается определение, будут синтаксически доступны (иначе в программе имеется ошибка).
Будем писать n > m, если спецификация модуля m зависит от специфи- кации модуля n. Предположим теперь, что в данной программе выделе- ны слои L
0
, L
1
, …, L
n
. Реально можно распознать только слой базовых
284 модулей L
0
. Процесс, описываемый ниже, введет более высокие уровни и отношение порядка.
Рассмотрим множество модулей M, не входящих в выделенные слои и таких, что для каждого модуля m M имеем:
1) если спецификация модуля m зависит от модуля m l
, а модуль m
1 принадлежит слою L
i
, то 0 i n;
2) хотя бы один из модулей, используемых в спецификации m, при- надлежит слою L
n
Любое множество M, удовлетворяющее этим двум требованиям, формирует страту над слоем L
n
. Максимальной стратой S (L
n
) над слоем L
n будем называть страту, содержащую все модули рассматри- ваемой программы, удовлетворяющие условиям 1) и 2) для страты.
Рассмотрим теперь множество всех модулей K(L
n
), таких, что для каждого модуля k K(L
n
) выполняются:
1) хотя бы один модуль, используемый в спецификации k, принад- лежит слою L
n
;
2) все модули, используемые в спецификации k, не являющиеся чле- нами L
0
SH(L
0
) … L
n – 1
SH(L
n – 1
) L
n
, должны быть членами
K(L
n
);
3) если m > k и m K(L
n
), то существует хотя бы один модуль p
K(L
n
), такой, что p > m.
Множество SH(L
n
) = K(L
n
) S(L
n
) называется покровом над слоем
L
n
Таким образом, произвольная (или максимальна) страта над L
n явля- ется некоторым (или конкретным) множеством (всех) модулей, специ- фицируемых с использованием слоев L
0
, L
1
, …, L
n
, которые по опреде- лению не могут быть реализованы при отсутствии доступа хотя бы к некоторым модулям слоя L
n
. На практике реализация модуля из страты над L
n потребует, вероятно, также доступа к модулям из других слоев
(L
0
, L
1
, …, L
n – 1
).
Взаимозависимые модули исключаются из страты, однако включа- ются в покровы как члены множеств K(L
n
). Для некоторых языков про- граммирования понятия покровов совпадают с понятием максимальной страты.
Дальнейший анализ расслоения программы обусловлен специфиче- ским множеством синтаксических правил, налагающих ограничения на правила локализации. Предположим, что имеется общедоступный базо- вый слой модулей и покров над ними. Если все модули покрова равно- доступны во всех частях программы, исключая базовые модули и моду- ли покрова, то весь покров можно рассматривать как первый слой мо- дулей. Тем не менее, в большинстве случаев некоторые части покрова, т.е. некоторые модули, принадлежащие покрову, будут резервироваться для собственных нужд компонентов более высокого уровня. Следова- тельно, при анализе программы может возникать тенденция рассматри- вать в качестве первого слоя модулей только ту часть покрова, которая
285 равнодоступна всем компонентам программы, не относящимся ни к ба- зовому слою, ни к модулям самого покрова.
Отметим, что в дальнейшем анализе будем включать в покров над первым уровнем только такие модули, которые строятся из общедос- тупных модулей, т.е. из базовых модулей и тех модулей покрова над базовым слоем, которые являются “общественными”. Тем не менее, не следует опасаться, что такой анализ не учтет какой-нибудь модуль про- граммы: программа в целом может рассматриваться как модуль. С дру- гой стороны, программа в целом одновременно является и наивысшим слоем, и покровом над предыдущим слоем.
В хорошо структурированной программе спецификация программы как модуля будет семантически зависеть только от спецификаций не- скольких модулей предыдущего слоя, и для связок будет использовать некоторые модули базового слоя. Использование базовых модулей во всех покровах и слоях, очевидно, необходимо – это “плата” за использо- вание в качестве базового слоя базовых операторов определенного язы- кового уровня.
Разбиение программы на слои показано на примере по рис. 6.8, где отношение n > m обозначается стрелкой, проведенной из n в m. Базовый слой обозначен горизонтальной линией в нижней части рисунка. Моду- ли A, B, F, G и H принадлежат максимальной страте над базовым слоем; модули C, D, E составляет множество K(L
0
). Следовательно, все модули самого нижнего ряда принадлежат покрову над базовым слоем. Модули
A, B, C, D, E и H равнодоступны “сверху” и, таким образом, принадле- жат первому слою. Модули F и G являются собственностью модуля M, что дает основание рассматривать эту группу модулей как единый мо- дуль M, а модули F и G как модуляризованные компоненты модуля M.
Модули I, J и L удовлетворяют условиям множества K(L
1
) и, следо- вательно, принадлежат покрову над первым слоем; действительно, по- скольку они равнодоступны, они являются членами второго слоя.
Рис. 6.8
Как классифицировать модуль M? Поскольку кроме базовых моду- лей он зависит только от модулей F и G, которые являются членами
286 страты над базовым слоем, не принадлежат никакому слою, модуль M принадлежит максимальной страте над нулевым слоем. Будучи также доступен, как и модули A, B, C, D, E и H, модуль M должен рассматри- ваться как модуль первого слоя.
При помощи аналогичных рассуждений можно показать, что модули
I, J, L и O принадлежат второму слою, а третий и четвертый слои состо- ят каждый из одного модуля (N и P соответственно). Модуль P – это, конечно, программа в целом.
Анализ представленного на рис. 6.8 примера выявляет несколько ин- тересных аспектов.
Во-первых, несмотря на искусственно введенную иерархию модулей
M, F и G, все-таки правильно относить модуль M к первому слою.
Во-вторых, обнаруживается, что модули, являющиеся чей-то “собст- венностью”, не попадают в какой-либо слой программы.
В-третьих, показана взаимосвязь семантической зависимости и пра- вил локализации: если модули F и G сделать столь же доступными, как другие модули покрова над базовым слоем, их можно будет рассматри- вать как модули первого слоя, а модуль M будет принадлежать второму слою.
В-четвертых, показано, что большое количество чисто механических понятий, конструируемых большей частью методом “снизу-вверх”, мо- жет использоваться для модульного расслоения программы, отражаю- щего взгляд ”сверху-вниз” на структуру программы.
Однако необходимо отметить, что во всех приведенных рассуждени- ях программа рассматривалась как готовый продукт, т.е. не рассматри- вались способы и средства получения данной структуры. Из многих возможных интерпретаций понятия “расслоение” использовано то, ко- торое определено, как структура из слоев равнодоступных модулей.
6.6. Методы структурного проектирования
6.6.1. Метод восходящей разработки (“снизу-вверх”)
Этот метод применялся в 60-х годах 20 века при разработке опера- ционных систем, в частности операционной системы THE. Он получил название метода восходящей разработки, или синтетического, или “сни- зу-вверх”. Применяя этот метод, разработчик начинает с основного ап- паратного оборудования. “Чистая” аппаратура представляет собой для пользователя довольно неудобную машину, чтобы решать на ней зада- чу. Поэтому разработчик на первом шаге добавляет к компьютеру слой программного обеспечения. Этот слой программ вместе с нижележащей аппаратурой обеспечивает выполнение некоторого множества команд, определяющих новую виртуальную машину. На следующем шаге вы- деляется другое нужное свойство, добавляется новый слой программно- го обеспечения и получается очередная виртуальная машина. Процесс продолжается до тех пор, пока не будет получена виртуальная машина с требуемыми пользователю свойствами (рис. 6.9).
287
Аналогично проводится процесс конструирования программных систем, который назван в [10. 17] архитектурным подходом. Модуль- ная структура системы формируется в процессе программирования мо- дулей, начиная с самого низшего уровня, затем следующего и т. д. При этом модули реализуются в таком порядке, чтобы для каждого про- граммируемого модуля были уже запрограммированы все модули, к которым он может обращаться. Главной целью архитектурного подхода является повышение уровня используемого языка программирования, а не разработка конкретной программы. Это означает, что для заданной предметной области выделяют типичные функции, каждую из которых можно использовать при решении разных задач в этой области.
Основные преимущества метода проектирования “снизу-вверх” вы- текают из способа структурирования, ограничивающего построение системы. Между различными уровнями можно организовать четкий интерфейс. При этом, поскольку любой процесс может требовать об- служивания только от процесса на более низком уровне, практически исключаются непредвиденные ситуации. Тестовые процедуры, исполь- зующиеся в этом методе проектирования, могут быть исчерпывающими, так как каждый уровень можно проверить по отдельности и достаточно полным образом. Когда некоторый уровень проверен до конца, можно добавлять части следующего уровня, реализующего более сложные функции и продолжать проверку.
Рис. 6.9. Последовательность проектирования снизу-вверх
288
При восходящем программировании разработчик начинает с полного набора базовых средств, обеспечиваемых выбранным языковым уров- нем (например, машинным языком). Все, что можно запрограммировать на этом языковом уровне, можно выразить в терминах таких средств.
Как правило, это достаточно утомительно, а значит и ненадежно. Для облегчения процесса программирования разработчик создает более вы- сокие слои модулей таким образом, чтобы они облегчали использование доступных средств в форме, позволяющей абстрагироваться от обреме- нительных деталей. Но на каждом следующем уровне (слое) разработ- чик должен быть уверен, что все средства, выразимые в терминах уста- новленных на этом слое понятий, доступны и что слой модулей пред- ставляет собой полное и логически стройное описание всей совокупно- сти средств, обеспечиваемых базовым слоем.
Главная трудность в применении метода восходящей разработки за- ключается в выборе уровней и иерархическом упорядочении. Кроме того, головной модуль проектируется и реализуется в последнюю оче- редь, что не дает возможности продемонстрировать его работу заказчи- ку и проверить его соответствие спецификациям.
Разработчики, использующие синтетический подход к проектирова- нию программ, сталкиваются с серьезными трудностями: начальные этапы утомительны, затрагивают самые базовые понятия и связаны с написанием длинных текстов на языке программирования. С другой стороны, нельзя пропустить их в описании и сразу перейти к понятиям
“среднего уровня”. Описание представляет собой как бы двойную за- гадку: первое (что всегда имеет место в этом подходе в его чистом виде)
– как догадаться, какие понятия являются полезными для решения кон- кретной задачи; второе – как получить эти промежуточные понятия из базовых.
На первый взгляд порядок разработки снизу-вверх кажется вполне естественным. Каждый модуль при программировании выражается че- рез уже запрограммированные, непосредственно подчиненные модули, а при тестировании используются уже отлаженные модули. Однако со- временная технология не рекомендует такой порядок разработки про- граммной системы [17].
Во-первых, для программирования какого-либо модуля можно обой- тись без текстов используемых им модулей, достаточно чтобы исполь- зуемый модуль был специфицирован, а для его тестирования можно заменить используемые модули имитаторами. Во-вторых, каждая про- грамма в какой-то степени подчиняется некоторым внутренним для нее, но глобальной для ее модулей информацией (принципам реализации, предположениям, структурам данных и т. п.), что определяет ее концеп- туальную целостность. Такая информация формируется в процессе раз- работки программной системы. При восходящей разработке для моду- лей нижних уровней такая информация еще не ясна в полном объеме., поэтому эти модули приходится перепрограммировать. В-третьих, при восходящем тестировании для каждого модуля (кроме головного) при- ходится создавать ведущую программу, которая должна подготовить
Сложность и значительные размеры текста программы, предлагае- мой как решение реальной задачи, заставляют читателей этого текста искать возможности анализа программы на нескольких уровнях: снача- ла установить ее грубую функциональную структуру, затем выделить в ней функциональные единицы (компоненты, модули) и, наконец, про- анализировать каждый модуль отдельно. Сознательное систематиче-
282 ское применение некоторых методологий программирования позволяет создавать программы, которые легче поддаются анализу.
Большинство известных специалистов (9, 12, 20) еще в 70 – 80 годы прошлого века пришли к заключению, что разбиение на уровни являет- ся стандартным способом восприятия сложных текстов (не только про- грамм) независимо от того, помогает в этом автор текста или нет. На- сколько хорошо понимают это основное правило разработчики про- граммных систем, убедительно показывает пример выделения уровней в документации, сопровождающей большие программы: как правило, од- на и та же программа описывается в нескольких документах, отражаю- щих конкретные уровни абстракции (Руководство пользователя, Руко- водство оператора, Руководство системного программиста и т. п.). Раз- нообразие руководств указывает на возможность создания различных абстрактных описаний одной и той же программы. Эти описания могут
(но не обязательно) образовывать упорядоченное множество. Правда, на практике документация, предоставляемая разработчиками, во многих случаях вопреки декларируемых ими целям не является ни упорядочен- ной, ни полной.
Имеются весомые опытные данные в защиту того утверждения, что разбиение на уровни абстракции является стандартным методом разбо- ра сложных текстов, таких, как машинные программы. Синтаксический анализ (применяется ли он к предложению на русском языке или к опе- ратору программы), использование мнемонических имен, абзацев, от- ступов и других графических средств (таких, как блок-схемы, подчер- кивание строк, выделение фрагментов текста на распечатках и т. п.) – лишь немногие методы, используемые для этой цели.
Почти все машинные программы могут рассматриваться как конст- рукции с расслоенной структурой, вытекающей из общей структуры программного обеспечения. Программы, о которых идет речь, пишутся на языке, интерпретируемом компилятором, содержат библиотечные подпрограммы, выполняемые под управлением операционной системы, использующей различные служебные программы. Если рассмотреть теперь фактически выполняемую последовательность машинных ко- манд, то все упомянутые промежуточные средства будут отражать те или иные уровни абстракции. В сою очередь машинные команды можно интерпретировать как абстрактные описания микропрограмм, посредст- вом которых они реализуются.
Таким образом, мы получаем другое понятие расслоения программы, связанное со степенью отдаленности от оборудования компьютера. Ес- ли рассматривать переносимые программы, то, видимо, нужно выде- ляться другое множество слоев, определяемое разделением свойств про- граммы на машинно-независимые и присущие данной машине.
Наконец, можно обнаружить еще модульное расслоение программы: слои, соответствующие множествам равнодоступных модулей. Отме- тим, что (пока еще неточное) понятие равнодоступности модулей осво- бождает от языковых ограничений: языковые конструкции могут облег- чать модульное расслоение или даже обеспечивать синтаксическую
283 единицу, соответствующую модульному слою, но и при отсутствии та- ких языковых средств можно различать модульные слои в программе.
Дальнейшее изложение материала этого раздела следует В. Турскому
[37].
Изучение структуры программы или ее проектирование, поскольку оно почти неизбежно связано с разбиением программы на слои, должно начинаться с выбора принципа расслоения. Далее будем рассматривать программные системы одного определенного языкового уровня (с хо- рошо определенными синтаксическими единицами в соответствии с хорошо определенными синтаксическими правилами и хорошо опреде- ленной семантикой элементарных операторов и синтаксических конст- рукций). Из рассмотрения исключим все вопросы, относящиеся к дру- гим языковым уровням, таким, например, как интерпретация элемен- тарных операторов в терминах более примитивных составляющих.
Для полноты элементарные операторы данного языкового уровня будем рассматривать как модули базового (или нулевого) уровня, со- ставляющие базовый слой. Это можно сделать потому, что все элемен- тарные операторы повсеместно доступны: единственные возможные ограничения на использование элементарных операторов касаются вхо- дящих в них данных, т.е. фактических параметров активации модулей.
Затем, что здесь не учитываются привилегированные операторы ма- шинного языка, которые не могут использоваться в прикладных про- граммных системах.
Модули, построенные из модулей нулевого уровня, могут рассмат- риваться только как модули первого уровня. При этом не требуется, что- бы все модули первого уровня были равнодоступны: например, в про- граммах, написанных на языке, допускающем блочную структуру, неко- торые модули первого уровня могут быть локализованы в каком-либо блоке и, следовательно, доступны только внутри этого блока и его под- блоков.
С другой стороны, при конструировании модулей высших уровней в
некоторых языках можно использовать модули разных уровней и даже
того же самого уровня, что и конструируемый модуль (рекурсия, со- программы). Это заставляет отказаться от обманчиво простого опреде- ления слоев как модулей одного и того же уровня, где под уровнем мо- дуля должно пониматься нечто большее, чем наивысший уровень со- ставляющих его модулей. Поэтому уточним понятие равной доступно- сти.
Определение (семантическая спецификация) модуля, отличного от базового, зависит от спецификации других модулей. Будем предпола- гать, что в каком бы месте программы ни определялся модуль, специ- фикации всех требуемых модулей в том месте, где дается определение, будут синтаксически доступны (иначе в программе имеется ошибка).
Будем писать n > m, если спецификация модуля m зависит от специфи- кации модуля n. Предположим теперь, что в данной программе выделе- ны слои L
0
, L
1
, …, L
n
. Реально можно распознать только слой базовых
284 модулей L
0
. Процесс, описываемый ниже, введет более высокие уровни и отношение порядка.
Рассмотрим множество модулей M, не входящих в выделенные слои и таких, что для каждого модуля m M имеем:
1) если спецификация модуля m зависит от модуля m l
, а модуль m
1 принадлежит слою L
i
, то 0 i n;
2) хотя бы один из модулей, используемых в спецификации m, при- надлежит слою L
n
Любое множество M, удовлетворяющее этим двум требованиям, формирует страту над слоем L
n
. Максимальной стратой S (L
n
) над слоем L
n будем называть страту, содержащую все модули рассматри- ваемой программы, удовлетворяющие условиям 1) и 2) для страты.
Рассмотрим теперь множество всех модулей K(L
n
), таких, что для каждого модуля k K(L
n
) выполняются:
1) хотя бы один модуль, используемый в спецификации k, принад- лежит слою L
n
;
2) все модули, используемые в спецификации k, не являющиеся чле- нами L
0
SH(L
0
) … L
n – 1
SH(L
n – 1
) L
n
, должны быть членами
K(L
n
);
3) если m > k и m K(L
n
), то существует хотя бы один модуль p
K(L
n
), такой, что p > m.
Множество SH(L
n
) = K(L
n
) S(L
n
) называется покровом над слоем
L
n
Таким образом, произвольная (или максимальна) страта над L
n явля- ется некоторым (или конкретным) множеством (всех) модулей, специ- фицируемых с использованием слоев L
0
, L
1
, …, L
n
, которые по опреде- лению не могут быть реализованы при отсутствии доступа хотя бы к некоторым модулям слоя L
n
. На практике реализация модуля из страты над L
n потребует, вероятно, также доступа к модулям из других слоев
(L
0
, L
1
, …, L
n – 1
).
Взаимозависимые модули исключаются из страты, однако включа- ются в покровы как члены множеств K(L
n
). Для некоторых языков про- граммирования понятия покровов совпадают с понятием максимальной страты.
Дальнейший анализ расслоения программы обусловлен специфиче- ским множеством синтаксических правил, налагающих ограничения на правила локализации. Предположим, что имеется общедоступный базо- вый слой модулей и покров над ними. Если все модули покрова равно- доступны во всех частях программы, исключая базовые модули и моду- ли покрова, то весь покров можно рассматривать как первый слой мо- дулей. Тем не менее, в большинстве случаев некоторые части покрова, т.е. некоторые модули, принадлежащие покрову, будут резервироваться для собственных нужд компонентов более высокого уровня. Следова- тельно, при анализе программы может возникать тенденция рассматри- вать в качестве первого слоя модулей только ту часть покрова, которая
285 равнодоступна всем компонентам программы, не относящимся ни к ба- зовому слою, ни к модулям самого покрова.
Отметим, что в дальнейшем анализе будем включать в покров над первым уровнем только такие модули, которые строятся из общедос- тупных модулей, т.е. из базовых модулей и тех модулей покрова над базовым слоем, которые являются “общественными”. Тем не менее, не следует опасаться, что такой анализ не учтет какой-нибудь модуль про- граммы: программа в целом может рассматриваться как модуль. С дру- гой стороны, программа в целом одновременно является и наивысшим слоем, и покровом над предыдущим слоем.
В хорошо структурированной программе спецификация программы как модуля будет семантически зависеть только от спецификаций не- скольких модулей предыдущего слоя, и для связок будет использовать некоторые модули базового слоя. Использование базовых модулей во всех покровах и слоях, очевидно, необходимо – это “плата” за использо- вание в качестве базового слоя базовых операторов определенного язы- кового уровня.
Разбиение программы на слои показано на примере по рис. 6.8, где отношение n > m обозначается стрелкой, проведенной из n в m. Базовый слой обозначен горизонтальной линией в нижней части рисунка. Моду- ли A, B, F, G и H принадлежат максимальной страте над базовым слоем; модули C, D, E составляет множество K(L
0
). Следовательно, все модули самого нижнего ряда принадлежат покрову над базовым слоем. Модули
A, B, C, D, E и H равнодоступны “сверху” и, таким образом, принадле- жат первому слою. Модули F и G являются собственностью модуля M, что дает основание рассматривать эту группу модулей как единый мо- дуль M, а модули F и G как модуляризованные компоненты модуля M.
Модули I, J и L удовлетворяют условиям множества K(L
1
) и, следо- вательно, принадлежат покрову над первым слоем; действительно, по- скольку они равнодоступны, они являются членами второго слоя.
Рис. 6.8
Как классифицировать модуль M? Поскольку кроме базовых моду- лей он зависит только от модулей F и G, которые являются членами
286 страты над базовым слоем, не принадлежат никакому слою, модуль M принадлежит максимальной страте над нулевым слоем. Будучи также доступен, как и модули A, B, C, D, E и H, модуль M должен рассматри- ваться как модуль первого слоя.
При помощи аналогичных рассуждений можно показать, что модули
I, J, L и O принадлежат второму слою, а третий и четвертый слои состо- ят каждый из одного модуля (N и P соответственно). Модуль P – это, конечно, программа в целом.
Анализ представленного на рис. 6.8 примера выявляет несколько ин- тересных аспектов.
Во-первых, несмотря на искусственно введенную иерархию модулей
M, F и G, все-таки правильно относить модуль M к первому слою.
Во-вторых, обнаруживается, что модули, являющиеся чей-то “собст- венностью”, не попадают в какой-либо слой программы.
В-третьих, показана взаимосвязь семантической зависимости и пра- вил локализации: если модули F и G сделать столь же доступными, как другие модули покрова над базовым слоем, их можно будет рассматри- вать как модули первого слоя, а модуль M будет принадлежать второму слою.
В-четвертых, показано, что большое количество чисто механических понятий, конструируемых большей частью методом “снизу-вверх”, мо- жет использоваться для модульного расслоения программы, отражаю- щего взгляд ”сверху-вниз” на структуру программы.
Однако необходимо отметить, что во всех приведенных рассуждени- ях программа рассматривалась как готовый продукт, т.е. не рассматри- вались способы и средства получения данной структуры. Из многих возможных интерпретаций понятия “расслоение” использовано то, ко- торое определено, как структура из слоев равнодоступных модулей.
6.6. Методы структурного проектирования
6.6.1. Метод восходящей разработки (“снизу-вверх”)
Этот метод применялся в 60-х годах 20 века при разработке опера- ционных систем, в частности операционной системы THE. Он получил название метода восходящей разработки, или синтетического, или “сни- зу-вверх”. Применяя этот метод, разработчик начинает с основного ап- паратного оборудования. “Чистая” аппаратура представляет собой для пользователя довольно неудобную машину, чтобы решать на ней зада- чу. Поэтому разработчик на первом шаге добавляет к компьютеру слой программного обеспечения. Этот слой программ вместе с нижележащей аппаратурой обеспечивает выполнение некоторого множества команд, определяющих новую виртуальную машину. На следующем шаге вы- деляется другое нужное свойство, добавляется новый слой программно- го обеспечения и получается очередная виртуальная машина. Процесс продолжается до тех пор, пока не будет получена виртуальная машина с требуемыми пользователю свойствами (рис. 6.9).
287
Аналогично проводится процесс конструирования программных систем, который назван в [10. 17] архитектурным подходом. Модуль- ная структура системы формируется в процессе программирования мо- дулей, начиная с самого низшего уровня, затем следующего и т. д. При этом модули реализуются в таком порядке, чтобы для каждого про- граммируемого модуля были уже запрограммированы все модули, к которым он может обращаться. Главной целью архитектурного подхода является повышение уровня используемого языка программирования, а не разработка конкретной программы. Это означает, что для заданной предметной области выделяют типичные функции, каждую из которых можно использовать при решении разных задач в этой области.
Основные преимущества метода проектирования “снизу-вверх” вы- текают из способа структурирования, ограничивающего построение системы. Между различными уровнями можно организовать четкий интерфейс. При этом, поскольку любой процесс может требовать об- служивания только от процесса на более низком уровне, практически исключаются непредвиденные ситуации. Тестовые процедуры, исполь- зующиеся в этом методе проектирования, могут быть исчерпывающими, так как каждый уровень можно проверить по отдельности и достаточно полным образом. Когда некоторый уровень проверен до конца, можно добавлять части следующего уровня, реализующего более сложные функции и продолжать проверку.
Рис. 6.9. Последовательность проектирования снизу-вверх
288
При восходящем программировании разработчик начинает с полного набора базовых средств, обеспечиваемых выбранным языковым уров- нем (например, машинным языком). Все, что можно запрограммировать на этом языковом уровне, можно выразить в терминах таких средств.
Как правило, это достаточно утомительно, а значит и ненадежно. Для облегчения процесса программирования разработчик создает более вы- сокие слои модулей таким образом, чтобы они облегчали использование доступных средств в форме, позволяющей абстрагироваться от обреме- нительных деталей. Но на каждом следующем уровне (слое) разработ- чик должен быть уверен, что все средства, выразимые в терминах уста- новленных на этом слое понятий, доступны и что слой модулей пред- ставляет собой полное и логически стройное описание всей совокупно- сти средств, обеспечиваемых базовым слоем.
Главная трудность в применении метода восходящей разработки за- ключается в выборе уровней и иерархическом упорядочении. Кроме того, головной модуль проектируется и реализуется в последнюю оче- редь, что не дает возможности продемонстрировать его работу заказчи- ку и проверить его соответствие спецификациям.
Разработчики, использующие синтетический подход к проектирова- нию программ, сталкиваются с серьезными трудностями: начальные этапы утомительны, затрагивают самые базовые понятия и связаны с написанием длинных текстов на языке программирования. С другой стороны, нельзя пропустить их в описании и сразу перейти к понятиям
“среднего уровня”. Описание представляет собой как бы двойную за- гадку: первое (что всегда имеет место в этом подходе в его чистом виде)
– как догадаться, какие понятия являются полезными для решения кон- кретной задачи; второе – как получить эти промежуточные понятия из базовых.
На первый взгляд порядок разработки снизу-вверх кажется вполне естественным. Каждый модуль при программировании выражается че- рез уже запрограммированные, непосредственно подчиненные модули, а при тестировании используются уже отлаженные модули. Однако со- временная технология не рекомендует такой порядок разработки про- граммной системы [17].
Во-первых, для программирования какого-либо модуля можно обой- тись без текстов используемых им модулей, достаточно чтобы исполь- зуемый модуль был специфицирован, а для его тестирования можно заменить используемые модули имитаторами. Во-вторых, каждая про- грамма в какой-то степени подчиняется некоторым внутренним для нее, но глобальной для ее модулей информацией (принципам реализации, предположениям, структурам данных и т. п.), что определяет ее концеп- туальную целостность. Такая информация формируется в процессе раз- работки программной системы. При восходящей разработке для моду- лей нижних уровней такая информация еще не ясна в полном объеме., поэтому эти модули приходится перепрограммировать. В-третьих, при восходящем тестировании для каждого модуля (кроме головного) при- ходится создавать ведущую программу, которая должна подготовить