name: code-quality description: Жёсткая оценка качества Unity-кода (пакета/системы/подсистемы) по канонным формулам с архитектурным множителем. Триггеры — «оцени качество кода», «прогони по критериям оценки», «проверь пакет на качество», «оцени систему X». Размер кода определяет режим: детальный анализ (≤3000 LOC), детальный по подгруппам (3000–12000 LOC при естественном делении на 2–4 равные группы), частичный (≤12000) или субъективный обзор (>12000). Баллы не смягчаются — объём фикса считается отдельно.
Оценка качества кода (Unity)
Платформа: Claude Code (CLI от Anthropic). Размещать в
~/.claude/skills/code-quality/SKILL.md(user-level) или.claude/skills/code-quality/SKILL.md(project-level). Использует инструментыAgent(subagent),Bash,Grep,Read,Edit. На других LLM-платформах (Cursor, Continue, OpenAI Assistants) требует адаптации формата делегирования.
Скилл самодостаточен: канон, формулы, маркеры, чек-листы и итоговая формула — всё прописано ниже. Внешние ссылки не нужны.
На вход — целевой пакет/система/подсистема. На выходе — оценка по формулам с архитектурным множителем + чек-лист маркеров и red-flag'ов, либо явное предупреждение о невозможности применить канон.
0. Триггер и проверка контекста
Запускается, когда пользователь просит оценить качество кода конкретного пакета/системы.
Если цель не указана явно — уточнить:
«Какой пакет/система оценивается? Канон применим к одному пакету ≤3000 LOC; для большего объёма буду предупреждать о деградации точности.»
Без указания цели скилл НЕ выполняется.
1. Шаг 0 — измерение размера (всегда)
Перед любым анализом измерить LOC всех релевантных .cs файлов.
Что считается в LOC
- Включаем:
.csфайлы в целевой папке. - Исключаем:
*.Designer.cs, любые сгенерированные файлы (содержат маркер// <auto-generated>), тестовые сборки (*Tests*,*.Tests.*,Editor/Tests/),.meta-файлы. - LOC =
wc -l(с пустыми строками и комментариями). Для гейтинга порога этого достаточно — точная NCLOC не нужна.
Команды (по платформе)
bash / git-bash / WSL:
find <target-path> -name "*.cs" \
-not -name "*.Designer.cs" \
-not -path "*/Tests/*" -not -path "*Tests*" \
-exec wc -l {} + | tail -1
PowerShell (Windows):
Get-ChildItem -Path <target-path> -Filter *.cs -Recurse |
Where-Object {
$_.Name -notmatch '\.Designer\.cs$' -and
$_.FullName -notmatch '[\\/](Tests|.*Tests.*)[\\/]'
} |
Get-Content | Measure-Object -Line
Делегирование подсчёта
Подсчёт LOC выполнять через дешёвого агента, не в основном контексте:
- Запустить
Agentсsubagent_type=Explore(илиgeneral-purposeсmodel=haiku). - Задача: «Посчитай суммарный LOC всех
.csфайлов в<target-path>с исключениями выше. Верни одно число.»
Это экономит контекст основной сессии — инвентаризация не должна занимать его место.
2. Развилка по размеру
Развилка идёт только по LOC. Количество asmdef/папок верхнего уровня не влияет: один пакет может состоять из нескольких сборок, и при малом объёме его всё равно можно проанализировать детально.
Ветка A — детальный анализ
Условие: ≤3000 LOC.
→ Раздел 3 (полный канон + протокол).
Ветка A+ — разбиение на подгруппы под порог детального анализа
Условие: 3000–12000 LOC, и подсистема естественно делится на 2–4 смысловые группы, каждая ≤3000 LOC.
Жёсткие ограничения деления:
- Не более 4 групп (выше — дилюция сигнала через усреднение делает оценку недостоверной).
- Размеры групп сопоставимы:
max_LOC / min_LOC ≤ 3. Иначе крупная группа доминирует, мелкие тонут в среднем — переходить к ветке B. - Границы естественные (типы акторов, бэкенды/реализации, ядро/контракты/реализации). Если деление искусственное (раздробит классы одной зоны ответственности) — ветка B.
Протокол:
- Предложить разбиение с LOC по группам, проверкой соотношения size_ratio и обоснованием границ. Дождаться подтверждения пользователя.
- По каждой группе провести полный детальный анализ ветки A (раздел 3) — параллельно через
Agentсmodel=sonnet. - Свести результаты:
- Категории 1–4 — среднее арифметическое баллов по группам.
- Архитектурные маркеры — объединение с дедупом по типу маркера (одна и та же «Ложная точка расширения» в G1/G2/G3 = 1 единица в
arch, но в отчёте перечислить все проявления). Повторение одного типа в нескольких группах не умножает счётчик — каждый тип засчитывается один раз. - Red-флаги — аналогично с дедупом по типу, без мультипликации за совпадение в подсистемах.
- Yellow-флаги — объединение, информационно.
- Применить итоговую формулу к усреднённым категориям с объединёнными
arch/red(раздел 3.7).
→ Раздел 4.
Ветка B — частичный анализ (с предупреждением)
Условие: 3000–12000 LOC, разбиение невозможно или нецелесообразно (искусственные границы, неравные размеры, >4 групп).
→ Раздел 5.
Ветка C — субъективный обзор (с предупреждением)
Условие: >12000 LOC.
→ Раздел 6.
3. Ветка A — детальный анализ (полный канон)
3.1 Порядок чтения (критичен)
- Сначала интерфейсы и базовые классы — здесь живут архитектурные проблемы.
- Затем реализации — на соответствие контрактам.
- Затем endpoint-классы — поля (с суммированием по иерархии), цикломатика.
- В конце — количественные метрики по формулам.
Не начинать с подсчёта файлов и LOC как самоцели — это создаёт иллюзию прогресса при нулевом анализе. Инвентаризация (Шаг 0 + сбор счётчиков) — параллельно с чтением, как сбор данных для формул.
Делегирование инвентаря (Haiku): перед чтением запустить
Agentсsubagent_type=Exploreилиgeneral-purposeсmodel=haikuсо сводной задачей:
- сосчитать число
.csфайлов, классов, интерфейсов, структур, enum'ов;- для каждого
public interfaceвернуть имя и число членов ({ get,void,Task,UniTask);- вернуть строки документации (
///-комментарии) с суммой;- вернуть в виде таблицы.
Не делегировать: классификацию классов на endpoint / нейтральный / с логикой и подсчёт цикломатики по методам — это семантические задачи, выполняются основным контекстом по чтению кода.
3.2 Глубокое чтение (обязательное)
- Каждый публичный интерфейс — на ISP (число членов, смешение ответственностей).
- Каждый
as ConcreteType— на ложную расширяемость. - Каждый
async void— красный флаг (исключения: Unity event-handlers, input-handlers). - Каждый endpoint-класс — поля с суммированием базовых классов.
- Каждый override — не пустая ли заглушка.
3.3 Pre-check реальной расширяемости (перед скорингом 2.x)
Наличие интерфейса и virtual-методов само по себе не означает расширяемости. Перед скорингом 2.1–2.3 прогнать grep'ы.
Плейсхолдеры:
<ConcreteServiceName>— подставить реальное имя класса оцениваемого сервиса (например,UIManager,ScriptPlayer). Прогон с буквальной строкойConcreteServiceNameбессмысленен.
bash:
grep -rE "class \w+ : <ConcreteServiceName>\b" <target-path> # есть ли наследники?
grep -rE "GetService<<ConcreteServiceName>>" <target-path> # ходят через concrete?
grep -rE "GetService<I[A-Z]\w+>" <target-path> # vs через интерфейс?
grep -rE "InitializeAtRuntime\([^)]*typeof\(" <target-path> # механизм подмены: typeof(Concrete)?
grep -rE "OverrideService\b" <target-path>
PowerShell:
Select-String -Path <target-path>\*.cs -Pattern "class \w+ : <ConcreteServiceName>\b" -Recurse
Select-String -Path <target-path>\*.cs -Pattern "GetService<<ConcreteServiceName>>" -Recurse
Select-String -Path <target-path>\*.cs -Pattern "GetService<I[A-Z]\w+>" -Recurse
Select-String -Path <target-path>\*.cs -Pattern "InitializeAtRuntime\([^)]*typeof\(" -Recurse
Select-String -Path <target-path>\*.cs -Pattern "OverrideService\b" -Recurse
Делегирование: все grep-подсчёты — через дешёвого агента (Haiku) с задачей «прогони шаблоны и верни числа совпадений по каждому». В основном контексте остаются только цифры, не сырые матчи.
Интерпретация:
- 0 наследников + 0 ходов через concrete → расширяемость декларирована, не валидирована (фасадная). Балл по 2.1, 2.3 не выше минимума.
- ходы через concrete > 0 одновременно с интерфейсом → концертная связность утечкой через интерфейсный фасад.
- механизм подмены работает по
typeof(ConcreteClass)→ единица замены — класс, не интерфейс (см. маркер «Кустовая обязательность контрактов»).
3.4 Применение формул (жёстко)
Каждое снижение балла привязано к посчитанному счётчику нарушений с ссылкой на файл/строку. Если данных нет — n/a, нужны данные, не аппроксимировать.
Категория 1. Документируемость (0–10)
| Критерий | Формула | Баллы |
|---|---|---|
| 1.1 Строки документации / системы | 4 × log₁₀(docs_lines / systems + 1) / log₁₀(1000) |
0–4 |
| 1.2 Комментарии / объекты | Норма: 1/8 для полей/методов, 1/3 для классов | 0–3 |
| 1.3 Контракты и примеры | 3 × min(1, detailed_docs × completeness / systems) |
0–3 |
Делегирование (Haiku): подсчёт
docs_lines(строки///), числа классов, методов, полей — черезAgentсsubagent_type=Exploreилиgeneral-purposeсmodel=haiku. Возвращать только числа.
Категория 2. Масштабируемость и гибкость (0–10)
Pre-check (раздел 3.3) обязателен ДО выставления баллов 2.1–2.3.
| Критерий | Формула | Баллы |
|---|---|---|
| 2.1 Нейтральные классы / всего (без endpoint) | 1.5 × log₁₀(neutral / (total - endpoint) × 100 + 1) / 2 |
0–2.5 |
| 2.2 Атомарность (среднее полей на endpoint) | 2 × log₁₀(30 / avg_fields), норма = 3–5 полей |
0–2 |
| 2.3 Чистота паттернов | 1.5 × clean_patterns / detected_patterns |
0–1.5 |
| 2.4 Однонаправленность ссылок | 2 × (1 - violations / total_dependencies) |
0–2 |
| 2.5 Глубина наследования (DIT) | 1 × max(0, 1 - max(0,(avg_depth - 1) / 4)) |
0–1 |
| 2.6 Цикломатическая сложность | 1 × max(0, 1 - max(0,(avg_complexity - 5) / 15)) |
0–1 |
Определения:
- endpoint-класс: класс без ссылок на другие классы с логикой (чистые модели допустимы), кроме компонентов движка (Button, Text, Image и т.п.). Render-классы, работающие с GUIStyle/EditorGUI, в endpoint не входят.
- нейтральный класс: класс без ссылок на другие классы с логикой, кроме ссылок через интерфейсы или на чистые модели данных. Шины данных-синглтоны считаются как 0.5.
- DIT (Depth of Inheritance Tree): количество уровней наследования от базового класса.
- Цикломатическая сложность: количество независимых путей в методе (
if/else/switch/for/while/catch/?:).
Важно:
- При проверке чистоты паттернов обязательно проверять сброс Lazy-кешированных параметров.
- Кол-во полей в классе считать с учётом предков (суммирование по иерархии).
Делегирование (по уровню):
- Haiku — число
public interfaceс количеством членов (Fat Interface ≥15), DIT по цепочкамclass X : Y(безMonoBehaviour/ScriptableObject).- Sonnet — подсчёт полей в endpoint-классах с суммированием по иерархии: задача требует разворачивания цепочки
: BaseClassи сложения. Запускать черезAgentсmodel=sonnet.- Не делегировать — классификацию классов (endpoint / нейтральный / с логикой) и подсчёт цикломатики по методам. Это семантические задачи, выполняются основным контекстом.
Категория 3. Поддерживаемость (0–10)
| Критерий | Формула | Баллы |
|---|---|---|
| 3.1 Средний размер класса (LOC) | 2 × min(1, max(0, 1 - log₂(avg_lines / 100) / 3)) |
0–2 |
| 3.2 Системы / классы (без интерфейсов и моделей) | Чем больше — тем лучше | 0–2 |
| 3.3 Системы / константы | min(1.5, 1.5 × max(0,(1.3 - (constants / system)^(1/3)))) |
0–1.5 |
| 3.4 Документируемость % | 2 × (doc_score / 10) |
0–1.5 |
| 3.5 Дублирование кода (DRY) | 2 × max(0, 1 - duplicate_blocks / total_methods) |
0–2 |
| 3.6 Магические значения | 1 × max(0, 1 - magic_numbers / (systems × 10)) |
0–1 |
Определения:
- duplicate_blocks: блоки ≥5 строк с совпадением ≥80%.
- magic_numbers: числовые литералы кроме
0, 1, -1, 2.
Делегирование (Haiku): подсчёт
magic_numbers(regex[^0-9][3-9][0-9]*[^0-9f]|[^0-9][0-9]{2,}), числаconst-объявлений на систему, средний LOC класса. Также — запускnpx jscpd --min-lines 5 --threshold 80и парсинг отчёта в числоduplicate_blocks/total_methods.
Категория 4. Производительность архитектуры (0–10)
| Критерий | Формула | Баллы |
|---|---|---|
| 4.1 Аллокации в hot path | 4 × max(0, 1 - new_in_update / update_methods) |
0–4 |
| 4.2 Кеширование GetComponent | 3 × (cached_getcomponent / total_getcomponent) |
0–3 |
| 4.3 String операции в циклах | 3 × max(0, 1 - string_concat_in_loops / total_loops) |
0–3 |
Определения:
- new_in_update: использование
newвнутриUpdate/FixedUpdate/LateUpdate/OnGUI. - cached_getcomponent:
GetComponentвызванный вAwake/Start/OnEnableи сохранённый в поле. - total_getcomponent: все вызовы
GetComponent. - string_concat_in_loops: конкатенация строк (
+,+=) внутриfor/while/foreach.
Делегирование (Haiku):
new_in_update,total_getcomponent,cached_getcomponent(с проверкой контекста —Awake/Start/OnEnablevsUpdate/FixedUpdate/LateUpdate),string_concat_in_loops. Все четыре — детерминированный grep с-A/-Bконтекстом + классификация по методу-контейнеру. Возвращать только числа.
3.5 Прогон чек-листов (Шаги 2 и 3 канона)
Маркеры архитектурных проблем (подсчёт arch)
По каждому маркеру — явный grep или ссылка на код, подтверждающие наличие.
Ложная точка расширения
- Условие: есть публичный интерфейс IA, реализация A работает с конкретным классом B без интерфейса IB.
- Признак:
as ConcreteClassпосле загрузки «расширяемого» компонента, при этом ConcreteClass обязательно реализует интерфейс IB. - Проблема: подмена верхнего уровня бесполезна без переписывания внутренностей.
Ложная точка расширения (детализация)
- Класс A (с интерфейсом IA) использует класс B напрямую, хотя B имеет интерфейс IB в той же системе.
- Ключевой вопрос: «Есть ли у B интерфейс в этой системе?»
- Да, есть IB → использование B вместо IB = ПРОБЛЕМА.
- Нет интерфейса → прямая зависимость = честная связка (ОК).
- B это endpoint движка (Image, Button) → всегда ОК.
Кустовая обязательность контрактов
- Условие: класс A декларирован как реализация IA, но фактически реализует ещё несколько публичных контрактов IB, IC, ID, потребляемых другими сервисами напрямую. Их обязательность не зафиксирована в IA.
- Признак (механический):
- Концерт-класс A реализует более одного публичного интерфейса.
grep -rE "GetService<I[A-Z]\w+>"находит обращения через IB, IC, ID помимо IA.- Механизм подмены движка работает по
typeof(A)(концерт), не поtypeof(IA). - Документация IA не упоминает обязательность IB/IC/ID.
- A экспонирует public-state в формате конкретного типа (например
Stack<T>,Dictionary<K,V>) с неявным сериализационным контрактом.
- Проблема: «один интерфейс снаружи» — фактически куст из N контрактов внутри. Заместитель обязан реализовать ВСЕ, и список нигде не зафиксирован.
- Лечение: либо вынести IB/IC/ID в IA (расширить декларацию), либо ввести явный композитный контракт
IService = IA + IB + IC.
Смешение Model/View
- Признак: интерфейс содержит и Position/Rotation/Scale, и бизнес-данные.
- Проблема: нельзя реализовать логику без реализации визуала.
Fat Interface
- Признак: 15+ членов в интерфейсе.
- Проблема: нельзя реализовать часть — только всё или ничего.
Перегруженность настроек
- Признак: 20+ полей в Configuration/Metadata классе.
- Проблема: невозможно понять взаимосвязи без документации.
Низкая Discoverability
- Признак: базовая операция требует неочевидного пути.
- Пример: «Перейти к следующей реплике» →
InputManager.GetContinue().AddObjectTrigger(gameObject). - Проблема: решение нельзя найти через автодополнение или интерфейсы.
Критические red-flag'и (подсчёт red)
- [ ] Цикломатическая сложность >20 в любом методе.
- [ ] DIT >5 уровней.
- [ ] Fat interface >15 членов.
- [ ]
new/InstantiateвUpdateбез пула. - [ ]
GetComponentв каждом кадре. - [ ] >30 полей в endpoint-классе.
- [ ]
async voidна публичных методах (исключения: Unity event-handlers и обработчики input — там это вынужденная сигнатура).
Делегирование (Haiku) — детектирование маркеров с регулярной сигнатурой:
async voidна публичных методах:public\s+async\s+void\s+\w+.- Fat Interface: для каждого
public interfaceпосчитать число{ get/void/Task/UniTaskмежду{и}, вернуть имена с count ≥15.- Пустые override-заглушки:
override\s+\w+\s+\w+\([^)]*\)\s*\{\s*\}и=>\s*base\.\w+\([^)]*\);.- DIT-цепочки:
class \w+ : \w+с фильтромMonoBehaviour/ScriptableObject, построение цепочек до >5 уровней.new/InstantiateвUpdate-семействе иGetComponentвUpdate— через grep с-A 30контекстом.Не делегировать: цикломатику по методам и идентификацию endpoint-классов с >30 полей (нужно знание классификации классов из 3.4).
Жёлтые флаги (для информации, в множитель НЕ идут — учтены в базовом балле через 3.5/3.6/4.x)
- [ ] Пустые XML-теги документации.
- [ ] Магические числа без констант.
- [ ] Дублирование >10%.
- [ ] String конкатенация в циклах.
- [ ] Статические контроллеры с состоянием.
- [ ] Пустые override-заглушки (
override void Foo() { }или=> base.Foo()без изменений) — маркер раздутого базового класса.
3.6 Быстрые grep'ы (использовать через дешёвого агента)
Делегирование: прогон этих grep'ов — через
subagent_type=Exploreилиgeneral-purposeсmodel=haiku. Возвращать только числа совпадений, не сырые матчи.
bash:
# Ложные точки расширения
grep -rE "as [A-Z][a-z]+[A-Z]" <target-path>
# Перегруженность
grep -rc "\[SerializeField\]" <target-path>
grep -rc "\[Tooltip\]" <target-path>
# Fat interfaces
grep -rA 50 "public interface I" <target-path> | grep -c "{ get\|void \|UniTask "
# Магические строки
grep -rc "const string" <target-path>
# Скрытые зависимости
grep -rc "Engine.GetService\|GetServiceOrErr" <target-path>
# Глубина наследования
grep -rE "class \w+ : \w+ " <target-path> | grep -v "MonoBehaviour\|ScriptableObject"
# Цикломатическая сложность (приблизительно)
grep -rc "if\|else\|switch\|case\|for\|while\|foreach\|catch\|\?.*:" <target-path>
# Аллокации в Update
grep -rn "void Update\|void FixedUpdate" -A 30 <target-path> | grep "new \|Instantiate"
# GetComponent без кеширования
grep -rn "void Update\|void FixedUpdate" -A 30 <target-path> | grep "GetComponent"
# String concat в циклах
grep -rB5 -A5 "for\|while\|foreach" <target-path> | grep '\".*+\|+=.*\"'
# Дублирование (внешний инструмент)
npx jscpd --min-lines 5 --threshold 80 <target-path>
PowerShell:
# Замена grep -rc:
(Select-String -Path <target-path>\*.cs -Pattern "\[SerializeField\]" -Recurse).Count
(Select-String -Path <target-path>\*.cs -Pattern "const string" -Recurse).Count
(Select-String -Path <target-path>\*.cs -Pattern "Engine\.GetService|GetServiceOrErr" -Recurse).Count
# Глубина наследования (без MonoBehaviour/ScriptableObject):
Select-String -Path <target-path>\*.cs -Pattern "class \w+ : \w+ " -Recurse |
Where-Object { $_.Line -notmatch "MonoBehaviour|ScriptableObject" }
# async void поиск (red-flag):
Select-String -Path <target-path>\*.cs -Pattern "public\s+async\s+void\s+\w+" -Recurse
3.7 Итоговая формула
Базовый = (D/10 + S/10 + M/10 + P/20) / 3.5 × 10
Итог = Базовый × 0.5^arch × 0.9^red
Правило дизамбигуации: проблема, упомянутая и в архитектурных маркерах, и в критических red-flag'ах (например Fat Interface), считается один раз — в более тяжёлой категории (архитектурной).
Yellow-флаги отдельным множителем не учитываются — они уже заложены в базовый балл через формулы 3.5 (DRY), 3.6 (магические числа), 4.x (perf-аллокации).
Делегирование (Haiku): агрегацию
archиredпосле того, как маркеры выписаны в отчёт, можно поручить дешёвому агенту с задачей «пройди по списку выписанных маркеров, верни два числа: сколько архитектурных, сколько red-flag, с учётом правила дизамбигуации». Применение самой формулы — основной контекст.
Чувствительность множителя
| arch | red | Множитель |
|---|---|---|
| 0 | 0 | 1.000 |
| 0 | 1 | 0.900 |
| 0 | 2 | 0.810 |
| 0 | 3 | 0.729 |
| 1 | 0 | 0.500 |
| 1 | 1 | 0.450 |
| 2 | 0 | 0.250 |
| 2 | 1 | 0.225 |
| 3 | 0 | 0.125 |
Каждая архитектурная проблема — пополам, каждый red — минус 10%. Архитектурный штраф резкий намеренно: эти проблемы не лечатся коммитом.
3.8 Шкалы оценки (для интерпретации формул)
Атомарность (полей на endpoint):
| Полей | Восприятие | Балл |
|---|---|---|
| 3–5 | Понятно с первого взгляда | 1.5–2.0 |
| 10 | Нужно вчитаться | 1.0 |
| 20 | Нужна документация | 0.4 |
| 30+ | Архитектурный провал | 0 |
DIT:
| Уровней | Оценка | Балл |
|---|---|---|
| 1–2 | Хорошо | 0.75–1.0 |
| 3–4 | Допустимо | 0.25–0.5 |
| 5+ | Проблема | 0 |
Цикломатическая сложность:
| Сложность | Оценка | Балл |
|---|---|---|
| 1–5 | Простой метод | 1.0 |
| 6–10 | Умеренный | 0.5–0.75 |
| 11–20 | Сложный | 0.1–0.4 |
| 20+ | Требует рефакторинга | 0 |
3.9 Эталонные значения
| Метрика | Плохо | Норма | Хорошо |
|---|---|---|---|
| Полей на endpoint | 30+ | 10–15 | 3–5 |
| Членов в интерфейсе | 15+ | 7–10 | 3–5 |
| Классов на систему | 50+ | 15–25 | 5–10 |
| LOC на класс | 500+ или <10 | 150–250 или 20–40 | 50–100 |
| Констант на систему | 20+ | 5–10 | 1–3 |
| DIT | 5+ | 3–4 | 1–2 |
| Цикломатика | 20+ | 6–10 | 1–5 |
| Дублирование | >20% | 5–10% | <5% |
new в Update |
>50% | 10–30% | 0% |
3.10 Sanity check
- Сравнить Базовый и Итог.
- Перечитать выписанные нарушения. Каждое нарушение должно быть привязано к конкретному файлу/строке — иначе оценка невалидна.
- Большой разрыв Базовый ↔ Итог = архитектурные дефекты, локально невидимые. Это обязательно вывести в финальном блоке отдельным комментарием «Диагностический сигнал», см. формат вывода (раздел 6).
3.11 Что нельзя оценить по коду (вынести в финальный блок)
| Метрика | Как получить |
|---|---|
| Time to feature | Засечь на реальной задаче |
| Time to change | Попросить изменить неожиданное |
| Onboarding time | Дать новому разработчику задачу |
| Bug density | Статистика за месяцы |
| Реальная расширяемость | Попытаться расширить |
4. Ветка A+ — разбиение на подгруппы
Условие: 3000–9000 LOC, естественное деление на 2–3 группы ≤3000 LOC каждая.
4.1 Предложение разбиения
Перед запуском анализа вывести структуру разбиения с обоснованием:
Предлагаю разбить <target> (X LOC) на N групп:
- Группа 1 — <название> (Y₁ LOC): <состав, обоснование>
- Группа 2 — <название> (Y₂ LOC): <состав, обоснование>
- Группа 3 — <название> (Y₃ LOC): <состав, обоснование>
Сумма: Y₁+Y₂+Y₃ = X ✓ (все < 3000)
Каждая группа = отдельный полный анализ по канону (ветка A).
Итоговые баллы — среднее по 3 группам, флаги — объединение с дедупом.
Подтвердить?
Дождаться подтверждения. Без подтверждения — не запускать.
Критерий «естественности» деления:
- группы соответствуют разным зонам ответственности (ядро vs контракты vs реализации; разные типы данных; разные бэкенды);
- классы одной зоны ответственности не разрезаются между группами (если
ManagerXв G1,MetadataXиStateXтоже в G1); - базовые классы и контракты идут в более раннюю группу, реализации — в более позднюю.
Если группы неровные (одна 2900, другие по 100) — деление искусственное, лучше ветка B.
4.2 Параллельный запуск
Agent (subagent_type=general-purpose, model=sonnet) × N
prompt: «Полный анализ группы по канону code-quality (раздел 3 SKILL.md):
файлы [...], шаги 0–5, формат вывода — структурированный markdown
с разделами Инвентарь / Формулы / Pre-check / Архитектурные маркеры
/ Red / Yellow / Итог»
run_in_background: true
Каждый агент должен вернуть по канону раздела 3 полный отчёт со всеми категориями и обнаруженными маркерами. Никаких сокращений.
4.3 Сведение результатов
Усреднение баллов по категориям
Документируемость = avg(D₁, D₂, …, Dₙ)
Масштабируемость = avg(S₁, S₂, …, Sₙ)
Поддерживаемость = avg(M₁, M₂, …, Mₙ)
Производительность = avg(P₁, P₂, …, Pₙ)
Базовый = (D/10 + S/10 + M/10 + P/20) / 3.5 × 10
Дедуп архитектурных маркеров
Маркер идентифицируется типом (Ложная точка расширения, Кустовая обязательность, Смешение Model/View, Fat Interface, Перегруженность настроек, Discoverability), не конкретным проявлением.
- Если один и тот же тип маркера найден в G1, G2, G3 — это 1 маркер в
arch, но в отчёте перечислить все проявления. - Если в одной группе
Fat Interface— этоIActor, в другой —ITextPrinterActor, это всё равно 1 маркер «Fat Interface», просто с двумя проявлениями. - Никакой systemic-поправки нет: повторение типа в нескольких группах счётчик не умножает.
Дедуп red-флагов
Аналогично — по типу (cyclo>20, DIT>5, Fat Interface, new в Update, GetComponent в кадре, >30 полей в endpoint, async void). Без мультипликации за повтор.
Yellow-флаги
Объединение всех проявлений по группам без дедупа (информационно). В множитель не идут.
4.4 Итоговая формула
Итог = Базовый × 0.5^arch_dedup × 0.9^red_dedup
Где arch_dedup, red_dedup — счётчики после дедупа по типам.
4.5 Формат вывода
[Ветка A+] Сводная оценка <target>
- LOC: <total>
- Группы: G1 (Y₁), G2 (Y₂), G3 (Y₃)
- Применимость канона: полная (по подгруппам)
## Усреднённые баллы
| Категория | G1 | G2 | … | Среднее |
| ... |
## Объединённые арх. маркеры (дедуп)
| # | Маркер | Где зафиксирован |
| 1 | Ложная точка расширения | G1: …; G2: …; G3: … |
| ... |
## Объединённые red-флаги (дедуп)
…
## Объединённые yellow-флаги
…
## Итог
- Множитель: 0.5^arch × 0.9^red = …
- Базовый: X
- Итог: X
- Интерпретация: <по шкале>
## Диагностический сигнал
<если разрыв Базовый ↔ Итог большой — комментарий>
5. Ветка B — частичный анализ (с предупреждением)
Условие: 3000–10000 LOC.
Перед началом ОБЯЗАТЕЛЬНО вывести:
⚠️ Предупреждение: объём кода превышает порог детального анализа (>3000 LOC). Количественные метрики (формулы 1.x–4.x) применимы, но архитектурные маркеры могут быть пропущены —
async void, ложная расширяемость, Fat Interface требуют построчного чтения, которое на этом объёме неполное. Оценка достоверна по формулам; маркеры — выборочно.
Затем:
- Применить формулы количественно (раздел 3.4 — все категории 1.x–4.x).
- Маркеры архитектурных проблем — прогнать выборочно через grep'ы (раздел 3.3 + 3.6):
async voidв публичных методах.- Интерфейсы с >15 членами.
- Цепочки
as ConcreteType. - Прямые вызовы
GetService<Concrete>минуя интерфейс.
- Red-flag чек-лист (раздел 3.5) — только grep-проверяемые пункты.
- Итоговую формулу (раздел 3.7) применять, в выводе явно маркировать «оценка частичная, маркеры могут быть пропущены».
Делегирование: все grep-прогоны и подсчёты — через дешёвого агента (
Exploreилиgeneral-purposeсmodel=haiku). Основной контекст агрегирует только числа.
6. Ветка C — субъективный обзор (с предупреждением)
Условие: >10000 LOC.
Перед началом ОБЯЗАТЕЛЬНО вывести:
❌ Предупреждение: объём кода (X LOC) превышает порог применимости канона. На этом объёме LLM скатывается в каталогизацию вместо анализа: пропускает
async void, ложную расширяемость, Fat Interface, скрытые контракты. Корректная оценка по формулам невозможна в одну сессию.Доступно только субъективное мнение об архитектуре — без чисел, без формул, без баллов. Это НЕ оценка качества по канону.
Для корректной оценки — разбить скоуп на отдельные пакеты ≤3000 LOC и оценивать каждый по очереди.
Затем спросить:
«Продолжить с субъективным обзором (с явной маркировкой что это мнение, не оценка), или предпочтёте разбить на пакеты для детального анализа?»
Если пользователь выбрал субъективный обзор:
- Не выводить никаких числовых оценок по формулам канона.
- Дать качественные наблюдения по архитектуре с явной шапкой «Субъективный обзор, не оценка по формулам».
- Опционально перечислить grep-обнаруживаемые красные флаги (с пометкой что это не полная картина) — через дешёвого агента (раздел 3.6).
- Финальный вывод — рекомендация какие подсистемы стоит оценить детально по канону отдельно.
7. Формат вывода
Вне зависимости от ветки.
Шапка
[Ветка X] Оценка <target>
- LOC: <total>
- Применимость канона: [полная / частичная / субъективная]
Тело
- Ветка A: полная оценка по формулам с расчётами и ссылками на файлы.
- Ветка B: формулы + выборочные маркеры + явное «оценка частичная».
- Ветка C: только качественные наблюдения с шапкой «Субъективный обзор, не оценка».
Финальный блок
Ветка A:
- Базовый,
archиredсчётчики, Итог, интерпретация по шкале (8–10 / 6–8 / 4–6 / 2–4 / 0–2). - Диагностический сигнал — отдельный комментарий о разрыве Базовый ↔ Итог:
- если разрыв большой (Итог < 0.6 × Базовый): «Высокий Базовый при низком Итоге — код локально опрятен, но имеет фундаментальные архитектурные дефекты, которые осреднённые метрики не ловят. Этот разрыв — повод смотреть подсистему прицельно, а не доверять одному числу.»
- если разрыв малый: «Базовый и Итог близки — архитектурных штрафов мало, оценка устойчива.»
- Список «что нельзя оценить по коду» (раздел 3.11) — при необходимости.
Ветка B:
- Базовый, выборочные маркеры, предупреждение о неполноте.
- Если посчитан Итог — добавить тот же диагностический комментарий, что в ветке A.
Ветка C:
- Рекомендация декомпозировать для детального анализа.
Интерпретация шкалы итога
- 8–10 — Отличная архитектура.
- 6–8 — Хорошая, с minor проблемами.
- 4–6 — Приемлемая, технический долг копится.
- 2–4 — Проблемная, сложно поддерживать.
- 0–2 — Критическая, требует переписывания.
8. Жёсткие правила
- Применение формул — жёсткое. Балл не корректируется в сторону смягчения «потому что фикс будет дорогой» или «потому что эффект мал». Объём фикса — отдельная задача после оценки.
- Субъективное — отдельно. В ветке A — формулы. В ветке C — мнение. В ветке B — формулы плюс явное предупреждение что часть маркеров может быть пропущена. Не смешивать.
- Не оценивать «расширяемость» по наличию
interface + virtual + hooksбез проверки реальных подмен через grep (раздел 3.3). - Каждое снижение балла — привязано к посчитанному counted violation с ссылкой на код. Иначе
n/a. - Не подменять анализ инвентаризацией (LOC, число файлов, namespace-таблицы — это данные, не оценка).
- Если паттерн чистый по чек-листу — балл максимальный, не «0.7 на ощупь».
- Подсчёт LOC и grep-метрик — через дешёвого агента (Haiku,
subagent_type=Exploreилиgeneral-purpose). Основной контекст занимать инвентаризацией нельзя. - Хорошая документация плохой архитектуры ≠ хороший код — документация объясняет как обойти проблемы, а не почему их нет.