Денис Крешихин

Денис
Крешихин

iOS-разработчик с 15+ летним опытом

Тимлид/сеньор по обстоятельствам

Интересы: swift, uikit, rxswift, oop/ood, devops, agile

2024 © Денис Крешихин

Зависимость (dependency) и сцепленность (cohesion) на примере MVC-архитектуры

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

Тем не менее, полезно представлять, какие проектные решения повышают сцепленность (что хорошо), а какие увеличивают зависимость (что плохо).

Всего существует четыре возможных варианта развития проекта:

  • низкая сцепленность — высокая зависимость,

  • низкая сцепленность — низкая зависимость,

  • высокая сцепленность — высокая зависимость,

  • высокая сцепленность — низкая зависимость.

Рассмотрим каждый из них подробнее.

Низкая сцепленость — высокая зависимость

Это классическая ситуация наблюдается в основном в проектах, которые близки к завершению своего жизненного пути. Первое что бросается в глаза, неоправданно разросшийся код, фактическое отсутсвие каких-либо архитектурных слоёв. Всё приложение представляется клубком зависимостей.

Обычно такие проекты возникают при наличии трёх обстоятельств - хорошие инвестиции, горящие сроки, несработанная комманда. Каждый новый программист попадающий в комманду должен сразу же решить какую-то узкоспециализированную задачу. Ему показывают участок кода, и в этом месте начинает возникать вся необходимая инфраструктура для реализации задачи. Т.к. предыдущие разработчики обычно писали код в том же положении, то возможность повторного использования кода - минимальна.

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

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

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

Удивительное следствие высокой зависимости - "зависимость багов". Так, устранение одного бага, может вызвать целый ряд багов, т.к. глобальные перменные имеют свойство взаимодействовать прямо или косвенно самыми неожиданными способами.

Если бы в таком проекте можно было ввести MVC и каком-то образом разделить роли программных единиц, то картина была примерно такой:

Низкая сцепленность высокая зависимость

Низкая сцепленность — низкая зависимость

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

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

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

Что бы не быть голословным рассмотрим типичный случай. Мы имеем две модели User и Product, каждая отвечает за свою область ответсвтенности. Поэтому для каждой модели создаётся контроллер и вид. При этом, даже если пользователи и продукты выводятся в схожем виде, в проекте всё равно появляются две различные сущности UserView и ProductView.

Таких моделей в проекте может быть 10-ки, соответсвтенно код в том или ином виде дублируется 10-кратно. Проект становится в какой-то момент ужасно неповоротливым, т.к. малейшее унифицированное изменение приводит к большим трудозатратам.

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

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

Низкая сцепленность низкая зависимость

Высокая сцепленность — высокая зависимость

Такие проекты довольно редки, но тем не менее составляют замечательную часть мира Open Source.

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

Отсутствие чёткой архитектуры как правило пораждает в таких проектах высокую зависимость, но вместе с тем разработчики таких проектов заинтересованы в достижении и высокой сцепленности. Т.к. малобюджетный проект не может позволить себе масштабное тестирование, или 10-кратное дублирование кода.

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

Высокая сцепленность высокая зависимость

Высокая сцепленность — низкая зависимость

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

Такие проекты обладают широким рядом преимуществ.

Наблюдаются признаки низкой зависимости — приложение легко развивать, для того что бы добавить новый функционал, не требуется копировать код, создание новой модели не влечёт автоматически создание контроллеров и вида. Легко изменить нюансы в уже реализованном функционале.

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

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

Высокая сцепленность низкая зависимость