Help us improve
Share bugs, ideas, or general feedback.
From dex-skill-codebase-conventions
Конвенции проекта vs техническое решение — что копировать у соседей. Активируется при новый класс, конвенция, нейминг, sibling, аналог, размещение, структура папок, разнобой, как у соседей, throw vs Result, копировать lifetime, интеграция
How this skill is triggered — by the user, by Claude, or both
Slash command
/dex-skill-codebase-conventions:codebase-conventionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Конвенция — произвольный выбор, который ценен только когда применён единообразно. У произвольного выбора нет правильного ответа в вакууме — есть только «как принято здесь». Это отличает её от технических best-practices (там есть объективная правильность).
Share bugs, ideas, or general feedback.
Конвенция — произвольный выбор, который ценен только когда применён единообразно. У произвольного выбора нет правильного ответа в вакууме — есть только «как принято здесь». Это отличает её от технических best-practices (там есть объективная правильность).
При добавлении нового кода (класс, метод, файл) — сверься с соседними артефактами того же типа в проекте. 5 осей конвенций ниже + мета-граница «что копировать, а что нет».
Плохо: у типа есть и типизированный конструктор, и строковый DSL-парсер (например new Range(from, to) vs Range.Parse("[N:M]")); в проекте сосед использует типизированный путь, новый код — DSL
Правильно: поиск по имени типа — какую форму использует большинство соседей; следовать той же или явно обосновать отступление в коде / MR
Почему: разные формы одного смысла увеличивают когнитивную нагрузку — читателю выяснять «почему здесь иначе». Строковые / DSL-формы переносят ошибки из compile-time в runtime. Конвенция теряет силу с первой копипастой строкового стиля «как у нового места»
Плохо: соседние методы слоя возвращают Result<T> / Either для error-path; новый метод бросает исключение или возвращает null / undefined — или наоборот, везде throw, новый молча возвращает пустое значение
Правильно: посмотреть 2-3 соседних метода того же слоя — какая форма ошибки выбрана (throw vs Result vs nullable vs Option); следовать единой форме во всём слое
Почему: caller строит обработку ошибок под конвенцию слоя — try/catch если throw, pattern-match если Result, null-check если nullable. Один метод в другой форме ломает паттерн вызова, провоцирует пропуск ошибки или дубли try/catch вокруг Result-метода
Плохо: новый DTO с полем по технике (HasImplementation, ProcessedFlag), при этом соседние DTO используют доменные термины (HasOnboarding, IsActivated); новый Handler ProcessDataInLoopHandler при соседях <Domain>CompletedHandler
Правильно: поиск по соседям той же роли (Response / Handler / Repository / Service / Component) — на какой оси они именуются (назначение / доменное событие / техническое действие); следовать той же оси
Почему: соседи по слою задают ось нейминга в проекте. Имя на другой оси выпадает из ряда — читатель ожидает один словарь в одном слое. Имя по реализации устаревает при рефакторинге внутренней ветки, имя по назначению переживает изменения
Плохо: в проекте есть OrderNotFoundError, OrderInvalidStateError (или эквивалент в любом языке) — новый код кидает generic-исключение со строкой (InvalidOperationException("Order not in Draft"), Error("invalid"), RuntimeError("...")) вместо доменного типа
Правильно: посмотреть существующие доменные исключения / коды ошибок проекта — использовать существующий или добавить рядом, по той же конвенции (наследование, namespace / module, naming)
Почему: тип ошибки — часть API слоя. Caller catch (OrderInvalidStateError) не сработает на generic-исключении со строкой; обработка в API-слое теряет специфичность. Доменный словарь работает только пока пронизывает все слои в bounded context
Плохо: все async-методы слоя принимают токен отмены / контекст последним параметром (CancellationToken ct, AbortSignal signal, context.Context ctx); новый метод объявлен без него или с другим именем (token, cancel); соседи везде с суффиксом (Async, _async), новый — без
Правильно: посмотреть 2-3 соседних метода того же слоя — есть ли общая ось сигнатуры (отмена, логгер, корреляционный id, шейп запроса/ответа, суффикс); следовать ей
Почему: пайплайны (cancellation, tracing, retry) полагаются на однородность сигнатуры — один метод без токена отмены ломает graceful shutdown цепочки. Разный нейминг параметра / суффикс ломает auto-complete и кодогенерацию. Однородность слоя — основа для всего pipeline-кода
Плохо: все Validator'ы наследуют общий базовый тип (AbstractValidator<T>, BaseValidator, Schema) — новый ручной без базы; все Controller'ы помечены маркерным атрибутом / декоратором ([ApiController], @Controller, @app.route), новый без; все DTO в слое — иммутабельные (record, Readonly<T>, frozen dataclass), новый — мутабельный
Правильно: посмотреть 2-3 соседних артефакта того же типа на предмет общего базового типа, обязательных атрибутов / декораторов, иммутабельности; следовать единому правилу
Почему: общее правило слоя — это договор для инфраструктуры (auth-middleware ищет маркер, validation-pipeline ищет базовый тип, маппер ожидает иммутабельность). Артефакт вне правила выпадает из инфраструктуры — без auth, без валидации, с нежелательной мутабельностью. Один из ряда обесценивает остальной ряд
Плохо: новый Handler в Application/Services/, при этом все Handler'ы проекта лежат в Application/Handlers/<Feature>/; новый компонент в одной папке с моделями, остальные в components/
Правильно: перед созданием файла — find или дерево по ключевому суффиксу / роли (*Handler.*, *Validator.*, *Repository.*, *.component.*); положить рядом с соседями того же типа
Почему: разработчик ищет файлы по конвенции, не по «найти везде» каждый раз. Файл «не там» невидим при ревью соседних изменений, дублируется при следующем рефакторинге («не нашёл — создал заново»), ломает auto-discovery (DI-сканеры по namespace, build-globs, namespace = папка)
Плохо: валидация запроса размазана между Controller, Handler и Validator — у соседей вся валидация в *Validator через pipeline / middleware; маппинг entity → DTO разбросан в каждом Handler — у соседей через общий профиль / extension toDto()
Правильно: посмотреть, где у соседей живут одинаковые обязанности (валидация, маппинг, авторизация, логирование) — следовать тому же месту
Почему: одинаковая обязанность в одном месте — основа для cross-cutting изменений (поменять формат ошибки валидации, добавить поле в DTO). Размытое расположение требует править N мест, легко забыть одно. Параллельные стили — постоянный спор «куда писать новое»
Плохо: HTTP-клиент через фабрику / shared instance у соседей (IHttpClientFactory, axios.create(), requests.Session), новый код — new HttpClient() / fetch() / requests.get() напрямую; соседи читают конфиг через типизированный provider (IOptions<T>, convict, pydantic-settings), новый — прямое чтение env / json; в слое везде категорированный логгер, новый файл — глобальный
Правильно: посмотреть, как у соседей подключён тот же тип ресурса (HTTP / config / log / cache / persistence / messaging) — следовать тому же подходу
Почему: способ интеграции включает невидимые сбоку контракты — фабрика клиента даёт DNS-обновление и обёртку retry/circuit-breaker, типизированный конфиг даёт hot-reload и валидацию, категорированный логгер даёт scope. Уход с конвенции отрезает новый код от этих контрактов — отдельный клиент течёт сокетами, прямой доступ к env не валидируется
Плохо: соседние options регистрируются через типизированную секцию конфига (services.Configure<T>(GetSection), ConfigModule.forFeature, pydantic BaseSettings); новый — singleton с захардкоженными значениями; соседние модули регистрируются через extension / barrel (AddOrderModule(), OrderModule, register_router), новый — отдельные регистрации в композиционном корне
Правильно: посмотреть, как соседи регистрируют тот же класс артефактов (options, сервисы, hosted services, декораторы / interceptors) — следовать единому стилю композиции
Почему: единый стиль регистрации даёт предсказуемые точки расширения — добавить декоратор / interceptor / валидатор на все нужные сервисы можно правкой одного места. Разный стиль регистрации требует обходить все места поодиночке, легко пропустить новое. Hardcoded options теряют конфигурируемость по средам
Плохо: «здесь у соседа сервис зарегистрирован как Singleton — делаю так же» — без проверки, что Singleton корректен для нового кода (нет mutable state, нет captive dependency, thread-safe); «у соседа throw, и я throw» — без проверки, нужна ли клиенту обработка через Result; «сосед async, и я async» — хотя реальной IO-операции нет
Правильно: разделять две оси. Конвенции (произвольный выбор, валиден пока единый) — копируем у соседей: форма представления, нейминг, структура папок, сигнатура слоя, способ интеграции. Технические решения (валидность зависит от свойств кода) — обосновываем под новый код, не «как у соседей»: lifetime DI, throw vs Result для конкретного контракта, sync vs async, выбор exception type для семантики
Почему: «как у соседей» работает для конвенций — там нет «правильно» вне проекта, единство и есть ценность. Не работает для технических решений — там валидность зависит от свойств конкретного кода. Слепое копирование Singleton наследует captive dependency или race condition; копирование throw наследует утечку технического исключения в API. Перед копированием — спросить «это произвольный выбор или решение под требования?»
npx claudepluginhub dex-it/claude-code-marketplace --plugin dex-skill-codebase-conventionsProvides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.