bsl-architecture-review
Контур проверки архитектуры кода 1С: распределение ответственности, границы и контракты, связанность модулей, ветвление вместо единого метода-диспетчера, дублирование, переусложнение. Принципы SOLID, GRASP и паттерны проектирования в их штатной для 1С реализации. Уровень «требует
Install
npx skills add https://github.com/Romandredan/1c-quality-gate/tree/main/skills/bsl-architecture-review
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install romandredan-1c-quality-gate@llmmart
git clone https://github.com/Romandredan/1c-quality-gate.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole romandredan/1c-quality-gate collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
bsl-architecture-review — контур архитектуры
Проверяет то, что не чинится внутри тела метода. Граница с контуром кода механическая, а не тематическая:
Фикс укладывается в замену строк внутри метода — это код. Фикс требует нового шва (выделение метода, перенос в другой модуль, новый экспорт, изменение «кто кого вызывает», ввод диспетчера) — это архитектура.
Полные правила границы, отсева повторных находок и шкала важности — в shared/routing-contract.md
на уровне плагина. Здесь они намеренно не дублируются: копия разъедется с оригиналом при первой
же правке, а это ровно тот дефект, который контур и ищет.
<ЖЁСТКИЙ-ШЛЮЗ> Только анализ и отчёт. Архитектурная правка без согласования недопустима: она затрагивает вызывающих и переживает автора. Находка без предложенной целевой структуры не выпускается. </ЖЁСТКИЙ-ШЛЮЗ>
Глубина
Приходит от оркестратора вместе с профилем изменения. Контур свои пороги не пересчитывает.
| Класс | Уровень | Что смотрим | Бюджет обращений к индексу кода |
|---|---|---|---|
| C0, C1 | не запускается | — | — |
| C2 | 1–2 | тела изменённых методов; при необходимости — экспорты модуля и его вызывающие | ≤4 |
| C3 | 3 | плюс связи подсистем, проектирование метаданных, карта ответственностей | ≤8 |
Бюджет объявляется явно, потому что индекс кода режет выдачу по числу вызовов: без бюджета контур либо не доберёт фактов, либо упрётся в лимит на середине и отчитается по неполным данным.
Архетипы поднимают уровень независимо от класса: новый общий модуль — минимум уровень 2, новый объект метаданных — уровень 3, интеграция и CFE-перехват — минимум уровень 1.
Как работает: измеримые сигналы, а не «прочитай и подумай»
Источник истины — references/signs-map.json: у каждого признака сигнал, порог,
контр-сигнал и ссылки на принципы. Человекочитаемая версия — signs-map.md.
Порядок работы с каждым кандидатом:
- Измерь сигнал. Не «мне кажется, модуль перегружен», а «14 экспортных методов кластеризуются в 4 несвязанные группы».
- Проверь контр-сигнал. У каждого признака есть законная форма, в которой он не является дефектом. Ложноположительная архитектурная находка дороже пропущенной: она провоцирует переделку работающего кода.
- Запроси принцип по URL через MCP
v8std— формулировка берётся из источника, а не по памяти. - Сформулируй целевую структуру. Какие методы, модули и поля появляются, что удаляется.
Симметрия: пере-абстракция ловится так же строго
Механизм расширяемости с единственной реализацией, «стратегия» на одну ветку, абстракция без второй точки изменения — это находка уровня 🟠 с формулировкой «предъявите вторую реализацию или упростите».
Эта половина контура направлена в первую очередь на код, написанный языковой моделью: типовой отказ лежит именно здесь, а не в недостатке абстракций. Правило трёх обобщает после третьего повторения, не раньше.
Три ограничения точности
Без них контур теряет доверие после первой же ложной находки.
1. Одноимённые методы в разных объектах — норма для 1С. Индекс кода различает точные и эвристические совпадения. Любая эвристика, опирающаяся на счётчик вызывающих, требует точного разрешения; при эвристическом — понижай важность находки на ступень и формулируй её как вопрос, а не как утверждение.
2. Полнотекстовый поиск доказывает наличие, но не отсутствие. Он ограничен числом просматриваемых файлов. Область поиска — явный список изменённых файлов; пустой результат даёт формулировку «в изменённых файлах не найдено», но никогда — вердикт «чисто».
3. Пороги статического анализатора принадлежат проекту. Конфигурация анализатора может отключать диагностики или ограничивать анализ отдельными подсистемами — тогда изменённые прикладные файлы вообще не попадут в анализ. Держи свои пороги независимыми и используй анализатор как дешёвый предфильтр кандидатов, никогда — как источник самой находки.
Отдельное жёсткое правило про мёртвый экспорт. В 1С экспортные методы вызываются не только из кода: подписки на события, команды, регламентные задания, настройки библиотек живут в XML; плюс расширения и внешние обработки. Находка «экспорт без потребителей» не выводится вообще, пока не проверены триггеры — иначе контур предложит удалить работающий механизм.
Нет индекса кода — четыре признака уходят в skipped, а не в «чисто»
Признаки ARCH-A1, ARCH-A7, ARCH-A9 и ARCH-A11 опираются на граф вызовов: кластеризация
экспортов по вызывающим, дублирующая валидация у вызывающего и внутри вызываемого, экспорт без
потребителей, состав проверок по обе стороны диалога. Последний признак почти всегда пересекает
границы модулей: проверки живут в общих модулях, а вызывает их модуль формы. Без индекса кода
ни один из четырёх нельзя ни подтвердить, ни опровергнуть.
Факты по графу собирает субагент bsl-scout. Передавай ему вопрос, а не задачу: «экспорты
модуля и вызывающие по каждому», «есть ли у метода вызывающие и триггеры в XML». Независимые
вопросы задавай параллельно, по одному субагенту на вопрос. Бюджет обращений к индексу
расходует он, а твой контекст остаётся под разбор. В его отчёте ищи пометку об эвристическом
разрешении вызывающих: она понижает уверенность находки на ступень и меняет формулировку с
утверждения на вопрос.
Выводы делаешь ты. Субагент возвращает факты и архитектурных вердиктов не выносит. Если субагента в среде нет, работай с индексом сам в пределах объявленного бюджета.
Молча их не проверить — значит выдать отчёт, который выглядит полным. Это тот же класс ложной зелени, который контур ищет в чужом коде, только внутри него самого.
Поэтому при недоступном индексе пиши в след:
[qg skipped: layer=arch, scope=call-graph-signs, planned=[qg:ARCH-A1,qg:ARCH-A7,qg:ARCH-A9,qg:ARCH-A11], reason=rlm_unavailable]
и строкой в отчёте: «признаки по графу вызовов не проверялись — индекс кода недоступен». Вердикт «архитектурных замечаний нет» без этой оговорки не выпускается.
Остальные признаки от индекса не зависят и гоняются по телам изменённых методов как
обычно. Список зависимых живёт в машиночитаемой карте полем requires: ["call-graph"], а не в
этом тексте: две копии одного знания разъезжаются при первой правке — ровно то, что ловит
признак ARCH-A3.
Состязательный аудит на крупных изменениях
Для класса C3 с находками уровня 🔴 или 🟠 предложи в отчёте состязательный аудит: веер ревьюеров по измерениям (ответственность, границы, связанность, дублирование, переусложнение) и проверяющие, пытающиеся опровергнуть каждую находку.
Архитектурные находки выигрывают от этого больше кодовых: они опираются на эвристики, и доля
спорных среди них выше. Методология — ../quality-gate/references/adversarial-audit.md.
Запуск только после явного согласия пользователя.
Формат находки
Сверх общего формата обязательны четыре поля. Находка без любого из них не выпускается.
[🔴/🟠/🟡] <суть>
Где: <путь>::<Метод>:<строка>
Признак: qg:ARCH-AN — <название>
Сигнал: <измеренное значение> против порога <порог>
Принцип: <название> — <URL> (+ #stdNNN, если есть)
Целевая структура: <какие методы/модули/поля появляются, что удаляется>
Переусложнение: вводится сущностей N, реальных потребителей M, удаляется K
Уверенность: высокая | средняя (эвристическое разрешение вызывающих) | требует проверки
Целевая структура отличает находку от жалобы. «Модуль перегружен» без предложения, как его разделить, не является результатом работы.
Проверка на переусложнение обязательна, потому что иначе контур сам становится источником пере-абстракции: предлагает ввести три сущности там, где хватает одной.
Записи следа
[qg applied: layer=arch, scope=module-responsibility, ids=[qg:ARCH-A1,std440], verdict=violation:qg:ARCH-A1]
[qg applied: layer=arch, scope=branching-dispatch, ids=[qg:ARCH-A2], verdict=clean]
[qg skipped: layer=arch, reason=volume_below_threshold]
Специфика 1С
Каноничные реализации паттернов из литературы в 1С не работают: платформа не даёт
пользовательских иерархий классов. Штатные соответствия — в references/patterns-in-1c.md;
предлагать нужно именно их, а не абстрактный «интерфейс стратегии».
Антипаттерны архитектурного уровня, характерные для кода языковой модели, —
в references/ai-antipatterns-arch.md. Чеклист по семи областям —
в references/checklist-architecture.md.
Принципы
- Сигнал вместо вкусовщины. Каждая находка — измеренное значение против объявленного порога.
- Контр-сигнал обязателен. Прежде чем выпустить находку, проверь законную форму признака.
- Целевая структура обязательна. Нет предложения — нет находки.
- Пере-абстракция равна недо-абстракции. Обе стороны проверяются одинаково строго.
- Уверенность заявляется. Эвристическое разрешение ссылок понижает важность находки и меняет формулировку с утверждения на вопрос.
Files (1c-quality-gate)
-
references
-
ai-antipatterns-arch.md 31.8 KB
# Архитектурные антипаттерны кода, порождаемого моделью Продолжение каталога `catalog/AI-*.md` контура кода — здесь пункты, которые **не чинятся внутри метода**. Все получены из реальной переработки сгенерированного кода разработчиком. Общая черта этих ошибок: код работает и проходит любую статическую проверку. Дефект проявляется на следующем изменении — когда выясняется, что правку нужно вносить в четыре места, а проверить отдельно нельзя ничего. Слова плагина — контур, признак, эвристика, след прогона — объяснены в `../../quality-gate/references/glossary.md`. --- ## ARCH-AI-01 · Результат собственной функции перепроверяют у вызывающего 🟠 **Коротко.** Если функция по своему описанию возвращает структуру с известными полями, вызывающий не проверяет ни типы этих полей, ни их наличие. Либо описание врёт — и чинить надо функцию, либо проверки мёртвые. **Что искать.** У вызывающего: `ТипЗнч` над результатом своей же функции, `Свойство("Поле")` над структурой, состав которой объявлен, повторная проверка `ЗначениеЗаполнено` над тем, что вызванная функция уже обязалась заполнить. **Почему это архитектура, а не придирка.** Каждая такая проверка — заявление, что своей функции доверять нельзя. Если это правда, ненадёжна сама функция, и чинить надо её, а не обвешивать проверками всех вызывающих. Если неправда, проверки не срабатывают никогда, но выглядят как настоящие: следующий читатель добавит ещё одну, раз здесь так принято. Через несколько правок половина метода — проверки, которые ничего не проверяют, и отличить их от нужных уже нельзя. **Как правильно.** Граница, где данные считаются непроверенными, ровно одна: место, куда они приходят снаружи — ответ сервиса, файл, ввод пользователя. Там проверка обязательна. За этой границей форма данных известна, и обращение к полям прямое. Если поле по смыслу необязательное, это пишется в описании функции, и проверка наличия остаётся — но одна, а не в каждом вызывающем. #### Неправильно ```bsl ДанныеЗаказа = РазобратьОтветЗаказа(Ответ); Если ТипЗнч(ДанныеЗаказа) = Тип("Структура") И ДанныеЗаказа.Свойство("Номер") Тогда Номер = ДанныеЗаказа.Номер; КонецЕсли; ``` #### Правильно ```bsl // Состав результата объявлен функцией-конструктором, обращение прямое. ДанныеЗаказа = РазобратьОтветЗаказа(Ответ); Номер = ДанныеЗаказа.Номер; ``` **Когда так делать не нужно.** Функция действительно возвращает разное — например, разные виды ответа внешней системы. Тогда проверка законна, но её место сразу за вызовом и один раз, а вид результата лучше сделать отдельным полем (`ВидОтвета`), чтобы вызывающий разбирал по нему, а не угадывал по наличию полей. **Что проверяет инструмент.** Ничего. ## ARCH-AI-02 · Валидация на каждом уровне вместо границы 🟠 **Что искать:** одна и та же проверка у вызывающего (с логированием и возвратом) и внутри вызываемого метода. **Почему:** двойная проверка — мёртвый код плюс ложный сигнал, что значение здесь может быть пустым, хотя вызывающий это уже гарантировал. Каждая копия тащит свой текст в журнал и свой комментарий, и метод раздувается вдвое без добавления смысла. **Как правильно:** определить, **где граница валидации**. Проверяет тот, кто принимает решение об ошибке, возврате или повторе. Внутренние помощники ему доверяют. Защитная проверка ставится один раз на настоящей границе: данные, пришедшие снаружи, либо публичный вход, у которого несколько независимых вызывающих. **Важный частный случай:** довод «покажем пользователю понятную ошибку раньше» **не оправдывает** дубль, если вызываемый и так возвращает текст ошибки в общем для всех результате. Нужно показать этот текст, а не продублировать проверку. **Когда так делать не нужно.** Проверка законна по обе стороны, когда вызывающий и вызываемый — разные слои с разным уровнем доверия. Публичный метод библиотеки проверяет вход всегда, даже если сегодня его вызывает только ваш код: завтра его вызовет чужой. Внутренние помощники этого же модуля проверять вход не должны. **Что проверяет инструмент.** Ничего, признак опознаётся чтением. ## ARCH-AI-03 · Под каждый вариант входных данных пишется своя копия функции 🟠 **Коротко.** Две и более процедуры одинакового устройства, отличающиеся только именами полей, кодами или путями разбора, — это одна процедура плюс таблица различий. **Что искать.** Методы с одинаковой последовательностью шагов, где отличаются только строковые значения: имена полей ответа, коды видов операций, имена узлов XML. **Почему.** Каждая копия живёт своей жизнью: правку вносят в одну, в остальных она не появляется. Реальный случай: в одной из копий поле разбиралось как строка вместо даты, период записи оставался пустым, и вся история схлопывалась в одну строку. В общем механизме тип объявлен один раз и верен для всех вариантов. **Как правильно.** Различия выносятся в данные: таблица «поле ответа → реквизит объекта → тип», список узлов, перечисление кодов. Новый вариант становится строкой в этой таблице, а не новой процедурой, и правка механизма поднимает все варианты разом. **Когда так делать не нужно.** Если вариант доказуемо не описывается таблицей — значения лежат в атрибутах узла, а не в дочерних элементах; на одном уровне вперемешку разнородные элементы; вложенность нерегулярна, — отдельная процедура правильна. Но и она собирается из общих кирпичей (прочитать узел, привести тип), а не из отдельной функции на каждое поле, иначе получится ARCH-AI-01. Сначала докажи, что общий механизм не подходит, и только потом пиши отдельный. **Что проверяет инструмент.** Ничего. ## ARCH-AI-04 · Данные переливаются из переменной в переменную вместо отдельного метода 🟡 **Коротко.** Цепочка переменных, где каждая присваивается один раз и один раз читается — только чтобы передать значение следующему шагу, — это подготовка данных, и её место в отдельном методе. **Что искать.** Метод, в котором получили A, достали из A поле B, посчитали по B значение C, сложили C и D в структуру — и только после этого начинается собственно работа. **Почему.** Такой метод нельзя разделить: в нём нет места, где заканчивается подготовка и начинается действие. Его нельзя проверить по частям — только целиком и на настоящих данных. И он притягивает лишние проверки: на каждое промежуточное значение кто-нибудь однажды добавит свою. **Как правильно.** Подготовка выносится в отдельный метод, возвращающий готовую структуру. Туда же переезжает сопутствующее: чтение констант, значения по умолчанию, разбиение на порции. Родительский метод превращается в последовательность вызовов и ветвление по результату — его можно прочитать как сценарий, не держа в голове промежуточные значения. **Когда так делать не нужно.** Две-три строки подготовки, которые читаются с одного взгляда, выносить незачем: отдельный метод на две строки усложняет чтение, а не упрощает. **Что проверяет инструмент.** Ничего. #### Неправильно ```bsl Настройки = НастройкиОбмена(Узел); Адрес = Настройки.Адрес; Таймаут = ?(ЗначениеЗаполнено(Настройки.Таймаут), Настройки.Таймаут, 30); Заголовки = ЗаголовкиАвторизации(Настройки.Логин, Настройки.Пароль); Соединение = Новый HTTPСоединение(Адрес, , , , , Таймаут); // ... и только теперь начинается работа ``` #### Правильно ```bsl ПараметрыОбращения = ПодготовитьОбращение(Узел); // всё выше - внутри этого метода Ответ = ОтправитьЗапрос(ПараметрыОбращения); ``` ## ARCH-AI-05 · Вычисленный признак кладут в отдельное соответствие вместо поля в самой записи 🟠 **Коротко.** Если на следующем шаге к каждой записи добавится вычисленный признак, поле под него закладывается в саму запись сразу, с пустым значением по умолчанию. Отдельное соответствие «ключ записи → вычисленное значение» заводить не нужно. **Что искать.** Результаты вычисления собираются в новое соответствие, а потом связываются с исходными записями по ключу. **Почему это корень сразу нескольких проблем.** Обратное связывание требует функции поиска, которая больше нигде не нужна. Эта функция обрастает проверками, потому что связывание может не найти пару (ARCH-AI-01). Появляется переливание значений из коллекции в коллекцию (ARCH-AI-04). Причина всего одна: поля не оказалось там, где ему место. #### Неправильно ```bsl ПризнакиПоКлючу = Новый Соответствие; Для Каждого Запись Из Записи Цикл ПризнакиПоКлючу.Вставить(Запись.Идентификатор, ЦенаПроверена(Запись)); КонецЦикла; // ... а дальше по коду это соответствие сопоставляют с записями обратно по ключу ``` #### Правильно ```bsl Записи.Колонки.Добавить("ЦенаПроверена", Новый ОписаниеТипов("Булево")); Для Каждого Запись Из Записи Цикл Запись.ЦенаПроверена = ЦенаПроверена(Запись); КонецЦикла; ``` **Сигнал к применению.** Мысль «соберу результаты в отдельное соответствие, а потом сопоставлю обратно по ключу» означает, что поле должно жить в самой записи. **Когда так делать не нужно.** Признак нужен редкому подмножеству записей, а самих записей очень много: тогда отдельное соответствие экономит память. И второй случай: исходную коллекцию менять нельзя, потому что ею владеет чужой код — табличная часть документа, результат библиотечной функции. **Что проверяет инструмент.** Ничего. ## ARCH-AI-06 · Структуру, которую возвращает экспортная функция, собирают прямо в её теле 🟡 **Коротко.** Если структура уходит наружу — из экспортной функции, между слоями, — её состав объявляется отдельной функцией-конструктором (#std641). Она же единственное место, где видно, из каких полей структура состоит. **Что искать.** Экспортная функция, которая создаёт `Новый Структура` и наполняет её вставками прямо в теле, а состав полей описан только в комментарии-шапке либо нигде. **Почему.** Форма результата оказывается привязана к одному методу. Второй потребитель либо копирует набор полей к себе — и копии расходятся при первом же изменении, — либо не может воспользоваться результатом вовсе. Ещё дороже обходится добавление поля: его нужно не забыть во всех местах, где такую структуру собирают. **Как правильно.** Отдельная экспортная функция-конструктор со значениями по умолчанию и описанием полей в шапке. Она же — место, куда заранее закладывается поле под будущий признак (ARCH-AI-05). **Когда так делать не нужно.** Одноразовая структура на одно-два поля внутри неэкспортного метода, которая наружу не уходит: конструктор для неё — лишний код. **Что проверяет инструмент.** Ничего. #### Неправильно ```bsl Функция ДанныеЗаказа(Заказ) Экспорт Результат = Новый Структура; Результат.Вставить("Номер", ...); Результат.Вставить("Дата", ...); Возврат Результат; КонецФункции ``` #### Правильно ```bsl // Состав объявлен один раз и виден вызывающему; сюда же добавляются будущие поля. Функция НовыеДанныеЗаказа() Экспорт Результат = Новый Структура; Результат.Вставить("Номер", ""); Результат.Вставить("Дата", '00010101'); Возврат Результат; КонецФункции ``` --- ## ARCH-AI-07 · Проверки по обе стороны вопроса пользователю 🟠 Модель пишет обработчик команды: переход на сервер, часть проверок, возврат на клиент, вопрос пользователю. Затем обработчик ответа: снова на сервер, оставшиеся проверки, выполнение. Каждая половина идиоматична по отдельности, дефект существует только в их композиции — поэтому при чтении по одной процедуре он невидим. Проверки при этом не обязаны дублироваться. Типовой случай — они просто разной критичности, и модель раскладывает их «по важности» на до и после подтверждения. Цена двойная. Лишние рейсы на сервер за одно действие пользователя: #std487 требует обосновывать даже один дополнительный вызов. И, что хуже, **пользователь подтверждает действие на неполной информации** — отвечает «да», и только после этого узнаёт, что вторая проверка не прошла. **Как правильно:** все проверки, входы которых доступны до вопроса, выполняются одним серверным вызовом. Их результаты, включая некритичные предупреждения, показываются пользователю вместе с вопросом: он решает, зная всё. После положительного ответа второй вызов только выполняет команду — ни проверок, ни повторного сбора данных. Законная форма, в которой это не дефект: после ответа данные перечитываются под блокировкой перед необратимой операцией, потому что между вызовами их мог изменить другой пользователь; либо вход проверки появляется только из самого ответа, когда пользователь выбрал в диалоге значение, которое и требуется проверить. Признак в машиночитаемой карте — `qg:ARCH-A11`. Он опирается на граф вызовов: проверки обычно живут в общих модулях, а вызывает их модуль формы, поэтому без индекса кода признак уходит в `skipped`, а не в «чисто». ## ARCH-AI-08 · Соответствие вместо таблицы значений там, где нужен набор записей 🟠 **Коротко.** Набор однотипных записей передаётся между процедурами таблицей значений. Соответствие для этого не годится: у него нет объявленного состава полей, и всё, что таблица умеет сама, приходится писать руками в каждой процедуре. **Что искать — три формы.** 1. **Соответствие, где по каждому ключу лежит массив структур**, и оно передаётся дальше по цепочке процедур. Каждая следующая процедура обходит его двумя вложенными циклами: по ключам, потом по элементам. 2. **Массив структур, рядом с которым заведено соответствие** «ключ → номер элемента в массиве» или «ключ → Истина». Это индекс, собранный руками, и нужен он только этому месту. 3. **Соответствие, ключи которого — не данные, а имена полей:** `Вставить("Номер", …)`, `Получить("Статус")`. Здесь соответствие используется вместо структуры. **Чем это плохо.** - **Состав полей нигде не объявлен.** Описать соответствие в заголовке процедуры нечем: #std641 задаёт форму описания для структуры (перечисление свойств через `*`) и для таблицы значений (перечисление колонок), а для соответствия такой формы нет. Тот, кто читает вызов, узнает состав полей только прочитав весь код разбора. - **Опечатка в имени поля не обнаруживается.** Обращение к несуществующему ключу возвращает `Неопределено`, а не ошибку. Значение окажется пустым, и разбираться будут не там, где ошибка, а там, куда пустое значение доехало. - **Группировка и поиск пишутся заново в каждой процедуре.** Соответствие «ключ → массив» — это группировка, сделанная руками. Соответствие «ключ → номер элемента» — индекс, сделанный руками. У таблицы значений для этого есть `НайтиСтроки` и `Индексы`. - **Данные копируются из коллекции в коллекцию на каждом шаге.** Разбор ответа кладёт их в соответствие; следующая процедура перекладывает в массив структур со своим соответствием-индексом; третья — в таблицу для запроса; четвёртая — обратно в соответствие с результатом. На каждом шаге список полей пишется заново, поэтому одно новое поле приходится добавлять во все шаги сразу. **Когда соответствие — правильный выбор (менять не нужно).** - Ключом служат данные — ссылка, идентификатор, код, — и обращаются по нему многократно: время поиска в соответствии не зависит от числа элементов. - Состав ключей заранее неизвестен, потому что это разобранный ответ внешней системы. #std693 п. 4.2 прямо выводит структуры нефиксированного состава из-под общего правила. - Соответствие требует та функция, которую вы вызываете: например заголовки HTTP-запроса передаются именно соответствием. **Как правильно.** Ответ внешней системы разбирается один раз — в том месте, где он приходит. Результат разбора: для одного объекта структура, созданная отдельной функцией-конструктором (#std641), для набора записей таблица значений с объявленными колонками. Дальше по процедурам передаётся она. Группировка по значению — `НайтиСтроки` по нужным колонкам; отсев повторов — `Свернуть` у копии таблицы либо проверка через `НайтиСтроки`; связывание двух наборов, если оба пришли из базы, — соединение в запросе (AI-10). Отдельной колонки-ключа при этом не появляется: перенос склейки в колонку — та же ошибка в новом виде (AI-18). **Правка записи на месте сохраняется.** Обычное возражение против замены звучит так: «мы находим нужную запись и тут же правим ей поле, с таблицей так не выйдет». Выйдет: `НайтиСтроки` возвращает массив строк таблицы, а строка это не копия данных, а ссылка на них. Присваивание её полю меняет саму таблицу — ровно так же, как раньше меняло структуру внутри массива. **Обязательная проверка перед заменой — про скорость.** Выборка из соответствия по ключу не зависит от числа элементов. `НайтиСтроки` по неиндексированной таблице просматривает строки подряд. Поэтому если заменить соответствие «ключ → массив» на таблицу и вызывать `НайтиСтроки` для каждого ключа, получится обход всех ключей, а внутри каждого — просмотр всех строк: на тысяче строк это около миллиона сравнений вместо тысячи. Чтобы этого не произошло, при такой замене к таблице добавляется индекс по тем же колонкам, по которым идёт отбор (`Индексы.Добавить`), и состав колонок индекса должен совпадать с набором полей в условии поиска (подробности и цитата из документации — в AI-18). Это главный способ сделать хуже, следуя правилу. **Связь с ARCH-AI-05.** Там поле забыли заложить в структуру и завели рядом отдельное соответствие с вычисленным признаком; здесь соответствие сделали основной коллекцией. Причина одна: состав данных нигде не объявлен. Признака в машиночитаемой карте у пункта нет: он опознаётся по форме кода, а не по графу вызовов, и проверяется чтением. --- ## Мета-урок: не сдавай позицию под встречный довод, не сверив предмет спора Отдельный пункт, потому что он про поведение, а не про код. Реальный случай: модель верно отметила риск разрушающей записи набора, а затем отказалась от своей позиции под встречным доводом. Довод был про **намерение** («перезапись здесь допустима»), а исходное замечание — про **механику платформы** («отбор без периода стирает не текущий срез, а всю историю»). Это разные вопросы, и второй не опровергается первым. **Как правильно:** развести уровни явно — «с намерением согласен, но механически отбор без периода удалит и накопленную историю, это точно нужно?» — и сверить смысл по документации или по метаданным. Молчаливая смена позиции на непроверенное утверждение равносильна отчёту о непрогнанной проверке, даже если утверждение исходит от более авторитетной стороны. Симметричное правило действует и на ревью: находку нельзя понижать по доводу «так написан соседний код». Идиома окружения не отменяет механику платформы. -
checklist-architecture.md 8 KB
# Чеклист ревью архитектуры и метаданных 1С Ссылки на принципы бери из `signs-map.json` и запрашивай их текст по URL через MCP `v8std`; тексты стандартов — оттуда же по номеру (`v8std_get_page("stdNNN")`). Штатные для 1С реализации паттернов — в `patterns-in-1c.md`: предлагать нужно их, а не каноничные из литературы. Каждое замечание подкрепляй принципом, паттерном или номером стандарта — и **измеренным сигналом против порога**. Замечание без сигнала является вкусовщиной, даже когда оно по сути верное: спорить с ним нечем, и поэтому его справедливо игнорируют. Ниже — разделы чеклиста; бери только относящиеся к затронутому архетипу изменения. ## 1. Распределение ответственности (SOLID/GRASP) - [ ] У модуля/объекта одна ось изменений — нет «комбайна» (расчёт+печать+обмен+UI) → SRP ([single-responsibility](https://v8std.ru/patterns/solid/single-responsibility/)). - [ ] Расширение поведения возможно без правки существующего кода → OCP ([open-closed](https://v8std.ru/patterns/solid/open-closed/)). - [ ] Прикладной код не зависит напрямую от инфраструктуры (банк, API, внешняя компонента) → DIP ([dependency-inversion](https://v8std.ru/patterns/solid/dependency-inversion/)). - [ ] Данными управляет тот, у кого они есть → Information Expert ([information-expert](https://v8std.ru/patterns/grasp/information-expert/)). - [ ] Низкая связанность между подсистемами, высокая связность внутри → [low-coupling](https://v8std.ru/patterns/grasp/low-coupling/), [high-cohesion](https://v8std.ru/patterns/grasp/high-cohesion/). - [ ] Нестабильные точки изолированы → Protected Variations ([protected-variations](https://v8std.ru/patterns/grasp/protected-variations/)). ## 2. Полиморфизм вместо ветвлений - [ ] Нет разрастающихся `Если ТипЗнч() = ... ИначеЕсли` по типам — заменяемо стратегией/полиморфизмом ([strategy](https://v8std.ru/patterns/gof/strategy/), [polymorphism](https://v8std.ru/patterns/grasp/polymorphism/)). - [ ] Выбор алгоритма/канала/формата в рантайме оформлен как стратегия, а не как лес условий. ## 3. Переусложнение (инженерные принципы) - [ ] Нет абстракций без реальной точки изменения → YAGNI ([yagni](https://v8std.ru/patterns/engineering/yagni/)). - [ ] Решение не сложнее необходимого → KISS ([kiss](https://v8std.ru/patterns/engineering/kiss/)). - [ ] Обобщение введено после 3-го повторения, не раньше → Rule of Three ([rule-of-three](https://v8std.ru/patterns/engineering/rule-of-three/)). - [ ] Паттерн действительно нужен — сверься с разделом «Когда паттерн лишний» в файле паттерна. ## 4. Проектирование метаданных - [ ] Имена объектов метаданных по правилам (#std550); общие требования к конфигурации соблюдены (#std467). - [ ] Разбиение на подсистемы осмысленно (#std543). - [ ] Общие модули созданы по правилам (флаги «Сервер/Клиент/Вызов сервера/Повторное использование») (#std469). - [ ] Имя/синоним/комментарий, подсказки и проверка заполнения заданы (#std474, #std478). - [ ] Типы реквизитов адекватны: строковые (#std432), составные не злоупотребляются (#std728). - [ ] Регистры самодостаточны (#std477). - [ ] Проведение документов спроектировано верно (#std603). - [ ] Предопределённые элементы используются корректно (#std697). ## 5. Разделение модулей - [ ] Логика разнесена между модулем объекта, модулем менеджера и общими модулями по назначению (#std486). - [ ] Клиентская и серверная логика разделены; минимизированы переходы (#std487, #std629). ## 6. Границы и контракты - [ ] Внешние интеграции скрыты за обёрткой/фасадом, а не «размазаны» по прикладному коду → [facade](https://v8std.ru/patterns/gof/facade/), [adapter](https://v8std.ru/patterns/gof/adapter/). - [ ] Печать/вывод отделены от подготовки данных (ТабличныйДокумент как контракт) → DIP + #std548/#std789. - [ ] Набор однотипных записей передаётся между процедурами таблицей значений, а один объект — структурой от функции-конструктора (#std641). Соответствие «ключ → массив записей» и массив с отдельным соответствием-индексом рядом означают, что состав полей нигде не объявлен (разбор — ARCH-AI-08 в `ai-antipatterns-arch.md`). Соответствие законно, когда ключом служат данные и обращаются по нему многократно, когда состав ключей заранее неизвестен и когда его требует вызываемая функция. ## 7. Проектирование форм и командного интерфейса (UI) - [ ] Общие принципы форм соблюдены: структура, группировки, поведение элементов (#std468, #std430, #std642, #std755). - [ ] Логика формы разделена на клиент/сервер корректно, без лишних переходов (#std404, #std741, #std630, #std703). - [ ] Диалоги с пользователем неблокирующие (оповещение/закрытие с блоком оповещения) (#std400, #std418, #std700). - [ ] Формы списков и динамические списки настроены эффективно (отборы, ограничения) (#std489, #std495, #std397). - [ ] Интерфейс 8.3: командный интерфейс и проектирование форм по современным правилам (#std727, #std753, #std722, #std687). - [ ] Формы документов и их элементы (тумблеры, переключатели и др.) оформлены по стандартам 8.3 (#std716, #std717, #std718, #std719, #std720, #std721). ## Итог архитектурного ревью Сформируй: (1) карту ответственностей текущего решения; (2) выявленные нарушения с привязкой к принципу/паттерну/стандарту и severity; (3) предложение целевой структуры; (4) явную проверку, что предложение не переусложняет (KISS/YAGNI). -
patterns-in-1c.md 5.9 KB
# Паттерны проектирования в их штатной для 1С реализации Каноничные реализации из литературы в 1С не воспроизводятся: платформа не даёт пользовательских иерархий классов, наследования и интерфейсов в привычном виде. Роль абстракций играют другие средства — общие модули, подсистемы, планы видов характеристик, подписки на события, определяемые типы, переопределяемые процедуры библиотек. Предлагать в находках нужно **правую колонку**, а не абстрактный «интерфейс стратегии»: иначе рекомендация невыполнима и справедливо игнорируется. | Паттерн | Штатная реализация в 1С | |---|---| | Фасад | общий модуль-обёртка подсистемы как единственная точка входа | | Одиночка | модуль повторного использования (на время сеанса или вызова) — не самодельный кэш в переменной модуля | | Стратегия | таблица-диспетчер «код → имя метода» плюс вызов по имени; определяемые типы; подписки на события | | Наблюдатель | подписки на события объектов | | Шаблонный метод | переопределяемые процедуры библиотеки (модули с суффиксом «Переопределяемый») | | Адаптер | модуль-обёртка внешнего API, отдающий типизированный контракт | | Состояние | регистр сведений состояний плюс чистая функция-классификатор | | Команда | обработки и команды объектов; регламентные задания как отложенное выполнение | | Цепочка обязанностей | последовательность проверок с общим объектом результата и ранним выходом | | Строитель | функция-конструктор структуры с умолчаниями (имя вида «НовыйX») | | Заместитель | модуль-прослойка с кэшированием повторного использования перед дорогим источником | | Абстрактная фабрика | функция, возвращающая имя модуля-реализации по коду поставщика или среды | ## Что в 1С заменяет полиморфизм Ветвление само по себе не дефект. Метод-диспетчер с однострочными делегированиями — **и есть** штатная замена полиморфизма: ```bsl // Это норма, а не признак: единственная точка ветвления, тела вынесены. Если ВидОперации = "Продажа" Тогда Возврат ОбработатьПродажу(Данные); ИначеЕсли ВидОперации = "Возврат" Тогда Возврат ОбработатьВозврат(Данные); КонецЕсли; ``` Признаком проблемы это становится, когда: ветвей четыре и больше **и** внутри каждой лежит содержательная логика, а не делегирование; либо когда цепочка по одному и тому же признаку повторяется в двух и более местах — тогда добавление варианта требует правки в нескольких файлах, и это нарушение принципа открытости-закрытости. Целевая структура в таком случае — таблица соответствия «код → имя метода» и вызов по имени: новый вариант становится строкой данных, а не веткой кода. ## Где паттерн обычно лишний - **Фабрика на одну реализацию.** Пока поставщик один, функция выбора не нужна. - **Стратегия на два варианта, которые не растут.** Обычное ветвление читается лучше. - **Свой кэш вместо повторного использования.** Платформа уже даёт кэширование с понятным временем жизни; самодельный требует ручной инвалидации, о которой забывают. - **Слой абстракции над одной конкретной системой.** Адаптер оправдан, когда систем две или когда внешний контракт нестабилен; иначе это лишний уровень косвенности. - **Наблюдатель там, где хватает прямого вызова.** Подписка усложняет трассировку: вызов перестаёт быть виден в коде. Правило: прежде чем предложить паттерн, назови **ось изменчивости** — что именно будет меняться и почему. Нет оси — нет паттерна. -
signs-map.json 12.8 KB
{ "$comment": "Карта «архитектурный признак → измеримый сигнал → принцип проектирования». Признак — это симптом, требующий проверки, а не готовый вердикт: у каждого есть контр-сигнал, описывающий законную форму, в которой это не дефект. Первичный ключ ссылки — URL: он самопроверяем и устойчив к изменению схемы идентификаторов. Тексты принципов не хранятся — запрашиваются через MCP v8std по URL.", "version": 2, "signs": [ { "id": "ARCH-A1", "sign": "Модуль-комбайн", "signal": "экспортные методы кластеризуются по несвязанным предметным группам", "threshold": ">=3 несвязанные группы ИЛИ вызывающие из >=3 подсистем", "counter": "фасад подсистемы: >=80% экспортов — делегирования в 5 строк или короче", "principles": [ { "title": "Single Responsibility Principle (SRP)", "url": "https://v8std.ru/patterns/solid/single-responsibility/" }, { "title": "Separation of Concerns", "url": "https://v8std.ru/patterns/engineering/separation-of-concerns/" } ], "std": [ "std440" ], "requires": [ "call-graph" ] }, { "id": "ARCH-A2", "sign": "Ветвление вместо полиморфизма", "signal": "цепочка ИначеЕсли по типу значения или строковому коду", "threshold": ">=4 ветви ИЛИ 2 цепочки по одному признаку", "counter": "единственный метод-диспетчер с однострочными делегированиями — в 1С это и есть полиморфизм", "principles": [ { "title": "Полиморфизм (Polymorphism)", "url": "https://v8std.ru/patterns/grasp/polymorphism/" }, { "title": "Стратегия (Strategy)", "url": "https://v8std.ru/patterns/gof/strategy/" }, { "title": "Принципы ООП", "url": "https://v8std.ru/patterns/principles/" } ], "std": [] }, { "id": "ARCH-A3", "sign": "Дубль-алгоритм, форк-парсер", "signal": ">=2 метода одинаковой структуры, различие только в литералах", "threshold": "2 копии — сигнал, 3 — нарушение", "counter": "форма входа доказуемо не выражается общим дескриптором", "principles": [ { "title": "DRY (Don't Repeat Yourself)", "url": "https://v8std.ru/patterns/engineering/dry/" }, { "title": "Rule of Three (Правило трех)", "url": "https://v8std.ru/patterns/engineering/rule-of-three/" }, { "title": "Open/Closed Principle ( OCP )", "url": "https://v8std.ru/patterns/solid/open-closed/" } ], "std": [ "std440" ] }, { "id": "ARCH-A4", "sign": "Вычисленный признак кладут в отдельное соответствие вместо поля в записи", "signal": "результаты цикла собираются в Соответствие и связываются обратно по ключу из исходной коллекции", "threshold": "наличие пары «сбор + обратное связывание»", "counter": "связываются наборы из разных источников, и объединение в один носитель невозможно", "principles": [ { "title": "Информационный эксперт (Information Expert)", "url": "https://v8std.ru/patterns/grasp/information-expert/" }, { "title": "Высокая связность (High Cohesion)", "url": "https://v8std.ru/patterns/grasp/high-cohesion/" } ], "std": [] }, { "id": "ARCH-A5", "sign": "Данные переливаются из переменной в переменную", "signal": "цепочка переменных, каждая присваивается и читается ровно один раз, чтобы передать значение следующему шагу", "threshold": ">=4 звена", "counter": "шаги содержательно различны и каждый ветвится по-своему", "principles": [ { "title": "Separation of Concerns", "url": "https://v8std.ru/patterns/engineering/separation-of-concerns/" }, { "title": "Высокая связность (High Cohesion)", "url": "https://v8std.ru/patterns/grasp/high-cohesion/" } ], "std": [ "std453" ] }, { "id": "ARCH-A6", "sign": "Структуру-результат собирают прямо в теле экспортной функции", "signal": ">=4 подряд Вставить в структуру, которая возвращается из экспортной функции", "threshold": ">=4 вставок плюс возврат структуры", "counter": "одноразовая локальная структура на 1-2 поля внутри приватного метода", "principles": [ { "title": "Создатель (Creator)", "url": "https://v8std.ru/patterns/grasp/creator/" }, { "title": "Строитель (Builder)", "url": "https://v8std.ru/patterns/gof/builder/" } ], "std": [] }, { "id": "ARCH-A7", "sign": "Дублирующая валидация", "signal": "одна и та же проверка заполненности или типа у вызывающего и внутри вызываемого", "threshold": "2 уровня проверки одного значения", "counter": "вызываемый метод экспортный и имеет несколько независимых вызывающих", "principles": [ { "title": "Низкая связанность (Low Coupling)", "url": "https://v8std.ru/patterns/grasp/low-coupling/" }, { "title": "DRY (Don't Repeat Yourself)", "url": "https://v8std.ru/patterns/engineering/dry/" } ], "std": [], "requires": [ "call-graph" ] }, { "id": "ARCH-A8", "sign": "Инфраструктура в прикладном коде", "signal": "HTTP-соединение, внешняя компонента или COM-объект прямо в модуле объекта либо формы", "threshold": "любое вхождение", "counter": "одноразовая служебная обработка без прикладной логики", "principles": [ { "title": "Dependency Inversion Principle (DIP)", "url": "https://v8std.ru/patterns/solid/dependency-inversion/" }, { "title": "Фасад (Facade)", "url": "https://v8std.ru/patterns/gof/facade/" }, { "title": "Адаптер (Adapter)", "url": "https://v8std.ru/patterns/gof/adapter/" } ], "std": [] }, { "id": "ARCH-A9", "sign": "Экспорт без потребителей", "signal": "экспортный метод, у которого не найдено ни вызывающих, ни триггеров", "threshold": "нет точных вызывающих И нет триггеров", "counter": "ВАЖНО: в 1С экспорт вызывается из XML (подписки, команды, регламентные задания, настройки библиотек), из расширений и внешних обработок — без проверки триггеров находка не выводится вообще", "principles": [ { "title": "Interface Segregation Principle (ISP)", "url": "https://v8std.ru/patterns/solid/interface-segregation/" }, { "title": "YAGNI (You Aren't Gonna Need It)", "url": "https://v8std.ru/patterns/engineering/yagni/" } ], "std": [], "requires": [ "call-graph" ] }, { "id": "ARCH-A10", "sign": "Переусложнение, преждевременная гибкость", "signal": "механизм расширяемости с единственной реализацией; абстракция без второй точки изменения", "threshold": "1 реализация", "counter": "вторая реализация уже спроектирована и запланирована в этой же задаче", "principles": [ { "title": "KISS (Keep It Simple, Stupid)", "url": "https://v8std.ru/patterns/engineering/kiss/" }, { "title": "YAGNI (You Aren't Gonna Need It)", "url": "https://v8std.ru/patterns/engineering/yagni/" }, { "title": "Rule of Three (Правило трех)", "url": "https://v8std.ru/patterns/engineering/rule-of-three/" } ], "std": [] }, { "id": "ARCH-A11", "sign": "Проверки по обе стороны вопроса пользователю", "signal": "в цепочке одного действия пользователя проверки выполняются и до асинхронного диалога, и после ответа", "threshold": ">=1 проверка после ответа пользователя, вход которой был доступен до вопроса", "counter": "после ответа данные перечитываются под блокировкой перед необратимой операцией; либо вход проверки появляется только из самого ответа пользователя", "principles": [ { "title": "Оптимизация клиент-серверного взаимодействия прикладных решений", "url": "https://v8std.ru/metod8dev/4105/" } ], "std": [ "std487", "std636", "std629" ], "requires": [ "call-graph" ] }, { "id": "ARCH-A12", "sign": "Один обработчик на несколько функционально различных сущностей", "signal": "ветка диспетчера обслуживает два и более значения признака (тип чека, вид операции, тип документа), у которых правила различаются, тогда как у остальных значений обработчики свои", "threshold": ">=2 значения в одной ветке при >=1 значении со своим обработчиком; либо ветка Иначе, в которую сведены остальные значения", "counter": "значения неразличимы по правилам не сегодня, а по существу (общее основание названо в комментарии); либо ветка одна на всех и диспетчера нет вовсе — тогда это не сведение, а отсутствие разбора", "principles": [ { "title": "Единственная ответственность (Single Responsibility)", "url": "https://v8std.ru/patterns/solid/single-responsibility/" }, { "title": "Открытость-закрытость (Open-Closed)", "url": "https://v8std.ru/patterns/solid/open-closed/" }, { "title": "Полиморфизм (Polymorphism)", "url": "https://v8std.ru/patterns/grasp/polymorphism/" } ], "std": [] } ] } -
signs-map.md 11.9 KB
# Карта архитектурных признаков Производный файл — **источник истины `signs-map.json`**. Правки вносить туда, эту страницу перегенерировать: `node tools/gen-signs-map-md.mjs`. **Признак — это симптом, а не диагноз.** Он говорит «здесь стоит присмотреться», а не «здесь ошибка»: у каждого есть контр-сигнал — законная форма, в которой это не дефект. Тексты принципов здесь не хранятся — запрашиваются по URL через MCP `v8std`. Порядок работы с каждым признаком: измерить сигнал → проверить контр-сигнал → запросить принцип по ссылке → сформулировать целевую структуру. Ложноположительная архитектурная находка дороже пропущенной, поэтому контр-сигнал проверяется всегда. ## ARCH-A1 · Модуль-комбайн **Сигнал:** экспортные методы кластеризуются по несвязанным предметным группам **Порог:** >=3 несвязанные группы ИЛИ вызывающие из >=3 подсистем **Контр-сигнал (когда это НЕ дефект):** фасад подсистемы: >=80% экспортов — делегирования в 5 строк или короче **Требует индекс кода:** без графа вызовов признак не проверяется — это `skipped`, а не «чисто». **Принципы:** - [Single Responsibility Principle (SRP)](https://v8std.ru/patterns/solid/single-responsibility/) - [Separation of Concerns](https://v8std.ru/patterns/engineering/separation-of-concerns/) **Стандарты:** #std440 ## ARCH-A2 · Ветвление вместо полиморфизма **Сигнал:** цепочка ИначеЕсли по типу значения или строковому коду **Порог:** >=4 ветви ИЛИ 2 цепочки по одному признаку **Контр-сигнал (когда это НЕ дефект):** единственный метод-диспетчер с однострочными делегированиями — в 1С это и есть полиморфизм **Принципы:** - [Полиморфизм (Polymorphism)](https://v8std.ru/patterns/grasp/polymorphism/) - [Стратегия (Strategy)](https://v8std.ru/patterns/gof/strategy/) - [Принципы ООП](https://v8std.ru/patterns/principles/) ## ARCH-A3 · Дубль-алгоритм, форк-парсер **Сигнал:** >=2 метода одинаковой структуры, различие только в литералах **Порог:** 2 копии — сигнал, 3 — нарушение **Контр-сигнал (когда это НЕ дефект):** форма входа доказуемо не выражается общим дескриптором **Принципы:** - [DRY (Don't Repeat Yourself)](https://v8std.ru/patterns/engineering/dry/) - [Rule of Three (Правило трех)](https://v8std.ru/patterns/engineering/rule-of-three/) - [Open/Closed Principle ( OCP )](https://v8std.ru/patterns/solid/open-closed/) **Стандарты:** #std440 ## ARCH-A4 · Вычисленный признак кладут в отдельное соответствие вместо поля в записи **Сигнал:** результаты цикла собираются в Соответствие и связываются обратно по ключу из исходной коллекции **Порог:** наличие пары «сбор + обратное связывание» **Контр-сигнал (когда это НЕ дефект):** связываются наборы из разных источников, и объединение в один носитель невозможно **Принципы:** - [Информационный эксперт (Information Expert)](https://v8std.ru/patterns/grasp/information-expert/) - [Высокая связность (High Cohesion)](https://v8std.ru/patterns/grasp/high-cohesion/) ## ARCH-A5 · Данные переливаются из переменной в переменную **Сигнал:** цепочка переменных, каждая присваивается и читается ровно один раз, чтобы передать значение следующему шагу **Порог:** >=4 звена **Контр-сигнал (когда это НЕ дефект):** шаги содержательно различны и каждый ветвится по-своему **Принципы:** - [Separation of Concerns](https://v8std.ru/patterns/engineering/separation-of-concerns/) - [Высокая связность (High Cohesion)](https://v8std.ru/patterns/grasp/high-cohesion/) **Стандарты:** #std453 ## ARCH-A6 · Структуру-результат собирают прямо в теле экспортной функции **Сигнал:** >=4 подряд Вставить в структуру, которая возвращается из экспортной функции **Порог:** >=4 вставок плюс возврат структуры **Контр-сигнал (когда это НЕ дефект):** одноразовая локальная структура на 1-2 поля внутри приватного метода **Принципы:** - [Создатель (Creator)](https://v8std.ru/patterns/grasp/creator/) - [Строитель (Builder)](https://v8std.ru/patterns/gof/builder/) ## ARCH-A7 · Дублирующая валидация **Сигнал:** одна и та же проверка заполненности или типа у вызывающего и внутри вызываемого **Порог:** 2 уровня проверки одного значения **Контр-сигнал (когда это НЕ дефект):** вызываемый метод экспортный и имеет несколько независимых вызывающих **Требует индекс кода:** без графа вызовов признак не проверяется — это `skipped`, а не «чисто». **Принципы:** - [Низкая связанность (Low Coupling)](https://v8std.ru/patterns/grasp/low-coupling/) - [DRY (Don't Repeat Yourself)](https://v8std.ru/patterns/engineering/dry/) ## ARCH-A8 · Инфраструктура в прикладном коде **Сигнал:** HTTP-соединение, внешняя компонента или COM-объект прямо в модуле объекта либо формы **Порог:** любое вхождение **Контр-сигнал (когда это НЕ дефект):** одноразовая служебная обработка без прикладной логики **Принципы:** - [Dependency Inversion Principle (DIP)](https://v8std.ru/patterns/solid/dependency-inversion/) - [Фасад (Facade)](https://v8std.ru/patterns/gof/facade/) - [Адаптер (Adapter)](https://v8std.ru/patterns/gof/adapter/) ## ARCH-A9 · Экспорт без потребителей **Сигнал:** экспортный метод, у которого не найдено ни вызывающих, ни триггеров **Порог:** нет точных вызывающих И нет триггеров **Контр-сигнал (когда это НЕ дефект):** ВАЖНО: в 1С экспорт вызывается из XML (подписки, команды, регламентные задания, настройки библиотек), из расширений и внешних обработок — без проверки триггеров находка не выводится вообще **Требует индекс кода:** без графа вызовов признак не проверяется — это `skipped`, а не «чисто». **Принципы:** - [Interface Segregation Principle (ISP)](https://v8std.ru/patterns/solid/interface-segregation/) - [YAGNI (You Aren't Gonna Need It)](https://v8std.ru/patterns/engineering/yagni/) ## ARCH-A10 · Переусложнение, преждевременная гибкость **Сигнал:** механизм расширяемости с единственной реализацией; абстракция без второй точки изменения **Порог:** 1 реализация **Контр-сигнал (когда это НЕ дефект):** вторая реализация уже спроектирована и запланирована в этой же задаче **Принципы:** - [KISS (Keep It Simple, Stupid)](https://v8std.ru/patterns/engineering/kiss/) - [YAGNI (You Aren't Gonna Need It)](https://v8std.ru/patterns/engineering/yagni/) - [Rule of Three (Правило трех)](https://v8std.ru/patterns/engineering/rule-of-three/) ## ARCH-A11 · Проверки по обе стороны вопроса пользователю **Сигнал:** в цепочке одного действия пользователя проверки выполняются и до асинхронного диалога, и после ответа **Порог:** >=1 проверка после ответа пользователя, вход которой был доступен до вопроса **Контр-сигнал (когда это НЕ дефект):** после ответа данные перечитываются под блокировкой перед необратимой операцией; либо вход проверки появляется только из самого ответа пользователя **Требует индекс кода:** без графа вызовов признак не проверяется — это `skipped`, а не «чисто». **Принципы:** - [Оптимизация клиент-серверного взаимодействия прикладных решений](https://v8std.ru/metod8dev/4105/) **Стандарты:** #std487, #std636, #std629 ## ARCH-A12 · Один обработчик на несколько функционально различных сущностей **Сигнал:** ветка диспетчера обслуживает два и более значения признака (тип чека, вид операции, тип документа), у которых правила различаются, тогда как у остальных значений обработчики свои **Порог:** >=2 значения в одной ветке при >=1 значении со своим обработчиком; либо ветка Иначе, в которую сведены остальные значения **Контр-сигнал (когда это НЕ дефект):** значения неразличимы по правилам не сегодня, а по существу (общее основание названо в комментарии); либо ветка одна на всех и диспетчера нет вовсе — тогда это не сведение, а отсутствие разбора **Принципы:** - [Единственная ответственность (Single Responsibility)](https://v8std.ru/patterns/solid/single-responsibility/) - [Открытость-закрытость (Open-Closed)](https://v8std.ru/patterns/solid/open-closed/) - [Полиморфизм (Polymorphism)](https://v8std.ru/patterns/grasp/polymorphism/)
-
-
SKILL.md 15.9 KB
--- name: bsl-architecture-review description: >- Контур проверки архитектуры кода 1С: распределение ответственности, границы и контракты, связанность модулей, ветвление вместо единого метода-диспетчера, дублирование, переусложнение. Принципы SOLID, GRASP и паттерны проектирования в их штатной для 1С реализации. Уровень «требует нового шва» — то, что не чинится заменой строк внутри метода. Вызывается оркестратором quality-gate; напрямую — по запросу «архитектурное ревью», «разнести ответственность», «это нарушение SOLID», «оцени структуру модуля». license: MIT --- # bsl-architecture-review — контур архитектуры Проверяет то, что **не чинится внутри тела метода**. Граница с контуром кода механическая, а не тематическая: > Фикс укладывается в замену строк внутри метода — это код. Фикс требует нового шва > (выделение метода, перенос в другой модуль, новый экспорт, изменение «кто кого вызывает», > ввод диспетчера) — это архитектура. Полные правила границы, отсева повторных находок и шкала важности — в `shared/routing-contract.md` на уровне плагина. Здесь они намеренно не дублируются: копия разъедется с оригиналом при первой же правке, а это ровно тот дефект, который контур и ищет. <ЖЁСТКИЙ-ШЛЮЗ> Только анализ и отчёт. Архитектурная правка без согласования недопустима: она затрагивает вызывающих и переживает автора. Находка без предложенной целевой структуры не выпускается. </ЖЁСТКИЙ-ШЛЮЗ> ## Глубина Приходит от оркестратора вместе с профилем изменения. Контур свои пороги не пересчитывает. | Класс | Уровень | Что смотрим | Бюджет обращений к индексу кода | |---|---|---|---| | C0, C1 | не запускается | — | — | | C2 | 1–2 | тела изменённых методов; при необходимости — экспорты модуля и его вызывающие | ≤4 | | C3 | 3 | плюс связи подсистем, проектирование метаданных, карта ответственностей | ≤8 | Бюджет объявляется явно, потому что индекс кода режет выдачу по числу вызовов: без бюджета контур либо не доберёт фактов, либо упрётся в лимит на середине и отчитается по неполным данным. Архетипы поднимают уровень независимо от класса: новый общий модуль — минимум уровень 2, новый объект метаданных — уровень 3, интеграция и CFE-перехват — минимум уровень 1. --- ## Как работает: измеримые сигналы, а не «прочитай и подумай» Источник истины — `references/signs-map.json`: у каждого признака сигнал, порог, **контр-сигнал** и ссылки на принципы. Человекочитаемая версия — `signs-map.md`. Порядок работы с каждым кандидатом: 1. **Измерь сигнал.** Не «мне кажется, модуль перегружен», а «14 экспортных методов кластеризуются в 4 несвязанные группы». 2. **Проверь контр-сигнал.** У каждого признака есть законная форма, в которой он не является дефектом. Ложноположительная архитектурная находка дороже пропущенной: она провоцирует переделку работающего кода. 3. **Запроси принцип** по URL через MCP `v8std` — формулировка берётся из источника, а не по памяти. 4. **Сформулируй целевую структуру.** Какие методы, модули и поля появляются, что удаляется. ### Симметрия: пере-абстракция ловится так же строго Механизм расширяемости с единственной реализацией, «стратегия» на одну ветку, абстракция без второй точки изменения — это находка уровня 🟠 с формулировкой «предъявите вторую реализацию или упростите». Эта половина контура направлена в первую очередь на код, написанный языковой моделью: типовой отказ лежит именно здесь, а не в недостатке абстракций. Правило трёх обобщает после третьего повторения, не раньше. --- ## Три ограничения точности Без них контур теряет доверие после первой же ложной находки. **1. Одноимённые методы в разных объектах — норма для 1С.** Индекс кода различает точные и эвристические совпадения. Любая эвристика, опирающаяся на счётчик вызывающих, требует точного разрешения; при эвристическом — понижай важность находки на ступень и формулируй её как вопрос, а не как утверждение. **2. Полнотекстовый поиск доказывает наличие, но не отсутствие.** Он ограничен числом просматриваемых файлов. Область поиска — явный список изменённых файлов; пустой результат даёт формулировку «в изменённых файлах не найдено», но никогда — вердикт «чисто». **3. Пороги статического анализатора принадлежат проекту.** Конфигурация анализатора может отключать диагностики или ограничивать анализ отдельными подсистемами — тогда изменённые прикладные файлы вообще не попадут в анализ. Держи свои пороги независимыми и используй анализатор как дешёвый предфильтр кандидатов, никогда — как источник самой находки. **Отдельное жёсткое правило про мёртвый экспорт.** В 1С экспортные методы вызываются не только из кода: подписки на события, команды, регламентные задания, настройки библиотек живут в XML; плюс расширения и внешние обработки. Находка «экспорт без потребителей» **не выводится вообще**, пока не проверены триггеры — иначе контур предложит удалить работающий механизм. --- ## Нет индекса кода — четыре признака уходят в `skipped`, а не в «чисто» Признаки `ARCH-A1`, `ARCH-A7`, `ARCH-A9` и `ARCH-A11` опираются на граф вызовов: кластеризация экспортов по вызывающим, дублирующая валидация у вызывающего и внутри вызываемого, экспорт без потребителей, состав проверок по обе стороны диалога. Последний признак почти всегда пересекает границы модулей: проверки живут в общих модулях, а вызывает их модуль формы. Без индекса кода ни один из четырёх нельзя ни подтвердить, ни опровергнуть. **Факты по графу собирает субагент `bsl-scout`.** Передавай ему вопрос, а не задачу: «экспорты модуля и вызывающие по каждому», «есть ли у метода вызывающие и триггеры в XML». Независимые вопросы задавай параллельно, по одному субагенту на вопрос. Бюджет обращений к индексу расходует он, а твой контекст остаётся под разбор. В его отчёте ищи пометку об эвристическом разрешении вызывающих: она понижает уверенность находки на ступень и меняет формулировку с утверждения на вопрос. Выводы делаешь ты. Субагент возвращает факты и архитектурных вердиктов не выносит. Если субагента в среде нет, работай с индексом сам в пределах объявленного бюджета. Молча их не проверить — значит выдать отчёт, который выглядит полным. Это тот же класс ложной зелени, который контур ищет в чужом коде, только внутри него самого. Поэтому при недоступном индексе пиши в след: ``` [qg skipped: layer=arch, scope=call-graph-signs, planned=[qg:ARCH-A1,qg:ARCH-A7,qg:ARCH-A9,qg:ARCH-A11], reason=rlm_unavailable] ``` и строкой в отчёте: «признаки по графу вызовов не проверялись — индекс кода недоступен». Вердикт «архитектурных замечаний нет» без этой оговорки не выпускается. Остальные признаки от индекса не зависят и гоняются по телам изменённых методов как обычно. Список зависимых живёт в машиночитаемой карте полем `requires: ["call-graph"]`, а не в этом тексте: две копии одного знания разъезжаются при первой правке — ровно то, что ловит признак `ARCH-A3`. --- ## Состязательный аудит на крупных изменениях Для класса C3 с находками уровня 🔴 или 🟠 предложи в отчёте состязательный аудит: веер ревьюеров по измерениям (ответственность, границы, связанность, дублирование, переусложнение) и проверяющие, пытающиеся опровергнуть каждую находку. Архитектурные находки выигрывают от этого больше кодовых: они опираются на эвристики, и доля спорных среди них выше. Методология — `../quality-gate/references/adversarial-audit.md`. Запуск только после явного согласия пользователя. --- ## Формат находки Сверх общего формата обязательны четыре поля. Находка без любого из них не выпускается. ``` [🔴/🟠/🟡] <суть> Где: <путь>::<Метод>:<строка> Признак: qg:ARCH-AN — <название> Сигнал: <измеренное значение> против порога <порог> Принцип: <название> — <URL> (+ #stdNNN, если есть) Целевая структура: <какие методы/модули/поля появляются, что удаляется> Переусложнение: вводится сущностей N, реальных потребителей M, удаляется K Уверенность: высокая | средняя (эвристическое разрешение вызывающих) | требует проверки ``` **Целевая структура** отличает находку от жалобы. «Модуль перегружен» без предложения, как его разделить, не является результатом работы. **Проверка на переусложнение** обязательна, потому что иначе контур сам становится источником пере-абстракции: предлагает ввести три сущности там, где хватает одной. ### Записи следа ``` [qg applied: layer=arch, scope=module-responsibility, ids=[qg:ARCH-A1,std440], verdict=violation:qg:ARCH-A1] [qg applied: layer=arch, scope=branching-dispatch, ids=[qg:ARCH-A2], verdict=clean] [qg skipped: layer=arch, reason=volume_below_threshold] ``` --- ## Специфика 1С Каноничные реализации паттернов из литературы в 1С не работают: платформа не даёт пользовательских иерархий классов. Штатные соответствия — в `references/patterns-in-1c.md`; предлагать нужно именно их, а не абстрактный «интерфейс стратегии». Антипаттерны архитектурного уровня, характерные для кода языковой модели, — в `references/ai-antipatterns-arch.md`. Чеклист по семи областям — в `references/checklist-architecture.md`. --- ## Принципы - **Сигнал вместо вкусовщины.** Каждая находка — измеренное значение против объявленного порога. - **Контр-сигнал обязателен.** Прежде чем выпустить находку, проверь законную форму признака. - **Целевая структура обязательна.** Нет предложения — нет находки. - **Пере-абстракция равна недо-абстракции.** Обе стороны проверяются одинаково строго. - **Уверенность заявляется.** Эвристическое разрешение ссылок понижает важность находки и меняет формулировку с утверждения на вопрос.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.