Базовыми строительными блоками АСУП при использовании структурного подхода являются модули. Все виды модулей в любом языке программирования имеют ряд общих свойств, из которых существенны при структурном проектировании перечисленные ниже:
1) Модуль состоит из множества операторов языка программирования, записанных последовательно.
2) Модуль имеет имя, по которому к нему можно ссылаться как к единому фрагменту.
3) Модуль может принимать и/или передавать данные как параметры в вызывающей последовательности или связывать данные через фиксированные ячейки или общие области. При структурном проектировании выполняется два вида работ:
• проектирование архитектуры АСУП, включающее разработку структуры и интерфейсов ее компонент (автоматизированных рабочих мест), согласование функций и технических требований к компонентам, определение информационных потоков между основными компонентами, связей между ними и внешними объектами;
• детальное проектирование, включающее разработку спецификаций каждой компоненты, разработку требований к тестам и плана интеграции компонент, а также построение моделей иерархии программных модулей и межмодульных взаимодействий и проектирование внутренней структуры модулей.
При этом происходит расширение модели требований:
•за счет уточнения содержащихся в ней функциональных, информационных и, возможно, событийных моделей требований, построенных с помощью соответствующих средств структурного анализа;
•за счет построения моделей автоматизированных рабочих мест, включающих подсхемы информационной модели и функциональные модели, ориентированные на эти подсхемы вплоть до идентификации конкретных сущностей информационной модели;
|
• за счет построения моделей межмодульных и внутримодульных взаимодействий с использованием техники структурных карт.
В структурном подходе для целей проектирования модулей используются техники структурных карт (схем), демонстрирующие, каким образом системные требования будут отражаться комбинацией программных структур. При этом наиболее часто применяются две техники: структурные карты Константайна (Constantine), предназначенные для описания отношений между модулями, и структурные карты Джексона (Jackson), предназначенные для описания внутренней структуры модулей.
Структурные карты Константайна являются моделью отношений иерархии между программными модулями. Узлы структурных карт соответствуют модулям и областям данных, потоки изображают межмодульные вызовы (в том числе циклические, условные и параллельные). Межмодульные связи поданным и управлению также моделируются специальными узлами, привязанными к потокам, стрелками указываются направления потоков и связей. Фундаментальные элементы структурных карт Константайна стандартизованы IBM, ISO и ANSI.
Техника структурных карт Джексона восходит к методологии структурного программирования Джексона и заключается в продуцировании диаграмм и схем для графического иллюстрирования внутримодульных (а иногда и межмодульных) связей и документирования проекта архитектуры АСУП. При этом структурные карты Джексона позволяют осуществлять проектирование нижнего уровня АСУП и на этом этапе близки к традиционным блок-схемам, моделирующим последовательное, параллельное, условное и итерационное исполнение их узлов.
|
Структурные карты сами по себе ничего не говорят о качестве модели реализации, так как являются всего лишь инструментом моделирования структуры системы и взаимосвязей ее компонент, однако существуют критерии, позволяющие оценить это качество.
Один из фундаментальных принципов структурного проектирования заключается в том, что большая система должна быть расчленена на обозримые модули. Это расчленение должно быть выполнено таким образом, чтобы модули были как можно более независимы (критерий сцепления — coupling), и чтобы каждый модуль выполнял единственную (связанную с общей задачей) функцию (критерий связности — cohesion).
Таким образом, одним из способов оценки качества модели реализации является анализ сцепления модулей. Сцепление — это мера взаимозависимости модулей. В хорошем проекте сцепления должны быть минимизированы, т. е. модули должны быть слабозависимыми (независимыми) настолько, насколько это возможно. Слабое сцепление между модулями служит признаком хорошо спроектированной системы по следующим причинам:
•уменьшается количество соединений между двумя модулями, что приводит к уменьшению вероятности появления «волнового эффекта» (ошибка в одном модуле влияет на работу других модулей);
• минимизируется риск появления «эффекта ряби» (внесение изменений, например при исправлении ошибки, приводит к появлению новых ошибок), так как изменение влияет на минимальное количество модулей;
|
• при сопровождении модуля отпадает необходимость беспокоиться о внутренних деталях других модулей;
•система упрощается для понимания настолько, насколько это возможно.
На практике существуют три основных типа сцепления, используемых системными проектировщиками для связи модулей: нормальное сцепление, сцепление по общей области и сцепление по содержимому. С позиций структурного проектирования эти типы являются соответственно приемлемым, неприемлемым и запрещенным.
Два модуля А и В нормально сцеплены, если: А вызываетВ;
В возвращает управление А; вся информация, передаваемая между А иВ, представляется значениями параметров при вызове.
Нормальное сцепление в свою очередь делится на три типа: сцепление поданным, сцепление по образцу, сцепление по управлению. На практике наиболее часто используется сцепление по данным.
Два модуля сцеплены поданным, если они взаимодействуют через передачу параметров и при этом каждый параметр является элементарным информационным объектом. Отметим, что в случае небольшого количества передаваемых параметров сцепление по данным обладает наилучшими характеристиками.
Два модуля сцеплены по образцу, если один посылает другому составной информационный объект, т. е. объект, имеющий внутреннюю структуру. Примером составного объекта может быть объект Данные о клиенте, включающий в себя поля: Название организации, Почтовый адрес, Номер счета и т. п.
Два модуля сцеплены по управлению, если один посылает другому информационный объект — флаг, предназначенный для управления его внутренней логикой. Существует два типа флагов — описательный и управляющий. Описательный флаг обычно описывает ситуацию, которая произошла, и именуется с использованием существительных и прилагательных: Конец файла, Введенная кредитная карта. Управляющий флаг используется для декларирования определенных действий в вызываемом модуле и именуется с использованием глаголов: Читать следующую запись, Установить в начало. В общем случае управляющие флаги усиливают сцепление и, следовательно, ухудшают качество проекта.
Как уже отмечалось, перечисленные три типа нормального сцепления в разной степени поддерживают суть модульности и являются приемлемыми в структурном проектировании. Ниже определяются два вида сцепления, которые выходят за пределы хорошей модульности.
Два модуля сцеплены по общей области, если они ссылаются к одной и той же области глобальных данных. Сцепление по общей области является плохим по следующим причинам. Во-первых, ошибка в одном модуле, использующем глобальную область, может неожиданно проявить себя в любом другом модуле, также использующем эту глобальную область, поскольку эти глобальные данные не находятся «под защитой» модуля. Во-вторых, модули, ссылающиеся к глобальным данным, обычно используют точные имена (в отличие от модулей, которые вызываются с использованием параметров). Следовательно, гибкость модулей, сцепленных глобально, намного хуже, чем гибкость нормально сцепленных модулей. В-третьих, программы с большим количеством глобальных данных чрезвычайно трудны для понимания сопровождающим программистом, поскольку сложно определить, какие данные используются отдельным модулем.
Два модуля сцеплены по содержимому, если один ссылается внутрь другого любым способом, например, если один модуль передает управление или выполняет переход в другой модуль, если один модуль ссылается или изменяет значения информационных объектов в другом модуле или если один модуль изменяет код другого модуля. Такое сцепление делает абсурдной концепцию модулей как черных ящиков, поскольку оно вынуждает один модуль знать о точном содержании и реализации другого модуля. Вообще говоря, только ассемблер позволяет проектировщикам применять данный вид сцепления.
В табл. 6 приведены сравнительные характеристики по каждому типу сцепления.
Другим критерием оценки качества разбиения системы на части является критерий связности, который контролирует, как действия в одном модуле связаны друг с другом. Фактически сцепление и связность являются двумя взаимозависимыми способами измерения расчленения системы на части: связность модуля часто определяет качество его сцепления с другими модулями.
Связность — это мера прочности соединения функциональных и информационных объектов внутри одного модуля. Размещение сильносвязанных объектов в одном и том же модуле уменьшает межмо-
Таблица б
Тип сцепления | Устойчивость к волновому эффекту | Модифицируемость | Понятность | Используемость в других системах |
По данным | * | хорошая | хорошая | хорошая |
По образцу | * | средняя | средняя | средняя |
По управлению | средняя | плохая | плохая | плохая |
По общей области | плохая | средняя | плохая | плохая |
По содержимому | плохая | плохая | плохая | плохая |
* — зависит от количества параметров интерфейса
дульные взаимосвязи и взаимовлияния. Выделяются следующие связности: функциональная, последовательная, информационная, процедурная, временная, логическая и случайная.
Функционально связный модуль содержит объекты, предназначенные для выполнения одной и только одной задачи, например: Расчет заработной платы. Считывание данных кредитной карты. Каждый из таких модулей имеет одну четко определенную цель, при его вызове выполняется только одна задача (при этом она выполняется полностью без исполнения любого другого дополнительного действия).
Последовательно связный модуль содержит объекты, охватывающие подзадачи, для которых выходные данные предыдущей подзадачи служат входными данными для последующей, например: Открыть файл— Прочитать запись— Закрыть файл.
Информационно связный модуль содержит объекты, использующие одни и те же входные или выходные данные. Предположим, что мы хотим выяснить некоторые сведения о книге, зная ее ISBN:
название книги, автора и цену. Эти три подзадачи являются связанными потому, что все они работают с одним и тем же входным информационным объектом — ISBN, который и делает этот модуль информационно связным.
Процедурно связный модуль содержит объекты, которые включены в различные (возможно, несвязные) подзадачи, в которых управление переходит от каждой подзадачи к последующей (отметим, что в последовательно связном модуле данные, а не управление, переходили от одной подзадачи к последующей).
Временно связный модуль содержит объекты, которые включены в подзадачи, связанные временем исполнения.
Логически связный модуль содержит объекты, содействующие решению одной общей подзадачи, для которой эти объекты отобраны во внешнем по отношению к модулю мире.
Таким образом, логически связный модуль содержит некоторое количество подзадач (действий) одного и того же общего вида. Чтобы его использовать, необходимо выбрать именно ту часть (части), которая требуется. Эти различные подзадачи должны обладать одним и только одним интерфейсом с внешним миром. При этом семантика каждого параметра зависит от используемой подзадачи.
Случайно связный модуль содержит объекты, соответствующие подзадачам, незначительно связанным друг с другом.
Случайно связный модуль подобен логически связному модулю, его объекты не связаны ни потоками данных, ни потоками управления. Однако подзадачи в логически связном модуле являются по крайней мере одной категории; для случайно связного модуля даже это неверно.
В табл. 7 приведены сравнительные характеристики для каждого уровня связности.
Следовательно, связность является мерой функциональной зависимости объектов (исполняемых операторов, областей данных и т.д.) внутри одного модуля. В хорошей модели связность каждого модуля является высокой (последовательность введенных выше определений уровней связности соответствует направлению от луч-
Таблица 7
СВЯЗНОСТИ | Сцепление | Модифицируемость | Понятность | Сопровож-даемость |
функциональная | хорошее | хорошая | хорошая | хорошая |
последовательная | хорошее | хорошая | близкая к хорошей | хорошая |
информационная | среднее | средняя | средняя | средняя |
процедурная | переменное | переменная | переменная | плохая |
временная | плохое | средняя | средняя | плохая |
логическая | плохое | плохая | плохая | плохая |
случайная | плохое | плохая | плохая | плохая |
шей связности к худшей). Как и сцепление, связность является одним из лучших критериев оценки качества проекта.
Очевидно, что для оценки качества проектируемой АСУП критериев сцепления и связности недостаточно. Например, если бы мы осуществляли оценку только по критерию сцепления, мы бы всегда получали системы, состоящие из одного модуля. Связность этого единственного модуля также была бы вполне приемлемой.
Поэтому кроме этих двух взаимно дополняющих друг друга критериев в структурном проектировании существует ряд других принципов, которые могут применяться для оценки и улучшения качества модели на основании структурных карт.
Рассмотрим основные принципы, позволяющие получать качественные системы.
1) Принцип факторизации. Под факторизацией понимается выделение подзадачи, реализуемой некоторым модулем, в новый самостоятельный модуль. Это может быть сделано с целью:
• уменьшения размеров модуля;
• обеспечения возможности и преимущества классического проектирования сверху вниз, позволяющего строить систему более легкую для понимания и облегчающего модификацию системы;
• устранения дублирования подзадачи, выполняемой более чем одним модулем;
• отделения собственно вычислений от управления (вызовы и принятия решения);
• обеспечения более широкой пригодности модулей для использования их в различных частях системы;
• упрощения реализации.
2) Принцип единства решения. Процесс любого решения состоит из двух частей: распознавания, какое действие выбрать, и исполнения этого действия. Поскольку эти две составляющие решения различны, информационные объекты, используемые при распознавании и исполнении, также могут существенно различаться и, следовательно, могут быть недоступны в одном модуле. Такая ситуация получила название расщепления решения. Сильное расщепление решения (хотя иногда расщепления не удается избежать) обычно является симптомом плохой организации модулей. Исполнительная часть решения должна располагаться как можно ближе к распознавательной части, чтобы распознанной информации не пришлось долго «блуждать» для того, чтобы быть обработанной.
3) Обработка ошибок. Сообщения об ошибках целесообразно формировать и визуализировать в модуле, который ошибку обнаруживает (и, следовательно, «знает», что это за ошибка). Тексты сообщений рекомендуется хранить вместе, так как при таком походе:
• легче сохранять согласованность формулировок и форматов сообщений. Представьте себе состояние пользователя, когда он получает различные сообщения для одной и той же ошибки, когда она встречается в разных частях системы;
• появляется возможность хранить тексты сообщений в отдельном файле, а не внутри кода модуля;
• легче избежать дублирования сообщений;
• облегчается модификация сообщений (включая их перевод на другой язык).
4) Принцип отсутствия памяти состояния. Когда вызванный модуль после выполнения своей функции возвращает управление вызвавшему его модулю, он «умирает», оставляя после себя лишь результат. При повторном вызове он делает свою работу так, будто он только что «родился». Модуль «не помнит», что происходило в его предыдущих жизнях. Однако существует тип модуля, который «знает» о своем прошлом благодаря так называемой памяти состояния. Память состояния — это информационный объект внутри модуля, который продолжает существовать неизменным между двумя вызовами модуля. Работа модуля с памятью состояния в общем случае непредсказуема, это означает, что хотя модуль вызывался с одинаковыми фактическими параметрами, исполняться он может по-разному, и результаты его работы при разных вызовах могут быть различными. Сопровождение такого модуля резко усложняется.
5) Инициализация и завершение. Как правило, модули инициализации и завершения трудны для сопровождения из-за их плохой (временной) связности и сильного сцепления. Общая рекомендация по решению этой проблемы — инициализацию каждой функции желательно выполнять как можно позже, а действия по завершению каждой функции должны производиться как можно раньше. И конечно, необходимо проводить инициализацию и завершение как можно ближе к тому, что инициализируется или завершается.
6) Компромисс между ограниченностью и обобщенностью. Ограниченный модуль обладает по крайней мере одной из следующих характеристик:
• выполняет излишне специфическую работу. Например, модуль, вычисляющий среднюю ежемесячную температуру для месяца продолжительностью в 30 дней, является ограниченным; на самом деле необходим модуль, который генерировал бы среднюю температуру для месяца любой продолжительности. Продолжительность месяца могла бы передаваться ему как параметр, а не быть жестко установленной внутри;
• имеет дело с ограниченными значениями данных, их типами и структурами (например, модуль, предполагающий, что человек может быть собственником только одного автомобиля);
• включает в себя условия о месте и способе его использования.
Противоположная крайность ограниченному модулю — сверхобобщенный модуль, обладающий по крайней мере одной из следующих характеристик:
• выполняет слишком завышенный объем работы, результаты которой не используются в большинстве ситуаций. Примером является модуль, формирующий расписание игр чемпионата по футболу как по григорианскому, так и по юлианскому ка-лендарю;
• имеет дело со слишком избыточными типами данных, их значениями и структурами. Например, использование числа типа REAL вместо INTEGER для того, чтобы следить за количеством болтов на складе, было бы чрезмерным обобщением;
• принимает в качестве параметров данные, которые никогда не изменятся. Так, модуль, которому передается количество дней в неделе, является определенно сверхобобщенным, а также до смешного нелепым.
7) Минимизация избыточности, то есть устранение дублирования. Если любой факт, условие или реализационное решение явно проявляются в более чем одном модуле, то усилия по сопровождению, состоящие из нахождения всех случаев этого факта и их изменения, увеличиваются. Также возникает риск, что человек, сопровождающий такую систему, забудет изменить один из дублей.
8) Нагрузка по входу и выходу. Под нагрузкой модуля по входу понимается количество непосредственных вызывающих его модулей. Соответственно, нагрузка модуля по выходу — это количество непосредственно подчиненных ему модулей. По уже упоминавшимся выше причинам нагрузка по выходу не должна превышать 6—7 модулей. Высокая нагрузка по входу требует от модуля хорошей связности.
В заключение отметим, что при использовании структурного подхода обеспечивается естественный переход от этапа анализа к этапу проектирования за счет комбинирования транзакционных и трансформационных алгоритмов преобразования модели функциональных требований (а именно иерархии диаграмм потоков данных) в структурные карты.
ЛЕКЦИЯ 12