Чистая архитектура для фронтендера

в 11:59, , рубрики: angular, clean architecture, clean code, javascript, React, ооп, Разработка веб-сайтов, Совершенный код

Чистая архитектура для фронтендера - 1

Современный веб — это сложно. Количество фреймворков и темп их развития заставляет разработчика скакать галопом. Кто-то новые либы юзает, кто-то модные книжки читает. Но иногда чтение и потраченные силы на углубление в архитектуру, ООП, TDD, DDD и т.д. не оправдывают ожидания. А порой книжки запутывают! И даже, самое страшное, неимоверно поднимают ЧСВ!

Я рискну по-простому изложить основную мысль Чистой Архитектуры применительно к фронтенду. Надеюсь, это будет полезно и для людей, которые хотят прочитать эту книжку, и для тех кто уже читал, но не использует полученные знания в реальной жизни. И для тех, кому интересно, как я сюда приплел фронтенд.

Мотивация

Впервые я прочитал ЧА за года полтора до написания статьи по совету одного из старших разработчиков. Перед этим меня сильно впечатлили краткие выдержки из Чистого Кода адаптированные под JavaScript (https://github.com/ryanmcdermott/clean-code-javascript). Я держал эту вкладку открытой на протяжении полугода, чтобы применять лучшие практики в своей работе. Причем, мои первые попытки прочитать оригинал Чистого Кода провалились. Возможно, потому что слишком сильно прилип к особенностям фронтенд-проектов, или потому что чтение должно быть закреплено продолжительной практикой. Но тем не менее ЧК — это практическое руководство, которое можно взять и сразу применить к написанной функции (очень советую прочитать в первую очередь, если ещё не знакомы).

А вот с ЧА все сложнее — здесь приводятся наборы принципов, законов и советов по построению программы в целом. Там нет конкретики, которую можно сразу взять и заюзать в компоненте. Эта книга призвана изменить отношение к написанию ПО, и дать вам мысленные инструменты и метрики. Ошибочно считать, что ЧА полезна только архитекторам, и я постараюсь донести это на адаптированном под фронтенд примере.

Бизнес-логика и фронтенд

Чистая архитектура для фронтендера - 2

Когда речь идет о ЧА, у многих людей в воображении рисуются кружочки (рис. под заголовком), гигантских размеров проекты, невероятно сложная бизнес-логика, и куча вопросов — а по каким папочкам раскладывать ЮзКейсы? Мысль о применимости принципов из ЧА к созданию компонента аккордеона вводит в недоумение. Но проблемы, которые возникают при разработке современных интерфейсов требует серьезного отношения. Современные интерфейсы — это сложно, и часто мучительно. В первую очередь давайте разберемся, что на фронтенде самое важное.

Всем давным-давно известно, что нужно бизнес-логику от представления отделять, и принципы SOLID соблюдать. Дядюшка Боб в ЧА расскажет вам об этом очень подробно. Но что такое бизнес-логика? Р. Мартин предлагает несколько определений и подкатегорий, одно из них звучит примерно так:

Бизнес-логика (бизнес-правила) — это правила, которые приносят организации деньги даже без средств автоматизации.

В общем, бизнес-логика, это что-то очень важное. (Слышу, как бэкендеры хихикают, когда слышат про бизнес-логику от фронтов). Но я предлагаю нам фронтендерам немного расслабиться и вспомнить, что у нас на фронте может быть очень важным? И как бы это странно ни звучало, самым важным на фронте является пользовательский интерфейс. Первым шагом к пониманию ЧА для меня стало осознание того, что на фронте в центре кружочка должна быть логика интерфейса! (рис. под заголовком).

Я понимаю, что это голословное утверждение, и с ним можно сильно поспорить. Но давайте вспомним, что у нас на фронте меняется часто и больно? Не знаю как вас, а меня чаще всего просят менять поведение интерактивных элементов — аккордеонов, формочек, кнопочек. Стоит отметить, что верстка (дизайн) меняется намного реже поведения интерфейса. В ЧА особо обсуждаются потоки изменений и акторы (инициаторы изменений), обратите внимание.

Противная формочка

Давайте не будем пока сильно заморачиваться за терминологию. Приведем пример и немного проясним, что я называю логикой интерфейса.

Есть пара инпутов с валидацией и условным отображением. Можно заполнить только одно поле, и форма валидна, но появятся опциональные инпуты. (Обратите внимание, нам безразлично что там за данные. Главное — интерактивность)

Чистая архитектура для фронтендера - 3
Чистая архитектура для фронтендера - 4
Чистая архитектура для фронтендера - 5

Надеюсь, логика понятна. И на первых парах реализация не вызывает вопросов. Вы засовываете это дело в компонент своего фреймворка и смело переиспользуете в других формочках как составную часть и самостоятельную. Где-то приходится передавать флаги, чтобы чуток поменять валидацию, где-то немного верстку, где-то особенности подключения к родительской форме. В общем, кодите по тонкому льду. И однажды, вы получаете задачу по этой форме (и использующим ее) и зависаете на пару дней, хотя казалось бы дел на 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.

Реализация ContactFormGroup (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, но часто лишь чтобы отсеять кандидатов. Более того, порывы к развитию в области ООП в некоторых командах могут пресекаться на ревью. И людям часто проще разобраться в новой библиотеке, чем прочитать скучную книжку или отстоять свое право на вынесение логики из компонента. Прошу, поддержите ООП активистов)

Автор: Сергей Клевакин

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js