Современный веб — это сложно. Количество фреймворков и темп их развития заставляет разработчика скакать галопом. Кто-то новые либы юзает, кто-то модные книжки читает. Но иногда чтение и потраченные силы на углубление в архитектуру, ООП, TDD, DDD и т.д. не оправдывают ожидания. А порой книжки запутывают! И даже, самое страшное, неимоверно поднимают ЧСВ!
Я рискну по-простому изложить основную мысль Чистой Архитектуры применительно к фронтенду. Надеюсь, это будет полезно и для людей, которые хотят прочитать эту книжку, и для тех кто уже читал, но не использует полученные знания в реальной жизни. И для тех, кому интересно, как я сюда приплел фронтенд.
Мотивация
Впервые я прочитал ЧА за года полтора до написания статьи по совету одного из старших разработчиков. Перед этим меня сильно впечатлили краткие выдержки из Чистого Кода адаптированные под JavaScript (https://github.com/ryanmcdermott/clean-code-javascript). Я держал эту вкладку открытой на протяжении полугода, чтобы применять лучшие практики в своей работе. Причем, мои первые попытки прочитать оригинал Чистого Кода провалились. Возможно, потому что слишком сильно прилип к особенностям фронтенд-проектов, или потому что чтение должно быть закреплено продолжительной практикой. Но тем не менее ЧК — это практическое руководство, которое можно взять и сразу применить к написанной функции (очень советую прочитать в первую очередь, если ещё не знакомы).
А вот с ЧА все сложнее — здесь приводятся наборы принципов, законов и советов по построению программы в целом. Там нет конкретики, которую можно сразу взять и заюзать в компоненте. Эта книга призвана изменить отношение к написанию ПО, и дать вам мысленные инструменты и метрики. Ошибочно считать, что ЧА полезна только архитекторам, и я постараюсь донести это на адаптированном под фронтенд примере.
Бизнес-логика и фронтенд
Когда речь идет о ЧА, у многих людей в воображении рисуются кружочки (рис. под заголовком), гигантских размеров проекты, невероятно сложная бизнес-логика, и куча вопросов — а по каким папочкам раскладывать ЮзКейсы? Мысль о применимости принципов из ЧА к созданию компонента аккордеона вводит в недоумение. Но проблемы, которые возникают при разработке современных интерфейсов требует серьезного отношения. Современные интерфейсы — это сложно, и часто мучительно. В первую очередь давайте разберемся, что на фронтенде самое важное.
Всем давным-давно известно, что нужно бизнес-логику от представления отделять, и принципы SOLID соблюдать. Дядюшка Боб в ЧА расскажет вам об этом очень подробно. Но что такое бизнес-логика? Р. Мартин предлагает несколько определений и подкатегорий, одно из них звучит примерно так:
Бизнес-логика (бизнес-правила) — это правила, которые приносят организации деньги даже без средств автоматизации.
В общем, бизнес-логика, это что-то очень важное. (Слышу, как бэкендеры хихикают, когда слышат про бизнес-логику от фронтов). Но я предлагаю нам фронтендерам немного расслабиться и вспомнить, что у нас на фронте может быть очень важным? И как бы это странно ни звучало, самым важным на фронте является пользовательский интерфейс. Первым шагом к пониманию ЧА для меня стало осознание того, что на фронте в центре кружочка должна быть логика интерфейса! (рис. под заголовком).
Я понимаю, что это голословное утверждение, и с ним можно сильно поспорить. Но давайте вспомним, что у нас на фронте меняется часто и больно? Не знаю как вас, а меня чаще всего просят менять поведение интерактивных элементов — аккордеонов, формочек, кнопочек. Стоит отметить, что верстка (дизайн) меняется намного реже поведения интерфейса. В ЧА особо обсуждаются потоки изменений и акторы (инициаторы изменений), обратите внимание.
Противная формочка
Давайте не будем пока сильно заморачиваться за терминологию. Приведем пример и немного проясним, что я называю логикой интерфейса.
Есть пара инпутов с валидацией и условным отображением. Можно заполнить только одно поле, и форма валидна, но появятся опциональные инпуты. (Обратите внимание, нам безразлично что там за данные. Главное — интерактивность)
Надеюсь, логика понятна. И на первых парах реализация не вызывает вопросов. Вы засовываете это дело в компонент своего фреймворка и смело переиспользуете в других формочках как составную часть и самостоятельную. Где-то приходится передавать флаги, чтобы чуток поменять валидацию, где-то немного верстку, где-то особенности подключения к родительской форме. В общем, кодите по тонкому льду. И однажды, вы получаете задачу по этой форме (и использующим ее) и зависаете на пару дней, хотя казалось бы дел на 15 минут. Проклинаете менеджера, формочки и скучные тупые таски.
В чем ошибка? Казалось бы, вы не первый день работаете, отлично продумали композицию компонентов, попробовали разные хоки, передачу темплейтов через пропсы, колдовали с фреймворком по построению форм, даже старались следовать SOLID при написанисании этих компонентов.
Note: компоненты в ЧА !== компоненты в реакт/ангулар и ко.
А дело в том, что вы забыли выделить логику. Немного успокоимся, вернемся к задаче и поиграем в моделирование.
Слишком простая задача
В ЧА подчеркивается, что для больших проектов архитектура имеет критическое значение. Но это не отменяет полезности подходов ЧА и к маленьким задачам. Нам кажется, что задача слишком проста, чтобы говорить об архитектуре какой-то формочки. А как обозначить способ внесения изменений, если не через архитектуру? Если не определить границы и составные части интерактивного интерфейса, запутаться будет еще проще. Вспомните с какими мыслями вы берете таск по изменениям формы на своей работе.
Но давайте к делу. Что это за формочка? Пробуем моделировать, излагая мысли псевдокодом.
ContactFormGroup
+getValue()
+isValid()
По-моему, вся наша задача для внешнего мира сводится к созданию объекта с двумя методами. Звучит легко — так оно и есть. Продолжим описывать, что видим и что нас интересует.
ContactFormGroup
emailFormGroup
phoneFormGroup
getValue()
=> [emailFormGroup.getValue(), phoneFormGroup.getValue()]
isValid()
=> emailFormGroup.isValid() || phoneFormGroup.isValid()
Наверно, стоит явно обозначить видимость второстепенных инпутов. Когда менеджер просит быстренько внести 10-ую правку в форму, то в его голове все выглядит просто — прям как этот псевдокод.
EmailFormGroup
getValue()
isValid()
isSecondaryEmailVisible()
=> isValid() && !!getValue()
Можно наметить место для странных требований…
PhoneFormGroup
getValue()
isValid()
isSecondaryPhoneVisible()
=> isValid() && today !== ‘sunday’
Так могла бы выглядеть одна из реализаций нашей формочки на Angular.
export class ContactFormGroup {
emailFormGroup = new EmailFormGroup();
phoneFormGroup = new PhoneFormGroup();
changes: Observable<unknown> = merge(this.emailFormGroup.changes, this.phoneFormGroup.changes);
constructor() {}
isValid(): boolean {
return this.emailFormGroup.isValid() || this.phoneFormGroup.isValid();
}
getValue() {
return {
emails: this.emailFormGroup.getValue(),
phones: this.phoneFormGroup.getValue(),
};
}
}
export class EmailFormGroup {
emailControl = new FormControl();
secondaryEmailControl = new FormControl();
changes: Observable<unknown> = merge(
this.emailControl.valueChanges,
this.secondaryEmailControl.valueChanges,
);
isValid(): boolean {
return this.emailControl.valid && !!this.emailControl.value;
}
getValue() {
return {
primary: this.emailControl.value,
secondary: this.secondaryEmailControl.value,
};
}
isSecondaryEmailVisible(): boolean {
return this.isValid();
}
}
Таким образом, мы получаем три интерфейса (или класса, не важно). Следует поместить эти классы в отдельный файл на видном месте, чтобы можно было разобраться в подвижных частях интерфейса, просто заглянув в него. Мы выделили, вытащили и подчеркнули проблемную логику, и теперь управляем поведением формочки, комбинируя реализации отдельных частей ContactFormGroup. А требования для разных вариантов использования можно легко представить в виде отдельных объектов.
Кажется, это стандартная реализация паттерна MVC, и не более того. Но я бы не стал пренебрежительно относиться к элементарным вещам, которые на практике вообще не соблюдаются. Смысл не в том, что мы вытащили кусок кода из представления. Смысл в том, что мы выделили важную часть, подверженную изменениям, и описали ее поведение так, что она стала простой.
Итого
ЧА рассказывает нам о законах написания ПО. Дает метрики, по которым мы можем выделять важные части от второстепенных и правильно направлять зависимости между этими частями. Описывает преимущества ООП и подходов к решению задачи через моделирование.
Если вы хотите улучшить качество своих программ, сделать их гибкими, использовать в своей работе ООП, научиться управлять зависимостями в вашем проекте, говорить в коде о решении задачи, а не о деталях вашей библиотеки, то я очень рекомендую прочитать Чистую Архитектуру. Советы и принципы из этой книги актуальны для любого стека и парадигмы. Не бойтесь экспериментов и на своих задачах. Удачи!
P.S. О стейт-менеджменте
Очень большим препятствием для понимания ЧА может стать приверженность библиотеке для стейт-менеджмента. На самом деле, такие библиотеки как redux/mobx попутно решают задачу оповещения компонентов об изменениях. И для некоторых разработчиков фронт без стейт-менеджера — что-то немыслимое. Я считаю, что принципы ЧА можно применять и с использованием стейт-менеджера и без него. Но сделав упор на стейт-менеджмент библиотеку, часть гибкости вы потеряете неизбежно. Если мыслить в терминах ЧА и ООП, то понятие стейт-менеджмента вообще отпадает. Простейшая реализация без стейт-менеджмента здесь habr.com/ru/post/491684
P.P.S.
Честно говоря, я показывал решение похожей интерфейсной задачи своему другу — он не оценил, и переписал все на реакт-хуки. Мне кажется, что в большей степени отторжение происходит из-за того, что в реальных проектах ООП практически не используется, и у большинства молодых фронтендеров нет ни малейшего опыта с ООП решениями. На собеседованиях бывает спрашивают про SOLID, но часто лишь чтобы отсеять кандидатов. Более того, порывы к развитию в области ООП в некоторых командах могут пресекаться на ревью. И людям часто проще разобраться в новой библиотеке, чем прочитать скучную книжку или отстоять свое право на вынесение логики из компонента. Прошу, поддержите ООП активистов)
Специально для сайта ITWORLD.UZ. Новость взята с сайта Хабр