Источник: Блог С. Теплякова

Date: 2019-11-29


Существует много разных взглядов на разработку архитектуры и дизайна современных приложений. Некоторые архитекторы стремятся продумать все до мелочей, разрисовать use case-ы всех классов и модулей, проанализировать миллион возможных способов их использования, все их обязательно задокументировать и уже потом приступить к этапу кодирования.

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

Когда речь заходит о дизайне классов, архитектуре модулей или об ответственности различных слоев приложения, то решающую роль при их проектировании играют понятия сцепления (cohesion) и связанности (coupling). Еще Кристофер Александер, «папа» шаблонов проектирования, писал о том, что основной задачей при декомпозиции системы является осуществление двух условий: (1) максимизация связей внутри компонентов (высокое внутреннее сцепление, tight internal cohesion) и (2) минимизация связей между компонентами (низкая внешняя связанность, loose external coupling).

Подробнее об истории возникновения такого уникального понятия, как шаблоны проектирования (design patterns) можно прочитать в статье: Шаблоны проектирования. История успеха.

Благодаря Бобу Мартину и некоторым другим известным личностям, в нашем арсенале появились принципы, такие как S.O.L.I.D., которые позволяют определить более точно (или, быть может, более формально), отвечает ли дизайн системы приведенным выше основополагающим принципам или нет. Я же обычно, прежде чем переходить к «тяжелой артиллерии», в виде подобных принципов использую более простой подход. Я задаю себе следующий вопрос: «а насколько реально покрыть основную функциональность этого класса модульными тестами?» Если ответ положительный, то наверняка указанный класс обладает достаточно высоким сцеплением и низкой связанностью с другими классами. Если даже чисто теоретическое написание юнит-теста невозможно, поскольку ответственность класса непонятна, он содержит кучу несвязанных друг с другом полей и методов, и зависит от двух десятков других сущностей, тогда с дизайном явно что-то не так.

Рисунок 1 – И как это чудо тестировать? А ведь в реальной жизни связей может быть в пару раз больше

СОВЕТ Используйте принцип «тестируемости» класса в качестве «лакмусовой бумажки» хорошего дизайна класса. Даже если вы не напишите ни строчки тестового кода (хотя зря!), ответ на этот вопрос в 90% случаев поможет понять, насколько все «хорошо» или «плохо» с его дизайном.

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

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

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

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

СОВЕТ Если «лакмусовой бумажкой» качества дизайна классов была их тестируемость, то лакмусовой бумажкой качества архитектуры можно считать ее «гибкость». Спросите у себя: «А что будет, если текущее архитектурное решение окажется неверным?», «Какое количество модулей подвергнется при этом изменениям?». По возможности, архитектурные решения не должны «вырубаться в камне», и последствия архитектурных ошибок должны быть в разумной степени ограничены.

Отступление от темы. Clean Architecture от Боба Мартина

О важности прагматичного взгляда на архитектуру написано довольно много. Гради Буч в своей знаменитой книге неоднократно останавливается на важности абстрагирования и инкапсуляции на самых разных уровнях. В замечательной книге «97 этюдов для архитекторов программных систем» многие авторы не раз говорят о вреде «вырубания решений в камне» и преимуществах простоты перед гибкостью в вопросах архитектуры.

Одним из последних известных авторов, тему архитектуры поднял Боб Мартин. В одной из своих презентаций, а также в одном из своих постов об архитектуре Боб написал следующее: … Хорошая архитектура позволяет ОТКЛАДЫВАТЬ принятие ключевых решений… Хорошая архитектура максимизирует количество НЕ ПРИНЯТЫХ решений.

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

ПРИМЕЧАНИЕ Презентацию Боба Мартина, о которой я упоминал выше, можно найти здесь, небольшое видео, с обсуждением этого вопроса, здесь. И вот несколько постов Боба на эту же тему: Clean Architecture и Screaming Architecture.