В этой статье мы поговорим о хорошо структурированном коде и арихитектуре программного обеспечения. Для многих архитектор ПО — вершина карьеры разработчика, человек, который знает все, а архитектура — нечто необъятное, что можно познать только спустя многие годы в профессии. Мы попробуем разобраться, из чего складывается хорошая программа на разных уровнях абстракций и что же из себя представляет архитектура ПО.
Говоря об уровнях абстракций, я имею ввиду следующую иерархию:
На каждом уровне цель разработчиков и архитекторов добиться легко поддерживаемого и расширяемого приложения. Именно эту цель важно приследовать при разработке ПО. Казалось бы, без правильно работающего приложения, все это бессмысленно. Однако, с хорошо структурированным кодом не будет проблемы исправить работу приложения, а вот плохо написанную рабочую программу придется переписывать с нуля в случае изменения требований. Рассмотрим принципы успешного построения каждого из уровней. В этой статье я основываюсь на книге Роберта Мартина “Чистая архитектура”.
Можно много рассуждать о монолитах, микросервисах, клиент-серверной архитектуре или других высокоуровневых понятиях, но все это может разрушиться о плохо написанный код. На этом уровне находятся такие фундаментальные задачи как выбор имен переменных, декомпозиция функций, дедупликация кода и многие другие. Качественное решение этих задач сильно упростит поддержку программного продукта.
Проблемы этого уровня называют дурными запахами кода. Приведу некоторые из таких проблем:
Дурные запахи часто возникают в течение длительной разработки. Нужно уметь замечать такие проблемы и не откладывать их исправление на более поздний срок. Избавление от дурных запахов называется рефакторингом, то есть процессом улучшения кода без изменения функциональности. Почитать про разные виды запахов кода можно почитать здесь.
Разобравшись с низкоуровневыми проблеми, можно подниматься выше и решать задачи правильной организации функций и данных в классы и сочетания этих классов друг с другом. Этот уровень определяет все, из чего состоят ваши программные модули. Здесь вам могут помочь пять основных принципов объектно-ориентированного проектирования. Вместе они составляют акроним SOLID. Рассмотрим каждый из принципов по отдельности:
Под компонентами системы я подразумеваю единицы развертывания. Например, это может быть библиотека или исполняемый файл. Из компонентов складывается программный продукт и от правильной их организации зависит стройность вашей архитектуры. Принципы построения хороших компонентов можно разбить на две группы: связность (какие классы стоит поместить?) и сочетаемость компонентов (как должны взаимодействовать друг с другом?).
Рассмотрим три принципа связности компонентов:
Перечисленные принципы несколько противоречат друг другу и преследуют разные цели. REP стремится к объединению классов для обеспечения удобства пользователя, CCP — для удобства разработки и сопровождения, а CRP призывает разделять компоненты, чтобы избежать лишних выпусков. Задача архитектора — найти золотую середину среди них.
Теперь перейдем ко второй группе принципов. Сочетаемость компонентов:
Принципы организации компонентов показывают, почему их проектирование сверху вниз — не лучшая идея. Формирование новых компонентов часто происходит не только для выделения некоторой функциональности или отделения бизнес-логики; часто нам приходится создавать компоненты для избавления от цикличных зависимостей или других проблем, которые усложняют поддержку кода. Поэтому проектировать компоненты до начала программирования и создания конкретных классов обычно не имеет смысла.
Итак, мы подобрались к самому верхнему уровню и готовы ответить на главные вопросы. Архитектура — это форма вашей системы. Она представляется набором высокоуровневых компонент, взаимосвязями между ними. А архитектор стремится поддерживать ее максимально доступной для изменений. Главное свойство хорошей архитектуры — гибкость, и добиться ее можно, придерживаясь следующей стратегии: как можно дольше иметь как можно больше вариантов.
Создание архитектуры состоит в проведении границ между программными элементами. Отделять границами нужно все, что не относится к бизнес-правилам. Это детали, выбор которых можно отложить и впоследствии без труда заменить. Например, это может быть ввод/вывод или хранилище данных. Границы могут не иметь физического представления — распологаться на уровне исходного кода. Такой вариант архитектуры называется монолитом. Также границы могут представлять из себя отдельные компоненты развертывания, локальные процессы или микросервисы.
Еще один признак хорошей архитектуры заключается в следующем: посмотрев на высокоуровневую структуру пакетов исходного кода вашего продукта, вы должны понимать, в чем суть вашего приложения. Если вместо того, чтобы увидеть, что это система документооборота или онлайн-магазин, вы видите только то, что это ASP.NET MVC или Spring приложение, то у вас проблемы. Архитектура должна отражать бизнес-правила и варианты использьвания, а фреймворки — это детали.
Все это приводит к многоуровневому варианту чистой архитектуры. На верхнем уровне находятся сущности системы. Каждая сущность представляет собой бизнес-правила предприятия — правила, которые действуют и в отсутствие электронного приложения. Ниже находятся варианты использования. Это бизнес-правила, которые относятся конкретно к вашему продукту. Далее следует уровень адаптеров интерфейсов. На нем данные из сущностей и вариантов использования преобразуются в формат удобный для внешних агентов. Это могут быть различные шлюзы, презенторы и т.д. Паттерн MVC целиком находится на этом уровне. На последнем же уровне находятся фреймворки, внешние хранилища и прочие детали.
Помимо разделения уровней границами есть еще одно правило, применимое к такой архитектуре. Зависимости в исходном коде должны быть направлены вверх, в сторону высокоуровневых политик. Это правило вытекает из тех принципов, что мы уже обсудили ранее. Такой вариант архитектуры позволит создать легко тестируемое и поддерживаемое приложение.
Written on February 6th, 2019 by Alexey Kalina