четверг, 2 февраля 2017 г.

"Контуры" сложности

Как понятие "сложность" может мигрировать из электрофизики
или почему наложение контуров сложности друг на друга создает много дополнительных проблем?

В данном посте будет затронута тема, напрямую связанная с темой предыдущего поста - борьбой с зависимостями. Но с зависимостями в более узком смысле - с зависимостями на уровне отдельных модулей.

Что такое "модуль"? Модулем обычно называют логически независимую и практически самодостаточную часть системы. Например, функционалы печати и управления БД, в совершенстве, находятся в двух обособленных друг от друга модулях. Здесь уместна аналогия с компьютерным железом - модуль ОЗУ, модуль видеокарты, сетевой карты и т.д. Аналогия хороша потому, что отчетливо демонстрирует "независимость" компонентов друг от друга. Так, любой современный модуль ОЗУ, хоть и подчиняется множеству конвенций и контрактов проектирования, принятым на уровне "железных" производителей всего мира, но, тем не менее, является относительно независимой частью, с которой все остальное оборудование работает по принятому контракту, прося(отсылка к статье, предшествующей предыдущей) ОЗУ(или любой другой похожий модуль) через шину данных об осуществлении каких-либо операций. О деталях же думает сам аппаратный модуль на своей внутренней кухне.

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

Итак, что такое "контур сложности"? Предположим, у нас имеется некоторая сложная система, занимающаяся анализом вредоносного траффика.
Вот ее часть:


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

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

В процессе дальнейшего развития системы может произойти следующее:


или так:


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


Возникает логичный вопрос: каким размером должен обладать идеальный модуль(сколько атомарных классов)? К сожалению, я не в силах дать ответ на этот вопрос, потому что его нет. Все зависит от того, какого уровня абстракции в вашей системе принято называть атомарными. Если они слабо похожи на абстракции и с ними нельзя работать как с абстракциями(просить что-либо сделать), то единым модулем вскоре может стать и вся система! Что плохо. 

Как с этим бороться? Ответ на этот вопрос уже частично присутствует в предыдущем посте, но лучше повториться. 

Ответ: следует использовать интерфейсное(абстрактное) проектирование, чтобы модули, зависимостей между которыми не устранить, превращались в "водопроводные узлы".

Иллюстрация исправления ситуации с зависимостями выше:


Теперь TrafficTable зависит от абстракции AbstractTraffic, а элемент ConcreteTraffic из другого модуля реализует эту абстракцию так, как удобно его внутренней кухне. В итоге все довольны.
Зависимый модуль(нижний) зависит от контракта, а реализующий(верхний) - реализует этот контракт. О контрактном программировании мы еще как-нибудь поговорим.

Теперь нижний модуль выглядит как деталь, готовая к использованию(встраиванию):



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


P.S Важно! Лично у меня описанный  подход получалось применять в полной мере только тогда, когда все тонкости учитывались с самого начала, т.е на этапе разработки архитектуры системы. Иными словами, взять и внедрить его в середине проекта, который игнорировал абстракции все предыдущее время будет очень сложно - слишком большой объем кода придется рефакторить.


Удачных вам абстракций!
(ваш мастер абстракций)



Комментариев нет:

Отправить комментарий