Статьи

Управление зависимостями пакетов с помощью Degraph

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

Рассмотрим мрамор в космосе, то есть планету, луну или звезду. Без какого-либо взаимодействия это так скучно, как система может получить. Ничего не произошло. Если мрамор движется, он продолжает двигаться точно так же. Честно говоря, нет даже способа определить, движется ли он. Boooring.

Добавьте второй мрамор в систему и позвольте им притягивать друг друга, как земля и луна. Теперь система стала более интересной. Два объекта вращаются вокруг друг друга, если они не слишком быстрые. Несколько интересно.

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

Но какое это имеет отношение к Java? Это больше похоже на физику.

Я думаю, что разработка программного обеспечения похожа в некоторых аспектах. Полное приложение является сложным для понимания в целом. Чтобы справиться с этой сложностью, мы разделяем систему на части (классы), которые можно понять сами по себе и которые скрывают их внутреннюю сложность, поэтому, когда мы смотрим на более широкую картину, нам не нужно беспокоиться о каждой отдельной строке кода в класс, но только о классе как единое целое. На самом деле это очень похоже на то, что физики делают с системами.

Но давайте посмотрим на масштаб вещей. Основным строительным блоком программного обеспечения является строка кода. И чтобы не усложнять контроль, мы объединяем строки кода, которые работают вместе в методах. Сколько строк кода входит в один метод, варьируется, но это порядка 10 строк кода.

Далее вы собираете методы в классы. Сколько методов входит в один класс? Обычно в порядке 10 методов!

А потом? Мы объединяем 100-10000 классов в одну банку! Я надеюсь, что я не единственный, кто думает, что что-то не так.

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

Большинство команд используют пакеты, но не очень хорошо структурированно, но специально. Результат похож на попытку рассмотреть луну и солнце как часть системы, а землю как другую часть. Результат может сработать, но он, вероятно, такой же интуитивный, как планетарная модель Птолемея . Вместо этого определитесь с критериями, как вы хотите дифференцировать свои пакеты. Я лично называю их нарезками, вдохновленными статьей Оливера Гирке . Возможные нарезки в порядке важности:

  • развертываемый jar-файл, в котором должен находиться класс
  • сценарий использования / функция / часть бизнес-модели, которой принадлежит класс
  • технический уровень, к которому принадлежит класс

Пакеты, в которых это приводит, будут выглядеть следующим образом: <домен>. <Развертываемый>. <Часть домена>. <Слой>

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

Но что вы получаете от этого? Классы легче найти, но это все. Вам нужно еще одно правило, чтобы сделать это действительно стоящим: не должно быть циклических зависимостей!

Это означает, что если класс в пакете A ссылается на класс в пакете B, ни один класс в B не может ссылаться на A. Это также применяется, если ссылка является косвенной через множество других пакетов. Но этого все еще недостаточно. Срезы также должны быть свободными от циклов, поэтому, если доменная часть X ссылается на другую доменную часть Y, обратной зависимости не должно быть!

Это на самом деле наложит довольно строгие правила на ваш пакет и структуру зависимостей. Преимущество этого в том, что он становится очень гибким.

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

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

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

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

Сюрприз! Я могу порекомендовать такой замечательный инструмент: Degraph ! (Я автор, поэтому я могу быть предвзятым)

Вы можете написать тесты в JUnit следующим образом:

1
2
3
4
5
6
7
assertThat(
classpath().including("de.schauderhaft.**")
.printTo("degraphTestResult.graphml")
.withSlicing("module", "de.schauderhaft.(*).*.**")
.withSlicing("layer", "de.schauderhaft.*.(*).**"),
is(violationFree())
);

Тест проанализирует все в пути к классам, который начинается с de.schauderhaft. Классы будут разделены двумя способами: с помощью третьей части имени пакета и четвертой части имени пакета. Таким образом, имя класса de.schauderhaft.customer.persistence.HibernateCustomerRepository заканчивается в модуле customer и в персистентности слоя. И это позволит убедиться, что модули, слои и пакеты не имеют циклов.

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

JUnit

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

Также обратите внимание, что графики окрашены в основном в зеленый цвет с небольшим красным, что прекрасно подходит для сезона!