Как стать автором
Обновить

Cohesion и Coupling: отличия

Время на прочтение6 мин
Количество просмотров54K
Автор оригинала: Vladimir Khorikov

Эта статья является переводом материала «Cohesion and Coupling: the difference». 

Возможно, вы слышали рекомендацию, в которой говорится, что мы должны стремиться к достижению low coupling (низкой связанности) и high cohesion (высокого сцепления) при работе над кодовой базой. В этой статье хотелось бы обсудить, что на самом деле означает эта рекомендация, и взглянуть на некоторые примеры кода, иллюстрирующие ее. И также хочется провести границу между этими двумя идеями и показать различия в них.

Далее в статье я не буду переводить такие термины, как coupling и cohesion, чтобы не возникло недоразумений.

Cohesion и Coupling: отличия

В то время как coupling довольно интуитивное понятие (почти ни у кого нет трудностей с ним), тогда как cohesion труднее понять. Более того, различия между ними часто кажутся неясными. Это неудивительно: идеи, лежащие в основе этих терминов, схожи. Тем не менее, они действительно отличаются.

Cohesion представляет собой степень, в которой часть кодовой базы образует логически единую атомарную единицу (юнит).

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

Блок (юнит) здесь необязательно является классом. Это может быть метод, класс, группа классов или даже модуль: понятие cohesion (а также coupling) применимо на разных уровнях. 

С другой стороны, coupling представляет собой степень взаимосвязи между блоками. Другими словами, это количество соединений между двумя или более блоками. Чем меньше число, тем ниже coupling.

По сути, высокий cohesion означает хранение связанных друг с другом частей кода в одном месте. В то же время низкий coupling заключается в максимально возможном разделении несвязанных частей кодовой базы.

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

Это означает, что в отличие от такого показателя, как цикломатическая сложность , степень cohesion  и coupling не может быть измерена напрямую. Это сильно зависит от семантики кода.

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

Этот принцип имеет прямое отношение к другому принципу: разделение проблем (Separation of Concerns). Эти две рекомендации очень похожи с точки зрения лучших практик, которые они предлагают.

Типы кода с точки зрения cohesion и coupling

Помимо кода, который одновременно имеет высокий cohesive и слабый coupling, существует, по крайней мере, еще три типа:

Давайте рассмотрим их.

1. Идеальным является код, который следует рекомендациям. У него слабый coupling и высокий cohesive. Мы можем проиллюстрировать такой код следующим образом:

На рисунке выше круги одного цвета представляют части кодовой базы, связанные друг с другом.

2. God Object (божественный объект) является результатом введения высокого cohesion и высокого coupling. Это анти-паттерн в основном означает один фрагмент кода, который выполняет всю работу сразу:

Другое название такого кода - Big Ball of Mud (Большой шар/ком грязи).

3. Третий тип имеет место, когда границы между различными классами или модулями выбраны плохо:

В отличие от God Object, код этого типа имеет границы. Проблема здесь в том, что они выбраны неправильно и часто не отражают фактическую семантику домена. Такой код довольно часто нарушает Single Responsibility Principle (Принцип единой ответственности).

4. Деструктивная развязка (decoupling)– самый интересный случай. Это может происходить, когда программист пытается так сильно развязать кодовую базу, что код полностью теряет фокус:

Последний тип заслуживает более подробного обсуждения.

Сohesion и Сoupling: подводные камни

Часто, когда разработчик пытается реализовать рекомендации по низкому coupling, высокому cohesion, он или она прикладывает слишком много усилий к реализации первой рекомендации (низкий coupling) и полностью забывает о другой. Это приводит к ситуации, когда код действительно разделен (decoupled), но в то же время не имеет четкой направленности. Его части настолько отделены друг от друга, что становится трудно или даже невозможно понять их значение. Эта ситуация называется деструктивной развязкой (destructive decoupling).

Давайте рассмотрим пример:

