Сцепление модулей
Сцепление модулей является мерой взаимозависимости модулей по данным и определяется как способом передачи данных между модулями, так и свойствами передаваемых данных.
Практика показала, что чем выше степень независимости модулей, тем
легче разобраться в отдельном модуле и всей программе и, соответственно, легче тестировать, отлаживать и модифицировать как модуль, так и программу в целом;
меньше вероятность появления новых ошибок при исправлении старых или внесении изменений в программу, т.е. вероятность появления “волнового” эффекта;
проще организовать разработку программного обеспечения группой программистов, легче управлять процессом разработки и сопровождать такое ПО.
Для достижения минимальной сложности программного комплекса необходимо добиться такого сопряжения между модулями, чтобы все данные передовались между ними в форме явных и простых параметров.
Виды сцеплений охарактеризуем в порядке уменьшения сцепления.
Сцепление по содержимому – модуль ссылается на данные (содержимое) другого модуля. По сути вызывающий модуль обращаясь к внутренним компонентам вызываемого модуля может не только передавать управления, но и изменять внутренние данные вызываемого модуля или сами коды. В этом варианте вызываемый модуль не является блоком (“черным ящиком”) его содержимое должно учитываться при разработке вызывающего модуля.
Большинство языков программирования высокого уровня делает такое сцепление практически невозможным, однако для языков низкого уровня, например, Ассемблера, такой вид сцепления остается возможным.
2. Сцепление по общей области – модули ссылаются на одну и ту же глобальную структуру данных. При этом основная проблема та, что имена глобальных переменных связывают модули на этапе их кодирования (программирования), а, следовательно, использование таких модулей в других программах или других контекстах затруднено или невозможно вообще. И, кроме того, любые изменения в структуре глобальных данных влекут проверку (тестирование) всех сцепленных по общей области модулей.
Например, функция MaxA, использующая глобальный массив А сцеплена с основной программой по общей области:
Function MaxA: integer;
Var i: word;
Begin
MaxA:= a[Low(a)];
For i:=Low(a) +1 to High(a) do
If a(i)>MaxA then MaxA:=a(i);
End;
3. Сцепление по управлению – один модуль управляет функционированием другого. При этом в вызываемый модуль передается значение управляющей переменной.
Предполагается, что вызывающий модуль “знает” логику работы вызываемого, что уменьшает их независимость. В качестве примера можно привести функцию, которая в зависимости от значения логической переменной возвращает в вызываемую программу либо значение минимального элемента массива, либо значение максимального элемента:
Function MinMax(a,b: integer; flag:boolean): integer;
Begin
If (a>b) and (flag) then MinMax:=a
Else MinMax:=b;
End;
Здесь вызывающий модуль передает в вызываемый некоторый информационный объект (флаг), предназначенный для управления внутренней логикой модуля. Таким способом часто выполняют настройку режимов работы ПО. Подобные настройки снижают наглядность взаимодействия модулей.
4. Сцепление по формату (образцу) – модули ссылаются на одну и ту же структуру данных. Если модуль А вызывает модуль В и передает ему запись анкетных данных служащего и при этом как А так и В чувствительны к изменению структуры или формата записи, то А и В сцеплены по формату. Другим примером может быть функция поиска максимального элемента в открытом массиве а:
Function MaxEl (a:array of integer): integer;
Var i: word;
Begin
MaxEl:= a[1];
For i:=2 to High(a) do
If a(i)>MaxEl then MaxEl:=a(i);
End;
Такого сцепления, по возможности, следует избегать, поскольку оно создает ненужные связи между модулями.
5. Сцепление по данным – передаваемые параметры – простые (неструктурированные) данные. При небольшом количестве передаваемых параметров этот вид сцепления модулей обеспечивает наилучшие технологические характеристики разрабатываемого ПО. Примером является функция, возвращающая из двух переданных ей целых переменных максимальное:
Function Max(a,b: integer): integer;
Begin
If a>) then Max:=a
Else Max:=b;
End;
Следует иметь в виду, что “модули с памятью”, действия которых зависят от истории вызовов (модуль меняет данные и при следующем вызове работает с другими данными, чем при предыдущем вызове), спользуют сцепление по общей области, что делает их работу в общем случае непредсказуемой. Именно этот вариант используют статические переменные С и С++.
Сцепление модулей может удовлетворять определению нескольких типов сцепления. В этом случае принято относить тип сцепления к самому жесткому типу, т.е. к меньшему по номеру из рассмотренных типов сцеплений. В таблице 2 приводятся характеристики модулей в зависимости от типа их сцепления.
Таблица 2. Характеристики сцепления модулей
|
Тип сцепления |
балл |
устойчивость к ошибкам других модулей |
Нагляд-ность (понят-ность) |
Возмож-ность изменения |
Возможность повторного использования |
|
По данным |
10 |
хорошая* |
хорошая |
хорошая |
большая |
|
По образцу |
6 |
средняя |
хорошая* |
средняя |
средняя |
|
По управлению |
4 |
средняя |
плохая |
плохая |
малая |
|
По общей области |
3 |
плохая |
плохая |
средняя |
малая |
|
По содержимому |
1 |
плохая |
плохая |
плохая |
малая |
*-зависит от количества параметров интерфейса.
При разработке модульной структуры ПО следует стремиться к усилению связей в модуле и ослаблению их взаимодействия. В некоторых случаях сцепление модулей можно уменьшить, удалив необязательные связи и структурировав необходимые связи. При этом получение идеальных по прочности и сцеплению модулей не должно быть самоцелью. Нужно понимать, какие проблемы создают неидеальные модули и не забывать описать их в документации.
В литературе /1/ предлагается для оценки приемлемости программного модуля использовать следующие характеристики:
размер модуля;
прочность модуля;
сцепление с другими модулями;
рутинность модуля (независимость от предыстории обращений к нему).
Размер модуля измеряется числом содержащихся в нем программных операторов или команд. Рекомендуется создавать программные модули размером от нескольких десятков до нескольких сотен операторов.
Прочность и сцепление как основополагающие принципы модульного программирования пояснены выше.
Рутинность интуитивно понятна и означает, что в модуле не должно быть команд, которые изменяются в зависимости от обрабатываемых данных.
Вывод: Подытожим обсуждение модульного программирования перечислением принципов Хольта:
1.Сложность взаимодействия модуля с другими модулями должна быть меньше сложности его внутренней структуры.
2.Хороший модуль снаружи проще, чем изнутри.
3.Хороший модуль проще использовать, чем построить.
Использование модульного программирования существенно упростило разработку программного обеспечения несколькими программистами. Теперь каждый из них мог разрабатывать свои модули независимо, обеспечивая взаимодействие модулей через специально оговоренные межмодульные интерфейсы. Кроме того, модули в дальнейшем без изменений можно было использовать в других разработках, что повысило производительность труда программистов.
Практика показала, что структурный подход в сочетании с модульным программированием позволяет получать достаточно надежные программы, размер которых не превышает 100 000 операторов [10]. Узким местом модульного программирования является то, что ошибка в интерфейсе при вызове подпрограммы выявляется только при выполнении программы (из-за раздельной компиляции модулей обнаружить эти ошибки раньше невозможно). При увеличении размера программы обычно возрастает сложность межмодульных интерфейсов, и с некоторого момента предусмотреть взаимовлияние отдельных частей программы становится практически невозможно. Для разработки программного обеспечения большого объема было предложено использовать объектный подход.
