Оптимизируйте согласованность и взаимосвязь, чтобы упростить сопровождение кода.
«На уровне программной архитектуры сложность проблемы снижается за счет разделения системы на подсистемы. Людям легче понять несколько простых фрагментов информации, чем одну сложную ».
- Стив МакКоннелл
Архитектура программного обеспечения часто рассматривается как произведение искусства, и, честно говоря, как и другие дисциплины в этой области, она охватывает творчество, науку и стиль.
Это похоже на архитектуру зданий? Давайте посмотрим.
Представьте себе дизайн небоскреба; Я предполагаю, что вам нужен дизайн, который включает в себя причудливый вход, дружелюбный вестибюль, безопасные и пригодные для проживания пространства. Кроме того, здание должно выдерживать свой вес и противостоять ветру и землетрясениям.
Точно так же при разработке программного обеспечения вам понадобятся интуитивно понятные интерфейсы, чистые и надежные компоненты кода. Дизайн также должен быть осуществимым, модульным и безопасным, не допускающим сбоев или утечек, и, в конечном итоге, не допускать регрессий при редактировании одного из его компонентов.
Я бы сказал, что есть некоторые сходства, и даже если немного пугает думать о своей кодовой базе как о небоскребе, не беспокойтесь! За созданием хорошего программного обеспечения стоят правила - даже научные.
В сегодняшней статье мы рассмотрим двухэтапный процесс улучшения ремонтопригодности.
Чтобы гарантировать, что рефакторинг одного модуля в будущем не повлияет на остальные модули, вам нужно будет предвидеть возможные изменения кода, что совсем не простая задача.
Как на помощь приходят сплоченность и сцепление? Читай дальше что бы узнать.
Давайте познакомимся с условиями
Прежде чем приступить к процессу, очень важно понять упомянутые термины согласованность и взаимосвязь. Итак, давайте сделаем несколько секунд краткое введение.
Предположим, у вас есть модульная конструкция с функциональным разделением на дискретные масштабируемые и повторно используемые модули.
Сплоченность - это показатель того, насколько все связано с целью модуля.
Связь - это степень зависимости модулей от других модулей или внешних ресурсов.
Давайте погрузимся.
Шаг 1. Максимизируйте сплоченность
Сплоченность описывает отношения внутри модуля, и важно убедиться, что каждый элемент внутри данного модуля связан с его назначением.
Давайте посмотрим на пример класса с высокой сплоченностью.
Класс «Дом» должен содержать дверь, массив окон и массив комнат.
House ----- Door _door; Array<Window> _windows; Array<Room> _rooms;
Давайте добавим адрес в наш класс «Дом».
House ----- Door _door; Array<Window> _windows; Array<Room> _rooms; String _address;
Теперь возникнет вопрос. Какие элементы _address
? И как клиенты могут его проверить на правильность.
Предположим, что _address
состоит из названия улицы, города и почтового индекса.
Первый вариант:
House ----- Door _door; Array<Window> _windows; Array<Room> _rooms; String _address; ----- boolean ValidateAdress();
В этом варианте House
следует реализовать функцию ValidateAdress()
, которая на самом деле не связана с назначением House
как класса. Есть вариант получше?
Чтобы добиться максимальной согласованности, вы должны добавить новый класс для Address
.
Второй вариант:
Address ------- String _street; String _city; Long _postalCode; ------- boolean ValidateAdress();
В результате получится:
House ----- Door _door; Array<Window> _windows; Array<Room> _rooms; Address _address;
В этом варианте House
не будет содержать никакой логики, не связанной с его назначением. Это все. Достигнута максимальная сплоченность.
Шаг 2: минимизируйте сцепление
В то время как сплоченность показывает отношения внутри модуля, связь показывает отношения между модулями. Было бы лучше, если бы вы попытались максимально уменьшить зависимости между модулями.
Теперь давайте посмотрим, как минимизировать взаимосвязь.
Давайте добавим Garden
к разработанному нами House
.
Garden ------ Array<Tree> _trees; Grass _grass;
Теперь давайте посмотрим, как мы можем поддерживать эти функции, не вызывая связи между классами.
void WaterTheGarden(); void CutATree(); void CleanTheHouse(); void FaxTheAddress();
Один из вариантов - House
будет содержать внутри Garden
и Address
(композиция). В результате вызов любой из функций должен выполняться House
.
Первый вариант:
House ----- Door _door; Array<Window> _windows; Array<Room> _rooms; Address _address; Garden _garden; ----- void WaterTheGarden(); void CutATree(); void CleanTheHouse(); void FaxTheAddress();
Связь содержимого между House
и Garden
может возникнуть, если CutATree()
реализовано внутри House
для удаления tree
из массива Tree
inside Garden
.
Лучшим подходом было бы создание нового класса с именем Property
, который будет содержать House
, Garden
и Address
.
Второй вариант:
Property ----- House _house; Address _address; Garden _garden; ----- void WaterTheGarden(); void CutATree(); void CleanTheHouse(); void PrintTheAddress();
Используя последний подход. Кроме того, реализация всех вышеперечисленных функций внутри соответствующих классов снизит связь до минимума.
В заключение, глядя на окончательный проект, который обладает как высокой связностью, так и слабой связью, становится ясно, что редактирование или рефакторинг одного из классов, House
, Address
или Garden
, не повлияет на два других класса.
Ортогональность - это более сложный математический термин, описывающий этот подход к разработке программного обеспечения .
Если вы не большой поклонник математики, я предполагаю, что вы можете пропустить следующую часть и сразу перейти к выводу.
Далее следует математическое доказательство того, почему ортогональность делает свое дело.
Что такое ортогональность?
Ортогональность - это обобщение перпендикулярности - математического термина, указывающего, равняется ли скалярное произведение двух векторов 0.
Для большей ясности: в геометрии два евклидовых вектора ортогональны, если они перпендикулярны, то есть образуют прямой угол (90 градусов).
Что такое скалярный продукт?
Величина вектора a
обозначается ‖a‖
.
‖a‖ = ∑ an² = a1² + a2² + ,..., an²
Скалярное произведение двух векторов a
и b
определяется как
a·b = ‖a‖ * ‖b‖ * cos(θ)
θ
это угол между a
и b
.
Два вектора ортогональны, если их скалярное произведение равно 0.
a·b = ‖a‖ * ‖b‖ * cos(θ) = 0
С
cos(90) = 0
Если два вектора образуют прямой угол (90 градусов), их скалярное произведение равно 0. Независимо от того, какие значения мы присваиваем a
и b
или какова их величина.
Вы можете лучше понять точечный продукт, попробовав этот замечательный инструмент визуализации точечного продукта:
Теперь наконец-то можно объяснить ортогональность программными терминами. Представьте это таким образом.
Предположим, мы думаем о двух векторах a
и b
как о module-a
и module-b
. И скалярное произведение как объем работы, необходимой в module-a
для редактирования module-b
.
Затем, если предположить, что θ
- это степень, в которой module-a
зависит от module-b
, где θ=0
- случай, они очень зависимы (низкая когезия и высокая связь), а θ=90
- это случай, когда они вообще не зависят (высокая когезия и низкая связь ).
Мы можем узнать, что если два модуля ортогональны ( не зависят ), объем работы (скалярный продукт), необходимый в module-a
в результате редактирования module-b
, равен 0.
Насколько это мощно ?!
Вывод
Код - это постоянно развивающийся живой организм; И подлежит постоянным изменениям и пересмотрам.
Помимо обычных вещей, таких как документация, простота и согласованность - одна из вещей, которая отличает «неподдерживаемый» от «обслуживаемого» кода, - это отсутствие ортогональности, то есть низкая связность и высокая взаимосвязь.
Попробуйте этот процесс, работая над своим следующим проектом; ортогональность поможет ему жить долго и процветать.
«… При правильном дизайне функции будут дешевыми. Этот подход труден, но продолжает приносить успех ».
- Деннис Ричи
Ресурсы: