Зависимости между компонентами

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

На основе книги Роберта Мартина «Чистая архитектура».

Ода билду, который не смог

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

«Синдром завтрашнего утра»

Оказывается, Алексей не единственный, кто задержался. Этажом выше Евгений заканчивал срочную задачу и так вышло, что провел в офисе на две чашки кофе дольше Алексея. Коммит. Сборка.

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

Выгрузка последних изменений. Сборка. Запуск…. В глазах — грусть, в руках — дебагер.

После мучительного внесения исправлений Алексей вспоминает о книге Роберта Мартина «Чистая архитектура», смутно припоминая что-то о «Синдроме завтрашнего утра» и каких-то там зависимостях между компонентам. Он открывает книгу и начинает разбираться.

Вот оно! «Синдром завтрашнего утра» — то, что прекрасно работает вечером вдруг ломается на утро из-за того, что код, от которого зависит ваш компонент, был изменен. «Будет интересно, » — подумал Алексей.

Циклические зависимости

Первое, о чем узнает Алексей — циклические зависимости. Компоненты должны представлять собой ориентированный ациклический граф:

Ориентированный ациклический граф, иллюстрация из книги «Чистая архитектура»
Ориентированный ациклический граф, иллюстрация из книги «Чистая архитектура»

Зависимости между компонентами расположены так, что с какого компонента не начни движение, обратно не вернешься. Изменение компонента Database (который нужно собрать вместе с компонентов Entitities) затронет только компонент Main. Все просто и понятно.

Циклы в зависимостях делают код более хрупким. Алексей переходит к следующей главе, где представлена та же диаграмма компонентов, но уже с циклическими зависимостями (Entities -> Authorizer):

Ориентированный граф с циклами, иллюстрация из книги «Чистая архитектура»
Ориентированный граф с циклами, иллюстрация из книги «Чистая архитектура»

Вот и всё. Изменение Database требует проверки работы с Entities, но Entities зависит от Authorizer, который зависит от Interactors. По сути Database, Entities, Authorizer, Interactors вместе становятся одним большим компонентом. Вот к чему могут привести циклические зависимости. Алексей задумался и проверил свой код. Циклы! Вот же они, ровно там, куда он вчера коммитил допивая ароматный кофе!

Алексей читает дальше и узнает, что лучший способ избавиться от циклических зависимостей — принцип инверсии зависимостей (Dependency Inversion Principle). Алексей объявляет крестовый поход за ацикличность ориентированных графов под знаменем инверсии зависимостей и перелистывает страницу.

Принцип стабильных зависимостей

Оказывается, компоненты изменяются с различной интенсивностью. UI-компоненты могут изменяться чаще, чем компоненты бизнес-логики, а те немного интенсивнее компонентов работы с базой данных. Основное правило — «зависимости между компонентами должны быть направлены в сторону устойчивости», то есть компоненты, с трудом поддающиеся изменениям не должны зависеть от часто изменяемых компонентов.

Пример неправильно организованной структуры компонентов:

Контр-пример для принципа стабильных зависимостей
Контр-пример для принципа стабильных зависимостей

Что такое стабильность? Алексей читает дальше. Стабильность — это объем работы, требуемой для внесения изменений. Компоненты, от которых зависят многие другие компоненты (например — фреймворк логирования) — очень стабильные, так как их трудно изменить. Напротив, компоненты, полагающиеся на множество других компонентов (UI) имеют высокую волатильность (изменчивость).

«Вот бы это выразить в числах!», — подумал Алексей, перевернул страницу и увидел формулу:

I: Instability = Fan Out / (Fan Out + Fan In), где
Instability — нестабильность,
Fan Out — исходящие зависимости,
Fan In — входящие зависимости.

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

Устойчивый и неустойчивый компоненты
Иллюстрация из книги «Чистая архитектура».
I(X) = 0 / (0+3) = 0
I(Y) = 3 / (3 + 0) = 1

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

Что, если нестабильный компонент оказался где-то в середине иерархии? Тогда следует применить принцип инверсии зависимостей с созданием абстрактного компонента.

Принцип стабильности абстракций

«Устойчивость компонента пропорциональная его абстрактности». «Ого, — подумал Алексей, — а у нас-то все в точности наоборот! Мы абстракции плодим для того, что чаще меняем, по инерции!». Стабильные компоненты должны быть абстрактными, чтобы оставалась возможность их расширения, нестабильные — должны быть конкретными, что облегчает их изменение. Таким образом, зависимости идут в сторону увеличения уровня абстракции, которую тоже можно измерить.

Измерение уровня абстрактности

A: Абстрактность = Na / Nc, где 
Na - число абстрактных классов в компоненте,
Nc — число классов в компоненте.

Абстрактность лежит в интервале от 0 до 1; 0 — отсутствие абстрактных классов, 1 — в компоненте нет ничего, кроме абстрактных классов.

«Здесь должна быть какая-то система», — не унимался Алексей. И действительно. Теперь, когда нам известны абстрактность и нестабильность мы можем построить график и разместить на нем все компоненты. В идеале мы увидим распределение на абстрактные/стабильные и конкретные/нестабильные (забегая вперед, — Алексей увидел совсем иную картину):

Зоны исключения, иллюстрация из книги «Чистая архитектура»
Зоны исключения, иллюстрация из книги «Чистая архитектура»
A — абстрактность, I — нестабильность

Роберт Мартин выделяет несколько областей на графике:

  • Зона боли
  • Зона бесполезности
  • Главная последовательность

Зона боли

Устойчивые, конкретные компоненты. Алексей читает дальше и в каждой строчке видит боль, особенно в примере — схема базы данных подвержена сильной изменчивости, она невероятно конкретная и от нее зависят многие компоненты. Органично встраивается в зону боли проекта Алексея. При этом и в своем продукте он нашел немало «конкретно-стабильных» компонентов, запланировав на следующей неделе провести их рефакторинг, выделив ряд абстракций.

Зона бесполезности

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

Главная последовательность

Это та линия, вокруг которой мы хотим видеть распределение компонентов. Это идеал. Осталось подсчитать удаленность компонента от идеала:

D = |A + I - 1|, где
A - абстрактность,
I — нестабильность.

D == 0 означает, что компонент на главной последовательности, D == 1 — компонент максимально далеко:

Диаграмма рассеивания компонентов, иллюстрация из  книги «Чистая архитектура»
Диаграмма рассеивания компонентов, иллюстрация из книги «Чистая архитектура»

Алексей понял всё. Среднее значение D для его продукта равно 0.8. Это серьезная проблема, которую нужно было срочно решать. Он построил диаграмму компонентов, составил список компонентов, находящихся дальше всего от главной последовательности, для каждого компонента обозначил стратегию: увеличить уровень абстрактности, использовать принцип инверсии зависимостей, снизить уровень абстрактности. Он поместил задачи на рефакторинг в очередь, отправил коллегам в календарь приглашение под заголовком «Правильные отношения между компонентами — залог долгого и успешного развития продукта», сходил за свежим кофе и устроившись поудобнее в кресле взялся за очередную задачу. Ему не терпелось применить новые знания на практике.

Другие статьи по теме:

Share

Добавить комментарий