Связанность (программирование).
Степень взаимозависимости между программными модулями
В программной инженерии связанность — это степень взаимозависимости между программными модулями, мера того, насколько тесно связаны две подпрограммы или модуля, и сила взаимосвязей между модулями. Связанность не бинарна, а многомерна.
Связанность обычно противопоставляется связности. Низкая связанность часто коррелирует с высокой связностью, и наоборот. Низкая связанность часто считается признаком хорошо структурированной компьютерной системы и хорошего проектирования, и в сочетании с высокой связностью способствует достижению общих целей — высокой читаемости и сопровождаемости.
История
Метрики качества программного обеспечения — связанность и связность — были предложены Ларри Константайном в конце 1960-х годов в рамках структурного проектирования на основе характеристик «хороших» практик программирования, снижавших затраты на сопровождение и модификацию. Структурное проектирование, включая связность и связанность, было опубликовано в статье Stevens, Myers & Constantine (1974) и книге Yourdon & Constantine (1979), и введённые в последней термины впоследствии стали стандартными.
Связанность и связность
Связанность и связность — термины, которые очень часто встречаются вместе. Связанность относится к взаимозависимостям между модулями, тогда как связность описывает, насколько связаны между собой функции внутри одного модуля. Низкая связность означает, что данный модуль выполняет задачи, слабо связанные друг с другом, и поэтому может создавать проблемы по мере увеличения размера модуля.
Степень
Связанность может быть «низкой» (также «слабой» и «нестрогой») или «высокой» (также «тесной» и «сильной»). Некоторые типы связанности, в порядке убывания от наивысшей к наименьшей, перечислены ниже:
Процедурное программирование
Под модулем здесь понимается подпрограмма любого рода, то есть набор из одного или нескольких операторов, имеющий имя и, желательно, собственный набор имён переменных.
- Связанность по содержимому (высокая)
- Связанность по содержимому возникает, когда один модуль использует код другого модуля, например ветвление. Это нарушает сокрытие информации — базовую концепцию проектирования программного обеспечения.
- Общая связанность
- Общая связанность возникает, когда несколько модулей имеют доступ к одним и тем же глобальным данным. Однако при внесении изменений это может приводить к неконтролируемому распространению ошибок и непредвиденным побочным эффектам.
- Внешняя связанность
- Внешняя связанность возникает, когда два модуля совместно используют навязанный извне формат данных, протокол связи или интерфейс устройства. По сути, это связано со взаимодействием с внешними инструментами и устройствами.
- Связанность по управлению
- Связанность по управлению — это когда один модуль управляет ходом выполнения другого, передавая ему информацию о том, что делать (например, передавая флаг с указанием действия).
- Связанность по образцу (связанность по структуре данных)
- Связанность по образцу возникает, когда модули совместно используют составную структуру данных и применяют лишь её части, возможно разные (например, передача целой записи в функцию, которой нужно лишь одно её поле).
- В этой ситуации изменение поля, которое модулю не нужно, может привести к изменению того, как модуль читает запись. Чтобы проиллюстрировать концепцию связанности по образцу, рассмотрим сценарий с компонентом
UserProfile. Этот компонент спроектирован так, чтобы в ответ на запросы возвращать всю информацию профиля пользователя, даже когда потребителям нужен лишь определённый атрибут. Такая практика является примером связанности по образцу, которая может приводить к существенным проблемам с пропускной способностью, особенно при масштабировании. При изменении любого атрибута внутри компонентаUserProfileвсе взаимодействующие с ним потребители могут потребовать тестирования, даже если они не используют изменённый атрибут. - Связанность по данным
- Связанность по данным возникает, когда модули обмениваются данными, например через параметры. Каждый элемент данных является элементарным, и обмениваются только этими данными (например, передача целого числа в функцию, вычисляющую квадратный корень).
Объектно-ориентированное программирование
- Связанность подклассов
- Описывает отношение между потомком и его родителем. Потомок связан со своим родителем, но родитель не связан с потомком.
- Временная связанность
- Это когда два действия объединяются в один модуль только потому, что они выполняются в одно и то же время.
В недавних работах исследовались и использовались различные другие концепции связанности в качестве показателей для разных принципов модуляризации, применяемых на практике.
Динамическая связанность
Цель определения и измерения этого типа связанности — обеспечить оценку программной системы во время выполнения. Утверждается, что статические метрики связанности теряют точность при интенсивном использовании динамического связывания или наследования. В попытке решить эту проблему были учтены меры динамической связанности.
Семантическая связанность
Этот вид метрики связанности учитывает концептуальное сходство между программными сущностями, используя, например, комментарии и идентификаторы и опираясь на такие методы, как латентно-семантическое индексирование (LSI).
Логическая связанность
Анализ логической связанности (или эволюционной связанности, или связанности по изменениям) использует историю выпусков программной системы для поиска паттернов изменений среди модулей или классов: например, сущностей, которые, вероятно, будут изменяться вместе, или последовательностей изменений (за изменением класса A всегда следует изменение класса B).
Измерения связанности
По мнению Грегора Хопе, связанность многомерна:
- Зависимость от технологии
- Зависимость от местоположения
- Зависимость от топологии
- Зависимость от формата и типа данных
- Семантическая зависимость
- Зависимость от диалога
- Зависимость от порядка
- Временная зависимость
Недостатки сильной связанности
Сильно связанные системы, как правило, обладают следующими характеристиками разработки, которые часто рассматриваются как недостатки:
- Изменение в одном модуле обычно вызывает эффект домино — каскад изменений в других модулях.
- Сборка модулей может требовать больше усилий и/или времени из-за возросшей межмодульной зависимости.
- Конкретный модуль может быть труднее повторно использовать и/или тестировать, поскольку приходится включать зависимые модули.
Проблемы производительности
Независимо от того, является ли система слабо или сильно связанной, её производительность часто снижается из-за создания, передачи, преобразования (например, маршалинга) сообщений и параметров, а также интерпретации сообщений (которые могут быть ссылкой на строку, массив или структуру данных), что требует меньше накладных расходов, чем создание сложного сообщения, такого как сообщение SOAP. Более длинные сообщения требуют больше ресурсов процессора и памяти для формирования. Для оптимизации производительности во время выполнения длину сообщения следует минимизировать, а смысловую нагрузку сообщения — максимизировать.
- Накладные расходы на передачу сообщений и производительность
- Поскольку сообщение должно передаваться целиком, чтобы сохранить свой полный смысл, передачу сообщений необходимо оптимизировать. Более длинные сообщения требуют больше ресурсов процессора и памяти для передачи и приёма. Кроме того, при необходимости получатели должны собрать сообщение в его исходное состояние, чтобы полностью его принять. Следовательно, для оптимизации производительности во время выполнения длину сообщения следует минимизировать, а смысловую нагрузку — максимизировать.
- Накладные расходы на преобразование сообщений и производительность
- Протоколы сообщений и сами сообщения часто содержат дополнительную информацию (то есть сведения о пакете, структуре, определениях и языке). Поэтому получателю часто приходится преобразовывать сообщение в более очищенную форму, удаляя лишние символы и сведения о структуре и/или преобразуя значения из одного типа в другой. Любое преобразование увеличивает накладные расходы на процессор и/или память. Для оптимизации производительности во время выполнения форму и содержание сообщения следует сокращать и очищать, чтобы максимизировать его смысл и уменьшить объём преобразований.
- Накладные расходы на интерпретацию сообщений и производительность
- Все сообщения должны интерпретироваться получателем. Простые сообщения, такие как целые числа, могут не требовать дополнительной обработки для интерпретации. Однако сложные сообщения, такие как сообщения SOAP, требуют синтаксического анализатора и преобразователя строк, чтобы отобразить заложенный в них смысл. Для оптимизации производительности во время выполнения сообщения следует очищать и сокращать, чтобы минимизировать накладные расходы на интерпретацию.
Решения
Один из подходов к снижению связанности — функциональное проектирование, которое стремится ограничить обязанности модулей в соответствии с функциональностью. Связанность между двумя классами A и B возрастает, если:
Aимеет атрибут типаB(ссылающийся на него).Aобращается к услугам объектаB.Aимеет метод, ссылающийся наB(через возвращаемый тип или параметр).Aявляется подклассом классаB(или реализует его).
Низкая связанность подразумевает отношение, при котором один модуль взаимодействует с другим через простой и стабильный интерфейс и не должен заботиться о внутренней реализации другого модуля (см. сокрытие информации).
Системы, такие как CORBA или COM, позволяют объектам взаимодействовать друг с другом, не зная ничего о реализации другого объекта. Обе эти системы даже позволяют объектам взаимодействовать с объектами, написанными на других языках.
Связанность и коннасценция
Связанность описывает степень и природу зависимости между программными компонентами, акцентируя внимание на том, что они разделяют (например, данные, поток управления, технологию), и насколько тесно они связаны. Она оценивает два ключевых измерения: силу, которая измеряет, насколько трудно изменить зависимость, и область видимости (или видимость), которая показывает, насколько широко зависимость раскрыта между модулями или границами. Традиционные типы связанности обычно включают связанность по содержимому, общую связанность, связанность по управлению, связанность по образцу, внешнюю связанность и связанность по данным.
Коннасценция, введённая Мейлиром Пейдж-Джонсом, предоставляет систематическую методику для анализа и измерения зависимостей связанности. Она оценивает зависимости по трём измерениям: сила, которая измеряет усилия, необходимые для рефакторинга или изменения зависимости; локальность, которая учитывает, насколько физически или логически близки зависимые компоненты в кодовой базе; и степень, которая измеряет, сколько компонентов затрагивает зависимость. Коннасценцию можно разделить на статическую (выявляемую во время компиляции) и динамическую (выявляемую во время выполнения) формы. Статическая коннасценция относится к зависимостям времени компиляции, таким как сигнатуры методов, тогда как динамическая коннасценция относится к зависимостям времени выполнения, которые могут проявляться в таких формах, как коннасценция момента времени, значений или алгоритма.
Каждая разновидность связанности может проявлять несколько типов коннасценции, какой-то конкретный тип или, в редких случаях, не проявлять ни одного — в зависимости от того, как реализована зависимость. Распространённые типы коннасценции включают коннасценцию имени, типа, позиции и смысла. Некоторые типы связанности естественным образом соответствуют конкретным типам коннасценции; например, связанность по данным часто включает коннасценцию имени или типа. Однако не каждое сочетание связанности и коннасценции имеет практический смысл. Зависимости, опирающиеся на порядок параметров в сигнатуре метода, демонстрируют коннасценцию позиции, которая хрупка и трудна для рефакторинга, поскольку изменение порядка параметров нарушает интерфейс. Напротив, коннасценция имени, которая опирается на имена полей или параметров, как правило, более устойчива к изменениям. Сами типы коннасценции образуют естественную иерархию по силе, при этом коннасценция имени обычно считается слабее, чем коннасценция смысла.
Зависимости, пересекающие границы модулей или распределённых систем, обычно имеют более высокие затраты на координацию, что увеличивает сложность рефакторинга и распространения изменений через отдалённые границы. Современные практики, такие как внедрение зависимостей и программирование на основе интерфейсов, часто применяются для снижения силы связанности и улучшения сопровождаемости зависимостей.
В то время как связанность определяет, что разделяется между компонентами, коннасценция оценивает, как ведут себя эти зависимости, как распространяются изменения и насколько трудно их рефакторить. Сила, локальность и степень взаимосвязаны; зависимости с высокой силой, широкой областью видимости и пересекающие отдалённые границы значительно труднее рефакторить и сопровождать. В совокупности связанность даёт высокоуровневый обзор отношений зависимости, тогда как коннасценция предлагает детализированную методику для анализа силы, локальности, степени и устойчивости зависимости к изменениям, поддерживая проектирование сопровождаемых и надёжных систем.
Связанность модулей
В программной инженерии связанность описывает один из вариантов метрик, связанных с этой концепцией.
Для связанности по потоку данных и управления:
- : число входных параметров данных
- : число входных управляющих параметров
- : число выходных параметров данных
- : число выходных управляющих параметров
Для глобальной связанности:
- : число глобальных переменных, используемых как данные
- : число глобальных переменных, используемых как управление
Для связанности с окружением:
- : число вызываемых модулей (fan-out)
- : число модулей, вызывающих рассматриваемый модуль (fan-in)
Coupling(C) даёт тем большее значение, чем сильнее связан модуль. Это число варьируется примерно от 0.67 (низкая связанность) до 1.0 (высокая связанность)
Например, если модуль имеет только один входной и один выходной параметр данных
Если модуль имеет 5 входных и выходных параметров данных, столько же управляющих параметров и обращается к 10 элементам глобальных данных, с fan-in, равным 3, и fan-out, равным 4,