Как стать автором
Обновить
36
0

Frontend developer (React, MobX)

Отправить сообщение

Попытаюсь представить полный отказ от SOLID. Получаем десятки и сотни хуков в каждом компоненте. В каждый компонент добавляем switch для обработки всех компонентов, которые он получает через children. Для каждой кнопки с отличным действием на onClick делаем новый компонент-кнопку, т.к. каждый onClick работает не с абстрактными функциями, а только с одной конкретной функцией.

Многие почему-то считают, что сущность - это класс.

В 2-х моих проектах сущностью было то, для чего выделен url по rest с набором методов. Для отображения каждой такой сущности были свои страницы. Часть сущностей были вложенными в другие.

Я общался в чате на эту тему и мне сторожилы fsd предупредили, что не надо дробить, ради дробления. Всё должно быть компактно по возможности.

Когда я спрашивал в чате, мне наоборот порекомендовали сразу дробить, чтобы потом не рефакторить, с чем я был не согласен)

Не могли бы вы скинуть ссылку на эту дискуссию? Будет аргументом для объединения лишних слоев.

"Большинство используют общепринятые сегменты, разделяя функционал по типу, а не по связанному назначению."

Это тоже не проблема fsd, а его прочтения. Человек приходит со своими тараканами в голове и пытается делать что-то новое по-старому и прогресса не получается.

Что тут имеете ввиду? Что разработчики не правильно делят на сегменты или что я пытаюсь делать по-привычному? У нас плюс-минус стандартные сегменты и большая их часть написана коллегами, да и я сначала попробовал по-новому и пока продолжаю. Чем больше работаю с сегментами, тем больше ощущаю, что по-старому было лучше. В вашем примере я тоже вижу стандартные папки-сегменты, имена которых мне не говорят, что за функционал внутри них и с каким функционал соседних сегментов он связаны. Если слайс станет большим и в нем будет не 1-2 файла в каждом сегменте, а по 5-10, то сомневаюсь, что будет так же легко разобраться, что с чем связано.

Поэтому я склонен считать, что эти беды не от fsd, а от непонимания его.

Учитывая, что даже у сеньоров (причем у большинства) разное понимание fsd и непонимание каких-то его моментов, то тут беда в том, что или он сложноват для понимания, или документация не достаточно полная и не достаточно понятно его описывает.

На предпоследнем и текущем проектах использовали FSD. За мои 14 лет опыта работы ни в одном проекте не ощущал столько проблем от структуры папок проекта, как в случае этих 2-х проектов с FSD. До этого же в моей практике в основном была группировка по функционалу (folder by feature или feature-based).

Плюсы FSD, которые я ощутил в проектах:

  • единственная подобная методология, для которой легко можно найти описания ее правил.

  • структура папок схожа в разных проектах.

  • быстрое вхождения в проект новых разработчиков, если у них уже был опыт с FSD и на страницах мало логики.

Минусы FSD, которые я ощутил в проектах:

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

  • не понятно, что делать со связанными бизнес-сущностями, т.к. по FSD они должны быть в разных слайсах, но они используются друг в друге. Они могут быть связаны уже на уровне dto типов, используемых для данных с бека. Попытки запретить или обойти костылями связь между связанными сущностями выглядит для меня ошибкой.

  • хоть документация обширна, на некоторые важные моменты в ней нет ответа. Официальная документация не избавляет от необходимости заведения своей документации под конкретный проект.

  • у всех разное понимание FSD. Из-за этого в каждом проекте своя вариация FSD.

  • зачастую сложно понять, в какой слой поместить тот или иной файл.

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

  • из-за большой раздробленности сложнее держать функционал в голове. То есть возрастает когнитивная нагрузка.

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

  • в случае удаления, переименования сущности в проекте изменения затрагивает не пару верхнеуровневых папок, а 4-5 (app, entities, features, widgets, pages).

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


Также добавлю, что на мой взгляд структура папок слоев и сегментов нарушает high cohesion. Вместо этого получаем destructive decoupling (можно найти описание в этой статье https://habr.com/ru/articles/568216/). Связанный код, который должен располагаться рядом, разбросан по разным папкам-слоям и разным сегментам.

Думаю это хорошо заметно в случае размещение файлов одной страницы по разным слоям (entities, features, widgets, pages).

То же относится и к сегментам. Насколько вижу в проектах, связанный код сегментов одного слайса обычно разбросан по разным сегментам и к тому же получается мешанина из классических MV* слоев. Большинство используют общепринятые сегменты, разделяя функционал по типу, а не по связанному назначению. В итоге получаются например свалки typescript типов в одном файле/папке, когда типы для view слоя, типы для стора (слайса в случае redux toolkit) и dto (dal) типы находятся в одной папке или файле. То же самое с утилитами — утилиты, которые предназначены для разных задач, помещают в один сегмент.

В FSD для слоя shared https://feature-sliced.design/ru/docs/reference/layers#shared описано, что не стоит превращать папку утилит в свалку. Но на практике вижу, что папка утилит и сегменты превращаются в свалки не связанной по назначению бизнес логики.

Спасибо за статью! Попал на проект с redux-toolkit и как раз сейчас исследуем, как лучше переиспользовать код слайсов. В интернете мало информации об этом.

Будут ли в будущем статье на перечисленные темы? Толковые статьи по redux-toolkit полезны тем, у кого проект на нем.

Прокомментирую описанные подходы и добавлю еще один.

  1. Первый вариант выглядит тем, к чему стоит стремиться. В отличие от всех остальных подходов в нем происходит разделение большого слайса на более мелкие слайсы, отвечающие за разный функционал даже в пределах одной страницы. Это соответствует SRP принципу из SOLID.

    Правда на redux-toolkit придется подключать больше слайсов для каждой страницы, что довольно неудобно.
    Еще могут быть проблемы, если вдруг потребуется хранить и работать с одними и теми же данными в нескольких слайсах.

  2. Вариант 2, предлагаемый также в официальной документации, это создание обертки с переиспольезуемым кодом и смешивание его в одном объекте (может под капотом и не в одном, не разбирался) с передаваемым кодом. Напоминает наследование. То есть самописная вариация наследования для redux-toolkit c большими сложностями типизации редьюсеров.

    Данный вариант не подойдет, если в одном слайсе планируется разместить большое количество функционала, т.к. тем самым он превратится в антипаттерн God Object, с которым будет трудно работать. Так же в этоv подходе можно случайно перезаписать другие редьюсеры в создаваемом слайсе.

  3. Вариант 3 (Ваш вариант) - вынесение переиспользуемого функционала в отдельный объект. Далее экземпляр этого объект используется внутри других слайсов, в которых пишутся редьюсеры, обращающиеся к этому объекту. Вроде бы это паттерн стратегия.

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

  4. Вынесение переиспользуемого кода в функции-миксины, которые можно применять при создании слайсов.
    Меньше кода, чем в варианте 3, но те же проблемы (за исключением проблем с типизацией), что и в варианте 2, т.к. в обоих вариантах происходит смешивание функционала.

Для переиспользования кода еще используется паттерн декоратор. Но не уверен, что он применим к redux-toolkit слайсам.

Принцип единой ответственности (SRP)

Каждый модуль и функция имеют четко определенную ответственность. Например, ReactFiberBeginWork.js фокусируется на начальной фазе процесса согласования, а ReactFiberCompleteWork.js обрабатывает завершающую фазу.

Компонент отвечает и за логику и за отображение. Нарушение SRP.

Принцип открытости/закрытости (OCP)

Библиотека открыта для расширения, но закрыта для модификации. Это видно по использованию хуков, которые позволяют добавлять новую функциональность без изменения основных компонентов React.

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

Для добавления объекту новой функциональности есть 3 подхода - смешивание с другим объектом, обертка другим объектом, замена объектов/функций в полях-ссылках объекта на другие объекты/функции.

Вариации 3-го подхода:
Можно вынести метод в функцию вне класса и вызывать ее в соответствующем методе жизненного цикла в нужных компонентах.

Можно завести в компонентах массив ссылок на подобные функции, тем самым еще и позволяя менять функциональность компонента, чего нет в подходе с хуками.

Можно пойти дальше и сделать ссылки не на функции, а на объекты, в которых сделать методы жизненного цикла, аналогичные компонентам. Далее в каждом методе жизненного цикла сделать перебор объектов с вызовом в них соответствующего метода жизненного цикла. Еще понадобиться предоставить этим объектам доступ к компоненту с его состоянием. Тем самым получим механизм, где обработка логики компонентов реакта будет делегирована вложенным переиспользуемым и заменяемым объектам. Плюс верстка будет отделена от логики.

Реализацию такого подхода я описывал в https://habr.com/ru/articles/545064

<skz>Вот же этот Ден Абрамов, стервец, дурачит газилионы и газилиарды разработчиков!</skz>

Пишу на реакте с 2015-го. И что-то не ощущаю профита в разработке от стольких лет его эволюции. Скорее наоборот, разработка на нем стала сложнее и медленнее(

под fsd вы имеете ввиду использования feature-list вместо layer-list структуры топлевел директорий

Под fsd я имею ввиду то же, что и автор статьи - https://feature-sliced.design/ru/
Вы верно подметили про layer-list - что неудобно распределять фичу по топлевел директориям. FSD как раз грешит этим.

Про MV* не совсем понял. Это же паттерны презентации, а не архитектура ...

Я тоже не совсем понял, что именно вы имеете ввиду под архитектурой?) Ок, именно они - не архитектура, а паттерны, используемые для разбиения приложения на слои в многослойной архитектуре.
Но я согласен, что эти слои должны находиться рядом для конкретного виджета/страницы.

Люди слишком много думают, а на деле фсд очень даже простой)

Судя по множеству вопросов и разногласий даже среди senior разрабов в том, что и где должно лежать, фсд на самом деле довольно сложный)

Даже выше в комментах было:

"Подход имеет место быть, но стоит держать в уме, что просто взять и перенести средний/большой проект на fsd не выйдет без специалиста, который хорошо знаком с данным подходом. Сталкивался с ситуацией когда на проекте fsd и всё круто, но в какой-то момент людям приходит понимание, что большая часть разработчиков распихивает код по слоям рандомно и проект превращается в кашу."

Всё это разделение на множество слоев ради переиспользования без кросс импортов скорее приводит не к упрощению, а к усложнению. Плюс функционал, связанный с одной бизнес сущностью, становиться разбросан по нескольким слоям проекта. Думаю, в большинстве проектов было бы эффективней не делить на слои widgets, features, entities, а объединить эти 3 слоя в один слой и разрешить в нем кросс импорты между слайсами.

Тоже добавлю про fsd. Решили его попробовать на текущем среднем проекте - несколько десятков тысяч строк кода.

Плюсы:

  • Много документации по подходу.

  • Хорошая идея с разделением на слайсы, сегменты.

  • Знакомишься со сторонними идеями по улучшению файловой структуры проекта.

Минусы:

  • В ходе работы возникает путаница, когда разговор заходит о слоях. В архитектурах MV*, Flux и т.д. слоями называется другое.

  • При разработке возникает много непонятных моментов - какие слои нужны именно в твоем случае (а при избегания кросс-доменности складывается впечатление, что понадобятся все), к какому слою относится тот или иной функционал (у всех вызывает сложность, когда надо определиться, данный модуль это feature или entity? Также возникают сложности с определением, куда поместить такой функционал, как локализация, темизация, переиспользуемые пагинация/сортировка/фильтрация). Все в какой-то степени по-разному понимают FSD, поэтому в команде скорее всего будут частые обсуждение, что и где должно лежать.

  • Логика страницы вместо того, чтобы находиться рядом в соседних файлах/папках, постоянно размазывается по 3-4 папкам - pages, widgets, features, entities. Это повышает когнитивную нагрузку. Часто приходиться переключаться между далеко расположенными друг от друга папками. На мой взгляд, структурировать проект, беря декомпозицию компонентов за значительную часть основы - это не удачный подход.

  • Тратиться немало времени на поддержку следования данному подходу. Если бизнесу постоянно нужны фичи и не выделяется достаточно временно на техдолг, то времени заниматься поддержкой FSD в проекте не будет.

  • Сложно контролировать на code review, т.к. сходу часто не понятно, в какой слой правильней поместить новый модуль.

В следующем проекте вместо слоев FSD, я подумываю использовать примерно следующие:

  1. app - по аналогии с FSD

  2. pages - конкретные страницы и всё (включая конкретные api), относящееся только к конкретной страницы. (вместо постоянного разделения на pages, widgets, features, entities по FSD)

  3. reusable-in-pages - переиспользуемый функционал для нескольких страниц. (вместо widgets, features, entities по FSD)

  4. components - переиспользуемые визуальные компоненты, а также react хуки/улилиты и т.д., относящиеся к этим компонентам.


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

    Хотя возможно 4-ый и 5-ый слои лучше объединить в один, т.к. некоторый функционал (например, toasts) связан с компонентами. А возможно shared стоит использовать как в FSD. Т.е. 4-6 слои из моего комментария сделать одним слоем.

  5. shared-business - переиспользуемый функционал, не относящаяся к конкретным страницам или к какой-либо группе страниц, но привязанная к работе, процессам именно этого приложения. (вместо shared/libs по FSD)

  6. shared-not-business - всё прочее переиспользуемое, не привязанное к приложению, и которое в теории может быть переиспользовано в любом другом проекте. В основном это типы, утилиты, некоторые react-hook-и. (по аналогии с FSD, но без shared/ui и без shared/libs)

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

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

Про то, что хуки это исключительно стратегия не соглашусь

Я попытался показать, что механизмы расширения функционала при помощи паттерна стратегия и при помощи хуков довольно похожи. Абстрагируйтесь, не зацикливайтесь на ограничениях. Тогда станет заметно, что механизмы, применимые к функциям, можно применить к объектам, и наоборот.

Единственным способом переиспользования классовых методов жизненного цикла - является как раз написание HOC’ов.

Я писал статью про другие способы, которые при желании можно применить и к классовым компонентам реакта: Техники повторного использования кода и разбиения сложных объектов на составные

HOC - это аналог паттерна "декоратор" для функциональных компонентов.

Хук - это аналог паттерна "стратегия" для функциональных компонентов. Только у функциональных компонентов недостаток в том, что в них нельзя заменить хук (стратегию), а в объектах/классах при использовании паттерна "стратегия", можно.

Намертво прибиты к реакту - тоже выдуманный пункт, классовые методы тоже прибиты к реакту, точнее даже к конкретному компоненту.

Классовые методы можно выносить в отдельные объект и переиспользовать в других компонентах. Просто из коробки нет общего подхода для такого переиспользования.

Касательно хуков. Группа хуков, вызов которых прописан в конкретном функциональном компоненте - это фиксированный список "стратегий". В геймдеве давно есть похожий подход, но применяемый к объектам/классам. Но там используется не фиксированный список, а динамический. Плюс более грамотное разделение ответственности. В реакте же компонент является и контейнером для стратегий (хуков), содержит в себе разметку, а также позволяет писать логику в самих компонентах. Такое объединение функционала является недостатком как функциональных, так и в классовых компонентах. Это нарушение принципа SRP, приводящее к уменьшение гибкости компонента и менее качественному коду пользователей react-а.

Недавно наткнулся на видео на ту же тему, что и ваша статья
https://www.youtube.com/watch?v=c3JGBdxfYcU
У меня вопросы:

  1. С каких пор во фронтенде архитектурой стали называть организацию структуры папок в проекте?

  2. "Components, Store, Containers", "Feature Oriented" - откуда взяты такие названия для подходов?

В Redux используется паттерн издатель-подписчик. В вашей статье описан тоже он.

Паттерн наблюдатель немного другой. По ссылке описана разница между ними:
https://medium.com/clean-code-channel/observer-vs-pub-sub-pattern-4fc1da35d11

useEffect(() => {
  (async () => {
    const {data} = await axios.get('api/posts')
    setPosts(data)
  })()
}, [])

Я бы предпочел избавиться от такого количества скобок и писать например так:

useMountEffectAsync(async () => {
  const {data} = await axios.get('api/posts')
  setPosts(data)
})

Про оптимизацию я глупость написал, так как ни разу не приходилось использовать requestAnimationFrame.

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

Думаю, BuccapuoH, как и я, хотел узнать, что у вас за проект такой, в котором понадобилось столько раз использовать requestAnimationFrame и process.nextTick) И была ли в этом необходимость или же это просто преждевременная оптимизация.

Стандартные подходы Vue, где во Vuex экшенах делаются запросы и кладутся в стор и т.п не расширяемые

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

Принципы SOLID конечно помогают, но одних их довольно маловато. Существует довольно много паттернов, подходов, позволяющих улучшить переиспользование кода. Я, кстати, писал статью о тех, с которыми знаком: https://habr.com/ru/post/545368/

Видимо я ошибался. Теперь начинаю видеть преимущества подхода, особенно в плане оптимизации. А недостаток в виде раздувания FormItem обходится с помощью вынесения логики из него в хуки, сторы. Спасибо, что открыли мне глаза!)

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

С формиком и react-hook-form не работал. И на глаза практически не попадался подход с вынесением общих пропсов в функции. А вот клонирование часто встречалось.

1
23 ...

Информация

В рейтинге
5 986-й
Откуда
Омск, Омская обл., Россия
Зарегистрирован
Активность