惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
The GitHub Blog
The GitHub Blog
美团技术团队
Know Your Adversary
Know Your Adversary
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The Register - Security
The Register - Security
Stack Overflow Blog
Stack Overflow Blog
Attack and Defense Labs
Attack and Defense Labs
G
Google Developers Blog
I
InfoQ
博客园 - 司徒正美
T
Troy Hunt's Blog
Google DeepMind News
Google DeepMind News
J
Java Code Geeks
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
A
About on SuperTechFans
云风的 BLOG
云风的 BLOG
S
Security Affairs
M
MIT News - Artificial intelligence
Simon Willison's Weblog
Simon Willison's Weblog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Tailwind CSS Blog
量子位
Vercel News
Vercel News
月光博客
月光博客
V
Vulnerabilities – Threatpost
N
News and Events Feed by Topic
Hugging Face - Blog
Hugging Face - Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
L
LangChain Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 最新话题
F
Full Disclosure
The Hacker News
The Hacker News
Hacker News: Ask HN
Hacker News: Ask HN
T
Tor Project blog
A
Arctic Wolf
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Forbes - Security
Forbes - Security
IT之家
IT之家
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Y
Y Combinator Blog
GbyAI
GbyAI
B
Blog RSS Feed
V
Visual Studio Blog
T
The Blog of Author Tim Ferriss
F
Fortinet All Blogs

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Мы увязли в Feature‑Sliced Design
strannik_k · 2026-05-05 · via Все публикации подряд на Хабре

Средний

17 мин

11K

Всем привет, меня зовут Сергей Сибара, я фронтенд‑разработчик в ИТ‑холдинге Т1. Эта статья — продолжение предыдущей: Мой справочник по Feature‑Sliced Design. На этот раз я рассмотрю, как по моему субъективному мнению улучшить файловую структуру проекта, нарушая рекомендации FSD — архитектурной методологии для проектирования фронтенд-приложений. Я разрабатываю на React.js, поэтому статья написана с уклоном на него.

В конце статьи есть ссылки на другие подходы к организации файловой структуры фронтенд-приложений.

Содержание

Терминология

Чтобы у нас с вами не было путаницы, я добавил небольшой глоссарий по используемым терминам:

  • Подсистема — группа любых программных сущностей (переменные, функции, объекты, типы), сфокусированная на реализации какой‑либо связанной части приложения. Примеры: утилиты для работы с датой, менеджер модальных окон, страница или связанная группа страниц, любой компонент интерфейса, состоящий из программных сущностей, реализующих его отображение и поведение). Подсистема обычно состоит из вложенных подсистем.

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

  • Принцип Low Coupling (низкое зацепление). Он связан с предыдущим и гласит, что подсистемы, файлы, классы, функции и прочие программные сущности, решающие разные задачи, желательно делать менее связанными друг с другом.

Допустимо ли нарушать рекомендации FSD?

Правила методологии носят рекомендательный, а не обязательный характер. Где‑то они помогут, а где‑то — наоборот. Разработчики методологии упоминают, да и в документации FSD написано, что она не является «серебряной пулей». Более того, со временем они приходят к выводу, что те или иные рекомендации часто неэффективны, и она понемногу меняется. Рамок, которыми она ограничивает структуру, недостаточно, чтобы покрыть все возможные сценарии, да ещё и с применением всех существующих технологий. Поэтому в реальности ограничения методологии могут играть роль палок в колесах разработки, и в таких случаях не возбраняется и нарушить FSD.

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

Давайте рассмотрим, когда и как можно отклониться от рекомендаций FSD, чтобы структура проекта стала лучше.

Зависимость количества слоёв и соблюдения рекомендаций от размера проекта и количества связей между подсистемами

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

Чем меньше проект, тем меньше необходимость разделять его на многочисленные слои и строго следовать архитектурным правилам. В совсем маленьких проектах они избыточны: на их соблюдение потратится время, но пользу они, скорее всего, не принесут.

В небольших приложениях размещать переиспользуемую бизнес‑логику в слое shared, скорее всего, будет более эффективно, чем вводить дополнительные слои. Для таких проектов хватит трёх слоёв, например:

shared
pages
app

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

В создаваемой альтернативе FSD — Evolution‑design — также демонстрируется, как количество слоёв можно адаптировать под масштаб проекта. Из интересных особенностей — в ней предлагается в shared слое размещать ещё и глобальные бизнес‑типы, и правила, нужные для работы инфраструктуры и всего приложения.

Альтернативой дальнейшему увеличению количества слоёв является разделение приложения на несколько подпроектов.

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

Подходы с организацией иерархии давно используются для решения проблем с масштабированием больших кодовых баз. Например:

Разделение структуры большого проекта на несколько подпроектов

Интерфейсы приложений довольно часто разделяют на разделы, работающие с разными бизнес‑сущностями и слабо (или вовсе не) связанные друг с другом. Эта ситуация встречается, например, при разработке нескольких микрофронтендов в одном монорепозитории (в этом видео с 31.14 до примерно 34.00 как раз обсуждают подобный пример). Стандартная FSD‑структура в таких проектах неэффективна: слои со временем перегружаются большим количеством слайсов, относящихся к разным микрофронтендам, что затрудняет навигацию по проекту и поддержку.

Решить эту проблему можно введением ещё одного уровня иерархии и группированием связанной функциональности внутри отдельных подпроектов. Получается, как бы несколько FSD‑приложений внутри одного. У каждого подпроекта будут только необходимые ему слои. При необходимости слои shared и app могут быть общими, а могут быть свои у каждого подпроекта. Пример:

📂 src  
├── 📂 shared  
├── 📂 app  
├── 📂 fsdApp1  
│   ├── 📂 entities  
│   ├── 📂 features  
│   ├── 📂 widgets  
│   └── 📂 pages  
├── 📂 fsdApp2  
│   ├── 📂 features  
│   └── 📂 pages  
└── 📂 fsdApp3  
    └── 📂 pages

В данном примере нет нарушений методологии, так как она не регламентирует структуру проекта выше уровня слоёв. Наоборот, в официальном чате можно встретить рекомендации разделять несвязанные или слабо связанные части приложения на отдельные подпроекты.

Вынесение связанного изолированного кода вне основного проекта

Чтобы снизить зацепление и соблюсти последовательность импортов, в некоторых проектах иногда уместно вынести связанную часть проекта вне его FSD структуры. Это может быть как инфраструктурная, так и бизнес‑часть. Можно сделать её отдельным пакетом, а можно изменить структуру проекта, например так:

📂 src  
├── 📂 mainApp  
│   ├── 📂 shared
│   ├── 📂 entities  
│   ├── 📂 features  
│   ├── 📂 widgets  
│   ├── 📂 pages
│   └── 📂 app
└── 📂 packages  
    ├── 📂 subSystemA
    └── 📂 subSystemB

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

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

Слои entities и features для тонких и толстых клиентов

В FSD есть общее описание слоёв, не привязанное к особенностям проекта. И тут кроется проблема — не понятно, для каких проектов подходят описанные слои.

В больших проектах с толстым клиентом возможно эффективнее будет отделять доменную часть (та бизнес‑логика, которая при тонком клиенте располагается на бекенде) проекта от части, отвечающей за интерфейс. Для таких проектов понадобиться либо ввести ещё слои для доменной части, либо использовать другое определение слоёв entities и features. Слой entities может использоваться для задания сущностей и связей между разными сущностями, а в слой features может быть вынесена не относящаяся к интерфейсу бизнес‑логика по аналогии с Use Cases из Clean Arhitecture.

В тонких клиентах связи между сущностями уже отражены в DTO. Поэтому в таких проектах слой entities зачастую избыточен. Лучше редкие связи (кросс‑импорты) в слайсах слоя, чем ещё один слой.

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

📂 src  
├── 📂 domainApp // здесь для примера взяты слои из Clean Arhitecture
│   ├── 📂 entities
│   ├── 📂 useCases
│   └── 📂 controllers
└── 📂 fsdApp  
    ├── 📂 shared  
    ├── 📂 widgets  
    ├── 📂 pages
    └── 📂 app

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

Стоит ли использовать разделение на слои entities, features, widgets, pages?

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

В проектах со слабосвязанными страницами и группами страниц, введение дополнительного слоя помимо pages будет избыточным. В проектах, где требуется выносить часто переиспользуемую в разных страницах функциональность, я предпочёл бы вынести её всю только в один бизнес‑слой с разрешением кросс‑импортов, а также при необходимости разделял проект не несколько подпроектов.

Допустимы ли кросс‑импорты в рамках одного слоя?

Согласно методологии FSD, кросс‑импорты между слайсами одного слоя нежелательны, но допускаются при использовании @x‑нотации в слое entities.

Следует минимизировать связи между слайсами одного слоя. Одним из способов уменьшения количества этих связей в FSD являются публичные API, которые можно считать аналогом паттерна фасад. Лично я не стал бы так сильно ограничивать кросс‑импорты между слайсами и не вводил бы символьные нотации.

Если страницы или разделы веб‑приложения часто связаны между собой, то наличие связей между их слайсами нежелательно. С ростом проекта количество связей будет расти, и он будет становиться всё более запутанным. Однако в проектах со слабосвязанными страницами и разделами веб‑приложения редкие кросс‑импорты, как правило, не создают проблем. В таких случаях они могут оказаться более эффективными, чем вынесение редко переиспользуемой бизнес‑логики в отдельный слой.

Александр Моргунов в своей статье писал, плохи ли кросс‑импорты.

В моих прошлых проектах с тонким клиентом, объёмом в несколько десятков тысяч строк и написанных без FSD, было несколько связей между «слайсами» слоя pages. При этом, истины ради нужно отметить, что ни в одном проекте такие связи не привели к реальным проблемам. Структуры проектов остались достаточно прозрачными.

Вложенные слайсы

В FSD можно объединять в одной папке группу слайсов без кросс‑импортов. Но не рекомендуется создавать вложенные слайсы. В прошлой статье я уже писал, что это разные понятия.

В больших слайсах появляется необходимость как‑то сгруппировать связанные группы файлов. Для этого есть предложение разрешить вложенные слои, за счёт чего внутри слайсов получается фрактальная структура (слой → слайс → сегмент → слой ‑> слайс → сегмент). На мой взгляд, повторять внутри слайса иерархию FSD — это избыточное усложнение. Добавлять слои внутри слайса возможно имеет смысл в редких и очень сложных ситуациях.

В другом подходе к организации файловой структуры проекта — модульном — вместо фрактальной иерархии создаются вложенные папки, аналогичные вложенным слайсам, но без разделения на слои и сегменты. В проектах без FSD, где применялась модульная структура, мы с коллегами часто размещали в одной директории папки с подсистемами, отдельные файлы, иногда ещё и технические папки (вроде utils, stores). Запутанной свалки нигде не образовалось.

В описании Evolution Design в главе Этап 3: Grouped module есть подглава «Группы и подмодули», в которой приводится пример, который был бы аналогичен смешиванию сегментов и вложенных слайсов:

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

📂 someSlice 
├── 📂 common // Общий код, используемый в subSlices. Не может импортировать из subSlices и main. 
├── 📂 subSlices // В эту папку нельзя импортировать из слайса main
│   ├── 📂 subSlice1
│   └── 📂 subSlice2
└── 📂 main // Основой функционал слайса. Нет запрета на импорт в рамках someSlice

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

Группирование по функциональности (по вложенным слайсам) вместо группирования по сегментам

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

Для простых слайсов деление на папки‑сегменты избыточно. Слайс в FSD часто начинается сразу с ненужного усложнения — для 3–4 файлов в слайсе создаются папки‑сегменты. В методологии Evolution Design предлагается, на мой взгляд, более грамотный подход.

Для сложных слайсов деление на папки‑сегменты приводит к тому, что в папках config, lib, api, model, ui располагаются части нескольких подсистем. Иногда есть польза от группирования на папки‑сегменты, но внутри одной подсистемы.

Также встречается другая проблема — когда в сегменте ui появляются вложенные папки при практически полном отсутствии других сегментов в слайсе. Например:

📂 slice
├── 📄 pageModel.ts
└── 📂 ui
    ├── 📂 modal
    │   ├── 📄 someInnerModalComponent.tsx
    │   └── 📄 modalContent.tsx
    ├── 📂 actionButtons
    │   ├── 📄 button1.tsx
    │   └── 📄 button2.tsx
    ├── 📂 form
    │   ├── 📄 useSubmit.ts
    │   └── 📄 form.tsx
    └── 📄 page.tsx

В этом примере файлы внутри сегмента ui сгруппированы по функциональному назначению. Если не следовать FSD и переместить их на уровень слайса, то структура не станет сложнее, но уменьшится вложенность. Если в будущем появятся модели, нужные только для определённых компонентов этого слайса, то их можно разместить рядом со связанными компонентами, а не в отдельной папке model, оторванной от контекста использования. Например (добавлены несколько файлов моделей: modalModel.ts, validationScheme.ts, formLogics.ts):

📂 slice
├── 📄 pageModel.ts
├── 📄 page.tsx
├── 📂 modal
│   ├── 📄 someInnerModalComponent.tsx
│   ├── 📄 modalModel.ts
│   └── 📄 modalContent.tsx
├── 📂 actionButtons
│   ├── 📄 button1.tsx
│   └── 📄 button2.tsx
└── 📂 form
    ├── 📄 useSubmit.ts
    ├── 📄 validationScheme.ts
    ├── 📄 formLogics.ts
    └── 📄 form.tsx

Чтобы было легче понять, к какому сегменту относится файл, а также с помощью линтеров контролировать связи между сегментами, можно добавлять название сегмента в конце имени файла, например:

order.api.ts
orderFormScheme.model.ts 
orderForm.ui.tsx

Если в сегменте много файлов и нужно видеть какой файл относится к какому сегменту, то чтобы не добавлять суффикс сегмента к каждому файлу, можно завести папку, указывающую одновременно принадлежность к сегменту и назначение, например:

📂 slice
├── 📄 orderForm.tsx
├── 📄 orderApi.ts
└── 📂 formModel
    ├── 📄 orderFormScheme.ts
    ├── 📄 dtoToFormData.ts
    └── 📄 formDataToDto.ts

В отличие от FSD, при группировании по функциональности получится меньше вложенность и не будет дублирования структуры в каждом сегменте. Нарушения Low Coupling и High Cohesion не будут настолько сильными. При этом структура останется понятной.

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

В одном из проектов с очень высоким темпом разработки была папка с группой связанных страниц — в сумме в ней было около 225 файлов. Эту группу страниц не я начинал делать, но я и другой разработчик продолжили. Местами была большевата вложенность, а также вместе с папками, в которых размещался весь функционал для определённых частей страниц, иногда размещались технические папки вроде utils, stores. Тем не менее, там было не особо сложно разобраться, не было споров, что и где разместить. Если бы файлы этой группы страниц были сгруппированы не по функциональному назначению, а по FSD, то проект разрабатывался бы намного медленнее и был бы более запутанным.

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

Структура слайса с несколькими компонентами интерфейса и моделями

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

В рамках FSD один из вариантов будет такой:

📂 slice
├── 📂 model
│   ├── 📄 model1.ts
│   ├── 📄 model2.ts
│   └── 📄 model3.ts
└── 📂 ui
    ├── 📄 component1.tsx
    ├── 📄 component2.tsx
    └── 📄 component3.tsx

Такая структура нарушает принципы Low Coupling и High Cohesion: связанные модель и компонент лежат в разных сегментах, а несвязанные друг с другом файлы — в одном.

В более сложных случаях, когда есть несколько связанных групп файлов, структура может выглядеть так:

📂 slice 
├── 📂 model
│   ├── 📂 group1
│   │   ├── 📄 model1ForGroup1.ts
│   │   └── … // прочие файлы для этой группы
│   ├── 📂 group2
│   │   ├── 📄 model1ForGroup2.ts
│   │   ├── 📄 model2ForGroup2.ts
│   │   └── …
│   └── 📂 group3
│       ├── 📄 model1ForGroup3.ts
│       └── …
└── 📂 ui
    ├── 📂 group1
    │   ├── 📄 component1ForGroup1.tsx
    │   └── … // прочие файлы для этой группы
    ├── 📂 group2
    │   ├── 📄 component1ForGroup2.tsx
    │   ├── 📄 component2ForGroup2.tsx
    │   └── …
    └── 📂 group3 
        ├── 📄 component1ForGroup3.tsx
        └── …

Как видно из примера, в сегментах model и ui повторяется структура папок. В некоторых ситуациях может потребоваться ещё больше сегментов, в которых структура папок будет повторяться.

Будет удобнее группировать файлы по функциональной принадлежности, объединив каждую модель с соответствующим компонентом интерфейса:

📂 slice
├── 📂 subSlice1
│   ├── 📂 model 
│   │   ├── 📄 model1.ts
│   └── 📂 ui 
│       └── 📄 component1.tsx
├── 📂 subSlice2
│   ├── 📂 model2 
│   │   ├── 📄 model1ForSubSlice2.ts 
│   │   └── 📄 model2ForSubSlice2.ts 
│   └── 📂 ui 
│       ├── 📄 component1ForSubSlice2.tsx
│       └── 📄 component2ForSubSlice2.tsx
└── 📂 subSlice3
    ├── 📂 model 
    │   ├── 📄 model3.ts
    └── 📂 ui 
        └── 📄 component3.tsx

Если во вложенном слайсе мало файлов, то можно обойтись без группирования сегментов в папки:

📂 slice
├── 📂 subSlice1
│   ├── 📄 model1.ts
│   └── 📄 component1.tsx
├── 📂 subSlice2
│   ├── 📄 model1ForSubSlice2.ts 
│   ├── 📄 model2ForSubSlice2.ts 
│   ├── 📄 component1ForSubSlice2.ts 
│   └── 📄 component2ForSubSlice2.ts 
└── 📂 subSlice3
    ├── 📄 model3.ts
    └── 📄 component3.tsx

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

Структура слайса для нескольких вариантов одной сущности или очень похожих сущностей

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

Для такой ситуации будет удобнее нарушить FSD и создать вложенные слайсы с кросс‑импортами. Также где‑то должны храниться общие файлы для всех вложенных слайсов. Чтобы при этом не было путаницы в зависимостях между вложенными слайсами, потребуется самому продумывать правила импорта между ними исходя из ситуации.

Должна ли структура папок отражать архитектуру проекта?

Я считаю, что нет. Не так давно наткнулся на статью, автор которой считает так же. Он пишет, что «Чистую архитектуру» многие применяют для структурирования папок проекта, что на его взгляд ошибочно и приводит к плохой структуре папок проекта.

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

Группирования по типам файлов, по сегментам, по уровню абстракции (сущность, фича, виджет, страница) сильно противоречат принципам Low Coupling и High Cohesion. А эти принципы довольно полезны для создания удобных, масштабируемых и менее запутанных структуры и архитектуры проекта. Даже при группировании по функциональности эти принципы будут нарушаться, но в меньшей степени.

Разделение кода на сегменты — правильно, но на уровне архитектуры (в связях между программными сущностями), а не файловой структуры проекта. Связи между программными сущностями не зависят от расположения файлов.

Заключение

В FSD есть как полезные, так и вредные рекомендации. Мои краткие выводы о них:

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

  • Pages‑first — правильное направление развития FSD. Подобное решение успешно применялось задолго до появления FSD.

  • В чате методологии иногда рекомендуют разделять структуру проекта на несколько FSD иерархий (на несколько подпроектов). Считаю правильным разделение большого проекта на подпроекты.

  • Экспериментальная фрактальная структура — отчасти правильное направление развитие методологии. В предлагаемом виде чрезмерно усложнено. В большинстве случаев результат будет лучше, если убрать вложенные слои и папки‑сегменты, оставив вложенные слайсы и файлы‑сегменты.

  • Рекомендация не импортировать из верхних слоёв в нижние — несомненно правильна.

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

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

  • В методологии чрезмерно рекомендуется избегать кросс‑импортов. Но контролируемые кросс‑импорты в умеренном количестве зачастую эффективнее запрета. Они не так уж и страшны, пока всё под контролем и структура получается прозрачной. Кросс‑импорты надо не запрещать, а ограничивать их количество и поддерживать в них порядок. Для контроля связей можно воспользоваться eslint‑plugin‑boundaries или другими линтерами.

  • Ради избегания кросс‑импортов в методологии часто советуют использовать подходы, усложняющие проект — передача зависимостей через пропсы, использование контекста (Context API) вместо импорта и экспорта, вынесение на слои ниже и так далее. Эти подходы хороши в больших проектах и в средних проектах с большим количеством связей. В маленьких проектах и в проектах с малым количеством связей (особенно, если в них не планируется тестов) они лишь замедлят разработку.

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

К сожалению, из‑за отсутствия документированных альтернатив, в русскоязычном сообществе всё чаще используют FSD, несмотря на довольно серьёзные недостатки. В последнее время стали появляться и описываться более гибкие и более универсальные методологии — Evolution Design, Fractal Entity Oriental Design. К тому же, давно есть модульная структура (везде называется по‑разному: Feature‑based, Feature Folders, Folder‑by‑feature), взяв которую за основу, можно получить гораздо лучшую структуру, чем с использованием FSD. Но, к сожалению, она описана только в общих чертах, без подробностей.

Пора бы frontend сообществу перестать усложнять проекты использованием FSD и вместо этого пробовать другие подходы, дорабатывать и документировать более эффективные методологии.

Ссылки на полезные материалы