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

Сгенерированный код меняет повседневную работу. Вы не только создаёте фичи, вы управляете системой, которая может быстро порождать много файлов. Скорость реальна, но мелкие несоответствия быстро множатся.
На первый взгляд сгенерированный результат часто выглядит нормально. Расходы проявляются при втором и третьем изменении: непонятно, где должна жить та или иная часть, вы исправляете одно и то же поведение в двух местах или боитесь править файл, потому что не знаете, что ещё он тронет.
«Хитрая» структура обходится дорого, потому что её трудно предсказать. Кастомные паттерны, скрытая магия и сильная абстракция имеют смысл в первый день. На шестой неделе следующее изменение замедляется, потому что вам придётся заново учить трюк, прежде чем безопасно его обновить. С генерацией, поддерживаемой ИИ, эта хитрость может ещё сильнее запутать будущие поколения кода и привести к дублированию логики или к образованию новых слоёв поверх.
Скучная архитектура — это противоположность: простые границы, простые имена и очевидные дефолты. Речь не о совершенстве. Речь о выборе структуры, которую уставший коллега (или будущий вы) поймёт за 30 секунд.
Простая цель: сделать следующее изменение лёгким, а не впечатляющим. Обычно это означает одно ясное место для каждого типа кода (UI, API, данные, общие утилиты), предсказуемые имена, которые соответствуют тому, что файл делает, и минимум «магии» вроде автосвязывания, скрытых глобалов или метапрограммирования.
Пример: если вы попросите Koder.ai добавить «приглашения в команду», вы захотите, чтобы UI поместили в область UI, добавили один маршрут API в область API и сохранили данные приглашения в слое данных, не изобретая новую папку или паттерн только для этой фичи. Именно такая скучная последовательность держит будущие правки дешёвыми.
Сгенерированный код становится дорогим, когда он даёт много способов сделать одно и то же. Правило скучной архитектуры простое: сделайте следующее изменение предсказуемым, даже если первый результат кажется менее изящным.
Вы должны быстро ответить на эти вопросы:
Выберите одну простую структуру и применяйте её везде. Когда инструмент (или коллега) предлагает модный паттерн, ответ по умолчанию — «нет», если только это не устраняет реальную боль.
Практические дефолты, которые выдерживают испытание временем:
Представьте, что новый разработчик открыл ваш репозиторий и должен добавить кнопку «Отменить подписку». Ему не должно понадобиться сначала изучать кастомную архитектуру. Он должен найти понятную область фичи, компонент UI, одно место для клиента API и один путь доступа к данным.
Это правило особенно хорошо работает с инструментами «vibe‑coding», такими как Koder.ai: вы можете генерировать быстро, но всё равно направлять вывод в одни и те же скучные границы.
Сгенерированный код имеет тенденцию быстро разрастаться. Самый надёжный способ сохранить его поддерживаемым — это скучная карта папок, где любой может догадаться, куда относится изменение.
Небольшая верхнеуровневая структура, подходящая для многих веб‑приложений:
app/ — экраны, маршрутизация и состояние на уровне страницcomponents/ — переиспользуемые UI‑элементыfeatures/ — по одной папке на фичу (billing, projects, settings)api/ — код клиента API и хелперы запросовserver/ — обработчики бэкенда, сервисы и бизнес‑правилаЭто делает границы очевидными: UI живёт в app/ и components/, вызовы API — в api/, логика бэкенда — в server/.
Доступ к данным тоже должен быть скучным. Держите SQL‑запросы и репозитории рядом с бэкендом, а не разбросанными по UI‑файлам. В связке Go + PostgreSQL простое правило: HTTP‑хендлеры вызывают сервисы, сервисы вызывают репозитории, репозитории общаются с базой.
Общие типы и утилиты заслуживают ясного дома, но держите его маленьким. Поместите сквозные типы в types/ (DTO, enum, общие интерфейсы) и мелкие помощники в utils/ (формат даты, простые валидаторы). Если utils/ начинает ощущаться как второе приложение, код, вероятно, принадлежит в папку фичи.
Относитесь к сгенерированным папкам как к заменяемым.
generated/ (или gen/) и избегайте правок в нём напрямую.features/ или server/, чтобы регенерация не перезаписывала её.Пример: если Koder.ai сгенерировал клиент API, храните его под generated/api/, а затем пишите тонкие обёртки в api/, где можно добавить повторные попытки, логирование или понятные сообщения об ошибках, не трогая сгенерированные файлы.
Сгенерированный код легко создать и легко накопить. Именование — вот что делает его читабельным через месяц.
Выберите один стиль именования и не смешивайте его:
kebab-case (user-profile-card.tsx, billing-settings)PascalCase (UserProfileCard)camelCase (getUserProfile)SCREAMING_SNAKE_CASE (MAX_RETRY_COUNT)Называйте по роли, а не по текущей реализации. user-repository.ts — это роль. postgres-user-repository.ts — деталь реализации, которая может измениться. Суффиксы с реализацией используйте только когда действительно есть несколько реализаций.
Избегайте «ящиков с мусором» вроде misc, helpers или огромного utils. Если функция используется только одной фичей, держите её рядом. Если она общая — имя должно описывать способность (date-format.ts, money-format.ts, id-generator.ts) и модуль должен быть маленьким.
Когда роуты, обработчики и компоненты следуют паттерну, вы можете находить вещи без долгого поиска:
routes/users.ts с путями вроде /users/:userIdhandlers/users.get.ts, handlers/users.update.tsservices/user-profile-service.tsrepositories/user-repository.tscomponents/user/UserProfileCard.tsxЕсли вы используете Koder.ai (или любой генератор), вложите эти правила в промпт и сохраняйте их при последующих правках. Суть в предсказуемости: угадав имя файла, будущие изменения остаются дешевыми.
Сгенерированный код может впечатлять в первый день и причинять боль на тридцатый. Выбирайте дефолты, которые делают код очевидным, даже если он немного повторяется.
Начните с уменьшения магии. Пропустите динамическую загрузку, трюки с reflection и автосвязывание, если нет измеримой нужды. Эти механизмы скрывают, откуда берутся вещи, что замедляет отладку и рефакторинг.
Предпочитайте явные импорты и прозрачные зависимости. Если файл нуждается в чём‑то — импортируйте это напрямую. Если модули требуют связывания, делайте это в одном видимом месте (например, в единственном composition-файле). Читатель не должен догадываться, что запускается первым.
Держите конфигурацию скучной и централизованной. Соберите переменные окружения, фич-флаги и глобальные настройки в одном модуле с единым стилем именования. Не разбрасывайте конфиг по случайным файлам потому, что это было удобно.
Правила‑эмпирики для согласованности команды:
Обработка ошибок — место, где хитрости особенно вредны. Выберите один паттерн и используйте его везде: возвращайте структурированные ошибки из слоя данных, мапьте их в HTTP‑ответы в одном месте и переводите в понятные пользователю сообщения на границе UI. Не используйте три разных типа ошибок в зависимости от файла.
Если вы генерируете приложение с помощью Koder.ai, попросите эти дефолты заранее: явная связка модулей, централизованный конфиг и единый паттерн ошибок.
Понятные линии между UI, API и данными держат изменения локализованными. Большинство загадочных багов происходит, когда один слой начинает делать работу другого.
Рассматривайте UI (часто React) как место для рендеринга экранов и управления состоянием, относящимся только к UI: какая вкладка открыта, ошибки формы, индикаторы загрузки и базовая обработка ввода.
Держите состояние сервера отдельно: загруженные списки, кэшированные профили и всё, что должно соответствовать бэкенду. Когда компоненты UI начинают вычислять итоги, валидировать сложные правила или решать права доступа, логика расползается по экранам и становится дорогой в изменении.
Держите слой API предсказуемым. Он должен переводить HTTP‑запросы в вызовы бизнес‑логики и обратно — в стабильные формы request/response. Избегайте отправки моделей базы данных напрямую по проводу. Стабильные ответы позволяют рефакторить внутренности без поломки UI.
Простой путь, который хорошо работает:
Помещайте SQL (или ORM‑логику) за границу репозитория, чтобы остальная часть приложения «не знала», как хранятся данные. В Go + PostgreSQL это обычно означает репозитории вроде UserRepo или InvoiceRepo с небольшими понятными методами (GetByID, ListByAccount, Save).
Конкретный пример: добавление промокодов. UI рендерит поле и показывает обновлённую цену. API принимает code и возвращает {total, discount}. Сервис решает, действителен ли код и как суммируются скидки. Репозиторий читает и сохраняет необходимые строки.
Сгенерированные приложения могут выглядеть «готовыми» быстро, но именно структура делает изменения дешевыми позже. Решите скучные правила сначала, затем генерируйте только столько кода, чтобы их проверить.
Начните с короткой фазы планирования. Если вы используете Koder.ai, Planning Mode — хорошее место, чтобы записать карту папок и несколько правил именования до генерации.
Затем следуйте этой последовательности:
ui/, api/, data/, features/) и несколько правил именования.CONVENTIONS.md и относитесь к нему как к контракту. Когда кодовая база вырастет, менять имена и структуру будет дорого.Проверка реальности: если новый человек не может угадать, куда положить «редактировать контакт», не спрашивая, архитектура всё ещё недостаточно скучная.
Представьте простой CRM: страница со списком контактов и форма редактирования контакта. Вы быстро делаете первую версию, а через неделю нужно добавить «теги» для контактов.
Относитесь к приложению как к трём скучным коробкам: UI, API и данные. Каждой коробке дайте ясные границы и буквальные имена, чтобы изменение «теги» оставалось небольшим.
Чистая раскладка может выглядеть так:
web/src/pages/ContactsPage.tsx и web/src/components/ContactForm.tsxserver/internal/http/contacts_handlers.goserver/internal/service/contacts_service.goserver/internal/repo/contacts_repo.goserver/migrations/Теперь «теги» становятся предсказуемыми. Обновите схему (новая таблица contact_tags или столбец tags), затем касайтесь по одному слою: репозиторий читает/пишет теги, сервис валидирует, хендлер открывает поле, UI рендерит и редактирует его. Не прячьте SQL в обработчиках и не вносите бизнес‑правила в React‑компоненты.
Если продукт позже попросит «фильтр по тегу», вы в основном будете работать в ContactsPage.tsx (состояние UI и query params) и в HTTP‑хендлере (парсинг запроса), в то время как репозиторий обработает сам запрос.
Для тестов и фикстур держите вещи маленькими и близко к коду:
server/internal/service/contacts_service_test.go для правил вроде «имена тегов должны быть уникальны для контакта»server/internal/repo/testdata/ для минимальных фикстурweb/src/components/__tests__/ContactForm.test.tsx для поведения формыЕсли вы экспортируете это из Koder.ai, правило остаётся тем же: держите папки скучными, имена буквальными, и правки перестанут походить на археологию.
Сгенерированный код может выглядеть аккуратно в первый день и всё равно быть дорогим позже. Обычная причина — не «плохой код», а непоследовательность.
Одна дорогая привычка — позволять генератору изобретать структуру каждый раз. Фича появляется со своими папками, стилем имен и вспомогательными функциями, и в итоге у вас оказывается три способа сделать одно и то же. Выберите один паттерн, запишите его и рассматривайте любой новый паттерн как сознательное изменение, а не как дефолт.
Ещё одна ловушка — смешивание слоёв. Когда UI‑компонент обращается к базе данных или API‑хендлер строит SQL, маленькие изменения превращаются в рискованные правки по всему приложению. Держите границы: UI вызывает API, API вызывает сервис, сервис вызывает доступ к данным.
Чрезмерное введение общих абстракций на раннем этапе добавляет стоимость. Универсальные «BaseService» или «Repository» кажутся аккуратными, но ранние абстракции — это догадки. Когда реальность меняется, вы боретесь с собственным фреймворком вместо того, чтобы выпускать фичи.
Постоянные переименования и реорганизации — тихая форма долга. Если файлы перемещаются каждую неделю, люди перестают доверять раскладу, и быстрые исправления попадают в случайные места. Стабилизируйте карту папок сначала, а потом рефакторьте в плановых кусках.
Наконец, будьте осторожны с «платформенным кодом», не имеющим реальных пользователей. Общие библиотеки и самописные инструменты окупаются только когда есть повторяющиеся, подтверждённые потребности. До тех пор держите дефолты простыми.
Если кто‑то новый откроет репозиторий, он должен быстро ответить на вопрос: «Куда я добавлю это?»
Дайте проект коллеге (или будущему себе) и попросите добавить мелкую фичу, например «добавить поле в форму регистрации». Если он не может быстро найти нужное место, структура не делает своей работы.
Проверьте три явных дома:
Если ваша платформа поддерживает это, держите путь отката. Снимки и откат особенно полезны, когда вы экспериментируете со структурой и хотите безопасный способ вернуться назад.
Поддерживаемость растёт быстрее, когда вы перестаёте спорить о стиле и начинаете принимать несколько правил, которые действительно соблюдаются.
Запишите небольшой набор соглашений, которые убирают ежедневные сомнения: куда кладутся файлы, как их называют и как обрабатываются ошибки и конфиг. Держите это достаточно коротким, чтобы можно было прочитать за минуту.
Затем сделайте один проход очистки, чтобы привести код в соответствие с этими правилами, и прекратите еженедельные перестановки. Частая реорганизация делает следующее изменение медленнее, даже если код выглядит аккуратнее.
Если вы строите с Koder.ai (koder.ai), полезно сохранить эти соглашения как стартовый промпт, чтобы каждая новая генерация попадала в ту же структуру. Инструмент может двигаться быстро, но именно скучные границы сохраняют код простым для изменений.