public class Order
{
    public Order(IOrderLineFactory factory, IOrderPriceCalculator calculator)
    {
        _factory = factory;
        _calculator = calculator;
    }
 
    public decimal Amount
    {
        get { return _calculator.CalculateAmount(_lines); }
    }
 
    public void AddLine(IProduct product, decimal price)
    {
        _lines.Add(_factory.CreateOrderLine(product, price));
    }
}

Этот код является результатом деструктивной развязки. Вы можете видеть, что, с одной стороны, класс Order полностью отделен от Product и даже от OrderLine. Он делегирует логику расчета специальному интерфейсу IOrderPriceCalculator; создание OrderLine выполняется фабрикой.

В то же время этот код совершенно бессвязный (incohesive). Классы, семантика которых тесно связана, теперь отделены друг от друга. Это довольно простой пример, поэтому скорее всего вы понимаете, что здесь происходит, но представьте, как сложно было бы понять такой код, описывающий какую-то незнакомую модель предметной области. В большинстве случаев отсутствие согласованности делает код нечитаемым.

Деструктивная развязка часто идет рука об руку с подходом «интерфейсы повсюду». То есть соблазн заменить каждый конкретный класс интерфейсом, даже если этот интерфейс не представляет собой абстракцию.

Итак, как бы мы переписали приведенный выше код? Как-то так:

public class Order
{
    public decimal Amount
    {
        get { return _lines.Sum(x => x.Price); }
    }
 
    public void AddLine(Product product, decimal amount)
    {
        _lines.Add(new OrderLine(product, amount));
    }
}

Таким образом, мы восстановили связи между Order, OrderLine и Product. Этот код является кратким и цельным (cohesive).  

Тут я немного не согласен с автором оригинала. Но скорее всего он просто не стал заморачиваться, так как это простой пример кода. Однако я все же хочу указать на этот спорный момент. Поскольку Order и Product могут быть разными агрегатами или даже находится в разных сервисах (микросервисах, если проект достаточно большой), то во время добавления OrderLine (метод AddLine) лучше не передавать объект product, а использовать его id.

Важно понимать связь между cohesion и coupling. Невозможно полностью разделить кодовую базу без нарушения ее согласованности. Точно так же невозможно создать полностью цельный (cohesive) код без введения ненужного coupling, но такое отношение встречается редко, потому что, в отличие от cohesion, концепция coupling более или менее интуитивно понятна.

Баланс между ними является ключом к созданию кодовой базы с высоким (но не полностью) cohesion и слабым coupling (но не полностью развязанным).

Сohesion и Сoupling на разных уровнях

Как упоминалось ранее, cohesion и coupling могут применяться на разных уровнях. Уровень класса наиболее очевиден, но он не единственный. Примером здесь может служить структура папок внутри проекта:

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

Он попадает в 3-ю категорию на нашей диаграмме: плохо выбранные границы. Хотя внутренние компоненты проекта действительно слабо связаны, их границы не отражают их семантику.

Структура проекта с высоким cohesion (и слабым coupling) была бы следующая:

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

Сohesion и SRP

Понятие cohesion похоже на Принцип единой ответственности. SRP утверждает, что класс должен нести единую ответственность (единую причину для изменения), что аналогично тому, что делает код с высоким cohesion.

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

Резюме

Давайте подведем итоги:

  • Cohesion представляет собой степень, в которой часть кодовой базы образует логически единую атомарную единицу.

  • Coupling представляет собой степень, в которой один блок независим от других.

  • Невозможно добиться полного разделения (decoupling) без нарушения целостности (cohesion), и наоборот.

  • Старайтесь придерживаться принципа "high cohesion и low coupling" на всех уровнях вашей кодовой базы.

  • Не попадайтесь в ловушку деструктивной развязки.

Теги:
Хабы:
+19
Комментарии2

Публикации

Изменить настройки темы

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн