Claude Skill

bsl-code-review

Контур проверки кода BSL: диагностики статического анализатора, антипаттерны производительности и механики платформы, стандарты разработки #stdNNN, именование, верификация сигнатур API и существования общих модулей. Уровень «внутри тела метода» — то, что чинится заменой строк. Вы

LLM Mart · 0 points · 1 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download romandredan-1c-quality-gate-skills_bsl-code-review-c92a1dd.zip · 124 KB
Part of romandredan/1c-quality-gate — 5 skills

Install

skills CLI npx skills add https://github.com/Romandredan/1c-quality-gate/tree/main/skills/bsl-code-review
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install romandredan-1c-quality-gate@llmmart
Git 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-code-review — контур кода

Проверяет то, что чинится внутри тела метода: замена строк, без нового шва. Всё, что требует выделения метода, переноса в другой модуль, нового экспорта или изменения «кто кого вызывает», принадлежит контуру bsl-architecture-review — граница и правила отсева повторов находок в shared/routing-contract.md.

<ЖЁСТКИЙ-ШЛЮЗ> Только проверка и отчёт. НЕ переписывай логику, запросы, транзакции и права по своей инициативе. В режиме --fix допустимы лишь безопасные категории (см. ниже). </ЖЁСТКИЙ-ШЛЮЗ>

Инварианты контура

Пять утверждений, без которых прогон контура недействителен.

  1. Каталог антипаттернов проходится всегда — читателем либо самостоятельно, но след печатает catalog.mjs attest.
  2. Строку следа инструментальной проверки печатает инструмент — переноси дословно, своих находок этого класса не добавляй: результат детерминирован.
  3. Каждое замечание доказуемо: номер стандарта, код диагностики или название антипаттерна плюс строка кода. «Так лучше» — не находка.
  4. Пропуск фиксируется. Недоступный инструмент или субагент даёт skipped с причиной; молчание неотличимо от выполнения.
  5. Файл, который анализатор не разобрал, не проверен — вердикт «чисто» по нему невозможен, и в отчёте он назван поимённо.

Вход

От оркестратора: класс изменения (C0…C3), сработавшие архетипы, список изменённых файлов. Глубину (Слой 1 / Слой 1+2 / Слой 1+2 с предложением Слоя 3) печатает план (gate.mjs plan, поле resolved: code=...) — архетип может поднять её сверх класса, понизить нельзя. При прямом вызове — определи профиль сам по правилам quality-gate.


Слой 1а — статический анализ

Строка плана для analyzer-run.mjs — одна команда: она находит корень конфигурации, прогоняет только изменённые файлы, проверяет часового и формирует записи следа. Вывод — находки по файлам и готовый блок ## quality evidence. Перенеси его в отчёт как есть: записи следа по слою code сочинять руками не нужно и нельзя.

Твоя работа здесь — триаж, а не припоминание. Список нарушений детерминирован. От тебя требуется отделить то, что надо чинить сейчас, от того, что является осознанной нормой этого проекта, и назвать последствие каждой оставленной находки. Коды расшифровывай через v8std_explain_diagnostics и привязывай к номеру стандарта.

Четыре режима вывода, каждый из которых меняет то, что можно утверждать по результату:

  • Информационные находки свёрнуты в одну строку, полный список — флаг --all. В след коды попадают в любом случае.
  • Проект без основной конфигурации (репозиторий одного расширения): диагностики о неразрешённых именах понижены до информационных — обратно не поднимай, отличить их от настоящих ошибок в этом режиме нечем.
  • «НЕ РАЗОБРАНО файлов» — по этим файлам не проверено ничего. Назови их в отчёте поимённо: вердикт «чисто» по ним невозможен.
  • Часовой status=not_found — прогон недостоверен, вердикт «чисто» запрещён; разберись с анализатором и повтори.

Что стоит за каждым режимом и известные случаи — references/analyzer-output.md. Гейтовый анализ идёт с конфигом из состава плагина: проектный subsystemsFilter вывести изменённые файлы из проверки не может.

Если анализатор недоступен — команда сама запишет [qg skipped: layer=code, scope=static-analysis, planned=[bslls:*], reason=analyzer_unavailable] и вернёт код 1. Продолжай со Слоя 1б: он ловит другое и от анализатора не зависит.

Второй движок — сверка со справочником платформы

Строка плана для platform-context-run.mjs. Ловит то, чего анализатор не видит вовсе: несуществующий член платформенного типа, значение системного перечисления, конструктор, свойство объекта. Всё это компилируется и падает при выполнении. Сервер справки движок заводит сам: ищет поднятый, а не найдя — ставит закреплённый релиз и поднимает свой по установленной платформе. Где платформы на машине нет, пишет skipped с причиной и возвращает код 1 — это законный исход.

Два правила: info «низкая уверенность» не отбрасывать (класс смешанный) и часовой not_found — «чисто» запрещено. Остальное — references/platform-api.md.

Слой 1б — то, чего анализатор не видит

1. Каталог антипаттернов — субагент antipattern-reader

Строка плана для catalog.mjs index печатает индекс триггеров; полная карточка читается по попаданию, не заранее. Сначала сохрани git diff HEAD -- по изменённым .bsl в файл — признаку qg:AI-11 нужно сравнение версий — у читателя нет оболочки, чтобы построить его самому, а attest вычисляет diff сам и без него карточку не пропустит; переданный файл лишь сверяется. Сравнивать не с чем (новый файл без истории) — attest --no-diff-available печатает честный skipped вместо молчания.

Делегируй субагенту antipattern-reader: передай вывод index, список изменённых .bsl и путь к файлу диффа. Он не знает задачи и возвращает JSON — сохрани в файл: путь к нему, список файлов и путь к диффу — в строку плана "$QG/tools/catalog.mjs" attest --diff <файл>.

Инструмент сверяет полноту списка проверенных признаков, состав файлов и цитату каждой находки с самим файлом, после чего печатает строки следа ai-antipatterns и platform-antipatterns и пишет журнал. Отвергнутый результат — повтори запуск читателя с его замечаниями, не правь JSON руками. Читателя в среде нет — прогони индекс сам по той же процедуре и аттестуй так же: строку следа в обоих случаях печатает инструмент.

Признаки с инструментом (bsl-lint, query-lint, rename-check) в проход не входят — их строки печатают инструменты, а карточка нужна для «как чинить»: node "$QG/tools/catalog.mjs" card <ID>.

2. Проверки по тексту кода

Команды — строки плана для query-lint.mjs, bsl-lint.mjs, rename-check.mjs: план печатает их только когда применимо, а полный список признаков с разбором каждого — в references/catalog/INDEX.md (qg:BSL-UNBOUNDED-STRING-COLUMN там же — механическая половина AI-16). XML идёт в query-lint наравне с .bsl: <query> СКД и <QueryText> динамического списка — тоже носитель запроса.

attribute-access покрыт инструментом лишь частично. Доказать ссылочность в пределах одного файла удаётся не всегда: ссылка из чужой функции или из недокументированного параметра остаётся неопознанной. clean здесь означает «механическая часть чиста» и разбора #std437 глазами не отменяет — инструмент задаёт нижнюю границу, а не верхнюю.

Записи следа обоих — по инварианту 2, дословно, без своих находок.

Граф вызовов оба не строят: запрос, собранный конкатенацией или СтрШаблон, виден им лишь частями, и вердикт «чисто» этого не закрывает — разбор приближений в справочнике правила.

3. Стандарты под архетип

Не весь свод подряд — только релевантное: справочники и разделы checklist-code.md печатает план (gate.mjs plan) — общие разделы всегда, прочие под архетип; перечень — BASE_CHECKLIST и ARCHETYPES в tools/profile.mjs. Глубокая вложенность и длинные методы архетипом не считаются и в план не попадают — при такой правке открывай bsl-refactoring.md сам. Тексты самих стандартов запрашивай через MCP v8std по номеру.

4. Именование

#std454 — частая и легко пропускаемая ошибка: сокращения-префиксы, не-CamelCase, булево не в утвердительной форме. Детали и примеры — в references/checklist-code.md.

5. Символы в исходнике

В коде и комментариях только ASCII-дефис. Длинное тире и его родственники дают у анализатора ошибку недопустимого символа. Кавычки-ёлочки допустимы.

6. Верификация API — субагент bsl-verifier

Сигнатуры платформенных методов, существование и экспортность общих модулей, состав объектов метаданных. Процедура — references/api-verification.md.

Делегируй субагенту bsl-verifier, передав ему список изменённых .bsl-файлов. Он дешёвый, работает по той же процедуре и возвращает вердикт, список нарушений с локациями и раздел «Не проверено». Вызов один на весь список: каждый лишний инстанс поднимает свою сессию индекса кода, а справочник платформы на stdio-транспорте вдобавок не переносит параллельных обращений.

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

Субагента в среде может не быть — тогда прогоняй api-verification.md сам. Результат обязан попасть в след одинаково в обоих случаях (инвариант 4):

[qg applied: layer=code, scope=api-verification, ids=[qg:API-SIGNATURE,qg:API-MODULE], verdict=clean]
[qg skipped: layer=code, scope=api-verification, reason=platform_unavailable]

Для класса C1 на этом контур завершается — переходи к отчёту.


Слой 2 — ревью логики моделью

Вызови advisor(). Более сильная модель видит весь транскрипт: задачу, шаги, написанный код. Ловит то, что статика не видит в принципе — неверную бизнес-логику, упущенные сценарии, неучтённые состояния. Замечаниям давай весомый вес.

Холодный читатель — второй взгляд с противоположным входом

Дополнительно к advisor(), когда цена ошибки высока: класс C3 либо затронуты проведение, деньги, права, необратимые операции. Ценность даёт противоположность входов, а не второе мнение — почему, разбирает references/cold-reader.md.

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

Три вопроса, на которые он отвечает:

  1. Что этот код делает как написан, а не как задуман?
  2. На каких входных данных он ломается или ведёт себя неожиданно?
  3. Какое ожидаемое поведение из него не следует?

Модель не дешевле основной: уровень не ниже модели сессии. Расхождение с advisor() — сигнал, а не шум: код допускает два прочтения.

Слой заканчивается записью следа — иначе его пропуск на C3 неотличим от прогона; дефект без своего признака — qg:LOGIC-CONTRACT или qg:LOGIC-CASE-LOSS:

[qg applied: layer=code, scope=logic-review, ids=[qg:LOGIC-CONTRACT], verdict=violation:qg:LOGIC-CONTRACT]
[qg skipped: layer=code, scope=logic-review, reason=advisor_unavailable]

Слой 3 — состязательный аудит (только по подтверждению)

Никогда не запускается сам — контур лишь предлагает его в отчёте и ждёт явного согласия.

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

Состав измерений, пороги, правила голосования, асимметрия для находок 🔴 и порядок действий, когда оркестрация недоступна, — в ../quality-gate/references/adversarial-audit.md.


Автофикс (--fix)

Можно: именование (через переименование символа анализатором, не текстовой заменой), форматирование и отступы, канонические ключевые слова, магические литералы на системные константы, очевидные quick-fix анализатора.

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

После автофикса прогони Слой 1 заново — правки могли внести новые диагностики.


Выход

Находки

[🔴/🟠/🟡] <краткая суть>
Где: <путь:строка>
Правило: #stdNNN п.X | антипаттерн «<название>» | #bslls:<Код>
Проблема: <что именно не так здесь>
Как исправить: <конкретно; для 🔴 — со ссылкой на пример из справочника>
Уверенность: средняя | требует проверки — опускается при высокой

При не-высокой уверенности следом — проверка, которая находку закроет. Довод, снимающий находку без такой проверки, обязан опираться на прочитанный источник — правило и пример в ../quality-gate/references/evidence-format.md.

Ключ локации <путь>::<Метод>:<строка> обязателен — по нему оркестратор дедуплицирует находки с архитектурным контуром (правила — в shared/routing-contract.md).

Записи следа

Минимум одна на каждый слой — выполненный или пропущенный:

[qg applied: layer=code, scope=query-in-loop, ids=[std436,bslls:QueryInLoop], verdict=clean]
[qg applied: layer=code, scope=attribute-access, ids=[qg:BSL-REF-DOT-ACCESS,std437], verdict=violation:qg:BSL-REF-DOT-ACCESS]
[qg skipped: layer=code, scope=static-analysis, planned=[bslls:*], reason=analyzer_unavailable]

Вторую строку печатает инструмент: написанная руками, она валидатор не проходит.

Формат — ../quality-gate/references/evidence-format.md.

Два измерения контур закрыть не может и обязан об этом сказать. Компилируемость тел модулей проверяет только платформа: без запуска проверки конфигурации нужна запись [qg not_verified: dimension=compilation, reason=no_platform], иначе полностью чистый вердикт валидатор отклонит. Выполнимость запроса — то же самое при сработавшем архетипе «Запрос»:

[qg applied: layer=code, scope=query-execution, ids=[qg:QRY-EXECUTED], verdict=clean]
[qg not_verified: dimension=query-execution, reason=no_platform]

Проверка по тексту кода (пункт 2 Слоя 1б) её не заменяет — «Поле не найдено» и несовместимость типов в ОБЪЕДИНИТЬ всплывают только при выполнении. Почему оба измерения устроены так — ../quality-gate/references/evidence-format.md.

Files (1c-quality-gate)
  • references
    • catalog
      • AI-01.md 4.7 KB
        ---
        id: qg:AI-01
        title: Запись набора с удалением по неполному отбору
        severity: critical
        group: model
        tool: null
        archetypes: [record-set]
        std: []
        ---
        
        ## Триггер
        
        `НаборЗаписей…Записать(Истина)` с отбором не по всем ключевым измерениям.
        
        ## Почему
        
        Для периодического регистра тот же дефект — отсутствие `Период` в отборе. `Записать(Истина)`
        сначала **удаляет из базы все записи, удовлетворяющие установленному отбору**, и лишь затем
        пишет набор. Отбор по части измерений и без периода уничтожает всю накопленную историю по
        этим измерениям, а не «перезаписывает текущий снимок». Симптом обнаруживается спустя
        недели — когда историю уже не восстановить.
        
        **Признак в сравнении версий, который часто сопровождает ошибку.** В одном модуле соседние
        процедуры используют разные режимы записи, причём у одной стоит комментарий вида «здесь
        `Истина` стёрло бы всё». Самопротиворечивый код — сигнал, что режим выбран не по механике, а
        по совпадению.
        
        ## Как чинить
        
        - перезапись снимка на конкретный момент — `Записать(Истина)` с отбором по `Период` **и**
          всем измерениям, постоянным для набора; измерение, варьирующееся внутри набора, в отбор не
          ставится, иначе удаление станет точечным и не вычистит устаревшие строки;
        - одиночная запись — `СоздатьМенеджерЗаписи()`: ключ включает период и измерения, стереть
          историю физически не может;
        - накопление новых срезов — `Записать(Ложь)`;
        - период задавать детерминированно (дата документа, дата события), не
          `ТекущаяДатаСеанса()` — иначе повторный запуск плодит дубли;
        - перезапись без периода нельзя называть безопасной для повторного запуска: она удаляет
          данные, а не обновляет их.
        
        #### Неправильно
        
        ```bsl
        // Отбор по одному измерению: Записать(Истина) удалит ВСЕ записи по этой номенклатуре
        // за все периоды, а запишет только текущий набор.
        Набор = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
        Набор.Отбор.Номенклатура.Установить(Номенклатура);
        Набор.Записать(Истина);
        ```
        
        #### Правильно
        
        ```bsl
        // В отборе период и все измерения, постоянные для набора: удалится ровно то,
        // что перезаписывается.
        Набор = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
        Набор.Отбор.Период.Установить(ДатаДокумента);
        Набор.Отбор.Номенклатура.Установить(Номенклатура);
        Набор.Записать(Истина);
        ```
        
        ## Когда это не дефект
        
        - правило требует не «побольше измерений в отборе», а совпадения отбора с тем, что должно
          исчезнуть — проверять надо не число измерений, а ответ на вопрос «что удалит этот отбор»;
        - у регистра, подчинённого регистратору, отбор по регистратору и есть полный ключ: удалить
          все записи документа перед перезаписью — правильно.
        
        ## Что проверяет инструмент
        
        Ничего: смысл отбора инструменту недоступен. Проверяется чтением.
        
      • AI-02.md 3 KB
        ---
        id: qg:AI-02
        title: Чтение реквизита ссылки через точку
        severity: critical
        group: model
        tool: null
        archetypes: [always]
        std: [std453]
        ---
        
        ## Триггер
        
        `Ссылка.Реквизит` в новом или изменённом коде, где объект не нужен целиком.
        
        ## Почему
        
        Обращение через точку загружает **весь объект** — все реквизиты и табличные части. Для
        единичного чтения это лишняя нагрузка и заполнение кеша.
        
        **Отдельно про ревью.** «Так написан соседний код» не является основанием понизить эту
        находку. Легаси чинится отдельно; новый код пишется правильно сразу. Идиома окружения — не
        аргумент против механики платформы.
        
        ## Как чинить
        
        `ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "Имя")`, а для нескольких полей —
        `ЗначенияРеквизитовОбъекта(Ссылка, "Имя1,Имя2")` одним вызовом (возвращает структуру).
        
        #### Неправильно
        
        ```bsl
        Валюта = ДокументСсылка.Валюта;  // загружается весь документ вместе с табличными частями
        ```
        
        #### Правильно
        
        ```bsl
        Валюта = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(ДокументСсылка, "Валюта");
        // несколько полей - одним вызовом, а не несколькими
        Реквизиты = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(ДокументСсылка, "Валюта,Организация");
        ```
        
        ## Когда это не дефект
        
        - объект уже получен как объект — например, в обработчике события — и точка на нём не
          вызывает лишнюю загрузку;
        - имя после точки — поле выборки результата запроса, а не обращение через ссылку.
        
        ## Что проверяет инструмент
        
        **Механизированная часть — `qg:BSL-REF-DOT-ACCESS`** (`tools/bsl-lint.mjs`): ссылочность
        доказывается присваиванием в том же методе, типом параметра из описания #std453 либо именем
        на «Ссылка». Это нижняя граница проверки, а не верхняя — ссылку из чужой функции инструмент
        не распознаёт; основания, контр-сигналы и разбор — в `references/catalog/BSL-REF-DOT-ACCESS.md`.
        
      • AI-03.md 3.1 KB
        ---
        id: qg:AI-03
        title: Реквизиты и свойства заводятся кодом во время работы, минуя метаданные
        severity: critical
        group: model
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Программное создание видов характеристик/доп. свойств из кода; запись в регистры в обход
        API.
        
        ## Почему
        
        Создание метаданных во время выполнения хрупко — зависит от порядка инициализации и
        ломается при обновлении. Прямая запись в системные регистры библиотеки через менеджер
        записи в обход публичного программного интерфейса подсистемы создаёт договорённость, о
        которой сама подсистема не знает: о ваших данных она не подозревает и сохранять их не
        обязана.
        
        **Диагностический признак.** Мысль «стандартное API отбросит мои данные» означает не дефект
        API, а неверную модель данных. Чинить нужно модель.
        
        ## Как чинить
        
        Сначала инвентаризировать существующие реквизиты объекта — нужное часто уже есть в типовой
        конфигурации. Если нужен новый реквизит, он добавляется как объект метаданных, а не из кода.
        Запись значений — только через публичное API подсистемы.
        
        #### Неправильно
        
        ```bsl
        // Вид характеристики создаётся кодом при первом обращении.
        Свойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.СоздатьЭлемент();
        Свойство.Наименование = "Код партнёра";
        Свойство.Записать();
        ```
        
        #### Правильно
        
        ```bsl
        // Свойство заведено в метаданных (или найдено среди типовых), код только читает его
        // по программному имени - запросом сразу для всех нужных имён, см. AI-13.
        Свойства = СвойстваПоИменам("КодПартнёра,СрокГодности");  // имя -> ссылка
        ```
        
        ## Когда это не дефект
        
        - правило не про перенос данных обменом: при загрузке из другой базы элементы плана видов
          характеристик создаются программно — это перенос уже существующих объектов, а не
          изобретение новых во время работы.
        
        ## Что проверяет инструмент
        
        Ничего, правило проверяется чтением.
        
      • AI-04.md 2.5 KB
        ---
        id: qg:AI-04
        title: Отчёт о проверке, которая не прогонялась
        severity: critical
        group: model
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Утверждения «проверено», «собирается», «проходит» без следа фактического прогона.
        
        ## Почему
        
        Слово-маркер этого антипаттерна — не только «проверено», но и «зарегистрировано». Ложно-
        уверенный отчёт хуже честного «не проверял» — он закрывает вопрос фальшивой зеленью. Именно
        этот антипаттерн делает бессмысленной любую систему контроля качества, включая эту.
        
        ## Как чинить
        
        Такие слова допустимы только после реального прогона — поиска по файлу конфигурации,
        настоящей сборки, прогона на подготовленном образце данных. Не прогонял — так и пиши. В
        следе прогона для этого есть отдельная запись `not_verified`.
        
        Частный случай той же ошибки: строить логику на догадке о том, что означают поля во внешнем
        ответе («это, наверное, идентификатор», «оно приходит в этом методе»). Тип, наличие и смысл
        поля сверяются по настоящему ответу сервиса **до** написания кода, иначе код переписывается
        два-три раза.
        
        ## Когда это не дефект
        
        - отчёт прямо называет проверку непрогнанной и причину — это заявленный пропуск, а не
          дефект: для этого в следе прогона есть запись `not_verified`, а не молчание.
        
        ## Что проверяет инструмент
        
        След прогона: `tools/evidence-validator.mjs` не принимает вердикт без записи о запуске
        проверки, а неизвестный идентификатор `qg:*` считает ошибкой. Слова «проверено» в свободном
        тексте отчёта не проверяет никто — за них отвечает автор.
        
      • AI-05.md 3.3 KB
        ---
        id: qg:AI-05
        title: «Зелёная» сборка принята за доказательство компилируемости
        severity: critical
        group: model
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Вывод «код компилируется» по загрузке/выгрузке конфигурации, валидатору XML или
        анализатору.
        
        ## Почему
        
        Речь именно об успешной загрузке конфигурации **из файлов** — не о прогоне платформой.
        Загрузка и выгрузка конфигурации работают со **структурой** и не компилируют тела модулей.
        Синтаксическая ошибка внутри процедуры проходит их все, а всплывает при инициализации модуля
        в базе. Статический анализатор тоже толерантен к части конструкций, где платформенный
        компилятор строже.
        
        ## Как чинить
        
        Компилируемость подтверждает только проверка конфигурации платформой (`/CheckConfig`) либо
        реальный прогон в базе. Если ни то ни другое не выполнялось — запись
        `[qg not_verified: dimension=compilation, reason=no_platform]`.
        
        **Конкретная ловушка, породившая правило.** У блочных операторов (`КонецПопытки`,
        `КонецЕсли`, `КонецЦикла`) точка с запятой необязательна, **пока они последние в своём
        блоке**. Как только после такого оператора вставляется ещё один, он перестаёт быть
        последним — и требует `;`. При вставке кода после закрывающего оператора всегда проверяй
        строку прямо над местом вставки.
        
        ```bsl
        // Было: КонецЕсли последний в блоке, точка с запятой не нужна.
        Если Условие Тогда
            Обработать();
        КонецЕсли
        ```
        
        ```bsl
        // Стало: после КонецЕсли добавили ещё один оператор - теперь точка с запятой обязательна.
        Если Условие Тогда
            Обработать();
        КонецЕсли;
        ЗаписатьВЖурнал();
        ```
        
        ## Когда это не дефект
        
        - прогон платформой не требуется, если правка не трогала тела модулей: переименование
          файла, комментарий в XML, право в роли — тогда так и пишется: тела модулей не менялись,
          компиляция ни при чём.
        
        ## Что проверяет инструмент
        
        Компилируемость не проверяет ни один инструмент гейта: ни анализатор кода, ни валидаторы XML
        тела модулей не компилируют. Нужен прогон платформой (`/CheckConfig`) либо запуск в базе.
        
      • AI-06.md 2 KB
        ---
        id: qg:AI-06
        title: Самодельный флаг отмены вместо Отказ
        severity: major
        group: model
        tool: null
        archetypes: [object-event, form-module]
        std: []
        ---
        
        ## Триггер
        
        Выходные параметры «ЕстьПроблемы», структуры «Прервать»/«Текст» вместо отмены операции.
        
        ## Почему
        
        Речь о структурах-мешках с полями «Прервать» и «Текст» там, где по смыслу это обычная отмена
        операции. `Отказ` — конвенция платформы и библиотек, её понимают все стандартные обработчики
        и все читатели кода. Своя конструкция требует изучения и не стыкуется с типовым поведением.
        
        ## Как чинить
        
        Выходной параметр провала называется `Отказ` и имеет тип Булево; подробности пишутся в
        журнал регистрации.
        
        #### Неправильно
        
        ```bsl
        Процедура ПроверитьЗаполнение(Документ, ЕстьПроблемы, ТекстПроблемы)
        ```
        
        #### Правильно
        
        ```bsl
        Процедура ПроверитьЗаполнение(Документ, Отказ)
        ```
        
        ## Когда это не дефект
        
        - наружу нужен не только факт отказа, но и разбор — что именно не прошло и по каким
          строкам, — результат возвращается структурой; но признак провала в ней всё равно
          называется `Отказ` и имеет тип Булево: по нему принимают решение стандартные обработчики.
        
        ## Что проверяет инструмент
        
        Ничего, правило проверяется чтением.
        
      • AI-07.md 5.7 KB
        ---
        id: qg:AI-07
        title: У параметров, которые метод не изменяет, не стоит Знач
        severity: major
        group: model
        tool: null
        archetypes: [always]
        std: [std487, std640]
        ---
        
        ## Триггер
        
        Структура/массив/ТЗ без `Знач` в методе, который их не меняет — особенно у серверного
        метода из клиента.
        
        ## Почему
        
        Параметр, который метод только читает, объявляется с `Знач`. Это заявление о намерении, и у
        него есть измеримое следствие: у серверного метода, вызываемого с клиента, параметр без
        `Знач` после вызова передаётся обратно на клиент — лишний трафик (АПК:1412, #std487).
        
        **Чего `Знач` не делает — это важнее того, что делает.** `Знач` защищает саму
        переменную-параметр от присваивания: `Параметр = ДругоеЗначение` внутри метода не изменит
        переменную вызывающего. Содержимое переданной коллекции он не защищает:
        `Параметр.Добавить(…)`, `Параметр.Вставить(…)`, `Параметр.Очистить()`, удаление колонок
        таблицы вызывающий увидит — объект остаётся тем же самым. Разбор этого — в методической
        статье ИТС «Передача параметров по ссылке и по значению при вызове процедур и функций», на
        которую ссылается #std640 в разделе «См. также».
        
        Отсюда практическое следствие: `Знач` — объявление намерения и защита от подмены значения, но
        не гарантия неизменности данных. Если неизменность нужна по-настоящему, передают копию
        (`Скопировать()` у таблицы значений, обход с `Вставить` у структуры) либо фиксированную
        коллекцию (`Новый ФиксированнаяСтруктура(Структура)`, `Новый ФиксированныйМассив(Массив)`).
        
        ## Как чинить
        
        #### Неправильно
        
        ```bsl
        // Метод только читает таблицу, но объявлен так, будто может подменить её целиком.
        Функция ПодобратьЦены(ТаблицаТоваров)
        ```
        
        #### Правильно
        
        ```bsl
        Функция ПодобратьЦены(Знач ТаблицаТоваров)
        ```
        
        **Смежная ошибка:** `Знач` — модификатор **параметра**, а не локальной переменной.
        `Знач Переменная = …` внутри тела процедуры даёт синтаксическую ошибку.
        
        **Смежная ошибка:** `ЗаполнитьЗначенияСвойств(Приёмник, Источник)` копией структуры не
        является — метод переносит значения только в **уже существующие** свойства приёмника, и
        пустая структура после вызова останется пустой. Копия собирается обходом с `Вставить`. Ошибка
        живучая: на объектах и наборах записей метод работает как ожидается, поэтому перенос той же
        привычки на структуру даёт молчаливо пустой результат, а не отказ.
        
        **Как проявляется, когда метод меняет полученную коллекцию.** Метод, удаляющий ключ из
        полученной структуры, портит её для второго вызова с тем же аргументом: первый вызов
        проходит, второй падает на отсутствующем поле. Пока сценарий вызывает такой метод однократно,
        дефект невидим — он проявляется на ветке, где вызовов два подряд.
        
        ## Когда это не дефект
        
        - метод обязан изменить переданную коллекцию: заполнить таблицу результата, дописать структуру
          параметров, снять признак у строк — тогда `Знач` не ставится, а в описании параметра прямо
          сказано, что он изменяется (#std453);
        - параметр примитивного типа, который метод не меняет: `Знач` ничего не даёт по существу, но
          и не мешает; правило про коллекции.
        
        ## Что проверяет инструмент
        
        Диагностика для серверного метода, вызываемого с клиента, есть — АПК:1412, — но печатает её
        автоматизированная проверка конфигураций, а не инструменты гейта. В прогоне гейта правило
        проверяется чтением: изменяет метод коллекцию или нет, видно только по телу метода.
        
      • AI-08.md 3.4 KB
        ---
        id: qg:AI-08
        title: Ответ внешней системы разбирают по одному полю в разных местах
        severity: major
        group: model
        tool: null
        archetypes: [always]
        std: [std641]
        ---
        
        ## Триггер
        
        Набор функций-помощников, каждая достаёт из ответа одно поле и приводит его к нужному типу.
        
        ## Почему
        
        Набор мелких функций-помощников: своя для строки, своя для даты, своя для числа. Вызовы
        разбросаны по коду заполнения документов и регистров. Ответ сервиса
        превращается в данные 1С один раз и в одном месте. Дальше код работает с результатом
        разбора — структурой или таблицей значений, — а в исходный ответ больше не заглядывает.
        
        **Чем это плохо — три конкретные вещи.**
        
        1. **Переименование поля во внешней системе правится во всех местах, где это поле достают.**
           Одно пропущенное место — молча пустое значение вместо ошибки.
        2. **Нигде не видно целиком, что из ответа берут и во что превращают.** Проверить разбор
           нечем: каждый помощник проверяется отдельно, а вместе они не проверяются никак.
        3. **Типы расходятся.** В одном помощнике дата разбирается как дата, в другом то же поле
           остаётся строкой — и дальше по коду одно и то же значение ведёт себя по-разному.
        
        ## Как чинить
        
        ```bsl
        // Разбор один раз, на границе с внешней системой.
        ДанныеЗаказа = РазобратьОтветЗаказа(Ответ);  // структура с известным составом полей
        // Дальше по коду - работа с результатом разбора, а не с ответом.
        ЗаполнитьДокумент(Документ, ДанныеЗаказа);
        ```
        
        Состав полей результата объявляется отдельной функцией-конструктором (#std641): там видно и
        перечень полей, и тип каждого. Приведение типов делается платформенными средствами —
        `XMLЗначение` и `XMLСтрока`, — а не разбором формата даты вручную.
        
        ## Когда это не дефект
        
        - из ответа берут одно-два поля, и больше он нигде не нужен: отдельная функция разбора не
          окупится;
        - ответ разнородный: разные виды событий с разным составом полей — тогда разбор пишется по
          одному на вид события, но не по одному на поле.
        
        ## Что проверяет инструмент
        
        Ничего, правило проверяется чтением кода.
        
      • AI-09.md 3 KB
        ---
        id: qg:AI-09
        title: Настройки и значения по умолчанию вычисляются внутри цикла
        severity: major
        group: model
        tool: null
        archetypes: [always]
        std: [std724]
        ---
        
        ## Триггер
        
        Чтение константы или поиск предопределённого элемента внутри цикла по строкам.
        
        ## Почему
        
        Триггер — не только чтение внутри цикла по строкам документа, но и обращение к настройкам
        или к регистру сведений внутри процедуры, которую вызывают для каждой строки. Всё, что не
        меняется от строки к строке — константы, настройки, ссылки по умолчанию, курс валюты, —
        вычисляется один раз до цикла и передаётся внутрь готовым. Каждое чтение внутри цикла —
        обращение к базе данных: на документе в тысячу строк это тысяча обращений вместо одного.
        Вторая беда не про скорость: одна и та же настройка оказывается вычислена в трёх местах, а в
        четвёртом про неё забывают, и поведение в этих местах расходится.
        
        ## Как чинить
        
        ```bsl
        // Неправильно
        Для Каждого СтрокаТоваров Из Документ.Товары Цикл
            СтрокаТоваров.СтавкаНДС = Константы.СтавкаНДСПоУмолчанию.Получить();
        КонецЦикла;
        ```
        
        ```bsl
        // Правильно
        СтавкаПоУмолчанию = Константы.СтавкаНДСПоУмолчанию.Получить();
        Для Каждого СтрокаТоваров Из Документ.Товары Цикл
            СтрокаТоваров.СтавкаНДС = СтавкаПоУмолчанию;
        КонецЦикла;
        ```
        
        Если таких значений много, они собираются в одну структуру до цикла, и процедуры заполнения
        получают её параметром: тогда они только присваивают, а не вычисляют.
        
        ## Когда это не дефект
        
        - значение зависит от строки — ему и место в цикле;
        - значение отдаёт модуль с повторным использованием возвращаемых значений (#std724):
          повторный вызов в цикле дёшев, но первый вызов платный, а результат живёт до конца сеанса —
          если значение должно быть строго на момент выполнения, кэш не подходит.
        
        ## Что проверяет инструмент
        
        Ничего.
        
      • AI-10.md 3.1 KB
        ---
        id: qg:AI-10
        title: Сопоставление в памяти вместо соединения в запросе
        severity: major
        group: model
        tool: null
        archetypes: [query]
        std: []
        ---
        
        ## Триггер
        
        Вложенные циклы поиска соответствий и построение `Соответствие` для связывания наборов.
        
        ## Почему
        
        Речь о наборах, которые оба приходят из базы. Движок запросов делает это короче и быстрее.
        
        ## Как чинить
        
        Одним запросом с временными таблицами и соединениями, затем `ЗаполнитьЗначенияСвойств` —
        если выровнять псевдонимы колонок запроса под имена реквизитов приёмника, заполнение
        становится однострочным.
        
        **Не потеряй при этом** проверку однозначности ключа соединения. Если ключ не уникален в
        источнике, соединение размножит строки. Нужен либо контроль уникальности ключа, либо явный
        отсев повторов.
        
        #### Неправильно
        
        ```bsl
        // Два набора из базы сопоставляются вложенными циклами.
        Для Каждого СтрокаЗаказа Из Заказы Цикл
            Для Каждого СтрокаОстатка Из Остатки Цикл
                Если СтрокаОстатка.Номенклатура = СтрокаЗаказа.Номенклатура Тогда
                    СтрокаЗаказа.Остаток = СтрокаОстатка.Количество;
                    Прервать;
                КонецЕсли;
            КонецЦикла;
        КонецЦикла;
        ```
        
        #### Правильно
        
        ```sdbl
        ВЫБРАТЬ
            Заказы.Номенклатура КАК Номенклатура,
            ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
        ИЗ
            ВТ_Заказы КАК Заказы
            ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата, ) КАК Остатки
                ПО Заказы.Номенклатура = Остатки.Номенклатура
        ```
        
        ## Когда это не дефект
        
        - наборы пришли из разных источников: один из базы, другой из ответа сервиса или файла —
          соединить их запросом можно, только поместив второй во временную таблицу, и на десятке
          строк это дороже цикла;
        - наборы заведомо маленькие — единицы строк, — а запрос ради них добавит обращение к базе.
        
        ## Что проверяет инструмент
        
        Ничего, правило проверяется чтением.
        
      • AI-11.md 3.1 KB
        ---
        id: qg:AI-11
        title: При упрощении кода молча исчезают проверки и служебные записи
        severity: major
        group: model
        tool: null
        archetypes: [always]
        std: []
        needs: [diff]
        ---
        
        ## Триггер
        
        В `git diff` — исчезнувшие проверки редких случаев и записи без читаемого результата.
        
        ## Почему
        
        Когда метод сокращают, вместе с лишним уходят проверки редких случаев перед основной
        работой (`Если … Тогда Возврат КонецЕсли`) и записи, которые нужны не как результат, а как
        след: заполненный идентификатор, начальное состояние, отметка в регистре — их результат в
        этом же методе нигде не читается. Автор правки этого не замечает: код стал короче и
        по-прежнему работает на основном сценарии.
        
        **Почему это опаснее, чем выглядит.** Проверка редкого случая не срабатывает на обычных
        данных: она написана для тех, что бывают раз в месяц. Служебная запись не влияет на текущий
        сценарий — её читает соседняя подсистема или следующий запуск. Поэтому ни ручная проверка,
        ни прогон основного сценария пропажу не покажут; она вернётся ошибкой через недели, когда
        связь с той правкой уже не восстановить.
        
        ## Как чинить
        
        Сокращая метод, выписать всё удаляемое и по каждому пункту ответить на один вопрос: кто это
        читал. Нет ответа — удалять можно. Есть — оставлять.
        
        ## Когда это не дефект
        
        - не всякое сокращение — потеря: если у проверки нет ни одного случая, в котором она
          сработает — условие невозможно по типу данных либо уже проверено выше, — она мёртвая, и
          удаление правильно;
        - записывать правку в поломку, не выяснив, кто читал удалённое, так же неверно, как удалять
          не выяснив.
        
        ## Что проверяет инструмент
        
        Пропажу проверок — ничего: для инструмента это просто более короткий код. Смежный случай —
        вызов метода, объявление которого исчезло в той же правке, — ловит
        `qg:BSL-STALE-LOCAL-CALL` (`tools/rename-check.mjs`).
        
      • AI-12.md 3.3 KB
        ---
        id: qg:AI-12
        title: Проведение внутри транзакции записи
        severity: major
        group: model
        tool: null
        archetypes: [object-event, transaction]
        std: []
        ---
        
        ## Триггер
        
        Запись документа и его проведение в одной транзакции «ради атомарности».
        
        ## Почему
        
        Проведение проверяет остатки и может законно не пройти. Внутри общей транзакции такой сбой
        откатывает всё и **маскирует причину** — вместо понятной ошибки проведения получается пустой
        результат.
        
        ## Как чинить
        
        В транзакции — запись и сопутствующие следовые записи (они существуют тогда и только тогда,
        когда документ записан). Проведение — отдельным шагом после успешной записи. Тогда документ
        сохранён, а ошибка проведения видна и разбираема.
        
        #### Неправильно
        
        ```bsl
        НачатьТранзакцию();
        Попытка
            ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
            ЗаписатьСлед(ДокументОбъект.Ссылка);
            ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);  // остатков не хватило
            ЗафиксироватьТранзакцию();
        Исключение
            ОтменитьТранзакцию();  // откатилось всё, включая запись; причина потеряна
        КонецПопытки;
        ```
        
        #### Правильно
        
        ```bsl
        НачатьТранзакцию();
        Попытка
            ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
            ЗаписатьСлед(ДокументОбъект.Ссылка);
            ЗафиксироватьТранзакцию();
        Исключение
            ОтменитьТранзакцию();
            ВызватьИсключение;
        КонецПопытки;
        
        // Отдельным шагом: документ уже сохранён, ошибка проведения видна и разбираема.
        ПровестиДокумент(ДокументОбъект.Ссылка);
        ```
        
        ## Когда это не дефект
        
        - бизнес-правило требует, чтобы документ без успешного проведения вообще не сохранялся
          (незаписанный черновик недопустим) — тогда общая транзакция и есть нужная атомарность, а
          не маскировка: результат «ничего не сохранилось» соответствует замыслу.
        
        ## Что проверяет инструмент
        
        Собственную транзакцию внутри обработчика события объекта ловит `qg:BSL-TXN-IN-HANDLER`
        (`tools/bsl-lint.mjs`). Проведение внутри транзакции инструменту не видно — это проверяется
        чтением.
        
      • AI-13.md 3.6 KB
        ---
        id: qg:AI-13
        title: Поиск дополнительного свойства по наименованию
        severity: minor
        group: model
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        `НайтиПоНаименованию` у доп. реквизита; `НайтиПоКоду` у справочника в теле механизма.
        
        ## Почему
        
        У плана видов характеристик дополнительных реквизитов **программное имя** и
        **пользовательское наименование** — разные реквизиты. Библиотека ищет свойство только по
        имени. Поиск по наименованию работает случайно и ломается при первом же переименовании
        заголовка.
        
        **То же самое — поиск любого справочного значения по коду.** `НайтиПоКоду("XX-000017")` в
        теле механизма имеет ту же природу: код элемента меняется в пользовательском режиме, ссылка
        при этом остаётся прежней, и ветка перестаёт находить документы — молча, без ошибки. Замена
        выбирается по тому, одно ли значение на всю базу: предопределённый элемент
        (`ПредопределённоеЗначение`), константа или реквизит настройки, если у каждой точки
        применения значение своё. Довод «так же ищет соседний механизм» аргументом не является: у
        соседа та же уязвимость.
        
        ## Как чинить
        
        Правильно — запрос по реквизиту `Имя`, сразу пакетом для всех нужных имён, с получением
        соответствия «имя → ссылка».
        
        #### Неправильно
        
        ```bsl
        Свойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Код партнёра");
        ```
        
        #### Правильно
        
        ```sdbl
        ВЫБРАТЬ
            Свойства.Имя КАК Имя,
            Свойства.Ссылка КАК Ссылка
        ИЗ
            ПланВидовХарактеристик.ДополнительныеРеквизитыИСведения КАК Свойства
        ГДЕ
            Свойства.Имя В (&Имена)
        ```
        
        Результат разворачивается в соответствие «имя → ссылка» один раз на все нужные свойства.
        
        ## Когда это не дефект
        
        - наименование используют, когда его показывают пользователю — ключом поиска оно не служит
          никогда: его меняют в пользовательском режиме, и код ломается молча;
        - поиск по коду законен в разовых обработках и миграциях, живущих один прогон, по значению,
          введённому пользователем, и при загрузке из внешнего источника, где код и есть ключ
          сопоставления.
        
        ## Что проверяет инструмент
        
        Ничего, правило проверяется чтением.
        
      • AI-14.md 2.1 KB
        ---
        id: qg:AI-14
        title: Чтение ячейки табличного документа несуществующим методом
        severity: minor
        group: model
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        У табличного документа нет метода `Получить` — ячейка читается через
        `Область(Строка, Колонка)`.
        
        ## Почему
        
        Для текстовых ячеек, пришедших из внешнего файла, читать нужно `.Текст`, а не `.Значение`:
        свойство значения доступно, только когда в ячейке лежит объект-значение, иначе — ошибка
        чтения поля. `.Текст` не выбрасывает исключение никогда, пустая ячейка даёт пустую строку;
        типизацию выполняет вызывающий код.
        
        ## Как чинить
        
        #### Неправильно
        
        ```bsl
        Значение = Макет.Получить(1, 1);        // такого метода у табличного документа нет
        ТекстЯчейки = Макет.Область(1, 1).Значение;  // для текстовой ячейки даст ошибку чтения
        ```
        
        #### Правильно
        
        ```bsl
        Ячейка = Макет.Область(1, 1);
        ТекстЯчейки = Ячейка.Текст;  // пустая ячейка даст пустую строку, исключения не будет
        ```
        
        ## Когда это не дефект
        
        - ячейка гарантированно содержит объект-значение (не текст из внешнего файла) — тогда
          `.Значение` читается штатно, и `.Текст` не нужен.
        
        ## Что проверяет инструмент
        
        Ничего: тип значения переменной по тексту кода не выводится. Ошибка появляется при
        выполнении, на первом же обращении к ячейке.
        
      • AI-15.md 1.9 KB
        ---
        id: qg:AI-15
        title: Присваивание read-only свойствам параметров записи
        severity: minor
        group: model
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        `Новый ПараметрыЗаписиJSON` без аргументов, затем присваивание свойству — read-only.
        
        ## Почему
        
        У параметров записи JSON свойства доступны только для чтения — значения передаются **в
        конструктор**. Попытка присвоить свойство после создания объекта без аргументов даёт ошибку
        недоступности поля для записи.
        
        ## Как чинить
        
        #### Неправильно
        
        ```bsl
        Параметры = Новый ПараметрыЗаписиJSON;
        Параметры.ПереносСтрок = ПереносСтрокJSON.Авто;  // свойство только для чтения
        ```
        
        #### Правильно
        
        ```bsl
        Параметры = Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Авто, Символы.Таб);
        ```
        
        ## Когда это не дефект
        
        - правило касается объектов-настроек, чьи значения задаются конструктором;
        - у большинства других объектов платформы свойства доступны для записи — доступность
          смотрится в справочнике платформы по конкретному типу, а не выводится по аналогии.
        
        ## Что проверяет инструмент
        
        Ничего: доступность свойства на запись проверяется по справочнику платформы при написании
        кода.
        
      • AI-16.md 6.1 KB
        ---
        id: qg:AI-16
        title: Колонки неограниченной длины в запросе
        severity: major
        group: model
        tool: null
        archetypes: [query]
        std: [std432, std434]
        ---
        
        ## Триггер
        
        Строковая колонка ТЗ без `КвалификаторыСтроки` уходит в запрос — ошибка при выполнении.
        
        ## Почему
        
        Колонка таблицы значений, объявленная как строка **без квалификатора длины**
        (`Новый ОписаниеТипов("Строка")`), имеет неограниченную длину. Помещённая во временную
        таблицу, она даёт ошибку выполнения в `РАЗЛИЧНЫЕ`, `ОБЪЕДИНИТЬ` без `ВСЕ` (оно тоже устраняет
        дубли, #std434), `СГРУППИРОВАТЬ ПО`, соединении (`ПО`) и сравнении в `ГДЕ`. Корень один —
        движок не работает с полями неограниченной длины там, где значения приходится сравнивать
        между собой, — а сообщений у него несколько: «Нельзя сравнивать поля неограниченной длины»,
        «Недопустимое поле для группировки», «Недопустимое поле для упорядочивания» (`УПОРЯДОЧИТЬ
        ПО` под тот же запрет попадает), «Индексируемое поле не может иметь составной тип и тип
        неограниченной длины» (`ИНДЕКСИРОВАТЬ ПО`). Якорь — #std432 п. 3.1: для сравнения,
        группировки и `РАЗЛИЧНЫЕ` стандарт предписывает приводить такие поля к строке определённой
        длины. Условие соединения — то же сравнение, поэтому список конструкций шире перечисленного
        в стандарте.
        
        Ошибку не видит ни один статический слой — она доживает до первого выполнения запроса.
        Коварная форма: таблицу в запрос отправляет **другой метод**, по месту объявления колонки
        использование не видно, и автор не знает, что типизация вообще имеет значение. Реальный
        случай: колонка без квалификатора ушла параметром в общий метод подбора номенклатуры, а тот
        соединил её с ресурсом регистра — «Нельзя сравнивать поля неограниченной длины».
        
        **Почему 🟠, а не 🔴.** Запрос действительно не выполнится — но только если таблица дойдёт до
        перечисленных конструкций. По месту объявления колонки это неизвестно, и часть объявлений
        без квалификатора законна; правило одного класса с `qg:QRY-ALIAS-SHADOWS-FIELD`: ошибка
        появляется только при выполнении.
        
        ## Как чинить
        
        Правило по умолчанию: строковая колонка таблицы, передаваемой параметром в запрос,
        объявляется с квалификатором —
        `Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(N))`; как её использует вызываемый
        код, снаружи не видно. Повторяя существующий вызов, копируй объявление колонок у того кода,
        который уже работает. И не ставить `РАЗЛИЧНЫЕ` там, где ключ уникален по построению.
        
        #### Неправильно
        
        ```bsl
        Таблица.Колонки.Добавить("Артикул", Новый ОписаниеТипов("Строка"));
        ```
        
        #### Правильно
        
        ```bsl
        Таблица.Колонки.Добавить("Артикул",
            Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(50)));
        ```
        
        ## Когда это не дефект
        
        - квалификатор молча режет значение по длине, поэтому «дописать `КвалификаторыСтроки`
          везде» решением не является;
        - колонка, несущая длинный текст (комментарий пользователя, XML, JSON, тело ответа сервиса),
          неограниченной длины **законно** (#std432 п. 2);
        - если такое поле всё-таки нужно в запросе, длина назначается на стороне запроса —
          `ВЫРАЗИТЬ(Т.Поле КАК СТРОКА(N))`, в СКД вместо этого задаётся тип значения поля набора
          данных (#std432 п. 3.2), — а данные в таблице остаются целыми;
        - частое приведение к длине само по себе сигнал, что поле в запросе лишнее.
        
        ## Что проверяет инструмент
        
        Механическая половина проверяется инструментом: `qg:BSL-UNBOUNDED-STRING-COLUMN`
        (`tools/bsl-lint.mjs`) — колонка без квалификатора у таблицы, которая **в том же модуле**
        уходит в `УстановитьПараметр`. Уход таблицы в чужой метод инструменту не виден: графа вызовов
        он не строит, и эта половина остаётся за читателем.
        
      • AI-17.md 8.8 KB
        ---
        id: qg:AI-17
        title: Результат запроса переложен циклом в массив структур
        severity: major
        group: model
        tool: null
        archetypes: [query]
        std: []
        ---
        
        ## Триггер
        
        Цикл по выборке запроса собирает структуру и кладёт в массив — список полей задублирован.
        
        ## Почему
        
        В цикле по выборке результата запроса делается ровно три вещи и больше ничего: создаётся
        структура со списком полей, в неё копируются значения (`ЗаполнитьЗначенияСвойств`), структура
        добавляется в массив. Дополнительный признак: список полей написан в модуле дважды — сначала
        в тексте запроса, потом строкой в конструкторе структуры.
        
        Набор однотипных записей, полученный запросом, не нужно перекладывать в массив структур.
        Результат запроса выгружается в таблицу значений одной строкой:
        `Запрос.Выполнить().Выгрузить()`. Признак, которого в запросе нет, добавляется отдельной
        колонкой.
        
        **Чем это плохо — три конкретные вещи.**
        
        1. **Список полей написан дважды.** Поле, добавленное в запрос, но забытое во втором списке,
           в структуру не попадёт, и никакого сообщения об этом не будет: `ЗаполнитьЗначенияСвойств`
           копирует значения только в те свойства, которые в приёмнике уже есть, а о лишних полях
           источника молчит. Ошибка проявится позже и в другом месте — там, где к пропавшему полю
           обратятся.
        2. **У структуры нет типов.** У колонок таблицы, полученной через `Выгрузить()`, типы берутся
           из полей запроса. У свойств структуры типа нет вообще, поэтому положить в поле не то
           значение ничто не помешает.
        3. **Всё, что таблица умеет сама, приходится писать руками.** Поиск строк по значениям
           колонок, отсев повторов, сумма по колонке, сортировка, выгрузка колонки в массив, копия
           части строк — у таблицы значений это готовые методы (`НайтиСтроки`, `Свернуть`, `Итог`,
           `Сортировать`, `ВыгрузитьКолонку`, `Скопировать`). У массива структур их нет, и каждый
           следующий разработчик пишет очередной цикл.
        
        **Почему замена обычно не ломает вызывающий код.** Строка таблицы значений ведёт себя как
        структура: обращение по индексу (`Записи[0].Поле`), `Количество()`, обход `Для Каждого`,
        присваивание полю работают одинаково. Строку можно положить в отдельный массив и править
        через него — меняться будет та же строка в таблице, потому что строка это не копия данных, а
        ссылка на них. Отличий ровно два, и оба видны по коду:
        
        - у строки таблицы нет методов структуры `Свойство`, `Вставить`, `Удалить` — места, где они
          вызываются, придётся переписать;
        - `Добавить()` у таблицы не принимает готовый элемент: он создаёт пустую строку и возвращает
          её, поэтому заполнять поля надо после вызова, а не до.
        
        ## Как чинить
        
        #### Неправильно
        
        ```bsl
        Выборка = Запрос.Выполнить().Выбрать();
        Записи = Новый Массив;
        Пока Выборка.Следующий() Цикл
            Запись = Новый Структура("Идентификатор, Операция, Основание, Склад,
                |Статус, КоличествоПопыток, ДатаПостановки, Сумма");
            ЗаполнитьЗначенияСвойств(Запись, Выборка);
            Запись.Вставить("Обработана", Ложь);
            Записи.Добавить(Запись);
        КонецЦикла;
        ```
        
        #### Правильно
        
        ```bsl
        Записи = Запрос.Выполнить().Выгрузить();
        Записи.Колонки.Добавить("Обработана", Новый ОписаниеТипов("Булево"));
        ```
        
        Первая строка выгружает результат запроса в таблицу значений: колонки и их типы берутся из
        полей запроса, и список полей второй раз писать не нужно. Вторая строка добавляет колонку
        под признак, которого в запросе нет, — здесь это отметка «запись уже обработана», нужная
        только в памяти.
        
        **Тип колонки указывать обязательно.** Если написать
        `Записи.Колонки.Добавить("Обработана")` без `ОписаниеТипов`, то в уже существующих строках
        новая колонка получит значение `Неопределено`. Первое же `Если Строка.Обработана Тогда`
        завершится ошибкой преобразования значения к типу Булево. Ни анализатор кода, ни загрузка
        конфигурации этого не покажут: ошибка появится только при выполнении.
        
        **Ловушка при работе с таблицей.** Метод `Свернуть` удаляет из таблицы все колонки, которые
        не перечислены ни в списке группировок, ни в списке суммируемых. Если ту же таблицу читают
        другие процедуры, они молча потеряют поля. Сворачивать нужно копию: сначала `Скопировать()`,
        потом `Свернуть` у копии.
        
        ## Когда это не дефект
        
        - **записи разного вида**, у каждой свой набор полей — у таблицы значений колонки общие для
          всех строк, разнородные записи в неё не ложатся;
        - **набор дальше превращается в JSON или XML** для отправки во внешнюю систему —
          `ЗаписатьJSON` принимает ограниченный набор типов и для остальных требует функцию
          преобразования; проверять надо не в том методе, где коллекция создана, а дойдя по вызовам
          до места отправки;
        - **данные используются на клиенте или передаются между клиентом и сервером** — там способ
          передачи выбирают по другим соображениям: реквизит формы, временное хранилище;
        - **в наборе две-три записи**, и над ними не делают ни поиска, ни группировки, ни итогов —
          менять нечего, выигрыша не будет.
        
        ## Что проверяет инструмент
        
        Ничего, правило проверяется чтением кода. Форму цикла по тексту распознать можно, но
        отличить цикл, который только копирует значения, от цикла, в котором есть ещё какая-то
        работа, без разбора тела нельзя.
        
      • AI-18.md 10.1 KB
        ---
        id: qg:AI-18
        title: Ключ, склеенный из значений в одну строку, вместо поиска по колонкам
        severity: major
        group: model
        tool: null
        archetypes: [query]
        std: [std452]
        ---
        
        ## Триггер
        
        Ключ соответствия склеен из значений строкой, либо цикл ради поиска по двум-трём полям.
        
        ## Почему
        
        Когда запись опознаётся по нескольким полям сразу, эти поля не нужно склеивать в одну строку
        и делать её ключом соответствия — например,
        `Соответствие.Вставить(Идентификатор + "|" + Операция, Значение)` с последующим обратным
        разбором ключа через `СтрРазделить`. У таблицы значений есть поиск сразу по нескольким
        колонкам: `НайтиСтроки(Новый Структура("Поле1, Поле2", Значение1, Значение2))`.
        
        **Чем это плохо — четыре конкретные вещи.**
        
        1. **Значения теряют тип.** Чтобы попасть в строку-ключ, ссылка, дата или число приводятся к
           строке. `Строка(Ссылка)` даёт представление, а оно не обязано быть уникальным: два
           элемента справочника с одинаковым наименованием дадут одинаковую строку, а пустая ссылка —
           пустую. Если приводить через `XMLСтрока`, значение остаётся различимым, но три следующих
           недостатка никуда не деваются.
        2. **Ключ ломается о собственный разделитель.** Если выбранный разделитель встретится внутри
           значения, части ключа склеятся не так, как задумано: две разные записи могут совпасть, а
           нужная — не найтись.
        3. **Ключ одноразовый.** Он годится только для того сочетания полей, из которого склеен.
           Понадобится поиск по другому сочетанию — придётся строить второе соответствие по тем же
           данным.
        4. **Код нечитаем.** По выражению `А + "|" + Б + "|" + XMLСтрока(В)` не видно, что считается
           ключом; чтобы понять, нужно найти все места, где такой ключ собирают и разбирают.
        
        ## Как чинить
        
        Цикл по таблице ради того, чтобы найти в ней строку, не пишется никогда, если условие
        поиска — равенство: для этого есть
        `НайтиСтроки(Новый Структура("Поле1, Поле2", Значение1, Значение2))`. Само условие задаётся
        структурой: имена свойств это имена колонок, значения свойств — то, что ищем. Метод
        возвращает массив строк таблицы, подходящих под условие, в том же порядке, в каком строки
        лежат в таблице, — поэтому «первая подходящая» это просто первый элемент результата.
        Готовить что-либо заранее не нужно: поиск работает и без индекса.
        
        **Главная ошибка при переходе: ключ — это набор колонок, а не колонка.** Соблазн — оставить
        прежнюю склейку, положив её в отдельную колонку таблицы. Тип коллекции при этом сменится, а
        искусственный ключ со всеми своими недостатками останется на месте и вдобавок будет
        дублировать данные соседних колонок.
        
        ```bsl
        // Неправильно: склейка переехала в колонку.
        Строки.Колонки.Добавить("Ключ", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(100)));
        СтрокаНабора.Ключ = СтрокаНабора.Идентификатор + Символы.ПС + СтрокаНабора.НомерСтроки
            + Символы.ПС + XMLСтрока(СтрокаНабора.Период);
        Найденные = Строки.НайтиСтроки(Новый Структура("Ключ", ИскомыйКлюч));
        
        // Правильно: запись опознаётся набором колонок, каждая со своим типом.
        Найденные = Строки.НайтиСтроки(Новый Структура("Идентификатор, НомерСтроки, Период",
            Идентификатор, НомерСтроки, Период));
        ```
        
        Вместе со склейкой уходит и приведение к строке: период остаётся датой, идентификатор —
        своим типом. Единственная искусственная колонка, которая здесь законна, — та, что несёт
        данные, которых в других колонках нет. Пример: номер строки, добавленный к набору перед
        запросом, чтобы потом соединить результат запроса с исходными строками. Он не склеен из
        соседних полей, а добавляет к ним новое значение.
        
        **Про индексы — отдельно, потому что это про скорость, а не про правильность.** Сам совет
        искать `НайтиСтроки` от индексов не зависит: метод работает и без них, и цикл вместо него не
        пишется никогда. Выбор здесь другой — добавлять индекс к таблице или нет. Индекс нужен не
        всегда: его построение само стоит времени и памяти, поэтому ради одного-двух поисков его не
        заводят — `НайтиСтроки` и без индекса найдёт строки, просто просмотрит их подряд. #std452 п.
        1 называет ориентиром тысячу строк и больше, а для таблицы в сотню строк — сотню поисков по
        ней; там же сказано, что индексировать имеет смысл только колонки, у которых каждому
        значению соответствует немного строк, иначе выигрыша не будет. Если индекс добавляют, есть
        условие, о котором легко забыть. Синтакс-помощник платформы 8.3.27 про метод `НайтиСтроки`
        говорит дословно:
        
        > Если в таблице значений добавлены индексы, подбор индекса для поиска осуществляется по
        > точному соответствию состава колонок в индексе и в параметрах поиска, порядок следования
        > колонок значения не имеет.
        
        То же сказано в #std452 п. 2.2. Практический вывод: состав колонок индекса должен совпадать
        с набором полей в условии поиска — иначе индекс не будет использован, и `НайтиСтроки`
        просмотрит строки подряд. Результат при этом останется правильным, изменится только время
        работы. Если есть сомнение, срабатывает ли индекс на вашей версии платформы, это проверяется
        замером, а не чтением документации.
        
        ## Когда это не дефект
        
        - **по одному и тому же ключу обращаются многократно из кода, который сам выполняется много
          раз** — в цикле по строкам документа, в проведении, в регламентном задании по большому списку.
          Тогда соответствие остаётся правильным выбором: выборка из него по ключу не зависит от числа
          элементов, а `НайтиСтроки` по неиндексированной таблице просматривает строки подряд;
        - **условие поиска — не равенство:** диапазон дат, вхождение подстроки, сравнение с
          `Неопределено` — `НайтиСтроки` умеет только равенство, и обычный цикл здесь законен;
        - **в цикле, кроме поиска, делается работа над каждым элементом** — это обход, а не поиск, и
          правило не про него.
        
        ## Что проверяет инструмент
        
        Ничего. Склейку с разделителем-литералом в аргументе `Вставить` или `Получить` по тексту
        кода распознать можно, но проверки пока нет.
        
      • AI-19.md 3.3 KB
        ---
        id: qg:AI-19
        title: Проверка существования того, что существует всегда
        severity: minor
        group: model
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Обращение к реквизиту/ТЧ обложено проверкой наличия по метаданным «на всякий случай».
        
        ## Почему
        
        Модель, написав обращение к реквизиту или табличной части, часто обкладывает его проверкой
        наличия по метаданным — «на всякий случай». Для объектов конфигурации и для собственных
        объектов того расширения, где лежит код, она вредна дважды: выдаёт за необязательное то, без
        чего механизм не работает, и прячет опечатку в имени под видом штатной ветки — вместо ошибки
        получается тихий пропуск части данных.
        
        ## Как чинить
        
        #### Неправильно
        
        ```bsl
        // Табличная часть принадлежит конфигурации, её не может не быть
        ТабличнаяЧасть = Метаданные.Документы.РасходДокумент.ТабличныеЧасти.Найти("Строки");
        Если ТабличнаяЧасть = Неопределено Тогда
            Возврат Ложь;
        КонецЕсли;
        ```
        
        #### Правильно
        
        ```bsl
        ТабличнаяЧасть = Метаданные.Документы.РасходДокумент.ТабличныеЧасти.Строки;
        Возврат ТабличнаяЧасть.Реквизиты.Найти(ИмяРеквизита) <> Неопределено;  // реквизит из чужого расширения
        ```
        
        ## Когда это не дефект
        
        - проверка уместна ровно в одном случае: объект принадлежит **чужому** расширению, которое
          можно отключить;
        - имя объявлено в другом расширении — проверка обязательна, а прямое обращение, наоборот,
          находка: отключение чужого расширения уронит весь механизм, а не одну необязательную
          часть; то же для кода, который заявлен переносимым между конфигурациями.
        
        ## Что проверяет инструмент
        
        Ничего. Признак упирается в вопрос «кому принадлежит имя»: тег `ObjectBelonging` для
        собственных объектов расширения Конфигуратор не пишет, поэтому принадлежность определяется
        по файлам выгрузки — есть ли имя в XML основной конфигурации и в XML какого расширения оно
        объявлено. Детектор без этого шага пометит как находку ровно те проверки, которые законны.
        
      • AI-20.md 2.4 KB
        ---
        id: qg:AI-20
        title: Результат собственной функции читается цепочкой через точку
        severity: minor
        group: model
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Результат своей функции читается цепочкой через точку, особенно повторно в методе.
        
        ## Почему
        
        `МойМодуль.Суммы(Документ).Деньгами` в одном выражении прячет и сам вызов, и его стоимость: в
        отладке негде остановиться и посмотреть, что вернулось, а второе такое же обращение молча
        выполняет функцию заново — вместе с запросом внутри неё.
        
        ## Как чинить
        
        #### Неправильно
        
        ```bsl
        Деньгами = РасчетыСервер.СуммыДокумента(Документ, Заказ).Деньгами;
        ...
        Если Остаток > РасчетыСервер.СуммыДокумента(Документ, Заказ).Деньгами Тогда  // второй запрос
        ```
        
        #### Правильно
        
        ```bsl
        Суммы = РасчетыСервер.СуммыДокумента(Документ, Заказ);
        Деньгами = Суммы.Деньгами;
        ```
        
        ## Когда это не дефект
        
        - результат нужен один-два раза и функция дешёвая — цепочка короче и читается лучше, чем
          переменная, живущая ради одной строки;
        - правило не касается платформенной идиоматики: `Запрос.Выполнить().Выбрать()`,
          `Метаданные...Найти()`, `ОбщегоНазначения.ЗначенияРеквизитовОбъекта(...).Реквизит` — это
          устойчивые обороты, а не сокрытие своего вызова.
        
        ## Что проверяет инструмент
        
        Ничего. Механически различимый случай — два и более одинаковых разыменования результата
        одного и того же вызова в пределах метода либо вызов функции, в теле которой есть
        `Новый Запрос`.
        
      • AI-21.md 2.7 KB
        ---
        id: qg:AI-21
        title: Индекс известного элемента вычисляется от границы коллекции
        severity: minor
        group: model
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Элемент известной коллекции берётся расчётом от `ВГраница()` вместо явного индекса.
        
        ## Почему
        
        Порядок задан тут же в коде — например, текстом пакетного запроса, — а элемент берётся
        расчётом от `ВГраница()`. Выглядит как защита от изменения состава, но защитой не является:
        при вставке запроса в середину пакета расчёт так же молча возьмёт не ту таблицу, только
        заметить это будет труднее, чем ошибку в явном числе.
        
        ## Как чинить
        
        #### Неправильно
        
        ```bsl
        Пакет = Запрос.ВыполнитьПакет();
        Диагностика = Пакет[Пакет.ВГраница() - 1].Выгрузить();
        Кандидаты = Пакет[Пакет.ВГраница()].Выгрузить();
        ```
        
        #### Правильно
        
        ```bsl
        Пакет = Запрос.ВыполнитьПакет();
        
        // Индексы заданы порядком запросов в тексте: всего 12 запросов, 0-9 - временные таблицы,
        // 10 - диагностика, 11 - результат. Меняются только вместе с текстом запроса.
        Диагностика = Пакет[10].Выгрузить();
        Кандидаты = Пакет[11].Выгрузить();
        ```
        
        Числовой индекс с комментарием честнее расчёта: он сразу виден и проверяется по тексту
        запроса.
        
        ## Когда это не дефект
        
        - коллекция собрана динамически — текст запроса склеен из фрагментов по условию, и число
          запросов заранее неизвестно;
        - `ВГраница()` как граница цикла — это его прямое назначение, правило не про него.
        
        ## Что проверяет инструмент
        
        Ничего. Механически различим узкий случай: `ВГраница()` как индекс той же коллекции,
        полученной в этом же методе из `ВыполнитьПакет()` или литерального конструктора.
        
      • AI-22.md 4.1 KB
        ---
        id: qg:AI-22
        title: Значение по умолчанию не объявлено в сигнатуре
        severity: minor
        group: model
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Во все точки вызова метода уходит одно и то же значение параметра, а в сигнатуре умолчания
        нет.
        
        ## Почему
        
        Речь о служебном методе: в его параметр во всех точках вызова уходит одно и то же значение —
        чаще всего `Неопределено`, `Истина`, `Ложь` или пустая строка. У параметра есть типовое
        значение — объяви его в сигнатуре метода. Передавать это значение из каждой точки вызова не
        нужно.
        
        **Чем это плохо.** Договорённость «не передали — значит вот это» есть в обоих случаях, но в
        первом она нигде не записана: она держится тем, что все вызовы передают одинаковое значение.
        Новая точка вызова восстанавливает её по соседнему коду и ошибается молча — метод получает
        осмысленное с виду значение и отрабатывает не так, как задумано. Читателю же каждый такой
        аргумент приходится сверять с объявлением: значимое это значение или заполнение места.
        
        ## Как чинить
        
        #### Неправильно
        
        ```bsl
        Функция ЗаписиОчереди(Среда, УчитыватьСроки, КлючЗаписи)
            // …
        КонецФункции
        
        Записи = ЗаписиОчереди(Среда, Истина, Неопределено);   // регламентное задание
        Записи = ЗаписиОчереди(Среда, Ложь, КлючЗаписи);       // команда формы
        ```
        
        #### Правильно
        
        ```bsl
        Функция ЗаписиОчереди(Среда, УчитыватьСроки = Истина, КлючЗаписи = Неопределено)
            // …
        КонецФункции
        
        Записи = ЗаписиОчереди(Среда);                         // регламентное задание
        Записи = ЗаписиОчереди(Среда, Ложь, КлючЗаписи);       // команда формы
        ```
        
        ## Когда это не дефект
        
        - значения по умолчанию у параметра нет по существу: метод обязан получить решение от
          вызывающего, и умолчание скрыло бы забытый аргумент;
        - значение передаётся явно ради читаемости там, где смысл иначе неочевиден:
          `Записать(Истина)` рядом с `Записать()` говорит читателю, что режим выбран, а не унаследован;
        - параметр стоит в середине списка, а вызывающему нужен следующий за ним: пропуск
          позиционного аргумента (`Метод(А, , В)`) платформа допускает, но читается он хуже явного
          значения — лечится это перестановкой параметра в конец списка, а не отказом от умолчания.
        
        ## Что проверяет инструмент
        
        Ничего. Механически правило требует всех точек вызова по проекту: внутри одного файла двух
        вызовов слишком мало, чтобы отличить умолчание от совпадения, и проверка на таком основании
        давала бы находки на нормальном коде.
        
      • BSL-CONTEXT-CALL-UNNEEDED.md 1.6 KB
        ---
        id: qg:BSL-CONTEXT-CALL-UNNEEDED
        title: &НаСервере вместо &НаСервереБезКонтекста
        severity: major
        group: platform
        tool: null
        archetypes: [client-server, form-module]
        std: []
        ---
        
        ## Триггер
        
        `&НаСервере` там, где метод не читает и не пишет контекст формы.
        
        ## Почему
        
        Ненужная передача контекста формы — лишняя нагрузка на вызов там, где она не используется.
        
        ## Как чинить
        
        Заменить директиву на `&НаСервереБезКонтекста` и передавать нужные данные параметрами.
        
        #### Неправильно
        
        ```bsl
        // HIGH: ненужная передача контекста формы
        &НаСервере
        Функция ПолучитьДанныеНаСервере()
        	Возврат ВыполнитьЗапрос();
        КонецФункции
        ```
        
        #### Правильно
        
        ```bsl
        &НаСервереБезКонтекста
        Функция ПолучитьДанныеНаСервере(Параметры)
        	Возврат ВыполнитьЗапрос(Параметры);
        КонецФункции
        ```
        
        ## Когда это не дефект
        
        - метод читает или пишет реквизиты формы или её элементы;
        - метод вызывает другие контекстные методы.
        
        ## Что проверяет инструмент
        
        Ничего: инструмента нет. Проверяется чтением тела метода.
        
      • BSL-CORRELATED-SUBQUERY.md 2.8 KB
        ---
        id: qg:BSL-CORRELATED-SUBQUERY
        title: Коррелированный подзапрос в условии
        severity: critical
        group: platform
        tool: null
        archetypes: [query]
        std: []
        ---
        
        ## Триггер
        
        Вложенный `ВЫБРАТЬ` в `ГДЕ`, ссылающийся на поле внешнего запроса.
        
        ## Почему
        
        Влияние: подзапрос выполняется для каждой строки внешнего источника — тот же O(n) обращений,
        что и запрос в цикле, только спрятанный внутри одного запроса и потому незаметный при чтении.
        
        ## Как чинить
        
        Вынести агрегат во временную таблицу и один раз соединить с ней.
        
        #### Неправильно
        
        ```sql
        -- CRITICAL: подзапрос вычисляется заново для каждой строки Заказы
        ВЫБРАТЬ Заказы.Ссылка
        ИЗ Документ.Заказ КАК Заказы
        ГДЕ Заказы.Сумма > (ВЫБРАТЬ СРЕДНЕЕ(О.Сумма) ИЗ Документ.Заказ КАК О
                            ГДЕ О.Контрагент = Заказы.Контрагент)
        ```
        
        #### Правильно
        
        ```sql
        -- Оптимально: агрегат считается один раз во временной таблице, дальше — соединение
        ВЫБРАТЬ Контрагент, СРЕДНЕЕ(Сумма) КАК СредняяСумма
        ПОМЕСТИТЬ ВТСредние
        ИЗ Документ.Заказ
        СГРУППИРОВАТЬ ПО Контрагент
        ;
        ВЫБРАТЬ Заказы.Ссылка
        ИЗ Документ.Заказ КАК Заказы
        	ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТСредние КАК Средние
        	ПО Заказы.Контрагент = Средние.Контрагент
        ГДЕ Заказы.Сумма > Средние.СредняяСумма
        ```
        
        ## Когда это не дефект
        
        - `СУЩЕСТВУЕТ` в условии по индексированному полю на малой таблице; проверка наличия, где
          соединение дало бы дубли;
        - **не путать с некоррелированным подзапросом** — тем, который не ссылается на внешние поля и
          вычисляется однократно. Такой подзапрос допустим, хотя временная таблица обычно читается
          лучше.
        
        ## Что проверяет инструмент
        
        Ничего: текст запроса — строковый литерал, разбор доступен только чтением.
        
      • BSL-DB-READ-IN-LOOP.md 5.7 KB
        ---
        id: qg:BSL-DB-READ-IN-LOOP
        title: Чтение базы, достижимое из цикла через вызов
        severity: major
        group: platform
        tool: tools/bsl-lint.mjs
        archetypes: [always]
        std: [std436, std724]
        ---
        
        ## Триггер
        
        В теле цикла ни одного запроса — чтение прячется в методе, достижимом из цикла через вызов.
        
        ## Почему
        
        То же число обращений к базе, что при прямом запросе в цикле, только распределённое по
        методам. Чтение прячется за помощником библиотеки или за собственной функцией, цикл остаётся
        этажом или двумя выше — и глазами связь не видна: в теле цикла один вызов, в вызванном методе
        один запрос, и оба фрагмента по отдельности выглядят нормально.
        
        ## Как чинить
        
        Собрать данные по всему набору один раз до цикла и передать их внутрь готовыми.
        
        #### Неправильно
        
        ```bsl
        // в цикле ни одного запроса — они этажом ниже
        Для Каждого Запись Из Записи Цикл
        	ОбработатьСтроку(Запись);            // → ПроверитьГотовность(Запись)
        КонецЦикла;                              //   → ОбщегоНазначения.ЗначенияРеквизитовОбъекта(…)
        ```
        
        #### Правильно
        
        ```bsl
        // данные по всему набору собраны до цикла и переданы внутрь готовыми
        Реквизиты = ОбщегоНазначения.ЗначенияРеквизитовОбъектов(ДокументыНабора, "Проведен, ПометкаУдаления");
        Для Каждого Запись Из Записи Цикл
        	ОбработатьСтроку(Запись, Реквизиты);
        КонецЦикла;
        ```
        
        ## Когда это не дефект
        
        - цикл идёт по коллекции, собранной здесь же (`Новый Массив` плюс `Добавить`): обращений в нём
          столько, сколько различных ключей, — ровно ради этого набор и сворачивали;
        - вызванный метод живёт в модуле с повторным использованием возвращаемых значений
          (`…ПовтИсп`): повторный вызов дёшев. Первый при этом платный, а результат живёт до конца
          сеанса — если значение должно быть строго на момент выполнения, кэш не подходит (#std724,
          тот же разбор в `AI-09.md`);
        - чтение стоит в теле самого цикла — случай `bslls:CreateQueryInCycle`, соседний признак
          `qg:BSL-QUERY-IN-LOOP`.
        
        ## Что проверяет инструмент
        
        `qg:BSL-DB-READ-IN-LOOP` (`tools/bsl-lint.mjs`), 🟠 — из тела цикла достижим (через вызовы,
        глубина до 4) метод, в теле которого есть `Новый Запрос`, чтение реквизитов помощником
        библиотеки, поиск по коду или наименованию, чтение константы либо среза регистра сведений.
        **Правило не перекрывает `bslls:CreateQueryInCycle`, а дополняет его.** Та диагностика ловит
        `Новый Запрос` **прямо в теле цикла**; здесь проверка начинается с первого вызова, иначе на
        одну строку приходили бы две находки и одну из них справедливо отключили бы.
        
        **Чего не ловит.** `ПолучитьОбъект` и `Прочитать()` в список обращений намеренно не входят:
        обработка, меняющая каждый элемент набора, обязана прочитать каждый объект — это работа над
        разными данными, а не повторное чтение одних и тех же. На корпусе в 114 общих модулей двух
        расширений эти два маркера дали больше половины находок, и все они были формой «прочитать
        документ, чтобы его записать». Не видны и вызовы по вычисляемому имени.
        
        **Граф вызовов строится только по файлам одного прогона.** Метод из модуля, который в прогон
        не попал, инструменту не виден: вердикт «чисто» здесь означает «в переданном составе цепочек
        не нашлось», а не «чтений из цикла нет».
        
        **Достижимость — не место.** Инструмент утверждает, что из цикла есть путь до чтения, но не
        то, что чтение выполняется на каждом витке: в вызванном методе оно может стоять до его
        собственного цикла и выполняться однократно. Цепочку из текста находки нужно пройти глазами.
        
      • BSL-DEEP-NESTING.md 1.2 KB
        ---
        id: qg:BSL-DEEP-NESTING
        title: Глубокая вложенность
        severity: minor
        group: platform
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Более 4 уровней вложенности условий и циклов в одном методе.
        
        ## Почему
        
        Глубокая вложенность усложняет чтение и увеличивает число путей выполнения, которые нужно
        удержать в голове одновременно.
        
        ## Как чинить
        
        Применять ранний выход из метода — см. `bsl-refactoring.md`.
        
        ## Когда это не дефект
        
        - вложенность образована одним длинным условием с `И`, а не лестницей;
        - обработчик с обязательной структурой платформы (например, `Попытка` внутри цикла внутри
          транзакции).
        
        ## Что проверяет инструмент
        
        Ничего: инструмента нет. Проверяется чтением тела метода — подсчётом уровней вложенности.
        
      • BSL-DISPATCH-NO-FALLBACK.md 6.4 KB
        ---
        id: qg:BSL-DISPATCH-NO-FALLBACK
        title: Разбор значения по веткам без завершающего «Иначе»
        severity: major
        group: platform
        tool: tools/bsl-lint.mjs
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Цепочка `Если … ИначеЕсли …` по значениям/типам в трёх и более ветках без `Иначе`.
        
        ## Почему
        
        Цепочка `Если … ИначеЕсли …`, перебирающая значения перечисления или типы документа, — это
        разбор: автор перечислил случаи, которые знал. Значение, не попавшее ни в одну ветку — новое
        значение перечисления, незаполненная ссылка, запись, поставленная вручную, — проходит цепочку
        молча, и метод работает дальше так, будто разбор состоялся.
        
        Дальше происходит одно из двух. Либо падение в месте, не связанном с причиной: переменная
        осталась неинициализированной, и текст ошибки говорит про `Выгрузить()`, а не про неучтённый
        тип. Либо, что хуже, падения нет вовсе: в коде, который отправляет документ во внешнюю систему,
        проводит или пробивает фискальный чек, необработанное значение означает пропуск проверки —
        действие выполняется, а разбор, который должен был его запретить, не выполнялся.
        
        **Порог — три ветки.** Две ветки без `Иначе` — обычная двоичная развилка («если аванс — так,
        если нет — эдак»), и требование закрывать её дало бы находку в каждом втором модуле.
        
        ## Как чинить
        
        #### Неправильно
        
        ```bsl
        Функция ПолучитьДанныеДокумента(Документ)
            Если ТипЗнч(Документ) = Тип("ДокументСсылка.ЗаявкаНаВозвратТоваровОтКлиента") Тогда
                РезультатЗапроса = ВыгрузитьЗаявкуНаВозврат(Документ);
            ИначеЕсли ТипЗнч(Документ) = Тип("ДокументСсылка.ЗаказНаПеремещение") Тогда
                РезультатЗапроса = ВыгрузитьЗаказНаПеремещение(Документ);
            ИначеЕсли ТипЗнч(Документ) = Тип("ДокументСсылка.ЗаказПоставщику") Тогда
                РезультатЗапроса = ВыгрузитьЗаказПоставщику(Документ);
            КонецЕсли;
        
            ТаблицаСтрок = РезультатЗапроса.Выгрузить();   // четвёртый тип: «Значение не является
                                                           // значением объектного типа»
        ```
        
        #### Правильно
        
        ```bsl
            ИначеЕсли ТипЗнч(Документ) = Тип("ДокументСсылка.ЗаказПоставщику") Тогда
                РезультатЗапроса = ВыгрузитьЗаказПоставщику(Документ);
            Иначе
                ВызватьИсключение СтрШаблон(
                    НСтр("ru = 'Документ %1 контуру неизвестен: выгрузка для его типа не описана.'"), Документ);
            КонецЕсли;
        ```
        
        **Лечение — ветка `Иначе` с явным решением**, а не с пустым телом. Три законные формы: отказ
        с текстом, называющим неучтённое значение; значение по умолчанию, записанное явно; запись в
        журнал регистрации, если работу можно продолжать. Пустой `Иначе` тем и плох, что выглядит
        принятым решением, не будучи им.
        
        ## Когда это не дефект
        
        - у цепочки есть `Иначе` — неважно, что в нём: решение о неучтённом значении принято;
        - охранная форма: каждая ветка заканчивается `Возврат`, а после `КонецЕсли` в том же методе
          стоит `Возврат` или `ВызватьИсключение`. Формально ветки `Иначе` нет, по существу умолчание
          есть, и оно стоит на видном месте;
        - веток меньше трёх либо в них сравниваются разные выражения: это не перебор одного признака,
          а последовательность независимых условий.
        
        ## Что проверяет инструмент
        
        Проверка: `qg:BSL-DISPATCH-NO-FALLBACK` (`tools/bsl-lint.mjs`), 🟠 — одно и то же выражение
        сравнивается со значениями перечисления либо с типом в трёх и более ветках одной цепочки, а
        ветки `Иначе` у цепочки нет.
        
        **Чего не ловит.** Перебор строковых кодов и чисел: у сравнения с литералом нет признака,
        отличающего разбор от обычного условия, и правило дало бы находку на любом ветвлении. Не
        разбирается и обратный порядок сравнения (`Перечисления.ТипыЧеков.Аванс = Запись.ТипЧека`) —
        в прикладном коде он не встречается. Что делает ветка, инструмент не смотрит: цепочка перед
        записью в базу и цепочка, после которой метод сразу возвращает значение, для него одинаковы.
        
      • BSL-ENUM-STRING-ASSIGN.md 2 KB
        ---
        id: qg:BSL-ENUM-STRING-ASSIGN
        title: Примитив в поле строго ссылочного типа
        severity: major
        group: platform
        tool: tools/bsl-lint.mjs
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Присваивание примитива (`""`, `0`, `Ложь`, `Истина`) полю строго ссылочного типа.
        
        ## Почему
        
        Сборка на это молчит — тела модулей не компилируются, — и падение приходит при записи, часто в
        редко исполняемой ветке. В живом расширении класс выглядел так: одно `= ""` против семнадцати
        корректных `.ПустаяСсылка()` по соседству — след смены типа реквизита, при которой код не
        пересмотрели.
        
        ## Как чинить
        
        #### Неправильно
        
        ```bsl
        // HIGH: реквизит имеет тип ПеречислениеСсылка.СтатусыДоставки
        Запись.СтатусДоставки = "";
        ```
        
        #### Правильно
        
        ```bsl
        Запись.СтатусДоставки = Перечисления.СтатусыДоставки.ПустаяСсылка();
        ```
        
        ## Когда это не дефект
        
        - Составные типы: у поля «Строка или Ссылка» присваивание `""` законно;
        - сравнение вместо присваивания (`Если Запись.Статус = "" Тогда`) — другой класс: оно всегда
          ложно, но записи не роняет;
        - приёмник инструмент не разрешает: если `Запись` — структура с одноимённым ключом, находка
          ложная, и это сказано в её тексте.
        
        ## Что проверяет инструмент
        
        Проверяется механически: `qg:BSL-ENUM-STRING-ASSIGN` (`tools/bsl-lint.mjs`), 🟠.
        
      • BSL-FORM-ATTR-SHADOW.md 5.4 KB
        ---
        id: qg:BSL-FORM-ATTR-SHADOW
        title: Локальная переменная под именем реквизита формы
        severity: critical
        group: platform
        tool: tools/bsl-lint.mjs
        archetypes: [form-module]
        std: []
        ---
        
        ## Триггер
        
        Переменная с именем реквизита формы получает объектное значение — пишет реквизит, не переменную.
        
        ## Почему
        
        В модуле управляемой формы имя реквизита — свойство самой формы. Присваивание локальную
        переменную НЕ создаёт: значение уходит в реквизит и приводится к его типу, поэтому обращение
        через точку падает. Локальное имя создают только объявление `Перем`, параметр метода и
        переменная цикла — потому обработчик `Процедура Завершение(Результат, ДопПараметры)` работает
        и переименования не требует.
        
        Коварство в выборочности: половина методов модуля исправна, половина падает, и по симптому
        это читается как случайный сбой. Разницу даёт директива компиляции — в `…БезКонтекста`
        контекста формы нет.
        
        Не ловится ничем, кроме исполнения: анализатор молчит, `form-validate` доволен, сборка `.epf`
        проходит (тела модулей она не компилирует), стандарта на такую коллизию на v8std нет вовсе.
        Случай из практики: три разные обработки одного проекта за два дня, все три — реквизит-журнал
        с именем `Результат` и одноимённые локальные переменные почти в каждой функции.
        
        ## Как чинить
        
        ```bsl
        // Реквизит формы «Результат» — строка, журнал прогона.
        
        &НаСервере
        Функция ПодготовитьКонтекст()
            Результат = Новый Структура;             // пишет В РЕКВИЗИТ, а не в переменную
            Результат.Вставить("Границы", Границы);  // «Значение не является значением объектного типа»
            Возврат Результат;
        КонецФункции
        
        &НаСервереБезКонтекста
        Функция ОписаниеПериода()
            Результат = Новый Структура;             // здесь контекста формы нет — работает
            Возврат Результат;
        КонецФункции
        ```
        
        **Лечение — переименование реквизита**, а не переменных: реквизит один, переменных десятки.
        Имя ему давать предметное (`Журнал`), а не «Результат»: именно универсальные имена
        сталкиваются с локальными.
        
        ## Когда это не дефект
        
        - директива без контекста формы (`…БезКонтекста`);
        - имя, объявленное параметром или `Перем`;
        - реквизит непримитивного типа (у таблицы формы обращение через точку законно);
        - присваивание примитива — намеренная запись в реквизит;
        - явное `ЭтотОбъект.<Имя>`;
        - обращение через точку выше присваивания;
        - имя КОЛОНКИ реквизита-таблицы реквизитом формы не является и в проверку не попадает.
        
        ## Что проверяет инструмент
        
        Проверка: `qg:BSL-FORM-ATTR-SHADOW` (`tools/bsl-lint.mjs`), 🔴 — реквизит примитивного типа из
        `Ext/Form.xml` получает объектное значение (`Новый …` либо результат вызова) в методе с
        контекстом формы, и ниже к нему обращаются через точку.
        
        **Чего не ловит.** Присваивание ПРИМИТИВА с последующим обращением через точку — тоже падение,
        но по нему не отличить путаницу с реквизитом от намеренной записи в него, и правило такой
        случай пропускает намеренно. Не видит оно и коллизии по реквизиту составного типа, и модуль,
        у которого рядом нет `Ext/Form.xml` (в последнем случае говорит об этом записью следа, а не
        молчит). Реквизит объекта обработки, доступный в модуле формы через `Объект.<Имя>`, к этому
        классу не относится: там обращение через точку обязательно.
        
      • BSL-MESSAGE-AS-NOTIFY.md 2.5 KB
        ---
        id: qg:BSL-MESSAGE-AS-NOTIFY
        title: Сообщить() как механизм уведомления
        severity: major
        group: platform
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        `Сообщить(...)` вместо `ОбщегоНазначения.СообщитьПользователю` или записи в журнал регистрации.
        
        ## Почему
        
        Влияние: сообщение не привязано к объекту и полю, не попадает в журнал регистрации, теряется
        в фоновом задании и не может быть обработано вызывающим кодом. При выполнении на сервере
        пользователь может не увидеть его вовсе.
        
        **Правило разделения:** сообщение пользователю — через механизм с привязкой к реквизиту;
        диагностика для разбора инцидентов — в журнал регистрации. `Сообщить()` не делает ни того,
        ни другого.
        
        ## Как чинить
        
        #### Неправильно
        
        ```bsl
        // HIGH: сообщение никуда не привязано и нигде не сохранено
        Сообщить("Не заполнен склад");
        ```
        
        #### Правильно
        
        ```bsl
        // Оптимально: привязка к объекту и полю — пользователь видит, где именно ошибка
        ОбщегоНазначения.СообщитьПользователю("Не заполнен склад", , "Объект.Склад", , Отказ);
        
        // Для диагностики, а не для пользователя — журнал регистрации
        ЗаписьЖурналаРегистрации("Загрузка.Ошибка", УровеньЖурналаРегистрации.Ошибка, , ,
        	ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        ```
        
        ## Когда это не дефект
        
        - отладочный вывод в служебной обработке, которую запускает разработчик вручную и которая не
          идёт в поставку;
        - сообщение уже сопровождается записью в журнал регистрации.
        
        ## Что проверяет инструмент
        
        Ничего: инструмента нет. Проверяется чтением тела метода.
        
      • BSL-MULTI-SERVER-CALLS.md 2.3 KB
        ---
        id: qg:BSL-MULTI-SERVER-CALLS
        title: Множественные клиент-серверные вызовы
        severity: major
        group: platform
        tool: null
        archetypes: [client-server, form-module]
        std: []
        ---
        
        ## Триггер
        
        Несколько последовательных `...НаСервере()`-вызовов подряд с клиента в одном обработчике.
        
        ## Почему
        
        Несколько серверных вызовов вместо одного — каждый вызов платит за отдельный клиент-серверный
        переход.
        
        ## Как чинить
        
        Собрать данные на сервере одним вызовом и вернуть структурой.
        
        #### Неправильно
        
        ```bsl
        // HIGH: несколько серверных вызовов вместо одного
        &НаКлиенте
        Процедура Обработать(Команда)
        	Данные1 = ПолучитьДанные1НаСервере();
        	Данные2 = ПолучитьДанные2НаСервере();
        	Данные3 = ПолучитьДанные3НаСервере();
        КонецПроцедуры
        ```
        
        #### Правильно
        
        ```bsl
        &НаКлиенте
        Процедура Обработать(Команда)
        	ВсеДанные = ПолучитьВсеДанныеНаСервере();
        КонецПроцедуры
        
        &НаСервереБезКонтекста
        Функция ПолучитьВсеДанныеНаСервере()
        	Результат = Новый Структура;
        	Результат.Вставить("Данные1", ПолучитьДанные1());
        	Результат.Вставить("Данные2", ПолучитьДанные2());
        	Результат.Вставить("Данные3", ПолучитьДанные3());
        	Возврат Результат;
        КонецФункции
        ```
        
        ## Когда это не дефект
        
        - вызовы разделены ожиданием ответа пользователя (диалог между ними);
        - второй вызов зависит от результата первого и выполняется не всегда.
        
        ## Что проверяет инструмент
        
        Ничего: инструмента нет. Проверяется чтением модуля формы.
        
      • BSL-NESTED-LOOP-SEARCH.md 2.1 KB
        ---
        id: qg:BSL-NESTED-LOOP-SEARCH
        title: Алгоритм O(n²)
        severity: minor
        group: platform
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Вложенный цикл поиска совпадений между двумя коллекциями вместо `Соответствие`.
        
        ## Почему
        
        Вложенный цикл поиска — квадратичная деградация: число сравнений растёт как произведение
        размеров обеих коллекций вместо суммы.
        
        ## Как чинить
        
        Построить индекс по ключу одной коллекции через `Соответствие` и искать по нему.
        
        #### Неправильно
        
        ```bsl
        // MEDIUM: вложенный цикл поиска — экспоненциальная деградация
        Для Каждого Строка1 Из Таблица1 Цикл
        	Для Каждого Строка2 Из Таблица2 Цикл
        		Если Строка1.Ключ = Строка2.Ключ Тогда
        			// Обработка
        		КонецЕсли;
        	КонецЦикла;
        КонецЦикла;
        ```
        
        #### Правильно
        
        ```bsl
        // Оптимально: O(n) через Соответствие
        ИндексТаблицы2 = Новый Соответствие;
        Для Каждого Строка2 Из Таблица2 Цикл
        	ИндексТаблицы2.Вставить(Строка2.Ключ, Строка2);
        КонецЦикла;
        
        Для Каждого Строка1 Из Таблица1 Цикл
        	Строка2 = ИндексТаблицы2.Получить(Строка1.Ключ);
        	Если Строка2 <> Неопределено Тогда
        		// Обработка
        	КонецЕсли;
        КонецЦикла;
        ```
        
        ## Когда это не дефект
        
        - обе коллекции до 50 элементов;
        - внешняя коллекция обходится один раз, а внутренняя уже соответствие.
        
        ## Что проверяет инструмент
        
        Ничего: инструмента нет. Проверяется чтением тела метода.
        
      • BSL-NO-CACHE.md 2.1 KB
        ---
        id: qg:BSL-NO-CACHE
        title: Отсутствие кеширования
        severity: minor
        group: platform
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Повторные дорогие вызовы с одинаковыми параметрами внутри цикла без кеширования результата.
        
        ## Почему
        
        Повторный вызов дорогого метода с теми же параметрами каждый раз выполняет одну и ту же работу
        заново вместо использования уже вычисленного результата.
        
        ## Как чинить
        
        Кешировать через `Соответствие`.
        
        #### Неправильно
        
        ```bsl
        // MEDIUM: повторные дорогие вызовы
        Для Каждого Строка Из ТаблицаДанных Цикл
        	Курс = ПолучитьКурсВалюты(Строка.Валюта, Строка.Дата);
        КонецЦикла;
        ```
        
        #### Правильно
        
        ```bsl
        // Оптимально: кеширование через Соответствие
        КэшКурсов = Новый Соответствие;
        Для Каждого Строка Из ТаблицаДанных Цикл
        	Ключ = Строка.Валюта + "|" + Формат(Строка.Дата, "ДФ=yyyyMMdd");
        	Курс = КэшКурсов.Получить(Ключ);
        	Если Курс = Неопределено Тогда
        		Курс = ПолучитьКурсВалюты(Строка.Валюта, Строка.Дата);
        		КэшКурсов.Вставить(Ключ, Курс);
        	КонецЕсли;
        КонецЦикла;
        ```
        
        ## Когда это не дефект
        
        - вызов дешёвый (чтение константы модуля, арифметика);
        - вызов с побочным эффектом;
        - параметры между вызовами меняются.
        
        ## Что проверяет инструмент
        
        Ничего: инструмента нет. Проверяется чтением тела метода.
        
      • BSL-NO-TOP-LIMIT.md 1.9 KB
        ---
        id: qg:BSL-NO-TOP-LIMIT
        title: Отсутствие ПЕРВЫЕ N
        severity: major
        group: platform
        tool: null
        archetypes: [query]
        std: []
        ---
        
        ## Триггер
        
        Большой запрос без `ПЕРВЫЕ N` — загрузка всех записей без ограничения.
        
        ## Почему
        
        Загрузка всех записей без ограничения дорога сама по себе — и дороже там, где вызывающему
        коду нужны не все строки, а первые несколько.
        
        ## Как чинить
        
        Добавить `ПЕРВЫЕ N` вместе с `УПОРЯДОЧИТЬ ПО` (порядок строк — соседний признак
        `qg:QRY-TOP-WITHOUT-ORDER`).
        
        #### Неправильно
        
        ```sql
        -- HIGH: загрузка всех записей без ограничения
        ВЫБРАТЬ
        	Контрагенты.Ссылка КАК Ссылка
        ИЗ
        	Справочник.Контрагенты КАК Контрагенты
        ```
        
        #### Правильно
        
        ```sql
        ВЫБРАТЬ ПЕРВЫЕ 10
        	Контрагенты.Ссылка КАК Ссылка
        ИЗ
        	Справочник.Контрагенты КАК Контрагенты
        УПОРЯДОЧИТЬ ПО
        	Контрагенты.Наименование
        ```
        
        ## Когда это не дефект
        
        - запрос по ключу с заведомо одной строкой;
        - запрос с агрегатами без группировки;
        - запрос уже ограничен параметрами периода и отбора.
        
        ## Что проверяет инструмент
        
        Ничего: текст запроса — строковый литерал, разбор доступен только чтением. Порядок строк при
        ограничении проверяется отдельно и механически — `qg:QRY-TOP-WITHOUT-ORDER`.
        
      • BSL-QUERY-IN-LOOP.md 2.4 KB
        ---
        id: qg:BSL-QUERY-IN-LOOP
        title: Запрос в цикле
        severity: critical
        group: platform
        tool: null
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        `Для Каждого` с `Новый Запрос` внутри тела цикла — обращение к базе на каждой итерации.
        
        ## Почему
        
        Влияние: O(n) обращений к БД вместо O(1) — при N итерациях цикла выполняется N обращений к
        базе данных вместо одного.
        
        ## Как чинить
        
        Собрать ключи до цикла и выполнить один запрос с условием `В (&СписокСсылок)`.
        
        #### Неправильно
        
        ```bsl
        // CRITICAL: N обращений к БД
        Для Каждого Строка Из Данные Цикл
        	Запрос = Новый Запрос("ВЫБРАТЬ ... ГДЕ Ссылка = &Ссылка");
        	Запрос.УстановитьПараметр("Ссылка", Строка.Ссылка);
        	РезультатЗапроса = Запрос.Выполнить();
        КонецЦикла;
        ```
        
        #### Правильно
        
        ```bsl
        // Оптимально: 1 обращение к БД
        Запрос = Новый Запрос;
        Запрос.Текст =
        "ВЫБРАТЬ ...
        |ГДЕ
        |	Ссылка В (&СписокСсылок)";
        Запрос.УстановитьПараметр("СписокСсылок",
        	Данные.ВыгрузитьКолонку("Ссылка"));
        РезультатЗапроса = Запрос.Выполнить();
        ```
        
        ## Когда это не дефект
        
        - цикл по пакетам, где один запрос обрабатывает пакет строк (пакетная схема);
        - цикл по узлам обмена или базам, у которых разные источники;
        - запрос с разными текстами на витке.
        
        ## Что проверяет инструмент
        
        Ничего в этом наборе инструментов: прямую форму (`Новый Запрос` в теле цикла) ловит статический
        анализатор — диагностика `bslls:CreateQueryInCycle`. Чтение, достижимое из цикла через вызов
        метода, а не напрямую, покрывает соседний признак `qg:BSL-DB-READ-IN-LOOP`.
        
      • BSL-REF-DOT-ACCESS.md 5.2 KB
        ---
        id: qg:BSL-REF-DOT-ACCESS
        title: Обращение к реквизитам через точку
        severity: critical
        group: platform
        tool: tools/bsl-lint.mjs
        archetypes: [always]
        std: [std437, std453]
        ---
        
        ## Триггер
        
        `.Реквизит` у ссылочного значения — загрузка всего объекта из БД ради одного поля.
        
        ## Почему
        
        Влияние: загрузка всего объекта из БД. `qg:BSL-REF-DOT-ACCESS` доказывает ссылочность тремя
        способами, и каждое основание печатается в тексте находки — они разной силы:
        
        | Основание | Пример | Sev |
        |---|---|---|
        | присваивание в этом же методе | `Заказ = Выборка.Ссылка;` далее `Заказ.Контрагент` | 🔴 |
        | описание метода #std453 | `// УчётнаяЗапись - СправочникСсылка.УчётныеЗаписи - …` | 🟠 |
        | имя оканчивается на «Ссылка» | `ЗаказКлиентаСсылка.Дата` | 🔴 |
        
        Доказательство живёт от присваивания до следующего: `Заказ = Выборка.Ссылка` → чтение
        реквизитов → `Заказ = Заказ.ПолучитьОбъект()` — находки будут только в середине, у объекта
        точка законна. Описание метода даёт 🟠, а не 🔴, потому что комментарий переживает смену типа
        параметра, а код нет.
        
        **Имена переменных с именами объектов конфигурации инструмент не сопоставляет.** Переменную
        называют как угодно, и такое сопоставление давало бы находки на совпадении слов, а не на типе
        значения.
        
        Статический анализатор эту форму не видит в принципе: чтобы понять, что база цепочки —
        ссылка, нужен вывод типов через границы методов и модулей, а синтаксически `Структура.Ключ.Поле`
        неотличимо от обращения к вложенной структуре. До появления правила проверка закрывалась
        только чтением глазами — и её вердикт «чисто» ничем не фальсифицировалось.
        
        ## Как чинить
        
        Методы БСП:
        
        | Метод | Назначение |
        |---|---|
        | `ОбщегоНазначения.ЗначениеРеквизитаОбъекта` | Один реквизит одной ссылки |
        | `ОбщегоНазначения.ЗначенияРеквизитовОбъекта` | Несколько реквизитов одной ссылки |
        | `ОбщегоНазначения.ЗначениеРеквизитаОбъектов` | Один реквизит нескольких ссылок |
        
        #### Неправильно
        
        ```bsl
        // CRITICAL: полная загрузка объекта для каждого реквизита
        ИНН         = Контрагент.ИНН;
        КПП         = Контрагент.КПП;
        Наименование = Контрагент.Наименование;
        ```
        
        #### Правильно
        
        ```bsl
        // Оптимально: один целевой запрос через БСП
        Реквизиты    = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(
        	Контрагент, "ИНН, КПП, Наименование");
        ИНН          = Реквизиты.ИНН;
        КПП          = Реквизиты.КПП;
        Наименование = Реквизиты.Наименование;
        ```
        
        ## Когда это не дефект
        
        - вызов метода (`Ссылка.ПолучитьОбъект()`, `Ссылка.Пустая()`) — объект при этом читается
          осознанно и целиком;
        - `Ссылка.Ссылка` — поле возвращает саму ссылку без чтения;
        - элементы отбора (`Отбор.Ссылка.Значение`, `НаборЗаписей.Отбор.Регистратор.Использование`) —
          имя сегмента совпадает с именем реквизита по построению;
        - менеджеры (`Документы.ЗаказКлиента.ПустаяСсылка()`);
        - текст запроса — там действует своя диагностика.
        
        ## Что проверяет инструмент
        
        Механизированная часть: `qg:BSL-REF-DOT-ACCESS` (`tools/bsl-lint.mjs`) — три основания из
        таблицы выше. Это **нижняя граница** проверки #std437, а не верхняя: ссылка, пришедшая
        параметром недокументированного метода или из чужой функции, остаётся неопознанной — разбор
        глазами инструмент не отменяет.
        
      • BSL-STALE-LOCAL-CALL.md 4.4 KB
        ---
        id: qg:BSL-STALE-LOCAL-CALL
        title: Вызов метода, объявление которого исчезло из модуля
        severity: critical
        group: platform
        tool: tools/rename-check.mjs
        archetypes: [always]
        std: []
        ---
        
        ## Триггер
        
        Вызов метода, чьё объявление было в HEAD и исчезло после правки.
        
        ## Почему
        
        Ловится это плохо, и случай из практики показывает, чем именно: гейт был чист, `bsl-analyzer`
        дал 29 диагностик и ни одной про этот вызов, сборка внешней обработки прошла с кодом 0 — тела
        модулей она не компилирует, — а форма упала при первом открытии: «Процедура или функция с
        указанным именем не определена».
        
        ## Как чинить
        
        Довести переименование до всех точек вызова: найти оставшиеся обращения к старому имени и
        привести их к новому — либо, если правка ошибочна, вернуть прежнее объявление.
        
        ```bsl
        // было в HEAD
        Процедура ОбновитьСводкуНаСервере()
        
        // стало после рефакторинга
        Функция ТекстСводки()
        
        // а вызов остался прежним — модуль не скомпилируется
        ОбновитьСводкуНаСервере();
        ```
        
        ## Когда это не дефект
        
        - метод, оставшийся в модуле базовой формы или в расширяемом модуле, — вызов законен, и
          близнец из основной конфигурации проверяется;
        - вхождение имени в комментарии и в строковом литерале вызовом не считается;
        - вызов через точку не разбирается вовсе — это зона `UnresolvedMethodCall` анализатора.
        
        ## Что проверяет инструмент
        
        Прямая проверка «имя не разрешается» здесь не строится: она требует словаря глобального
        контекста платформы (замер на живом проекте — 281 различное имя после всех исключений), а
        каждое пропущенное имя словаря даёт взрыв ложных 🔴. Отдельно мешает слияние модулей
        расширения с расширяемым: 1 837 голых вызовов разрешаются только через базовый модуль,
        которого в репозитории расширения может не быть вовсе.
        
        Поэтому проверка инверсная: `qg:BSL-STALE-LOCAL-CALL` (`tools/rename-check.mjs`), 🔴 —
        сравнивается текущий файл с его же версией в HEAD. Имя, которое ЭТОТ модуль объявлял до
        правки, платформенным глобальным быть не могло, и словарь не нужен.
        
        **Чего не ловит.** Вызов имени, которого не было никогда (опечатка в новом коде), и вызов,
        протухший в прежних коммитах: сравнение идёт с HEAD, а не по всей истории. Третий случай
        важнее обоих на правках форм: обработчик, названный СТРОКОЙ, —
        `Новый ОписаниеОповещения("СтароеИмя", ЭтотОбъект)`, `ПодключитьОбработчикОжидания("СтароеИмя", 1)`.
        Литералы гасятся до разбора (иначе текст сообщения стал бы вызовом), поэтому переименование,
        разведённое по строкам, инструмент не увидит, а платформа уронит уже в работе, а не при
        компиляции. Проверка закрывает класс «переименовал и не довёл до голых вызовов», а не
        разрешение имён целиком.
        
      • BSL-SUBQUERY-IN-SELECT.md 2.1 KB
        ---
        id: qg:BSL-SUBQUERY-IN-SELECT
        title: Подзапрос в SELECT
        severity: critical
        group: platform
        tool: null
        archetypes: [query]
        std: []
        ---
        
        ## Триггер
        
        Вложенный `ВЫБРАТЬ` в списке полей — считается заново для каждой строки внешнего запроса.
        
        ## Почему
        
        Влияние: выполнение N+1 запросов.
        
        ## Как чинить
        
        Заменить подзапрос временной таблицей и соединением.
        
        #### Неправильно
        
        ```sql
        -- CRITICAL: подзапрос выполняется для каждой строки
        ВЫБРАТЬ
        	Заказы.Ссылка,
        	(ВЫБРАТЬ СУММА(Оплаты.Сумма)
        	 ИЗ Документ.Оплата КАК Оплаты
        	 ГДЕ Оплаты.Заказ = Заказы.Ссылка) КАК СуммаОплат
        ИЗ
        	Документ.Заказ КАК Заказы
        ```
        
        #### Правильно
        
        ```sql
        -- Оптимально: временная таблица + соединение
        ВЫБРАТЬ
        	Оплаты.Заказ КАК Заказ,
        	СУММА(Оплаты.Сумма) КАК СуммаОплат
        ПОМЕСТИТЬ ВТИтогиОплат
        ИЗ
        	Документ.Оплата КАК Оплаты
        СГРУППИРОВАТЬ ПО
        	Оплаты.Заказ
        ИНДЕКСИРОВАТЬ ПО
        	Заказ
        ;
        ВЫБРАТЬ
        	Заказы.Ссылка КАК Ссылка,
        	ЕСТЬNULL(ИтогиОплат.СуммаОплат, 0) КАК СуммаОплат
        ИЗ
        	Документ.Заказ КАК Заказы
        	    ЛЕВОЕ СОЕДИНЕНИЕ ВТИтогиОплат КАК ИтогиОплат
        	    ПО Заказы.Ссылка = ИтогиОплат.Заказ
        ```
        
        ## Когда это не дефект
        
        - подзапрос по константе или параметру без связи с внешней строкой (вычисляется один раз).
        
        ## Что проверяет инструмент
        
        Ничего: текст запроса — строковый литерал, разбор доступен только чтением.
        
      • BSL-TEMPTABLE-NO-INDEX.md 2 KB
        ---
        id: qg:BSL-TEMPTABLE-NO-INDEX
        title: Временная таблица без индекса в соединении
        severity: major
        group: platform
        tool: null
        archetypes: [query]
        std: []
        ---
        
        ## Триггер
        
        `ПОМЕСТИТЬ ВТ...` без `ИНДЕКСИРОВАТЬ ПО`, а таблица дальше участвует в соединении.
        
        ## Почему
        
        Влияние: соединение по неиндексированному полю временной таблицы даёт полный перебор её строк
        на каждую строку второго источника. На больших выборках это основная причина того, что «запрос
        без единой ошибки работает минуты».
        
        ## Как чинить
        
        Добавить `ИНДЕКСИРОВАТЬ ПО` по полю, задействованному в соединении.
        
        #### Неправильно
        
        ```sql
        -- HIGH: ВТ соединяется по Номенклатура, но индекса нет
        ВЫБРАТЬ Номенклатура, Количество ПОМЕСТИТЬ ВТОстатки ИЗ ...
        ```
        
        #### Правильно
        
        ```sql
        ВЫБРАТЬ Номенклатура, Количество ПОМЕСТИТЬ ВТОстатки ИЗ ...
        ИНДЕКСИРОВАТЬ ПО Номенклатура
        ```
        
        ## Когда это не дефект
        
        - временная таблица до 100 строк;
        - таблица, которая далее не участвует в соединении;
        - таблица заведомо мала (десятки строк) либо используется однократно без соединений —
          построение индекса тогда дороже выигрыша.
        
        ## Что проверяет инструмент
        
        Ничего: текст запроса — строковый литерал, разбор доступен только чтением.
        
      • BSL-TXN-IN-HANDLER.md 4.3 KB
        ---
        id: qg:BSL-TXN-IN-HANDLER
        title: Своя транзакция внутри неявной транзакции обработчика
        severity: major
        group: platform
        tool: tools/bsl-lint.mjs
        archetypes: [transaction, object-event]
        std: [std783]
        ---
        
        ## Триггер
        
        Своя `НачатьТранзакцию` внутри обработчика, который платформа уже выполняет в транзакции.
        
        ## Почему
        
        Влияние: платформа открывает транзакцию сама — вокруг записи и удаления ссылочного объекта
        (`ПередЗаписью` → запись → `ПриЗаписи`), вокруг проведения (`ОбработкаПроведения`) и вокруг
        записи набора записей. Вложенных транзакций платформа **не поддерживает**: собственная
        `НачатьТранзакцию` внутри обработчика создаёт видимость точки сохранения, а
        `ОтменитьТранзакцию` отменяет внешнюю транзакцию целиком. Дальше внешний код продолжает
        работать с уже отменённой транзакцией и получает «В этой транзакции уже происходили ошибки» —
        в месте, никак не связанном с причиной. Якорь: **#std783 п. 1.4 и 1.4.1**.
        
        Обработчиков, внутри которых транзакция уже открыта платформой, ровно пять: `ПередЗаписью`,
        `ПриЗаписи`, `ПередУдалением`, `ОбработкаПроведения`, `ОбработкаУдаленияПроведения`. Удаление и
        отмена проведения в этот список входят наравне с записью — их обычно и забывают.
        
        ## Как чинить
        
        #### Неправильно
        
        ```bsl
        // HIGH: транзакция внутри обработчика, который платформа и так выполняет в транзакции
        Процедура ОбработкаПроведения(Отказ, РежимПроведения)
        
        	НачатьТранзакцию();
        	Попытка
        		Движения.ТоварыНаСкладах.Записать();
        		ЗафиксироватьТранзакцию();
        	Исключение
        		ОтменитьТранзакцию();   // отменяет транзакцию ПРОВЕДЕНИЯ, а не «свою»
        	КонецПопытки;
        
        КонецПроцедуры
        ```
        
        #### Правильно
        
        ```bsl
        // Правильно: обработчик работает в уже открытой транзакции, отказ выражается через Отказ
        Процедура ОбработкаПроведения(Отказ, РежимПроведения)
        
        	Движения.ТоварыНаСкладах.Записывать = Истина;
        	// ... заполнение движений; при ошибке — Отказ = Истина и ВызватьИсключение
        
        КонецПроцедуры
        ```
        
        Отдельная транзакция нужна вызывающему коду — там, где несколько объектов пишутся атомарно.
        Тогда её открывают **снаружи**, до `Записать()`, по паттерну #std783 п.1.3.
        
        ## Когда это не дефект
        
        - `ПередЗаписью` **модуля формы** — другое событие, транзакции вокруг него нет, и
          `НачатьТранзакцию` там законна. Инструмент модули форм не проверяет; при разборе глазами не
          спутай их по совпадению имени обработчика.
        
        ## Что проверяет инструмент
        
        Проверяется механически: `qg:BSL-TXN-IN-HANDLER` (`tools/bsl-lint.mjs`), 🟠. Инструмент
        смотрит модули объекта и набора записей; вынесенную в общий модуль логику он не видит.
        
      • BSL-TXN-INSIDE-TRY.md 3.1 KB
        ---
        id: qg:BSL-TXN-INSIDE-TRY
        title: НачатьТранзакцию() внутри Попытка
        severity: major
        group: platform
        tool: null
        archetypes: [transaction]
        std: [std783]
        ---
        
        ## Триггер
        
        `Попытка` раньше `НачатьТранзакцию()` в одном блоке.
        
        ## Почему
        
        `ОтменитьТранзакцию()` вызовется в обработчике исключения, даже если `НачатьТранзакцию()` не
        успела выполниться — например, исключение произошло до неё.
        
        ## Как чинить
        
        `НачатьТранзакцию()` должна стоять строго до `Попытка`.
        
        #### Неправильно
        
        ```bsl
        // HIGH: ОтменитьТранзакцию() вызовется, даже если НачатьТранзакцию() не выполнялась
        Попытка
        	НачатьТранзакцию();
        	// ...
        	ЗафиксироватьТранзакцию();
        Исключение
        	ОтменитьТранзакцию();
        КонецПопытки;
        ```
        
        #### Правильно
        
        ```bsl
        // Правильно: НачатьТранзакцию() строго ДО Попытка
        НачатьТранзакцию();
        Попытка
        	// ...
        	ЗафиксироватьТранзакцию();
        Исключение
        	ОтменитьТранзакцию();
        	ЗаписьЖурналаРегистрации(
        		"Модуль.Функция",
        		УровеньЖурналаРегистрации.Ошибка, , ,
        		ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
        	ВызватьИсключение;
        КонецПопытки;
        ```
        
        ## Когда это не дефект
        
        - `НачатьТранзакцию()` стоит строго до `Попытка`, а `ОтменитьТранзакцию()` — в `Исключение`:
          это штатная форма по #std783, находки нет. Дефект — только когда открытие транзакции попало
          внутрь блока `Попытка`: тогда `ОтменитьТранзакцию()` в `Исключение` может выполниться по
          исключению, возникшему до `НачатьТранзакцию()`, и уронить чужую транзакцию либо упасть само.
        - Внешняя `Попытка` оборачивает целый метод ради записи в журнал регистрации или преобразования
          ошибки, а транзакция внутри оформлена штатно (своя `Попытка` ниже `НачатьТранзакцию()`,
          `ОтменитьТранзакцию()` в её `Исключение`): вложенность блоков здесь не признак, важен порядок
          внутри транзакционного блока.
        
        ## Что проверяет инструмент
        
        Ничего: инструмента нет. Проверяется чтением тела метода.
        
      • BSL-UNBOUNDED-STRING-COLUMN.md 2 KB
        ---
        id: qg:BSL-UNBOUNDED-STRING-COLUMN
        title: Строковая колонка без квалификатора у таблицы-параметра запроса
        severity: major
        group: platform
        tool: tools/bsl-lint.mjs
        archetypes: [query]
        std: [std432]
        ---
        
        ## Триггер
        
        Колонка ТЗ без `КвалификаторыСтроки` уходит в запрос через `УстановитьПараметр`.
        
        ## Почему
        
        Колонка неограниченной длины ломает `РАЗЛИЧНЫЕ`, `СГРУППИРОВАТЬ ПО`, соединение и сравнение в
        `ГДЕ` — движок запросов не умеет сравнивать поля неограниченной длины там, где значения нужно
        сопоставлять между собой. Полный разбор со всеми сообщениями об ошибках и якорем #std432 п. 3.1
        — в `AI-16.md`.
        
        ## Как чинить
        
        `Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(N))` — образец и оба случая
        (неправильно/правильно) в `AI-16.md`.
        
        ## Когда это не дефект
        
        - передача таблицы в чужой метод: инструмент видит только тот же модуль, что не значит
          «дефекта нет» — эта половина остаётся за читателем;
        - колонка с длинным текстом, которой на стороне запроса назначают длину через `ВЫРАЗИТЬ`
          (#std432 п. 2) — разбор в `AI-16.md`.
        
        ## Что проверяет инструмент
        
        Проверяется механически: `qg:BSL-UNBOUNDED-STRING-COLUMN` (`tools/bsl-lint.mjs`), 🟠. Уход
        таблицы в чужой метод инструменту не виден: графа вызовов он не строит.
        
      • BSL-VT-FILTER-IN-WHERE.md 2 KB
        ---
        id: qg:BSL-VT-FILTER-IN-WHERE
        title: Фильтр виртуальной таблицы в ГДЕ
        severity: major
        group: platform
        tool: null
        archetypes: [query]
        std: []
        ---
        
        ## Триггер
        
        Условие в `ГДЕ` на результате виртуальной таблицы вместо передачи фильтра в её параметры.
        
        ## Почему
        
        Влияние: полный перебор таблицы вместо использования индекса.
        
        ## Как чинить
        
        Переносить фильтр в параметры виртуальной таблицы.
        
        #### Неправильно
        
        ```sql
        -- HIGH: фильтр после вычисления виртуальной таблицы
        ВЫБРАТЬ
        	Остатки.Номенклатура КАК Номенклатура,
        	Остатки.КоличествоОстаток КАК Остаток
        ИЗ
        	РегистрНакопления.ТоварыНаСкладах.Остатки() КАК Остатки
        ГДЕ
        	Остатки.Склад = &Склад
        ```
        
        #### Правильно
        
        ```sql
        -- Оптимально: фильтр в параметрах виртуальной таблицы
        ВЫБРАТЬ
        	Остатки.Номенклатура КАК Номенклатура,
        	Остатки.КоличествоОстаток КАК Остаток
        ИЗ
        	РегистрНакопления.ТоварыНаСкладах.Остатки(, Склад = &Склад) КАК Остатки
        ```
        
        ## Когда это не дефект
        
        - условие по полю, которого нет среди параметров виртуальной таблицы (ресурсы, вычисляемые
          поля);
        - условие с `ИЛИ` между измерениями, которое параметры не выражают.
        
        ## Что проверяет инструмент
        
        Ничего: текст запроса — строковый литерал, разбор доступен только чтением.
        
      • INDEX.md 11.7 KB
        # Индекс триггеров каталога антипаттернов
        
        Производный файл — источник истины карточки `catalog/<ID>.md`. Правки вносить в карточки,
        индекс перегенерировать: `node tools/gen-catalog-index.mjs`.
        
        Здесь только то, что нужно для обнаружения: идентификатор, важность и триггер. Разбор,
        способ исправления и законные формы — в карточке; она читается по попаданию, а не заранее.
        Карточки с инструментом перечислены ради «как чинить»: их находки печатает инструмент,
        своих находок этого класса не добавляй. `*` — нужен `git diff`.
        
        Уроки об устройстве программы ведёт `bsl-architecture-review`: доверие результату своей
        функции, одна граница проверки, общий механизм вместо копии на каждый вариант входа,
        подготовка данных отдельным методом, поле под будущий признак, конструктор внешней
        структуры. Регистрацию объекта в составе конфигурации — `xml-structure-review`. Здесь их
        не отмечай.
        
        | Признак | Важность | Группа | Архетипы | Инструмент | Триггер |
        |---|---|---|---|---|---|
        | `qg:AI-01` | 🔴 | model | record-set | чтение | `НаборЗаписей…Записать(Истина)` с отбором не по всем ключевым измерениям. |
        | `qg:AI-02` | 🔴 | model | always | чтение | `Ссылка.Реквизит` в новом или изменённом коде, где объект не нужен целиком. |
        | `qg:AI-03` | 🔴 | model | always | чтение | Программное создание видов характеристик/доп. свойств из кода; запись в регистры в обход API. |
        | `qg:AI-04` | 🔴 | model | always | чтение | Утверждения «проверено», «собирается», «проходит» без следа фактического прогона. |
        | `qg:AI-05` | 🔴 | model | always | чтение | Вывод «код компилируется» по загрузке/выгрузке конфигурации, валидатору XML или анализатору. |
        | `qg:AI-06` | 🟠 | model | object-event, form-module | чтение | Выходные параметры «ЕстьПроблемы», структуры «Прервать»/«Текст» вместо отмены операции. |
        | `qg:AI-07` | 🟠 | model | always | чтение | Структура/массив/ТЗ без `Знач` в методе, который их не меняет — особенно у серверного метода из клиента. |
        | `qg:AI-08` | 🟠 | model | always | чтение | Набор функций-помощников, каждая достаёт из ответа одно поле и приводит его к нужному типу. |
        | `qg:AI-09` | 🟠 | model | always | чтение | Чтение константы или поиск предопределённого элемента внутри цикла по строкам. |
        | `qg:AI-10` | 🟠 | model | query | чтение | Вложенные циклы поиска соответствий и построение `Соответствие` для связывания наборов. |
        | `qg:AI-11`* | 🟠 | model | always | чтение | В `git diff` — исчезнувшие проверки редких случаев и записи без читаемого результата. |
        | `qg:AI-12` | 🟠 | model | object-event, transaction | чтение | Запись документа и его проведение в одной транзакции «ради атомарности». |
        | `qg:AI-13` | 🟡 | model | always | чтение | `НайтиПоНаименованию` у доп. реквизита; `НайтиПоКоду` у справочника в теле механизма. |
        | `qg:AI-14` | 🟡 | model | always | чтение | У табличного документа нет метода `Получить` — ячейка читается через `Область(Строка, Колонка)`. |
        | `qg:AI-15` | 🟡 | model | always | чтение | `Новый ПараметрыЗаписиJSON` без аргументов, затем присваивание свойству — read-only. |
        | `qg:AI-16` | 🟠 | model | query | чтение | Строковая колонка ТЗ без `КвалификаторыСтроки` уходит в запрос — ошибка при выполнении. |
        | `qg:AI-17` | 🟠 | model | query | чтение | Цикл по выборке запроса собирает структуру и кладёт в массив — список полей задублирован. |
        | `qg:AI-18` | 🟠 | model | query | чтение | Ключ соответствия склеен из значений строкой, либо цикл ради поиска по двум-трём полям. |
        | `qg:AI-19` | 🟡 | model | always | чтение | Обращение к реквизиту/ТЧ обложено проверкой наличия по метаданным «на всякий случай». |
        | `qg:AI-20` | 🟡 | model | always | чтение | Результат своей функции читается цепочкой через точку, особенно повторно в методе. |
        | `qg:AI-21` | 🟡 | model | always | чтение | Элемент известной коллекции берётся расчётом от `ВГраница()` вместо явного индекса. |
        | `qg:AI-22` | 🟡 | model | always | чтение | Во все точки вызова метода уходит одно и то же значение параметра, а в сигнатуре умолчания нет. |
        | `qg:BSL-CONTEXT-CALL-UNNEEDED` | 🟠 | platform | client-server, form-module | чтение | `&НаСервере` там, где метод не читает и не пишет контекст формы. |
        | `qg:BSL-CORRELATED-SUBQUERY` | 🔴 | platform | query | чтение | Вложенный `ВЫБРАТЬ` в `ГДЕ`, ссылающийся на поле внешнего запроса. |
        | `qg:BSL-DB-READ-IN-LOOP` | 🟠 | platform | always | `bsl-lint.mjs` | В теле цикла ни одного запроса — чтение прячется в методе, достижимом из цикла через вызов. |
        | `qg:BSL-DEEP-NESTING` | 🟡 | platform | always | чтение | Более 4 уровней вложенности условий и циклов в одном методе. |
        | `qg:BSL-DISPATCH-NO-FALLBACK` | 🟠 | platform | always | `bsl-lint.mjs` | Цепочка `Если … ИначеЕсли …` по значениям/типам в трёх и более ветках без `Иначе`. |
        | `qg:BSL-ENUM-STRING-ASSIGN` | 🟠 | platform | always | `bsl-lint.mjs` | Присваивание примитива (`""`, `0`, `Ложь`, `Истина`) полю строго ссылочного типа. |
        | `qg:BSL-FORM-ATTR-SHADOW` | 🔴 | platform | form-module | `bsl-lint.mjs` | Переменная с именем реквизита формы получает объектное значение — пишет реквизит, не переменную. |
        | `qg:BSL-MESSAGE-AS-NOTIFY` | 🟠 | platform | always | чтение | `Сообщить(...)` вместо `ОбщегоНазначения.СообщитьПользователю` или записи в журнал регистрации. |
        | `qg:BSL-MULTI-SERVER-CALLS` | 🟠 | platform | client-server, form-module | чтение | Несколько последовательных `...НаСервере()`-вызовов подряд с клиента в одном обработчике. |
        | `qg:BSL-NESTED-LOOP-SEARCH` | 🟡 | platform | always | чтение | Вложенный цикл поиска совпадений между двумя коллекциями вместо `Соответствие`. |
        | `qg:BSL-NO-CACHE` | 🟡 | platform | always | чтение | Повторные дорогие вызовы с одинаковыми параметрами внутри цикла без кеширования результата. |
        | `qg:BSL-NO-TOP-LIMIT` | 🟠 | platform | query | чтение | Большой запрос без `ПЕРВЫЕ N` — загрузка всех записей без ограничения. |
        | `qg:BSL-QUERY-IN-LOOP` | 🔴 | platform | always | чтение | `Для Каждого` с `Новый Запрос` внутри тела цикла — обращение к базе на каждой итерации. |
        | `qg:BSL-REF-DOT-ACCESS` | 🔴 | platform | always | `bsl-lint.mjs` | `.Реквизит` у ссылочного значения — загрузка всего объекта из БД ради одного поля. |
        | `qg:BSL-STALE-LOCAL-CALL` | 🔴 | platform | always | `rename-check.mjs` | Вызов метода, чьё объявление было в HEAD и исчезло после правки. |
        | `qg:BSL-SUBQUERY-IN-SELECT` | 🔴 | platform | query | чтение | Вложенный `ВЫБРАТЬ` в списке полей — считается заново для каждой строки внешнего запроса. |
        | `qg:BSL-TEMPTABLE-NO-INDEX` | 🟠 | platform | query | чтение | `ПОМЕСТИТЬ ВТ...` без `ИНДЕКСИРОВАТЬ ПО`, а таблица дальше участвует в соединении. |
        | `qg:BSL-TXN-IN-HANDLER` | 🟠 | platform | transaction, object-event | `bsl-lint.mjs` | Своя `НачатьТранзакцию` внутри обработчика, который платформа уже выполняет в транзакции. |
        | `qg:BSL-TXN-INSIDE-TRY` | 🟠 | platform | transaction | чтение | `Попытка` раньше `НачатьТранзакцию()` в одном блоке. |
        | `qg:BSL-UNBOUNDED-STRING-COLUMN` | 🟠 | platform | query | `bsl-lint.mjs` | Колонка ТЗ без `КвалификаторыСтроки` уходит в запрос через `УстановитьПараметр`. |
        | `qg:BSL-VT-FILTER-IN-WHERE` | 🟠 | platform | query | чтение | Условие в `ГДЕ` на результате виртуальной таблицы вместо передачи фильтра в её параметры. |
        | `qg:QRY-ALIAS-RESERVED-WORD` | 🔴 | platform | query | `query-lint.mjs` | Псевдоним — служебное слово языка запросов (`КАК Первые`). |
        | `qg:QRY-ALIAS-SHADOWS-FIELD` | 🔴 | platform | query | `query-lint.mjs` | Псевдоним источника совпадает с именем колонки, видимой в этом же запросе. |
        | `qg:QRY-ALIAS-SHADOWS-NESTED-TABLE` | 🔴 | platform | query | `query-lint.mjs` | Псевдоним источника-ТЧ совпадает с именем самой табличной части при соединённом владельце. |
        | `qg:QRY-TOP-WITHOUT-ORDER` | 🟡 | platform | query | `query-lint.mjs` | `ПЕРВЫЕ N` (N > 1) без `УПОРЯДОЧИТЬ ПО` в тексте запроса. |
        
      • QRY-ALIAS-RESERVED-WORD.md 2.3 KB
        ---
        id: qg:QRY-ALIAS-RESERVED-WORD
        title: Служебное слово языка запросов в псевдониме
        severity: critical
        group: platform
        tool: tools/query-lint.mjs
        archetypes: [query]
        std: []
        ---
        
        ## Триггер
        
        Псевдоним — служебное слово языка запросов (`КАК Первые`).
        
        ## Почему
        
        Платформа отвергает такой запрос при разборе, целиком. Текст при этом собирается, статический
        анализ молчит, сборка расширения проходит: литерал остаётся литералом до первого выполнения.
        Ветка кода с таким запросом не работает ни разу с момента написания.
        
        ## Как чинить
        
        Уточнить имя по предмету.
        
        ```sql
        ВЫБРАТЬ Заказы.Ссылка КАК Ссылка
        ИЗ Документ.ЗаказКлиента КАК Заказы
            ЛЕВОЕ СОЕДИНЕНИЕ ВТПервыеСтроки КАК Первые
            ПО Первые.Заказ = Заказы.Ссылка
        -- запрос не разбирается: «Первые» — служебное слово
        ```
        
        `Первые` → `ПервыеСтроки`, `Выбор` → `ВыборПользователя`.
        
        ## Когда это не дефект
        
        - `Ссылка` и `Представление` в списке выборки (`Заказы.Ссылка КАК Ссылка`): запрещены только
          псевдонимом источника;
        - слова, которые платформа псевдонимом принимает, — `Итоги`, `Соединение`, `Сумма`,
          `Количество`, `Значение`, `Дата` и другие: список ломающих слов измерен, а не выведен из
          синтаксиса;
        - приведение типа: `ВЫРАЗИТЬ(Х КАК Документ.Заказ)`, `КАК Число(15,2)`.
        
        ## Что проверяет инструмент
        
        Проверяется механически: `qg:QRY-ALIAS-RESERVED-WORD` (`tools/query-lint.mjs`) — 🔴. Списки
        слов по позициям и способ их замера — в `bsl-query-reference.md`.
        
      • QRY-ALIAS-SHADOWS-FIELD.md 2.2 KB
        ---
        id: qg:QRY-ALIAS-SHADOWS-FIELD
        title: Псевдоним источника совпадает с именем колонки
        severity: critical
        group: platform
        tool: tools/query-lint.mjs
        archetypes: [query]
        std: []
        ---
        
        ## Триггер
        
        Псевдоним источника совпадает с именем колонки, видимой в этом же запросе.
        
        ## Почему
        
        `Псевдоним.Поле` тогда читается двояко — как поле источника и как разыменование одноимённой
        колонки. Ошибка не синтаксическая, а разбора: до выполнения запроса её не видит ни один
        инструмент, кроме платформы — «Неоднозначное поле».
        
        ## Как чинить
        
        Переименовать источник.
        
        ```sql
        -- ВТ_Связи содержит колонку «Перемещение», а источник назван так же:
        ВЫБРАТЬ
            Связи.Перемещение КАК Перемещение
        ИЗ
            ВТ_Связи КАК Связи
                ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров КАК Перемещение
                ПО Связи.Перемещение = Перемещение.Ссылка
        -- {(N, M)}: Неоднозначное поле "Перемещение.Ссылка"
        ```
        
        Лечится переименованием источника (`КАК ДокументПеремещения`).
        
        ## Когда это не дефект
        
        - псевдоним совпадает с именем колонки, но `Псевдоним.Поле` нигде не используется — коллизия
          имени есть, а разыменования нет.
        
        ## Что проверяет инструмент
        
        Проверяется механически: `qg:QRY-ALIAS-SHADOWS-FIELD` (`tools/query-lint.mjs`) — 🔴, если
        псевдоним разыменован (`Псевдоним.Поле` встречается в тексте), иначе 🟠. Полный разбор
        конструкции — в `bsl-query-reference.md`.
        
      • QRY-ALIAS-SHADOWS-NESTED-TABLE.md 1.8 KB
        ---
        id: qg:QRY-ALIAS-SHADOWS-NESTED-TABLE
        title: Псевдоним источника совпадает с именем табличной части
        severity: critical
        group: platform
        tool: tools/query-lint.mjs
        archetypes: [query]
        std: []
        ---
        
        ## Триггер
        
        Псевдоним источника-ТЧ совпадает с именем самой табличной части при соединённом владельце.
        
        ## Почему
        
        Табличная часть — поле объектной таблицы: язык умеет выбирать вложенные таблицы
        (`Ордер.Строки.(Номенклатура, Количество)`). Пока владелец в ветке, имя `Строки` занято, и
        псевдоним с ним сталкивается.
        
        ## Как чинить
        
        Переименовать псевдоним источника-табличной-части.
        
        ```sdbl
        ИЗ
            Документ.РасходныйОрдер.Строки КАК Строки
                ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РасходныйОрдер КАК Ордер
                ПО Строки.Ссылка = Ордер.Ссылка
        -- {(N, M)}: Неоднозначное поле "Строки.Ссылка"
        ```
        
        ## Когда это не дефект
        
        - без владельца в ветке та же запись законна — это обычный и распространённый стиль.
        
        ## Что проверяет инструмент
        
        Проверяется механически: `qg:QRY-ALIAS-SHADOWS-NESTED-TABLE` (`tools/query-lint.mjs`) — 🔴 при
        разыменовании, иначе 🟠. Коллизию псевдонима с именем реквизита реальной таблицы инструмент не
        ловит — это остаётся глазам.
        
      • QRY-TOP-WITHOUT-ORDER.md 2.1 KB
        ---
        id: qg:QRY-TOP-WITHOUT-ORDER
        title: ПЕРВЫЕ N без УПОРЯДОЧИТЬ ПО
        severity: minor
        group: platform
        tool: tools/query-lint.mjs
        archetypes: [query]
        std: []
        ---
        
        ## Триггер
        
        `ПЕРВЫЕ N` (N > 1) без `УПОРЯДОЧИТЬ ПО` в тексте запроса.
        
        ## Почему
        
        `ПЕРВЫЕ N` ставится **вместе с `УПОРЯДОЧИТЬ ПО`**. Без явного порядка неизвестно, какие
        именно N строк вернутся: порядок задаёт СУБД, и он меняется между прогонами, версиями
        платформы и движками базы. «Первые десять договоров» на тестовой базе стабильны, в продуктиве
        — нет.
        
        ## Как чинить
        
        Добавить `УПОРЯДОЧИТЬ ПО` по детерминирующему полю рядом с `ПЕРВЫЕ N`.
        
        #### Неправильно
        
        ```sql
        ВЫБРАТЬ ПЕРВЫЕ 10 Контрагенты.Ссылка ИЗ Справочник.Контрагенты КАК Контрагенты
        ```
        
        #### Правильно
        
        ```sql
        ВЫБРАТЬ ПЕРВЫЕ 10 Контрагенты.Ссылка
        ИЗ Справочник.Контрагенты КАК Контрагенты
        УПОРЯДОЧИТЬ ПО Контрагенты.Наименование
        ```
        
        Помещение результата во временную таблицу законной формой **не является** — недетерминированным
        оказывается сам отбор, ещё до `ПОМЕСТИТЬ`.
        
        ## Когда это не дефект
        
        - **Законные формы:** `ПЕРВЫЕ 1` в проверке существования (важно только «пусто или нет»);
        - `АВТОУПОРЯДОЧИВАНИЕ`.
        
        ## Что проверяет инструмент
        
        Проверяется механически: `qg:QRY-TOP-WITHOUT-ORDER` (`tools/query-lint.mjs`), 🟡 —
        лексический разбор текста запроса.
        
    • analyzer-output.md 5.3 KB
      # Как читать вывод статического анализатора
      
      `tools/analyzer-run.mjs --changed` печатает находки по файлам и готовый блок
      `## quality evidence`. Правила прогона — в SKILL.md контура; здесь то, что стоит за режимами
      вывода: почему часть находок свёрнута, что означает работа без основной конфигурации и
      почему «файл не разобран» — не то же самое, что «в файле чисто».
      
      Читать при разборе спорного вывода и когда находку анализатора собираются оспорить или,
      наоборот, поднять в отчёт.
      
      ---
      
      ## Информационные находки свёрнуты в одну строку
      
      На реальном модуле их вдесятеро больше содержательных. Основную массу дают `MagicNumber` и
      смешение латиницы с кириллицей в идентификаторах вроде `ВызватьHTTPМетод` — для 1С это норма,
      а диагностика отличить не может.
      
      Полный список даёт флаг `--all`. Ничего не скрыто: коды попадают в след в любом случае, и
      свёрнутая строка — решение о показе, а не о проверке.
      
      Смысл в цене внимания. Отчёт, в котором содержательная находка тонет среди сорока
      информационных, читают по диагонали — и пропускают именно ту, ради которой прогон затевался.
      
      ## Работа без основной конфигурации
      
      Состав проекта команда определяет сама. Есть основная конфигурация — она подключается к
      анализу вместе со всеми расширениями, и имена БСП разрешаются нормально.
      
      Репозиторий одного расширения — команда сообщает об этом строкой и понижает диагностики о
      неразрешённых именах до информационных. Отличить «имя из основной конфигурации, которой
      здесь нет» от настоящей ошибки в таком режиме нельзя, поэтому поднимать их обратно и выдавать
      за дефекты нельзя тоже: это фабрикация находок, а не строгость.
      
      ## «НЕ РАЗОБРАНО файлов» — вердикта по ним нет
      
      Строка означает, что по этим файлам не проверено **ничего**. Их находки не выводятся вовсе:
      они получены на обрывке синтаксического дерева и потому недостоверны — как «чистый» результат,
      так и любой найденный дефект.
      
      В отчёте такие файлы называются явно. Вердикт «чисто» по ним невозможен, а молчание о них
      превращает нехватку разбора в видимость проверки.
      
      Известный случай — модуль `&ИзменениеИКонтроль` с директивами `#Удаление` внутри
      многострочного литерала: для платформы это законно, анализатор спотыкается. Дефект инструмента,
      а не кода, но результат тот же: файл остаётся непроверенным, и это заявляется.
      
      ## Гейтовый конфиг отделён от проектного
      
      Анализ гейта идёт с конфигом из состава плагина (`assets/analyzer/`), проектный
      (`.bsl-language-server.json`, `bsl-analyzer.toml`) продолжает править IDE.
      
      До разделения проектный `subsystemsFilter` мог молча вывести изменённые файлы из анализа:
      находок нет, метрик нет, вердикт «чисто» — и ни одной причины заподозрить, что движок не
      смотрел на них вовсе. Настройка среды разработчика не должна уметь ослаблять гейт.
      
      ## Часовой: недостоверный прогон
      
      `status=not_found` означает, что движок сломан, конфиг испорчен или потерян контекст
      конфигурации. Вердикт «чисто» в таком прогоне запрещён — валидатор следа это проверяет и гейт
      не снимет.
      
      Без часового «диагностик не найдено» и «анализатор не работает» дают одинаково зелёный отчёт.
      
    • api-verification.md 7.3 KB
      # Верификация API: сигнатуры, модули, метаданные
      
      Механическая проверка того, что вызванное **существует и вызвано правильно**. Не про логику
      и не про стиль — только факты, подтверждаемые справочником платформы и индексом кода.
      
      Зачем отдельно: модель уверенно пишет вызовы несуществующих методов и передаёт параметры в
      неверном порядке — синтаксически безупречный код, который падает при первом же выполнении.
      Статический анализатор такое ловит частично, а без индекса конфигурации не ловит вовсе.
      
      ## 1. Методы и типы платформы
      
      **Сначала проверь, не закрыт ли этот пункт прогоном.** Контур платформенного API
      (`tools/platform-context-run.mjs`) заводится сам и на машине с установленной 1С работает без
      настройки; прогнан — он сверяет
      модуль со справкой платформы целиком и детерминированно: члены типов, значения системных
      перечислений, конструкторы, свойства, число аргументов глобальных функций. Ручная сверка
      после него — та же работа менее надёжным способом: модель проверяет то, о чём догадалась
      спросить, а прогон — каждый вызов в файле.
      
      Если контур пропустил себя (в следе есть его `skipped`), сверяй вручную по справочнику платформы (MCP `bsl-context`,
      `1c-platform` либо аналог):
      
      - глобальный метод — поиск по имени с типом «метод»;
      - метод или свойство типа — запрос члена по имени типа и имени члена;
      - полный состав API типа — список членов типа.
      
      Проверяй: существование, число параметров, их порядок, обязательность, типы.
      
      > **У справочника на stdio-транспорте (Java-сервер `1c-platform`) вызовы — строго
      > последовательно.** Параллельные обращения убивают сессию транспорта. Один вызов за ход,
      > дождись результата. При `Connection closed` прекрати обращения к нему, отметь в «не
      > проверено» и продолжай остальные проверки. У серверов на HTTP (`bsl-context`) ограничения
      > нет.
      
      ## 2. Callback-процедуры
      
      Для асинхронных вызовов с оповещением сверь количество и типы параметров процедуры-обработчика
      и состав полей типа результата — он документирован в справочнике платформы. Ошибка здесь не
      диагностируется до выполнения и проявляется как «процедура не найдена» или пустой результат.
      
      ## 3. Общие модули конфигурации
      
      Для каждого вызова `ОбщийМодуль.Метод()` через индекс кода (MCP `rlm-tools-bsl` либо аналог)
      проверь: существует ли модуль, есть ли в нём метод, **экспортный ли он**, совпадает ли число
      аргументов. Отдельно — контекст доступности: серверный метод не вызывается с клиента, метод
      модуля с флагом «вызов сервера» не используется в клиентском контексте.
      
      Для перехватов расширений читай тело процедуры **с учётом переопределений** — иначе легко
      проверить оригинал вместо фактически исполняемого кода.
      
      ## 4. Объекты метаданных
      
      Обращения к реквизитам, табличным частям, измерениям и ресурсам сверь с фактической
      структурой объекта через индекс кода. Типичная ошибка модели — обращение к реквизиту, который
      существует в другой конфигурации или в другой версии той же.
      
      **Структуру объектов не читай сырым XML.** Разбор через индекс возвращает состав с типами и
      не тратит контекст на разметку.
      
      ## 5. Именование (#std454)
      
      Для каждого нового имени переменной или параметра:
      
      - имя от термина предметной области, смысл понятен без комментария;
      - CamelCase, включая предлоги и местоимения;
      - без сокращений-префиксов, обозначающих тип или роль;
      - не начинается с подчёркивания;
      - не однобуквенное, кроме счётчика цикла;
      - булево читается как утверждение о факте, а не как «признак» или «флаг» чего-то.
      
      ## 6. Символы
      
      Только ASCII-дефис в коде и комментариях: длинное тире, короткое тире и математический минус
      дают у анализатора ошибку недопустимого символа в файле. Кавычки-ёлочки допустимы.
      
      ## Когда проверка не нужна
      
      - Изменение чисто косметическое: комментарии, форматирование.
      - Используются только базовые конструкции языка без обращения к API платформы и к
        конфигурации.
      
      ## Что делать при недоступности инструментов
      
      Записать пропуск с точной причиной и продолжить остальные проверки:
      
      ```
      [qg skipped: layer=code, scope=api-verification, planned=[platform:*], reason=platform_unavailable]
      [qg skipped: layer=code, scope=api-verification, planned=[rlm:*], reason=rlm_unavailable]
      ```
      
      Деградация допустима, замалчивание — нет. Отчёт, в котором не видно, что половина проверок не
      выполнялась, хуже отсутствующего отчёта: он создаёт ложную уверенность.
      
    • bsl-async.md 9.2 KB
      # Асинхронные методы 1С (Асинх / Ждать / Обещание)
      
      Правила использования механизма асинхронности (платформа 8.3.18+).
      
      Применяется: при написании клиентского кода с асинхронными вызовами.
      
      ---
      
      ## Основные принципы
      
      1. Все новые асинхронные функции (суффикс `Асинч`) возвращают `Обещание`
      2. `Ждать` — оператор ожидания завершения `Обещание`
      3. `*Асинч`-методы вызывать **только внутри** процедур/функций с ключевым словом `Асинч`
      4. `Обещание` имеет 3 состояния: **Ожидание**, **Успех**, **Провал**
      5. Работает только **на клиенте** (`&НаКлиенте`)
      
      ---
      
      ## Соответствие старых и новых методов
      
      Предпочитать новый синтаксис `*Асинч` вместо `Показать*` с `ОписаниеОповещения`:
      
      | Модальный (запрещён) | Через оповещение | Новый Асинч |
      |---|---|---|
      | `Предупреждение()` | `ПоказатьПредупреждение()` | `ПредупреждениеАсинч()` |
      | `Вопрос()` | `ПоказатьВопрос()` | `ВопросАсинч()` |
      | `ОткрытьЗначение()` | `ПоказатьЗначение()` | `ОткрытьЗначениеАсинч()` |
      | `ВвестиЧисло()` | `ПоказатьВводЧисла()` | `ВвестиЧислоАсинч()` |
      | `НайтиФайлы()` | `НачатьПоискФайлов()` | `НайтиФайлыАсинч()` |
      | `ПоместитьФайлы()` | `НачатьПомещениеФайлов()` | `ПоместитьФайлыНаСерверАсинч()` |
      
      ### Исключение: предупреждение без ожидания ответа
      
      Если результат диалога не нужен — не заворачивать в `Асинч`/`Ждать`. Достаточно `ПоказатьПредупреждение` без обработчика (fire-and-forget):
      
      ```bsl
      // Достаточно — ответ не нужен:
      ПоказатьПредупреждение(, НСтр("ru = 'Документ проведён.'"));
      
      // Избыточно:
      Ждать ПредупреждениеАсинч(НСтр("ru = 'Документ проведён.'"));
      ```
      
      `ПредупреждениеАсинч` оправдан только когда после закрытия окна должен продолжиться код в той же процедуре.
      
      ---
      
      ## Возвращаемые значения
      
      | Метод | Результат `Ждать` |
      |---|---|
      | `ПредупреждениеАсинч` | `Неопределено` |
      | `ВопросАсинч` | `КодВозвратаДиалога` |
      | `ОткрытьЗначениеАсинч` | `Неопределено` |
      | `ВвестиЧислоАсинч` | `Число` |
      | `НайтиФайлыАсинч` | `Массив` объектов типа `Файл` |
      | `ПоместитьФайлыНаСерверАсинч` | `Массив` из `ОписаниеПомещенногоФайла` (успех) / `Неопределено` (провал) |
      
      ---
      
      ## Базовый шаблон
      
      ```bsl
      &НаКлиенте
      Асинч Процедура МояПроцедура()
      
      	Обещание = ВопросАсинч("Продолжить?", РежимДиалогаВопрос.ДаНет);
      	Результат = Ждать Обещание;
      
      	Если Результат = КодВозвратаДиалога.Да Тогда
      		// Действие
      	КонецЕсли;
      
      КонецПроцедуры
      ```
      
      ---
      
      ## Критические правила
      
      ### 1. Без `Ждать` исключения теряются молча
      
      Если вызвать асинхронную функцию без `Ждать` и внутри возникнет исключение — оно **проглочено без следа**:
      
      ```bsl
      // ОПАСНО: исключение внутри ВызватьОшибку() будет потеряно
      Процедура МояПроцедура(Команда)
      	ВызватьОшибку(); // Асинч-функция вызвана без Ждать
      КонецПроцедуры
      
      Асинч Функция ВызватьОшибку()
      	ВызватьИсключение "ошибка!"; // никто не увидит
      КонецФункции
      ```
      
      **Правило:** всегда использовать `Ждать` при вызове `Асинч`-функций, если важен результат или обработка ошибок.
      
      ### 2. Обработчики событий формы нельзя делать `Асинх`
      
      Обработчики `ПриОткрытии`, `ПередЗакрытием` и т.д. с ключевым словом `Асинч` **не работают** — платформа не ждёт их завершения:
      
      ```bsl
      // НЕ РАБОТАЕТ: Отказ = Истина будет проигнорирован
      &НаКлиенте
      Асинч Процедура ПриОткрытии(Отказ)
      	Отказ = Истина; // платформа уже продолжила выполнение
      КонецПроцедуры
      ```
      
      **Правило:** НЕ делать обработчики событий формы асинхронными. Вместо этого — вызывать отдельную `Асинч`-процедуру из обычного обработчика.
      
      ### 3. Обработчики команд — можно делать `Асинх`
      
      ```bsl
      &НаКлиенте
      Асинч Процедура МояКоманда(Команда)
      	Обещание = ВвестиЧислоАсинч(1, "Укажите число");
      	Результат = Ждать Обещание;
      КонецПроцедуры
      ```
      
      ---
      
      ## Паттерн: Вопрос при открытии формы
      
      ```bsl
      &НаКлиенте
      Процедура ПриОткрытии(Отказ)
      	ЗадатьВопросПриОткрытии();
      КонецПроцедуры
      
      &НаКлиенте
      Асинч Процедура ЗадатьВопросПриОткрытии()
      	Обещание = ВопросАсинч(
      		"Продолжить работу?",
      		РежимДиалогаВопрос.ДаНет, ,
      		КодВозвратаДиалога.Да);
      	Результат = Ждать Обещание;
      
      	Если Результат <> КодВозвратаДиалога.Да Тогда
      		ЭтотОбъект.Закрыть();
      	КонецЕсли;
      КонецПроцедуры
      ```
      
      ---
      
      ## Паттерн: Вопрос при закрытии формы
      
      ```bsl
      &НаКлиенте
      Перем ВопросПриЗакрытииЗадан;
      
      &НаКлиенте
      Процедура ПередЗакрытием(Отказ, ЗавершениеРаботы,
      		ТекстПредупреждения, СтандартнаяОбработка)
      	Если НЕ ВопросПриЗакрытииЗадан Тогда
      		ЗадатьВопросПриЗакрытии();
      		Отказ = Истина;
      	КонецЕсли;
      КонецПроцедуры
      
      &НаКлиенте
      Асинч Процедура ЗадатьВопросПриЗакрытии()
      	Обещание = ВопросАсинч(
      		"Закрыть форму?",
      		РежимДиалогаВопрос.ДаНет, ,
      		КодВозвратаДиалога.Да);
      	Результат = Ждать Обещание;
      
      	Если Результат = КодВозвратаДиалога.Да Тогда
      		ВопросПриЗакрытииЗадан = Истина;
      		ЭтотОбъект.Закрыть();
      	КонецЕсли;
      КонецПроцедуры
      
      // Инициализация в теле модуля:
      ВопросПриЗакрытииЗадан = Ложь;
      ```
      
      ---
      
      ## Паттерн: Работа с файлами
      
      ```bsl
      &НаКлиенте
      Асинч Процедура ЗагрузитьФайлыНаСервер()
      
      	Обещание        = НайтиФайлыАсинч(ПутьКПапке, "*.*", Ложь);
      	НайденныеФайлы  = Ждать Обещание;
      
      	ДобавляемыеФайлы = Новый Массив;
      	Для Каждого НайденныйФайл Из НайденныеФайлы Цикл
      		Описание      = Новый ОписаниеПередаваемогоФайла;
      		Описание.Имя  = НайденныйФайл.ПолноеИмя;
      		ДобавляемыеФайлы.Добавить(Описание);
      	КонецЦикла;
      
      	Обещание = ПоместитьФайлыНаСерверАсинч(
      		, , ДобавляемыеФайлы, ЭтотОбъект.УникальныйИдентификатор);
      	Результат = Ждать Обещание;
      
      КонецПроцедуры
      ```
      
      ---
      
      ## HTTP-методы (8.3.21+)
      
      Для объекта `HTTPСоединение` доступны асинхронные аналоги: `ВызватьHTTPМетодАсинч`, `ЗаписатьАсинч`, `ПолучитьАсинч` и др.
      
    • bsl-coding-standards.md 14.5 KB
      # Стандарты кода BSL
      
      Стандарты написания кода на платформе 1С:Предприятие.
      
      ---
      
      ## Комментарии
      
      **Не добавлять комментарии по умолчанию.** Уместны только когда добавляют ценность, которую нельзя выразить кодом:
      
      - Мотивация неочевидного решения (почему именно такой подход)
      - Нетривиальный алгоритм (суть, не пересказ кода)
      - Ограничения и граничные случаи
      - Технический долг (TODO, FIXME)
      
      **Запрещено:**
      - `// Получаем данные`, `// Проверяем условие` — дублируют код
      - Комментарии к каждой строке
      - Комментарий, дословно повторяющий имя функции
      
      ---
      
      ## Структура модулей
      
      Порядок областей в общем модуле:
      
      1. `#Область ПрограммныйИнтерфейс` — экспортные, доступны всем, документированы
      2. `#Область СлужебныйПрограммныйИнтерфейс` — экспортные, доступны внутри подсистемы
      3. `#Область СлужебныеПроцедурыИФункции` — неэкспортные
      
      Документация экспортных функций:
      
      ```bsl
      // Возвращает параметры области макета по имени.
      //
      // Параметры:
      //   Имя - Строка - имя области
      //
      // Возвращаемое значение:
      //   Структура - параметры области
      //
      Функция ПолучитьПараметрыОбласти(Имя) Экспорт
      ```
      
      ---
      
      ## Именование
      
      ### Переменные
      
      Не использовать венгерскую нотацию: `МассивКонтрагентов` → `Контрагенты`.
      Исключение: типы коллекций (`ТаблицаКлиентов`, `СписокДокументов`).
      
      Не использовать имена из глобального контекста 1С:
      `Документы`, `Справочники`, `РегистрыСведений`, `РегистрыНакопления`, `Метаданные`, `Константы`, `ПараметрыСеанса` и т.д.
      
      ```bsl
      // Плохо:
      Документы = Новый Массив;
      
      // Хорошо:
      СписокДокументов = Новый Массив;
      ```
      
      Не использовать ключевые слова BSL: `И`, `ИЛИ`, `НЕ`, `Если`, `Тогда`, `Для`, `Цикл`, `Процедура`, `Функция`, `Попытка`, `Истина`, `Ложь`, `Неопределено`, `Null` и т.д.
      
      ### Процедуры и функции
      
      - Процедуры (действие): `ЗаполнитьТаблицу()`, `ОбновитьДанные()`
      - Функции-предикаты: `ЭтоНовый()`, `ЕстьОшибки()`, `МожноИзменить()`
      - Функции (получение): `ПолучитьДанные()`, `НайтиКлиента()`, `ВычислитьСумму()`
      
      ---
      
      ## Запросы
      
      ### Русскоязычный синтаксис
      
      В коде — только русскоязычные ключевые слова языка запросов:
      
      | Неправильно | Правильно |
      |---|---|
      | `SELECT` | `ВЫБРАТЬ` |
      | `FROM` | `ИЗ` |
      | `WHERE` | `ГДЕ` |
      | `INNER JOIN` | `ВНУТРЕННЕЕ СОЕДИНЕНИЕ` |
      | `LEFT JOIN` | `ЛЕВОЕ СОЕДИНЕНИЕ` |
      | `GROUP BY` | `СГРУППИРОВАТЬ ПО` |
      | `ORDER BY` | `УПОРЯДОЧИТЬ ПО` |
      | `HAVING` | `ИМЕЮЩИЕ` |
      | `UNION ALL` | `ОБЪЕДИНИТЬ ВСЕ` |
      | `INTO` | `ПОМЕСТИТЬ` |
      | `INDEX BY` | `ИНДЕКСИРОВАТЬ ПО` |
      | `AS` | `КАК` |
      | `AND` / `OR` / `NOT` | `И` / `ИЛИ` / `НЕ` |
      | `IN` | `В` |
      | `IS NULL` | `ЕСТЬ NULL` |
      | `ISNULL()` | `ЕСТЬNULL()` |
      | `DISTINCT` | `РАЗЛИЧНЫЕ` |
      | `TOP` | `ПЕРВЫЕ` |
      | `CASE WHEN THEN END` | `ВЫБОР КОГДА ТОГДА КОНЕЦ` |
      
      ### Форматирование запроса
      
      ```bsl
      Запрос.Текст =
      "ВЫБРАТЬ
      |	Таблица.Поле КАК Поле,
      |	Таблица.Поле2 КАК Поле2
      |ИЗ
      |	Справочник.Таблица КАК Таблица
      |ГДЕ
      |	Таблица.Поле = &Параметр";
      ```
      
      ### Промежуточная переменная для результата
      
      ```bsl
      // Плохо:
      Выборка = Запрос.Выполнить().Выбрать();
      
      // Хорошо:
      РезультатЗапроса = Запрос.Выполнить();
      Выборка = РезультатЗапроса.Выбрать();
      ```
      
      ### Псевдонимы
      
      Псевдоним источника задаётся через `КАК` и должен быть информативным: однобуквенные (`Р`, `Т`) не годятся.
      
      **Псевдоним источника не должен совпадать ни с одним именем колонки, видимым в том же запросе** — ни с полем самой таблицы, ни с колонкой временной таблицы пакета. Совпадение делает обращение `Псевдоним.Поле` двояким: платформа читает его и как поле источника, и как разыменование одноимённой колонки, — и отказывается выполнять запрос с ошибкой «Неоднозначное поле».
      
      ```bsl
      // Плохо — колонка ВТ_Связи называется так же, как источник соседнего запроса:
      |ВЫБРАТЬ
      |	Перемещение.Ссылка КАК Перемещение      // колонка ВТ_Связи
      |ПОМЕСТИТЬ ВТ_Связи
      |ИЗ
      |	Документ.ПеремещениеТоваров КАК Перемещение
      |;
      |ВЫБРАТЬ
      |	Связи.Перемещение КАК Перемещение
      |ИЗ
      |	ВТ_Связи КАК Связи
      |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров КАК Перемещение
      |		ПО Связи.Перемещение = Перемещение.Ссылка   // «Неоднозначное поле "Перемещение.Ссылка"»
      
      // Хорошо — разведены имена источника и колонки:
      |		ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров КАК ДокументПеремещения
      |		ПО Связи.Перемещение = ДокументПеремещения.Ссылка
      ```
      
      Дефект не ловится ни статическим анализатором, ни сборкой: текст запроса — строковый литерал. Проверяется эвристикой `qg:QRY-ALIAS-SHADOWS-FIELD` (`tools/query-lint.mjs`).
      
      **Псевдонимы полей в неголовных ветках `ОБЪЕДИНИТЬ [ВСЕ]` не требуются.** Имена колонок результата берутся из первой выборки; от остальных нужны только количество полей и совместимость типов. Замечание «здесь не хватает `КАК`» в такой ветке — ложная находка. А вот псевдоним источника, затеняющий имя колонки, дефектом остаётся и внутри неголовной ветки.
      
      ---
      
      ## Пользовательские тексты (НСтр)
      
      Все тексты, видимые пользователю — только через `НСтр`:
      
      ```bsl
      // Хорошо:
      Текст = НСтр("ru = 'Документ не проведён.'");
      
      // Плохо — голый литерал:
      Текст = "Документ не проведён.";
      ```
      
      Подстановки — через `СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку` или `СтрШаблон`:
      
      ```bsl
      Текст = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
      	НСтр("ru = 'Документ %1 не проведён.'"), Ссылка);
      ```
      
      **Исключение:** строки технического назначения (ключи структур, имена полей запроса, тексты для журнала) в `НСтр` не оборачиваются.
      
      ---
      
      ## Сообщения и исключения
      
      ### Вывод сообщений
      
      Не использовать `Сообщить()`. Использовать БСП-методы:
      
      ```bsl
      // На сервере:
      ОбщегоНазначения.СообщитьПользователю(
      	НСтр("ru = 'Ошибка при обработке данных.'"), , "Объект.Реквизит", , Отказ);
      ```
      
      ### Обработка исключений
      
      Не глотать исключения молча. Логировать и пробрасывать:
      
      ```bsl
      Попытка
      	// Опасная операция
      Исключение
      	ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
      	ЗаписьЖурналаРегистрации(
      		"Модуль.Функция",
      		УровеньЖурналаРегистрации.Ошибка, , , ТекстОшибки);
      	ВызватьИсключение;
      КонецПопытки;
      ```
      
      Не использовать `Попытка...Исключение` для штатных операций с БД.
      
      ---
      
      ## Транзакции
      
      `НачатьТранзакцию()` — **строго до** `Попытка`, не внутри:
      
      ```bsl
      НачатьТранзакцию();
      Попытка
      	// ...
      	ЗафиксироватьТранзакцию();
      Исключение
      	ОтменитьТранзакцию();
      	ЗаписьЖурналаРегистрации(
      		"Модуль.Функция",
      		УровеньЖурналаРегистрации.Ошибка, , ,
      		ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
      	ВызватьИсключение;
      КонецПопытки;
      ```
      
      Правила:
      - `ЗафиксироватьТранзакцию()` — последний оператор перед `Исключение`
      - `ОтменитьТранзакцию()` — первый оператор в блоке `Исключение`
      - Между `НачатьТранзакцию()` и `Попытка` не должно быть другого кода
      
      ---
      
      ## Даты
      
      Использовать `ТекущаяДатаСеанса()`, не `ТекущаяДата()`. На клиенте — `ОбщегоНазначенияКлиент.ДатаСеанса()`.
      
      ---
      
      ## Конкурентность и блокировки
      
      Предпочитать управляемые блокировки:
      
      ```bsl
      Блокировка = Новый БлокировкаДанных;
      ЭлементБлокировки = Блокировка.Добавить("Справочник.Клиенты");
      ЭлементБлокировки.УстановитьЗначение("Ссылка", СсылкаКлиента);
      Блокировка.Заблокировать();
      ```
      
      ---
      
      ## Форматирование
      
      ### Отступы — только табы
      
      Все отступы — символом табуляции, не пробелами. Один уровень = один таб.
      
      ```bsl
      // Текст запроса — таб после "|":
      Запрос.Текст =
      "ВЫБРАТЬ
      |	Таблица.Поле КАК Поле
      |ИЗ
      |	Справочник.Таблица КАК Таблица";
      ```
      
      ### Ограничение строки — 120 символов
      
      ### Выравнивание в однотипных блоках — обязательно
      
      В блоках из идущих подряд однотипных строк выравнивать по самому длинному имени:
      
      ```bsl
      // Хорошо:
      Настройка.Наименование		= "Формирование уведомлений";
      Настройка.ИмяДляРазработчика	= "ФормированиеУведомлений";
      Настройка.СписокЗначений	= Ложь;
      ```
      
      ### `Если` в одну строку запрещён
      
      `Если` всегда оформляется как многострочный блок. Тернарный оператор `?(Условие, Значение1, Значение2)` разрешён.
      
      ---
      
      ## Проверка пустоты коллекций
      
      Использовать `ЗначениеЗаполнено()` вместо `.Количество() = 0`:
      
      ```bsl
      // Плохо:
      Если МассивДанных.Количество() = 0 Тогда ...
      
      // Хорошо:
      Если НЕ ЗначениеЗаполнено(МассивДанных) Тогда ...
      ```
      
      ---
      
      ## Привилегированный режим
      
      Использовать минимально и всегда выключать:
      
      ```bsl
      УстановитьПривилегированныйРежим(Истина);
      // Операция, требующая полных прав
      УстановитьПривилегированныйРежим(Ложь);
      ```
      
      ---
      
      ## Переиспользование кода
      
      Перед написанием нового кода проверять общие модули и модули менеджеров на наличие экспортных методов. Не дублировать логику, уже реализованную в конфигурации или БСП.
      
      ---
      
      ## Чистота diff
      
      При редактировании существующего кода каждая строка в diff должна быть обоснована задачей. Запрещено:
      
      - Переформатировать строки, которые не менялись функционально
      - Переименовывать переменные/функции в неизменённом коде
      - Добавлять комментарии к коду, который не менялся
      - Добавлять логирование без необходимости по задаче
      
    • bsl-form-module-rules.md 11.1 KB
      # Правила модулей форм 1С
      
      Клиент-серверное разделение, директивы компиляции и работа с данными формы.
      
      ---
      
      ## Директивы компиляции
      
      | Директива | Контекст | Когда использовать |
      |---|---|---|
      | `&НаКлиенте` | Клиент | Взаимодействие с UI, обработка ввода пользователя |
      | `&НаСервере` | Сервер с контекстом формы | Когда нужно изменять реквизиты/элементы формы |
      | `&НаСервереБезКонтекста` | Сервер без контекста | **Предпочтительно** для операций с данными — снижает объём передаваемых данных |
      | `&НаКлиентеНаСервереБезКонтекста` | Клиент и сервер | Общие утилитарные функции |
      
      **Правила выбора директивы:**
      
      - Предпочитать `&НаСервереБезКонтекста`. Если серверному методу не нужны реквизиты/элементы формы (он только получает или обрабатывает данные) — он **обязан** быть `&НаСервереБезКонтекста`, а нужные данные передаются параметрами. Передача контекста формы на сервер без необходимости — антипаттерн.
      - Если логика чисто вычислительная над переданными данными и не обращается к серверным объектам (БД, регистры) — делать метод `&НаКлиентеНаСервереБезКонтекста`, передавая `Форма` или `Объект` параметром. Один метод доступен и с клиента, и с сервера — дублирование не нужно.
      - `&НаСервере` (с контекстом) — только когда метод действительно изменяет реквизиты или элементы формы.
      
      ### Именование серверных методов
      
      Все методы с серверной директивой (`&НаСервере`, `&НаСервереБезКонтекста`) **обязаны** иметь окончание `НаСервере` в имени — чтобы на клиенте по имени вызова было видно обращение к серверу.
      
      ```bsl
      // Хорошо:
      &НаСервереБезКонтекста
      Функция ДанныеДляОбработкиНаСервере(знач Параметры)
      
      &НаСервере
      Процедура ЗаполнитьТаблицуНаСервере()
      
      // Плохо — по имени не видно серверного вызова:
      &НаСервереБезКонтекста
      Функция ДанныеДляОбработки(знач Параметры)
      ```
      
      Методы `&НаКлиенте` и `&НаКлиентеНаСервереБезКонтекста` суффикс `НаСервере` не несут.
      
      ---
      
      ## Обращение к реквизитам и свойствам формы
      
      Все реквизиты и свойства формы адресуются через `ЭтотОбъект.`. Дело не только в читаемости: имена реквизитов формы лежат в области видимости модуля, поэтому голое присваивание такому имени — это запись в реквизит формы, а не объявление локальной переменной.
      
      **Исключения — только два:** `Объект` (основной реквизит формы объекта/документа) и `Элементы` (коллекция элементов формы) пишутся без префикса.
      
      ```bsl
      // Хорошо:
      ЭтотОбъект.НастройкиПечати = НовыеНастройкиПечати();
      Если ЭтотОбъект.Модифицированность Тогда
      	ЭтотОбъект.Закрыть();
      КонецЕсли;
      Объект.Дата = ТекущаяДатаСеанса();   // исключение
      Элементы.Список.Видимость = Ложь;    // исключение
      
      // Плохо — реквизит формы без ЭтотОбъект:
      НастройкиПечати = НовыеНастройкиПечати();   // пишет реквизит формы, хотя выглядит как локальная переменная
      ```
      
      > Синоним `ЭтаФорма` устарел — использовать `ЭтотОбъект`.
      
      ### Локальная переменная с именем реквизита формы
      
      Обратная сторона того же правила и единственная его форма, в которой ошибка молчит. Автор
      заводит, как ему кажется, локальную переменную:
      
      ```bsl
      &НаСервере
      Процедура ЗаполнитьПоказателиНаСервере()
      	Вид = ?(ЭтотОбъект.РазвёрнутыйРежим, "Полный", "Краткий");
      	// если у формы есть реквизит «Вид» — присвоен он, локальной переменной не появилось
      КонецПроцедуры
      ```
      
      Значение остаётся в данных формы после выхода из метода и уезжает на клиент. Если тип не
      совпал с объявленным типом реквизита, форма падает при следующем обращении к нему — в другом
      методе и на другом действии пользователя. Разрыв между местом отказа и местом причины и делает
      дефект дорогим: ищут его в том коде, который упал.
      
      **Симптом.** Форма работает, пока не выполнена конкретная серверная операция; после неё
      обращение к реквизиту падает на несоответствии типов или на «поле объекта не обнаружено».
      
      **Контр-сигналы.**
      
      - Методы без контекста формы (`&НаСервереБезКонтекста`, `&НаКлиентеНаСервереБезКонтекста`)
        реквизитов формы не видят — там совпадение имён безвредно, переменная действительно локальная.
      - Намеренная запись в реквизит — законная и частая операция. Находка не «присваивание имени
        реквизита», а присваивание, где по смыслу метода нужна была локальная переменная: имя не
        отвечает назначению реквизита, значение дальше как реквизит не используется, тип с
        объявленным не совпадает.
      
      **Чем проверяется.** По тексту модуля признак не виден: без списка реквизитов формы локальная
      переменная и реквизит выглядят одинаково. Состав реквизитов брать из `Form.xml` той же формы —
      он лежит рядом с модулем, в `Forms/<ИмяФормы>/Ext/`. Если файл в изменение не попал и состав
      реквизитов недоступен, признак не выпускается, а отмечается как непроверенный.
      
      ---
      
      ## Клиент-серверное взаимодействие
      
      - Не вызывать серверные методы в циклах на клиенте
      - Собирать все нужные данные и передавать одним вызовом на сервер
      
      ```bsl
      // Плохо: множественные серверные вызовы
      &НаКлиенте
      Процедура Обработать(Команда)
      	Данные1 = ПолучитьДанные1НаСервере();
      	Данные2 = ПолучитьДанные2НаСервере();
      КонецПроцедуры
      
      // Хорошо:
      &НаКлиенте
      Процедура Обработать(Команда)
      	ВсеДанные = ПолучитьВсеДанныеНаСервере();
      КонецПроцедуры
      
      &НаСервереБезКонтекста
      Функция ПолучитьВсеДанныеНаСервере()
      	Результат = Новый Структура;
      	Результат.Вставить("Данные1", ПолучитьДанные1());
      	Результат.Вставить("Данные2", ПолучитьДанные2());
      	Возврат Результат;
      КонецФункции
      ```
      
      ### Вычислительный метод — `&НаКлиентеНаСервереБезКонтекста`
      
      Если метод только вычисляет над данными формы и не обращается к серверным объектам — сделать его `&НаКлиентеНаСервереБезКонтекста` и передать `Форма` или `Объект` параметром:
      
      ```bsl
      &НаКлиентеНаСервереБезКонтекста
      Функция ИтогоПоСтрокам(знач Форма)
      	Итого = 0;
      	Для Каждого Строка Из Форма.Объект.Товары Цикл
      		Итого = Итого + Строка.Сумма;
      	КонецЦикла;
      	Возврат Итого;
      КонецФункции
      ```
      
      ---
      
      ## Данные формы
      
      Использовать `ДанныеФормыВЗначение()` / `ЗначениеВДанныеФормы()` для конвертации между данными формы и объектами.
      
      Реквизиты формы — это **не** то же самое, что реквизиты объекта: это представления, специфичные для формы.
      
      ---
      
      ## Асинхронные вызовы
      
      Ключевые ограничения:
      - Обработчики событий формы (`ПриОткрытии`, `ПередЗакрытием` и т.д.) нельзя объявлять как `Асинх` — параметр `Отказ` будет проигнорирован
      - Для асинхронных операций при открытии/закрытии — вызывать отдельную `Асинх`-процедуру из обычного обработчика
      - Обработчики команд (`&НаКлиенте`) — можно безопасно делать `Асинх`
      
      Полные паттерны асинхронности — см. `bsl-async.md`.
      
    • bsl-query-optimization.md 15 KB
      # Оптимизация запросов 1С
      
      Советы по оптимизации запросов 1С (стандарты ИТС).
      
      Антипаттерны производительности (запрос в цикле, реквизиты через точку) — см.
      `references/catalog/INDEX.md`.
      Синтаксис и виртуальные таблицы — `bsl-query-reference.md`.
      
      ---
      
      ## Составные типы — ВЫРАЗИТЬ
      
      Обращение через точку к полям составного типа (например, Регистратор) создаёт соединения со **всеми** возможными типами. Использовать `ВЫРАЗИТЬ` для указания конкретного типа.
      
      ```bsl
      // Плохо: JOIN со всеми типами регистраторов
      "ВЫБРАТЬ
      |	ТоварыНаСкладах.Регистратор.Дата КАК ДатаДокумента
      |ИЗ
      |	РегистрНакопления.ТоварыНаСкладах КАК ТоварыНаСкладах"
      
      // Хорошо: конкретный тип
      "ВЫБРАТЬ
      |	ВЫРАЗИТЬ(ТоварыНаСкладах.Регистратор КАК Документ.ПоступлениеТоваровУслуг).Дата КАК ДатаДокумента
      |ИЗ
      |	РегистрНакопления.ТоварыНаСкладах КАК ТоварыНаСкладах"
      
      // Для нескольких типов — ВЫБОР/КОГДА
      "ВЫБРАТЬ
      |	ВЫБОР
      |	    КОГДА ТоварыНаСкладах.Регистратор ССЫЛКА Документ.ПоступлениеТоваровУслуг
      |	        ТОГДА ВЫРАЗИТЬ(ТоварыНаСкладах.Регистратор КАК Документ.ПоступлениеТоваровУслуг).Дата
      |	    КОГДА ТоварыНаСкладах.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
      |	        ТОГДА ВЫРАЗИТЬ(ТоварыНаСкладах.Регистратор КАК Документ.РеализацияТоваровУслуг).Дата
      |	КОНЕЦ КАК ДатаДокумента
      |ИЗ
      |	РегистрНакопления.ТоварыНаСкладах КАК ТоварыНаСкладах"
      ```
      
      ---
      
      ## ПРЕДСТАВЛЕНИЕ вместо .Наименование
      
      Когда нужно только текстовое представление ссылки, `ПРЕДСТАВЛЕНИЕ()` избегает лишнего соединения.
      
      ```bsl
      // Плохо: создаёт дополнительный JOIN к Справочник.Склады
      "ВЫБРАТЬ
      |	ТоварыНаСкладах.Склад.Наименование КАК Склад
      |ИЗ
      |	РегистрНакопления.ТоварыНаСкладах КАК ТоварыНаСкладах"
      
      // Хорошо:
      "ВЫБРАТЬ
      |	ПРЕДСТАВЛЕНИЕ(ТоварыНаСкладах.Склад) КАК Склад
      |ИЗ
      |	РегистрНакопления.ТоварыНаСкладах КАК ТоварыНаСкладах"
      ```
      
      ---
      
      ## Соединение с подзапросом → временные таблицы
      
      Никогда не использовать подзапросы в соединениях. Выносить во временную таблицу с `ИНДЕКСИРОВАТЬ ПО`.
      
      ```bsl
      // Плохо:
      "ВЫБРАТЬ ...
      |ИЗ
      |	Документ.Заказ КАК Заказы
      |	    ЛЕВОЕ СОЕДИНЕНИЕ (
      |	        ВЫБРАТЬ Товары.Заказ, СУММА(Товары.Сумма) КАК Сумма
      |	        ИЗ Документ.Заказ.Товары КАК Товары
      |	        СГРУППИРОВАТЬ ПО Товары.Заказ
      |	    ) КАК ИтогиТоваров
      |	    ПО Заказы.Ссылка = ИтогиТоваров.Заказ"
      
      // Хорошо:
      "ВЫБРАТЬ
      |	Товары.Ссылка КАК Заказ,
      |	СУММА(Товары.Сумма) КАК Сумма
      |ПОМЕСТИТЬ ИтогиТоваров
      |ИЗ
      |	Документ.Заказ.Товары КАК Товары
      |СГРУППИРОВАТЬ ПО
      |	Товары.Ссылка
      |ИНДЕКСИРОВАТЬ ПО
      |	Заказ
      |;
      |ВЫБРАТЬ ...
      |ИЗ
      |	Документ.Заказ КАК Заказы
      |	    ЛЕВОЕ СОЕДИНЕНИЕ ИтогиТоваров КАК ИтогиТоваров
      |	    ПО Заказы.Ссылка = ИтогиТоваров.Заказ"
      ```
      
      ---
      
      ## Соединение с виртуальными таблицами → временные таблицы
      
      Результат виртуальных таблиц (Остатки, Обороты и т.д.) выносить во временную таблицу перед соединением.
      
      ```bsl
      // Медленно:
      "ВЫБРАТЬ ...
      |ИЗ
      |	Справочник.Номенклатура КАК Номенклатура
      |	    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата,) КАК Остатки
      |	    ПО Номенклатура.Ссылка = Остатки.Номенклатура"
      
      // Хорошо:
      "ВЫБРАТЬ
      |	Остатки.Номенклатура КАК Номенклатура,
      |	Остатки.КоличествоОстаток КАК Остаток
      |ПОМЕСТИТЬ ВТОстатки
      |ИЗ
      |	РегистрНакопления.ТоварыНаСкладах.Остатки(&Дата,) КАК Остатки
      |ИНДЕКСИРОВАТЬ ПО
      |	Номенклатура
      |;
      |ВЫБРАТЬ ...
      |ИЗ
      |	Справочник.Номенклатура КАК Номенклатура
      |	    ЛЕВОЕ СОЕДИНЕНИЕ ВТОстатки КАК Остатки
      |	    ПО Номенклатура.Ссылка = Остатки.Номенклатура"
      ```
      
      ---
      
      ## ИЛИ в условиях → ОБЪЕДИНИТЬ ВСЕ
      
      `ИЛИ` в `ГДЕ` мешает использованию индексов. Разбивать на отдельные запросы через `ОБЪЕДИНИТЬ ВСЕ`.
      
      ```bsl
      // Плохо: ИЛИ препятствует индексу
      "ВЫБРАТЬ Товары.Ссылка
      |ИЗ
      |	Справочник.Номенклатура КАК Товары
      |ГДЕ
      |	Товары.Артикул = &Артикул
      |	ИЛИ Товары.Код = &Код"
      
      // Хорошо:
      "ВЫБРАТЬ Товары.Ссылка
      |ИЗ
      |	Справочник.Номенклатура КАК Товары
      |ГДЕ
      |	Товары.Артикул = &Артикул
      |
      |ОБЪЕДИНИТЬ ВСЕ
      |
      |ВЫБРАТЬ Товары.Ссылка
      |ИЗ
      |	Справочник.Номенклатура КАК Товары
      |ГДЕ
      |	Товары.Код = &Код"
      ```
      
      ---
      
      ## ОБЪЕДИНИТЬ vs ОБЪЕДИНИТЬ ВСЕ
      
      `ОБЪЕДИНИТЬ` выполняет группировку для удаления дубликатов. Если дубликатов не ожидается — использовать `ОБЪЕДИНИТЬ ВСЕ`.
      
      ---
      
      ## Выравнивание по индексам
      
      Условия запроса должны соответствовать доступным индексам.
      
      **Требования к индексу:**
      
      1. Индекс должен содержать **все поля** из условия
      2. Поля должны быть **в начале** индекса
      3. Поля должны идти **подряд** (без пропусков)
      
      ```bsl
      // Дан индекс: (Организация, Контрагент, Дата)
      
      // Индекс используется — поля в начале
      "ГДЕ Организация = &Орг И Контрагент = &Контр"
      
      // Индекс НЕ используется — пропущено первое поле
      "ГДЕ Контрагент = &Контр И Дата = &Дата"
      ```
      
      Для часто фильтруемых реквизитов: `Индексировать` = `Индексировать с доп. упорядочиванием`.
      Для временных таблиц в соединениях: `ИНДЕКСИРОВАТЬ ПО`.
      
      ---
      
      ## Оптимизация СКД
      
      1. **Параметры в виртуальных таблицах** — передавать прямо в ВТ, не в отборы СКД:
      
         ```sql
         РегистрНакопления.Остатки.Остатки(&Период, Склад = &Склад)
         ```
      
      2. **Ограничение данных на уровне источника** — условия добавлять в текст запроса набора данных, не в настройки СКД:
      
         ```sql
         ГДЕ Период >= &НачалоПериода
         ```
      
      3. **ЕСТЬNULL для внешних соединений** — предотвращает проблемы с NULL:
      
         ```sql
         ЕСТЬNULL(Остатки.Количество, 0) КАК Количество
         ```
      
      ---
      
      ## Основное и дополнительное условие
      
      Оптимизатор 1С делит условия ГДЕ на две категории:
      
      ### Основное условие (используется для поиска по индексу)
      
      - Определяет, какой диапазон индекса сканировать
      - Допустимые операторы: `=`, `>`, `<`, `>=`, `<=`, `ПОДОБНО`, `МЕЖДУ`, `В`
      - Соединяется с другими основными условиями только через `И`
      - **Запрещено:** `ИЛИ`, `ВЫБОР`, арифметические выражения, вызовы функций, `НЕ`
      
      ### Дополнительное условие (применяется после сканирования индекса)
      
      - Фильтрует строки одну за другой
      - Можно использовать любые операторы: `ИЛИ`, `ВЫБОР`, арифметику, функции
      
      ### Стратегия
      
      Строить ГДЕ по схеме: `<основное_условие> И <дополнительное_условие>`
      
      ```sql
      ГДЕ Организация = &Орг                                  // основное (индексируемое)
          И (Сумма > 1000 ИЛИ ПометкаУдаления = ИСТИНА)       // дополнительное
      ```
      
      ---
      
      ## Виртуальные таблицы регистра бухгалтерии — особенности
      
      ### Параметр Субконто ≠ значения субконто
      
      Параметр `Субконто` принимает **виды субконто** (ссылки из плана видов характеристик), а не значения. Для отбора по значениям субконто использовать параметр `Условие`:
      
      ```sql
      // Плохо — значение субконто в позиции параметра Субконто:
      РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, &Контрагент, )
      
      // Хорошо — вид субконто в Субконто, значение в Условии:
      РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, &ВидыСубконто, Субконто1 = &Контрагент)
      ```
      
      ### Счёт — в Условие, не в ГДЕ
      
      ```sql
      // Плохо — полный скан, затем фильтр:
      ВЫБРАТЬ * ИЗ РегистрБухгалтерии.Хозрасчетный.Остатки() КАК Ост
      ГДЕ Ост.Счет = &Счет
      
      // Хорошо — предфильтрация на уровне хранилища:
      ВЫБРАТЬ * ИЗ РегистрБухгалтерии.Хозрасчетный.Остатки(, , Счет = &Счет) КАК Ост
      ```
      
      Для включения субсчетов: `Счет В ИЕРАРХИИ(&Счет)`.
      
      ---
      
      ## Влияние RLS
      
      RLS добавляет неявные условия к каждому запросу. Если правила RLS содержат подзапросы или соединения, план запроса усложняется, и насколько именно вырастет время — предсказать нельзя: это зависит от объёма таблиц и от самих правил. Замеряй на своих данных, а не считай по общему правилу.
      
      **Рекомендации:**
      
      - Не допускать подзапросов и соединений в шаблонах RLS
      - При критичном замедлении из-за RLS — переписать с временными таблицами в привилегированном режиме
      
      ### РАЗРЕШЕННЫЕ — не оптимизация и не способ убрать ошибку доступа
      
      Ключевое слово ничего не ускоряет: оно молча отбрасывает строки, недоступные пользователю по
      RLS. Там, где результат влияет на расчёт или проведение, это меняет сам результат — один и тот
      же документ у пользователей с разными правами проводится по-разному, и расхождение не
      диагностируется ни ошибкой, ни логом. В таких запросах `РАЗРЕШЕННЫЕ` применять нельзя
      ([#std415](https://v8std.ru/std/415/)): нехватка прав закрывается выдачей права на чтение либо
      явным прерыванием операции с сообщением.
      
      **Контр-сигнал.** Законная форма — запрос, скрытые строки которого не участвуют в бизнес-логике:
      подбор, справочный список, вспомогательное представление. Само по себе наличие `РАЗРЕШЕННЫЕ`
      находкой не является; находка — `РАЗРЕШЕННЫЕ` в запросе, чей результат уходит в расчёт,
      движения или проведение.
      
      **Симптом, который часто чинят неправильно:** запрос работает под администратором и падает у
      обычного пользователя с «Недостаточно прав». Дописать `РАЗРЕШЕННЫЕ` — не починка, а замена явной
      ошибки тихо неверным результатом. Сначала выяснить, нужны ли скрываемые строки расчёту.
      
      Действие ключевого слова ограничено одним запросом: в пакете его указывают в том пакете, где
      оно нужно, а пакет, читающий только временные таблицы, под правило не подпадает.
      
    • bsl-query-reference.md 28.4 KB
      # Справочник языка запросов 1С
      
      Синтаксис, источники данных, виртуальные таблицы, соединения, временные таблицы, типовые паттерны.
      
      Оптимизация и производительность — `bsl-query-optimization.md`
      
      ---
      
      ## Структура запроса
      
      ```sql
      ВЫБРАТЬ [РАЗРЕШЕННЫЕ] [РАЗЛИЧНЫЕ] [ПЕРВЫЕ <N>]
          <список полей>
      ИЗ
          <источники данных>
      [ГДЕ <условие>]
      [СГРУППИРОВАТЬ ПО <поля>]
      [ИМЕЮЩИЕ <условие>]
      
      [ОБЪЕДИНИТЬ [ВСЕ]
      ВЫБРАТЬ ...]
      
      [УПОРЯДОЧИТЬ ПО <поля> [ВОЗР | УБЫВ]]
      [АВТОУПОРЯДОЧИВАНИЕ]
      
      [ИТОГИ <агрегаты> ПО [ОБЩИЕ] <контрольные точки>]
      ```
      
      | Ключевое слово | Значение |
      |----------------|----------|
      | `ВЫБРАТЬ` | SELECT — обязательно |
      | `РАЗРЕШЕННЫЕ` | Отбросить недоступные пользователю строки вместо ошибки доступа. Применимость ограничена — #std415, см. `bsl-query-optimization.md` |
      | `РАЗЛИЧНЫЕ` | DISTINCT |
      | `ПЕРВЫЕ N` | Вернуть только первые N строк (аналог LIMIT) |
      | `ГДЕ` | WHERE |
      | `СГРУППИРОВАТЬ ПО` | GROUP BY |
      | `ИМЕЮЩИЕ` | HAVING |
      | `ОБЪЕДИНИТЬ [ВСЕ]` | UNION [ALL] |
      | `УПОРЯДОЧИТЬ ПО` | ORDER BY. `ВОЗР` = ASC, `УБЫВ` = DESC |
      | `ИТОГИ` | TOTALS |
      
      Минимальный пример:
      
      ```sql
      ВЫБРАТЬ
          Ссылка,
          Наименование
      ИЗ
          Справочник.Контрагенты
      ГДЕ
          НЕ ПометкаУдаления
      ```
      
      ---
      
      ## Источники данных — имена таблиц
      
      ### Реальные таблицы
      
      | Тип объекта | Шаблон | Пример |
      |-------------|--------|--------|
      | Справочник | `Справочник.<Имя>` | `Справочник.Номенклатура` |
      | Документ | `Документ.<Имя>` | `Документ.РеализацияТоваровУслуг` |
      | Табличная часть документа | `Документ.<Имя>.<ТЧ>` | `Документ.РеализацияТоваровУслуг.Товары` |
      | Регистр накопления | `РегистрНакопления.<Имя>` | `РегистрНакопления.ОстаткиТоваров` |
      | Регистр сведений | `РегистрСведений.<Имя>` | `РегистрСведений.КурсыВалют` |
      | Регистр бухгалтерии | `РегистрБухгалтерии.<Имя>` | `РегистрБухгалтерии.Хозрасчетный` |
      | План счетов | `ПланСчетов.<Имя>` | `ПланСчетов.Хозрасчетный` |
      | План видов характеристик | `ПланВидовХарактеристик.<Имя>` | `ПланВидовХарактеристик.ВидыСубконто` |
      
      ### Значения перечислений и предопределённые элементы
      
      ```sql
      -- Значение перечисления:
      ЗНАЧЕНИЕ(Перечисление.ТипыЦен.Оптовая)
      
      -- Предопределённый элемент:
      ЗНАЧЕНИЕ(Справочник.Валюты.USD)
      
      -- Пустая ссылка:
      ЗНАЧЕНИЕ(Справочник.Контрагенты.ПустаяСсылка)
      ```
      
      ---
      
      ## Виртуальные таблицы
      
      Вычисляются на лету из данных регистра. **Всегда передавать условия отбора параметрами ВТ, а не в ГДЕ** — критично для производительности.
      
      ### Регистр накопления
      
      ```sql
      -- Текущие остатки (без даты = на сейчас):
      РегистрНакопления.ОстаткиТоваров.Остатки(, Номенклатура = &Ном)
      
      -- Остатки на дату:
      РегистрНакопления.ОстаткиТоваров.Остатки(&Дата, Склад = &Склад)
      
      -- Обороты за период:
      РегистрНакопления.ОстаткиТоваров.Обороты(&НачДата, &КонДата, , Номенклатура = &Ном)
      
      -- Остатки и обороты:
      РегистрНакопления.ОстаткиТоваров.ОстаткиИОбороты(&НачДата, &КонДата, , , Номенклатура = &Ном)
      ```
      
      **Суффиксы ресурсов регистра накопления:**
      
      | Виртуальная таблица | Суффикс | Пример |
      |---------------------|---------|--------|
      | `.Остатки` | `<Ресурс>Остаток` | `КоличествоОстаток` |
      | `.Обороты` | `<Ресурс>Оборот` | `КоличествоОборот` |
      | `.ОстаткиИОбороты` | `<Ресурс>НачальныйОстаток` | `КоличествоНачальныйОстаток` |
      | `.ОстаткиИОбороты` | `<Ресурс>Приход` | `КоличествоПриход` |
      | `.ОстаткиИОбороты` | `<Ресурс>Расход` | `КоличествоРасход` |
      | `.ОстаткиИОбороты` | `<Ресурс>КонечныйОстаток` | `КоличествоКонечныйОстаток` |
      
      ### Регистр сведений
      
      ```sql
      -- Срез последних (актуальные значения):
      РегистрСведений.КурсыВалют.СрезПоследних(&Дата, Валюта = &Валюта)
      
      -- Срез первых:
      РегистрСведений.КурсыВалют.СрезПервых(&Дата, Валюта = &Валюта)
      ```
      
      ### Регистр бухгалтерии
      
      5 виртуальных таблиц: Остатки, Обороты, ОстаткиИОбороты, ОборотыДтКт, ДвиженияССубконто.
      
      ```sql
      -- Остатки на дату:
      РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, , Счет = &Счет)
      
      -- Обороты за период:
      РегистрБухгалтерии.Хозрасчетный.Обороты(&НачДата, &КонДата, Месяц, , Счет В (&Счета), )
      
      -- Остатки и обороты:
      РегистрБухгалтерии.Хозрасчетный.ОстаткиИОбороты(&НачДата, &КонДата, Авто, , , Счет = &Счет)
      ```
      
      **Параметры ВТ регистра бухгалтерии (позиционно):**
      
      | Виртуальная таблица | Параметры |
      |---------------------|-----------|
      | `.Остатки` | Период, Субконто, Условие |
      | `.Обороты` | НачалоПериода, КонецПериода, Периодичность, Субконто, Условие, КорСубконто |
      | `.ОстаткиИОбороты` | НачалоПериода, КонецПериода, Периодичность, МетодДополненияПериодов, Субконто, Условие |
      
      Значения `Периодичность`: Авто, Период, Год, Полугодие, Квартал, Месяц, Декада, Неделя, День, Регистратор, Запись.
      
      **Суффиксы ресурсов регистра бухгалтерии:**
      
      | Виртуальная таблица | Суффикс | Пример |
      |---------------------|---------|--------|
      | `.Остатки` | `<Ресурс>Остаток` | `СуммаОстаток` |
      | `.Остатки` | `<Ресурс>ОстатокДт/Кт` | `СуммаОстатокДт` |
      | `.Обороты` | `<Ресурс>ОборотДт/Кт` | `СуммаОборотДт` |
      | `.ОстаткиИОбороты` | `<Ресурс>НачальныйОстатокДт/Кт` | `СуммаНачальныйОстатокДт` |
      | `.ОстаткиИОбороты` | `<Ресурс>ОборотДт/Кт` | `СуммаОборотДт` |
      | `.ОстаткиИОбороты` | `<Ресурс>КонечныйОстатокДт/Кт` | `СуммаКонечныйОстатокДт` |
      
      ---
      
      ## Псевдонимы таблиц
      
      ```sql
      Справочник.Номенклатура КАК Товары
      ```
      
      Псевдонимы — всегда **информативные**. Нельзя: однобуквенные (`Р`, `Т`).
      
      Псевдоним источника **не должен совпадать ни с одним именем колонки, видимым в этом запросе**: ни с полем самой таблицы, ни с колонкой временной таблицы пакета. Иначе `Псевдоним.Поле` читается двояко — как поле источника и как разыменование одноимённой колонки:
      
      ```sql
      -- ВТ_Связи содержит колонку «Перемещение», а источник назван так же:
      ВЫБРАТЬ
          Связи.Перемещение КАК Перемещение
      ИЗ
          ВТ_Связи КАК Связи
              ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров КАК Перемещение
              ПО Связи.Перемещение = Перемещение.Ссылка
      -- {(N, M)}: Неоднозначное поле "Перемещение.Ссылка"
      ```
      
      Лечится переименованием источника (`КАК ДокументПеремещения`). Ошибка не синтаксическая, а разбора: до выполнения запроса её не видит ни один инструмент, кроме платформы. Эвристика — `qg:QRY-ALIAS-SHADOWS-FIELD`.
      
      Вторая форма того же дефекта — псевдоним источника-табличной-части, равный имени самой ТЧ, когда владелец соединён в той же ветке:
      
      ```sdbl
      ИЗ
          Документ.РасходныйОрдер.Строки КАК Строки
              ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РасходныйОрдер КАК Ордер
              ПО Строки.Ссылка = Ордер.Ссылка
      -- {(N, M)}: Неоднозначное поле "Строки.Ссылка"
      ```
      
      Табличная часть — поле объектной таблицы: язык умеет выбирать вложенные таблицы (`Ордер.Строки.(Номенклатура, Количество)`). Пока владелец в ветке, имя `Строки` занято, и псевдоним с ним сталкивается. Без владельца в ветке та же запись законна — это обычный и распространённый стиль. Эвристика — `qg:QRY-ALIAS-SHADOWS-NESTED-TABLE`.
      
      ### Служебное слово в псевдониме
      
      Отдельный дефект, не связанный с коллизией имён: псевдонимом взято слово самого языка запросов.
      
      ```sql
      ВЫБРАТЬ Заказы.Ссылка КАК Ссылка
      ИЗ Документ.ЗаказКлиента КАК Заказы
          ЛЕВОЕ СОЕДИНЕНИЕ ВТПервыеСтроки КАК Первые
          ПО Первые.Заказ = Заказы.Ссылка
      ```
      
      Такой запрос **не выполняется вовсе** — платформа отвергает его при разборе, целиком, а не в одной строке результата. При этом текст собирается, статический анализ молчит, сборка расширения проходит: литерал остаётся литералом до первого выполнения. Ветка кода с таким запросом не работает ни разу с момента написания, и обнаруживает это не инструмент, а человек, однажды выполнивший запрос руками. Лечится предметным уточнением имени: `Первые` → `ПервыеСтроки`, `Выбор` → `ВыборПользователя`.
      
      Списки ломающих слов **измерены на платформе 8.3.27**, а не выведены из синтаксиса: каждое из 66 кандидатов подставлялось псевдонимом в `ВЫБРАТЬ ПЕРВЫЕ 1 <Т>.Ссылка КАК <поле> ИЗ Справочник.Валюты КАК <Т>`, и запрос выполнялся.
      
      | Позиция | Что запрещено |
      |---|---|
      | Псевдоним поля и псевдоним источника | `ПЕРВЫЕ`, `РАЗЛИЧНЫЕ`, `РАЗРЕШЕННЫЕ`, `ПОМЕСТИТЬ`, `ИЗ`, `ГДЕ`, `ПО`, `ОБЩИЕ`, `ВНУТРЕННЕЕ`, `ВОЗР`, `УБЫВ`, `АВТОУПОРЯДОЧИВАНИЕ`, `ПЕРИОДАМИ`, `ДЛЯ`, `И`, `ИЛИ`, `НЕ`, `В`, `МЕЖДУ`, `ПОДОБНО`, `СПЕЦСИМВОЛ`, `ЕСТЬ`, `ВЫБОР`, `КОГДА`, `ТОГДА`, `ИНАЧЕ`, `ВЫРАЗИТЬ`, `ИСТИНА`, `ЛОЖЬ`, `ТОЛЬКО`, `КАК` |
      | Только псевдоним источника | `ССЫЛКА`, `ПРЕДСТАВЛЕНИЕ` — в позиции таблицы разбираются как обращение и как функция; в списке выборки (`Заказы.Ссылка КАК Ссылка`) законны и встречаются повсеместно |
      
      Остальные 33 кандидата псевдонимом работают — `ИТОГИ`, `ОБЪЕДИНИТЬ`, `СОЕДИНЕНИЕ`, `УПОРЯДОЧИТЬ`, `СГРУППИРОВАТЬ`, `ЗНАЧЕНИЕ`, `СУММА`, `КОЛИЧЕСТВО`, `ДАТА`, `ТИП` и другие. Список из синтаксиса дал бы шум: `ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ КАК Итоги` стоит в типовом менеджере обмена через универсальный формат и выполняется.
      
      Приведение типа псевдонимом не является: `ВЫРАЗИТЬ(Х КАК Документ.Заказ)` и `КАК Число(15,2)` законны, инструмент их пропускает. Эвристика — `qg:QRY-ALIAS-RESERVED-WORD`.
      
      ---
      
      ## Иерархические справочники
      
      ```sql
      -- Прямые потомки (один уровень):
      ГДЕ Родитель = &Родитель
      
      -- Все элементы группы и подгрупп:
      ГДЕ Ссылка В ИЕРАРХИИ (&Группа)
      
      -- Только листовые элементы:
      ГДЕ ЭтоГруппа = ЛОЖЬ
      ```
      
      **Не использовать:** `В ИЕРАРХИИ (ПустаяСсылка)` — крайне медленно.
      
      ---
      
      ## Поля и составные типы
      
      ### Разыменование полей составного типа
      
      Обращение через точку к полю составного типа создаёт неявные JOIN со **всеми** возможными таблицами. **Всегда сужать тип через ВЫРАЗИТЬ:**
      
      ```sql
      -- Плохо — JOIN ко всем возможным таблицам-регистраторам:
      ВЫБРАТЬ Регистратор.Номер ИЗ РегистрНакопления.ОстаткиТоваров
      
      -- Хорошо — один JOIN:
      ВЫБРАТЬ ВЫРАЗИТЬ(Регистратор КАК Документ.РеализацияТоваровУслуг).Номер
      ИЗ РегистрНакопления.ОстаткиТоваров
      ГДЕ Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
      ```
      
      ### ПРЕДСТАВЛЕНИЕ вместо .Наименование
      
      ```sql
      -- Плохо — лишний JOIN:
      ВЫБРАТЬ ТоварыНаСкладах.Склад.Наименование КАК Склад
      
      -- Хорошо:
      ВЫБРАТЬ ПРЕДСТАВЛЕНИЕ(ТоварыНаСкладах.Склад) КАК Склад
      ```
      
      ### Обработка NULL
      
      - `ЕСТЬNULL(поле, значение_по_умолчанию)` — для полей из LEFT JOIN
      - Проверка NULL: `ЕСТЬ NULL` / `НЕ ЕСТЬ NULL` (не `= NULL`)
      
      ---
      
      ## Условия ГДЕ
      
      ### Операторы
      
      | Оператор | Синтаксис |
      |----------|-----------|
      | Равно | `=` |
      | Не равно | `<>` |
      | И / ИЛИ / НЕ | `И`, `ИЛИ`, `НЕ` |
      | Диапазон | `МЕЖДУ X И Y` |
      | В списке | `В (зн1, зн2, ...)` |
      | В подзапросе | `В (ВЫБРАТЬ ...)` |
      | Проверка NULL | `ЕСТЬ NULL` / `НЕ ЕСТЬ NULL` |
      | Проверка типа | `ССЫЛКА Документ.Реализация...` |
      | Шаблон | `ПОДОБНО "шаблон"` |
      | ВЫБОР | `ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ` |
      
      **Приоритет:** `НЕ` > `И` > `ИЛИ`.
      
      ### Ключевые правила
      
      - **ИЛИ снижает использование индекса.** Заменять на `В (...)` или разбивать через `ОБЪЕДИНИТЬ ВСЕ`
      - **ВЫБОР в ГДЕ:** только в дополнительных (неиндексируемых) условиях
      - **ПОДОБНО:** не должен начинаться с `%` или `_` в индексируемом условии
      
      ---
      
      ## Соединения
      
      ```sql
      ИЗ Документ.РеализацияТоваровУслуг КАК Док
          ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Конт
          ПО Док.Контрагент = Конт.Ссылка
      ```
      
      | Тип | Синтаксис |
      |-----|-----------|
      | Внутреннее | `ВНУТРЕННЕЕ СОЕДИНЕНИЕ ... ПО` |
      | Левое внешнее | `ЛЕВОЕ [ВНЕШНЕЕ] СОЕДИНЕНИЕ ... ПО` |
      | Правое внешнее | `ПРАВОЕ [ВНЕШНЕЕ] СОЕДИНЕНИЕ ... ПО` |
      | Полное внешнее | `ПОЛНОЕ [ВНЕШНЕЕ] СОЕДИНЕНИЕ ... ПО` |
      
      **Правила:**
      - **Никогда не использовать вложенные соединения** (JOIN подзапроса). Заменять временными таблицами
      - Для LEFT JOIN: индексировать поля соединения правой таблицы
      
      ---
      
      ## Группировка и итоги
      
      ```sql
      ВЫБРАТЬ
          Контрагент,
          СУММА(СуммаДокумента) КАК Итого,
          КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Ссылка) КАК КолДок
      ИЗ Документ.РеализацияТоваровУслуг
      СГРУППИРОВАТЬ ПО
          Контрагент
      ИМЕЮЩИЕ
          СУММА(СуммаДокумента) > 1000
      ```
      
      Агрегатные функции: `СУММА`, `СРЕДНЕЕ`, `МИНИМУМ`, `МАКСИМУМ`, `КОЛИЧЕСТВО`, `КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ...)`.
      
      Каждое неагрегируемое поле в ВЫБРАТЬ должно быть в СГРУППИРОВАТЬ ПО.
      
      ### ИТОГИ
      
      ```sql
      ИТОГИ СУММА(СуммаДокумента) ПО ОБЩИЕ, Контрагент
      ```
      
      Расширенный синтаксис с периодами:
      
      ```sql
      ИТОГИ
          СУММА(Количество), СУММА(Сумма)
      ПО
          ОБЩИЕ,
          Контрагент,
          ПЕРИОДАМИ(Дата, МЕСЯЦ, &НачДата, &КонДата)
      ```
      
      ---
      
      ## Временные таблицы
      
      ```sql
      -- Шаг 1: собрать элементы во временную таблицу
      ВЫБРАТЬ
          Ссылка КАК Номенклатура
      ПОМЕСТИТЬ ВТ_Товары
      ИЗ
          Справочник.Номенклатура
      ГДЕ
          Наименование ПОДОБНО &Маска
      ИНДЕКСИРОВАТЬ ПО
          Номенклатура
      ;
      -- Шаг 2: использовать ВТ
      ВЫБРАТЬ
          Ост.Номенклатура КАК Номенклатура,
          Ост.КоличествоОстаток КАК Остаток
      ИЗ
          РегистрНакопления.ОстаткиТоваров.Остатки(
              ,
              Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ ВТ_Товары)
          ) КАК Ост
      ```
      
      **Правила:**
      - Запросы в пакете разделяются `;`
      - `ПОМЕСТИТЬ <Имя>` создаёт ВТ
      - `ИНДЕКСИРОВАТЬ ПО <поле>` — добавлять для ВТ с >1000 строк, используемых в соединениях или `В`
      - Минимизировать объём данных и количество полей ВТ
      - Никогда не создавать/удалять ВТ в цикле
      
      ---
      
      ## Параметры запроса
      
      ```sql
      ВЫБРАТЬ * ИЗ Справочник.Контрагенты ГДЕ Наименование ПОДОБНО &Маска
      ```
      
      **Правила:**
      - Всегда использовать параметры — не конкатенировать строки в текст запроса
      - Значения перечислений — через `ЗНАЧЕНИЕ(...)` в тексте, не как параметр
      - Булево: `ИСТИНА` / `ЛОЖЬ` в тексте запроса, или `Истина`/`Ложь` как параметр
      
      **Таблица значений параметром — колонки типизировать.** Колонка, объявленная как
      `Новый ОписаниеТипов("Строка")` без `КвалификаторыСтроки`, имеет неограниченную длину.
      Помещённая во временную таблицу, она роняет запрос везде, где значения сравниваются между
      собой: `РАЗЛИЧНЫЕ`, `ОБЪЕДИНИТЬ` без `ВСЕ`, `СГРУППИРОВАТЬ ПО`, условие соединения, `ГДЕ`,
      `УПОРЯДОЧИТЬ ПО`, `ИНДЕКСИРОВАТЬ ПО` — «Нельзя сравнивать поля неограниченной длины»
      (#std432 п. 3.1). Квалификатор ставится при объявлении:
      `Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(N))`. Если колонка неограниченная
      законно (длинный пользовательский текст, XML, JSON — #std432 п. 2), длину назначает запрос
      при помещении в ВТ: `ВЫРАЗИТЬ(Т.Поле КАК СТРОКА(N))`; квалификатор в этом случае обрежет
      данные. Механически ловится половина: `qg:BSL-UNBOUNDED-STRING-COLUMN`
      (`tools/bsl-lint.mjs`) видит только `УстановитьПараметр` в том же модуле, а таблица, ушедшая
      параметром в чужой метод, остаётся за читателем (`qg:AI-16`).
      
      ---
      
      ## ПОДОБНО — шаблоны поиска
      
      | Символ | Значение |
      |--------|----------|
      | `%` | Любая строка (0+ символов) |
      | `_` | Ровно один символ |
      | `[abc]` | Один символ из набора |
      | `[^abc]` | Один символ НЕ из набора |
      | `[a-z]` | Один символ из диапазона |
      
      Символ экранирования:
      ```sql
      ГДЕ Код ПОДОБНО "%#_%" СПЕЦСИМВОЛ "#"
      ```
      
      **Производительность:** шаблон не должен начинаться с `%` или `_` — запрещает использование индекса.
      
      ---
      
      ## ССЫЛКА — проверка типа
      
      ```sql
      ГДЕ Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг
      ```
      
      Часто используется вместе с ВЫРАЗИТЬ для нескольких типов:
      
      ```sql
      ВЫБРАТЬ
          ВЫБОР
              КОГДА Регистратор ССЫЛКА Документ.Реализация
                  ТОГДА ВЫРАЗИТЬ(Регистратор КАК Документ.Реализация).Контрагент
              КОГДА Регистратор ССЫЛКА Документ.Поступление
                  ТОГДА ВЫРАЗИТЬ(Регистратор КАК Документ.Поступление).Контрагент
          КОНЕЦ КАК Контрагент
      ИЗ РегистрНакопления.ОстаткиТоваров
      ```
      
      ---
      
      ## ОБЪЕДИНИТЬ — что обязательно, а что нет
      
      Имена колонок результата берутся из **первой** выборки. От остальных веток требуются только количество полей и совместимость типов по позициям.
      
      ```sql
      -- Законно: во второй ветке псевдонимов нет, и они не нужны.
      ВЫБРАТЬ
          Док.Ссылка КАК Ссылка,
          Док.Дата КАК Дата
      ИЗ Документ.РеализацияТоваровУслуг КАК Док
      
      ОБЪЕДИНИТЬ ВСЕ
      
      ВЫБРАТЬ
          Ссылка,
          Дата
      ИЗ Документ.ВозвратТоваровОтКлиента
      ```
      
      | Признак в неголовной ветке | Дефект? |
      |---|---|
      | Нет псевдонимов полей | **Нет.** Имена берёт первая выборка |
      | Другой порядок полей при совпадающем количестве | Да — молча перемешивает данные |
      | Несовместимый тип поля по позиции | Да — ошибка выполнения |
      | Псевдоним источника затеняет имя колонки | Да — ошибка выполнения, см. «Псевдонимы таблиц» |
      
      Требование «дописать `КАК` в каждую ветку» — вкусовщина: находкой не является. Проверять здесь надо порядок, типы и затенение.
      
      ---
      
      ## ПУСТАЯТАБЛИЦА
      
      Используется в ОБЪЕДИНИТЬ, когда один запрос имеет ТЧ, а другой — нет:
      
      ```sql
      ВЫБРАТЬ Ссылка, Товары.(Номенклатура, Количество)
      ИЗ Документ.РеализацияТоваровУслуг
      
      ОБЪЕДИНИТЬ ВСЕ
      
      ВЫБРАТЬ Ссылка, ПУСТАЯТАБЛИЦА.(Номенклатура, Количество)
      ИЗ Документ.ВозвратТоваров
      ```
      
      ---
      
      ## Типовые паттерны
      
      ```sql
      -- Поиск по наименованию (нечёткий):
      ВЫБРАТЬ Ссылка, Наименование
      ИЗ Справочник.Контрагенты
      ГДЕ Наименование ПОДОБНО &Маска   -- параметр Маска = "%рога%"
      
      -- Последние N документов:
      ВЫБРАТЬ ПЕРВЫЕ 10
          Ссылка, Дата, Номер, СуммаДокумента
      ИЗ Документ.РеализацияТоваровУслуг
      УПОРЯДОЧИТЬ ПО Дата УБЫВ
      
      -- Текущие остатки:
      ВЫБРАТЬ Номенклатура, КоличествоОстаток
      ИЗ РегистрНакопления.ОстаткиТоваров.Остатки(, Номенклатура = &Ном) КАК Ост
      
      -- Срез последних регистра сведений:
      ВЫБРАТЬ Валюта, Курс
      ИЗ РегистрСведений.КурсыВалют.СрезПоследних(&Дата,) КАК Курсы
      
      -- Проверка существования:
      ВЫБРАТЬ ПЕРВЫЕ 1 Ссылка
      ИЗ Справочник.Контрагенты
      ГДЕ ИНН = &ИНН
      
      -- Исключить помеченные на удаление:
      ГДЕ НЕ ПометкаУдаления
      
      -- Получить структуру таблицы (без данных):
      ВЫБРАТЬ ПЕРВЫЕ 0 * ИЗ Справочник.Контрагенты
      ```
      
    • bsl-refactoring.md 7.1 KB
      # Правила рефакторинга BSL
      
      ---
      
      ## Подход
      
      1. **Top-down анализ** — сначала понять бизнес-логику: построить цепочку вызовов и проследить поток данных.
      2. **Bottom-up рефакторинг** — начать с низкоуровневых утилитарных функций.
      3. **Интеграция** — встроить обновлённые компоненты в высокоуровневые процедуры.
      
      ---
      
      ## Когда рефакторить
      
      | Критерий | Порог |
      |---|---|
      | Цикломатическая сложность | >15 (количество независимых путей выполнения) |
      | Длина функции | >200 строк |
      | Дублирование кода | 3+ повторения одного блока |
      | Уровень вложенности | >3 уровней Если/Цикл |
      | Количество параметров | >5 |
      | God-модуль | >2000 строк |
      
      Не рефакторить код, который не затронут текущей задачей (если не запрошено явно).
      
      ---
      
      ## Техники
      
      ### 1. Извлечение метода
      
      Выделить логически самостоятельный блок в отдельную процедуру/функцию:
      
      ```bsl
      // До:
      Процедура ОбработатьДокумент(Документ)
      	// ... 50 строк валидации ...
      	// ... 80 строк расчёта сумм ...
      	// ... 30 строк записи движений ...
      КонецПроцедуры
      
      // После:
      Процедура ОбработатьДокумент(Документ)
      	ПроверитьКорректность(Документ);
      	РассчитатьСуммы(Документ);
      	ЗаписатьДвижения(Документ);
      КонецПроцедуры
      ```
      
      ### 2. Guard clauses — ранний выход
      
      Заменить вложенные `Если` на проверки в начале с ранним выходом:
      
      ```bsl
      // Плохо: глубокая вложенность
      Если Условие1 Тогда
      	Если Условие2 Тогда
      		Если Условие3 Тогда
      			// Логика
      		КонецЕсли;
      	КонецЕсли;
      КонецЕсли;
      
      // Хорошо: ранний выход
      Если НЕ Условие1 Тогда
      	Возврат;
      КонецЕсли;
      Если НЕ Условие2 Тогда
      	Возврат;
      КонецЕсли;
      Если НЕ Условие3 Тогда
      	Возврат;
      КонецЕсли;
      // Логика
      ```
      
      Ранний выход в цикле через `Продолжить`:
      
      ```bsl
      // Плохо:
      Для Каждого Строка Из ТаблицаДанных Цикл
      	Если Строка.Активна Тогда
      		Если ЗначениеЗаполнено(Строка.Контрагент) Тогда
      			ОбработатьСтроку(Строка);
      		КонецЕсли;
      	КонецЕсли;
      КонецЦикла;
      
      // Хорошо:
      Для Каждого Строка Из ТаблицаДанных Цикл
      	Если НЕ Строка.Активна Тогда
      		Продолжить;
      	КонецЕсли;
      	Если НЕ ЗначениеЗаполнено(Строка.Контрагент) Тогда
      		Продолжить;
      	КонецЕсли;
      	ОбработатьСтроку(Строка);
      КонецЦикла;
      ```
      
      ### 3. Консолидация дублирующего кода
      
      Повторяющийся блок (3+ раз) — выносить в общий метод с параметрами.
      
      ### 4. Замена магических значений
      
      Выносить литералы в именованные переменные:
      
      ```bsl
      // До:
      Если ДнейПросрочки > 30 Тогда
      	Штраф = Сумма * 0.01;
      КонецЕсли;
      
      // После:
      МаксДнейБезШтрафа = 30;
      СтавкаШтрафа      = 0.01;
      
      Если ДнейПросрочки > МаксДнейБезШтрафа Тогда
      	Штраф = Сумма * СтавкаШтрафа;
      КонецЕсли;
      ```
      
      ### 5. Кеширование повторных вычислений
      
      Использовать `Соответствие` как кеш для дорогих операций в цикле — см.
      `references/catalog/BSL-NO-CACHE.md`.
      
      ### 6. Батчинг клиент-серверных переходов
      
      Собирать данные на клиенте, передавать одним вызовом — см.
      `references/catalog/BSL-MULTI-SERVER-CALLS.md`.
      
      ### 7. Декомпозиция при доработке существующего метода
      
      При добавлении нового блока логики в существующий метод — выделять его в отдельный изолированный метод, а не встраивать внутрь:
      
      ```bsl
      // Плохо: в существующую функцию добавляется новый блок
      Функция НаборДанных(знач СтрокаНабора, знач Данные)
      	// ... существующий код ...
      	// Новый блок расчётов (50 строк) — усложняет функцию
      	Для Каждого Строка Из ПланАгрегатов Цикл
      		// ...
      	КонецЦикла;
      КонецФункции
      
      // Хорошо: новая логика вынесена
      Функция НаборДанных(знач СтрокаНабора, знач Данные)
      	// ... существующий код ...
      	Итоги = РассчитатьИтоги(СтрокаНабора, Данные);
      КонецФункции
      
      Функция РассчитатьИтоги(знач СтрокаНабора, знач Данные)
      	Итоги = Новый Структура;
      	Если НЕ ЕстьРаботаДляНабора(СтрокаНабора) Тогда
      		Возврат Итоги;
      	КонецЕсли;
      	// ... расчёт ...
      	Возврат Итоги;
      КонецФункции
      ```
      
      Правила декомпозиции:
      - Новый блок логики в существующей функции → отдельный метод, вызываемый из исходной
      - Не добавлять в сигнатуру существующей функции новые флаги-переключатели — лучше новая функция рядом
      - Функцию, возвращающую результат, предпочитать процедуре с «out-параметром»
      
      ---
      
      ## Принципы
      
      - **Сохранять поведение** — рефакторинг не меняет что делает код, только как
      - **Атомарные шаги** — каждое изменение проверяемо отдельно
      - **Ясность важнее краткости** — не превращать понятный код в «умный»
      - **Не over-engineer** — не создавать абстракции для одноразовых операций
      
    • bsp-common-modules.md 17.1 KB
      # Общие модули БСП — справочник
      
      Краткий справочник по публичному API общих модулей БСП. Включены **только публичные методы** (`#Область ПрограммныйИнтерфейс`). Устаревшие и служебные — исключены.
      
      > **Не путать с проектными модулями `<prefix>_*`.** Вызов `ОбщегоНазначения.Метод(...)` без префикса проекта — это БСП из этого справочника. С префиксом проекта — проектный модуль.
      
      ---
      
      ## Какой модуль брать в каком контексте
      
      | Контекст | Подходящий суффикс |
      |---|---|
      | Серверная процедура / `&НаСервере` / `&НаСервереБезКонтекста` | `ОбщегоНазначения`, `СтроковыеФункции`, `Пользователи`, `ФайловаяСистема` |
      | Клиентская процедура (`&НаКлиенте`) | `ОбщегоНазначенияКлиент`, `СтроковыеФункцииКлиент`, `ПользователиКлиент`, `ФайловаяСистемаКлиент` |
      | Клиент-серверный модуль | `ОбщегоНазначенияКлиентСервер`, `СтроковыеФункцииКлиентСервер`, `ПользователиКлиентСервер` |
      | Прямой серверный вызов из клиента | `ОбщегоНазначенияВызовСервера` |
      
      **Правило выбора:** нужен и на клиенте, и на сервере — берём `*КлиентСервер`. Если только на одной стороне — соответствующий модуль.
      
      ---
      
      ## ОбщегоНазначения / *Клиент / *КлиентСервер
      
      ### Сообщения пользователю
      
      | Метод | Модуль |
      |---|---|
      | `СообщитьПользователю(...)` | `ОбщегоНазначения`, `ОбщегоНазначенияКлиент` |
      | `ДобавитьОшибкуПользователю(...)` | `ОбщегоНазначенияКлиентСервер` |
      | `СообщитьОшибкиПользователю(Ошибки, Отказ = Ложь)` | `ОбщегоНазначенияКлиентСервер` |
      
      Платформенный `Сообщить()` — **запрещено**.
      
      ### Чтение реквизитов объектов
      
      | Метод | Назначение |
      |---|---|
      | `ЗначениеРеквизитаОбъекта(Ссылка, ИмяРеквизита)` | Один реквизит одного объекта |
      | `ЗначенияРеквизитовОбъекта(Ссылка, Реквизиты)` | Несколько реквизитов одного объекта |
      | `ЗначениеРеквизитаОбъектов(МассивСсылок, ИмяРеквизита)` | Один реквизит для многих объектов |
      | `ЗначенияРеквизитовОбъектов(Ссылки, Реквизиты)` | Несколько реквизитов для многих объектов |
      
      > **Антипаттерн:** `Ссылка.Реквизит` — критический. Любое обращение к реквизиту через точку — через эти методы.
      
      ### Запись реквизитов
      
      - `УстановитьЗначениеРеквизита(Объект, ИмяРеквизита, Значение)`
      - `УстановитьЗначенияРеквизитов(Объект, Значения)`
      
      ### Предопределённые элементы
      
      - `ОбщегоНазначения.ПредопределенныйЭлемент(ПолноеИмяПредопределенного)` — серверный
      - `ОбщегоНазначенияКлиент.ПредопределенныйЭлемент(...)` — клиентский (кэшируется)
      
      Брать вместо `ПредопределенноеЗначение("...")` — обрабатывает удалённые предопределённые без ошибки.
      
      ### Ссылочные операции
      
      - `ЕстьСсылкиНаОбъект(СсылкаИлиМассивСсылок)`
      - `ЗаменитьСсылки(ПарыЗамен, ПараметрыЗамены = Неопределено)`
      - `МестаИспользования(НаборСсылок, АдресРезультата = "")`
      - `СсылкаСуществует(ПроверяемаяСсылка)`
      
      ### Метаданные
      
      Проверка типа объекта: `ОбщегоНазначения.Это*(ОбъектМетаданных)`:
      `ЭтоДокумент`, `ЭтоСправочник`, `ЭтоПеречисление`, `ЭтоРегистрСведений`, `ЭтоРегистрНакопления`, `ЭтоРегистрБухгалтерии` и т.д.
      
      Описания типов:
      - `ОписаниеТипаСтрока(ДлинаСтроки)`
      - `ОписаниеТипаЧисло(Разрядность, РазрядностьДробнойЧасти = 0)`
      - `ОписаниеТипаДата(ЧастиДаты)`
      
      ### Текущее окружение
      
      - `ЭтоWindowsКлиент()`, `ЭтоWindowsСервер()`, `ЭтоLinuxКлиент()`, `ЭтоLinuxСервер()`
      - `ЭтоВебКлиент()`, `ЭтоМобильныйКлиент()`
      - `ИнформационнаяБазаФайловая()`
      - `РазделениеВключено()`, `ДоступноИспользованиеРазделенныхДанных()`
      
      ### Подсистемы и общие модули
      
      - `ПодсистемаСуществует(ПолноеИмяПодсистемы)` — серверный и клиентский
      - `ОбщийМодуль(Имя)` — возвращает менеджер модуля по имени-строке
      
      ### Даты
      
      **Серверные:**
      - `УстановитьРабочуюДатуПользователя(НоваяРабочаяДата)`
      - `РабочаяДатаПользователя()`
      
      **Клиентские:**
      - `ОбщегоНазначенияКлиент.ДатаСеанса()` — серверное время с клиента
      
      > `ТекущаяДатаСеанса()` — платформенный, использовать на сервере. `ТекущаяДата()` — **запрещено**.
      
      ### Коллекции (КлиентСервер)
      
      - `ДополнитьМассив(МассивПриемник, МассивИсточник, ТолькоУникальныеЗначения = Ложь)`
      - `ДополнитьСтруктуру(Приемник, Источник, Заменять = Неопределено)`
      - `ДополнитьСоответствие(Приемник, Источник, Заменять = Неопределено)`
      - `УдалитьВсеВхожденияЗначенияИзМассива(Массив, Значение)`
      - `СвернутьМассив(Массив)` — оставить только уникальные значения
      - `РазностьМассивов(Массив, МассивВычитания)`
      - `ЗначениеВМассиве(Значение)` — обернуть одно значение в массив
      - `СвойствоСтруктуры(Структура, Ключ, ЗначениеПоУмолчанию = Неопределено)` — предпочитать платформенному `Структура.Свойство(...)`
      - `ЕстьРеквизитИлиСвойствоОбъекта(Объект, ИмяРеквизита)`
      
      **Серверные (`ОбщегоНазначения`):**
      - `ТаблицаЗначенийВМассив(ТаблицаЗначений)`
      - `ВыгрузитьКолонку(КоллекцияСтрок, ИмяКолонки, ТолькоУникальныеЗначения = Ложь)`
      - `СкопироватьРекурсивно(Источник)` — есть и в `ОбщегоНазначенияКлиент`
      - `ДанныеСовпадают(Данные1, Данные2)`
      - `ФиксированныеДанные(Данные)`
      
      ### Проверки и контракты (КлиентСервер)
      
      - `Проверить(Условие, Сообщение = "")` — assert
      - `ПроверитьПараметр(ИмяПроцедурыИлиФункции, ИмяПараметра, ЗначениеПараметра, ОжидаемыйТип)` — проверка типа параметра
      
      ### Формы (КлиентСервер)
      
      - `УстановитьСвойствоЭлементаФормы(ЭлементыФормы, ИмяЭлемента, ИмяСвойства, Значение)`
      - `УстановитьЭлементОтбораДинамическогоСписка(ДинамическийСписок, ИмяПоля, ...)`
      - `УдалитьЭлементыГруппыОтбораДинамическогоСписка(ДинамическийСписок, ...)`
      - `УстановитьПараметрДинамическогоСписка(Список, ИмяПараметра, Значение)`
      
      **Клиентские:**
      - `ОбщегоНазначенияКлиент.ОбновитьИнтерфейсПрограммы()`
      - `ОбщегоНазначенияКлиент.ОповеститьОбИзмененииОбъекта(Источник)`
      
      ### Хранилища настроек (Сервер)
      
      | Хранилище | Сохранить | Загрузить |
      |---|---|---|
      | Общие | `ХранилищеОбщихНастроекСохранить` | `ХранилищеОбщихНастроекЗагрузить` |
      | Системные | `ХранилищеСистемныхНастроекСохранить` | `ХранилищеСистемныхНастроекЗагрузить` |
      | Данные форм | `ХранилищеНастроекДанныхФормСохранить` | `ХранилищеНастроекДанныхФормЗагрузить` |
      
      > Не использовать `ХранилищеОбщихНастроек.Сохранить/Загрузить` напрямую — не работает в модели сервиса.
      
      ### Безопасное хранилище (Сервер)
      
      - `ЗаписатьДанныеВБезопасноеХранилище(Владелец, Данные, Ключ = "Пароль")`
      - `ПрочитатьДанныеИзБезопасногоХранилища(Владелец, Ключи = "Пароль")`
      - `УдалитьДанныеИзБезопасногоХранилища(Владелец)`
      
      ### Безопасное выполнение внешнего кода (Сервер)
      
      - `ВыполнитьВБезопасномРежиме(Алгоритм, Параметры = Неопределено)`
      - `ВычислитьВБезопасномРежиме(Выражение, Параметры = Неопределено)`
      
      Брать вместо платформенного `Выполнить`/`Вычислить` для внешнего кода.
      
      ### XML / XDTO (Сервер)
      
      - `ЗначениеВСтрокуXML(Значение)` / `ЗначениеИзСтрокиXML(СтрокаXML)`
      - `ОбъектXDTOВСтрокуXML(ОбъектXDTO)` / `ОбъектXDTOИзСтрокиXML(СтрокаXML)`
      
      ### Запросы (Сервер)
      
      - `СформироватьСтрокуДляПоискаВЗапросе(СтрокаПоиска)` — экранирование для `ПОДОБНО`
      
      ### Стили (Клиент)
      
      - `ОбщегоНазначенияКлиент.ЦветСтиля(ИмяЦветаСтиля)` — вместо жёстко зашитых цветов
      - `ОбщегоНазначенияКлиент.ШрифтСтиля(ИмяШрифтаСтиля)`
      
      ---
      
      ## СтроковыеФункцииКлиентСервер
      
      - `РазложитьСтрокуВМассивПодстрок(Значение, Разделитель = ",", ПропускатьПустыеСтроки = Неопределено)`
      - `ПодставитьПараметрыВСтроку(ШаблонСтроки, ...)` — позиционные `%1`, `%2`
      - `ВставитьПараметрыВСтроку(ШаблонСтроки, Параметры)` — именованные `[Имя]`
      - `ТолькоЦифрыВСтроке(Значение)`
      - `ТолькоЛатиницаВСтроке(СтрокаПроверки)`
      - `ЭтоУникальныйИдентификатор(Значение)`
      - `ДополнитьСтроку(Значение, ДлинаСтроки, Символ = "0", Режим = "Слева")`
      - `УдалитьПовторяющиесяСимволы(Значение, УдаляемыйСимвол)`
      - `СтрокаВЧисло(Значение)`, `СтрокаВДату(Значение)` — без исключений на парсинге
      
      ---
      
      ## Пользователи (Сервер)
      
      - `АвторизованныйПользователь()` — текущий пользователь ИБ как ссылка
      - `ЭтоПолноправныйПользователь(Пользователь = Неопределено)` — проверка ролей администрирования
      - `РолиДоступны(ИменаРолей)`
      - `НайтиПоИмени(ИмяДляВхода)`
      
      ---
      
      ## ФайловаяСистема / ФайловаяСистемаКлиент
      
      ### Временные файлы (Сервер)
      
      - `СоздатьВременныйКаталог(Расширение = "")` — возвращает путь
      - `УдалитьВременныйКаталог(Путь)`, `УдалитьВременныйФайл(Путь)`
      
      ### Запуск программ
      
      - `ФайловаяСистема.ЗапуститьПрограмму(КомандаЗапуска, ПараметрыЗапуска = Неопределено)` — сервер
      - `ФайловаяСистемаКлиент.ЗапуститьПрограмму(...)` — клиент, асинхронный
      
      > Брать вместо `ЗапуститьПриложение`/`НачатьЗапускПриложения` — кросс-платформенные и обрабатывают веб-клиента.
      
      ### Загрузка/сохранение файлов с диалогом (Клиент)
      
      - `ФайловаяСистемаКлиент.ЗагрузитьФайл(...)` — диалог открытия с загрузкой
      - `ФайловаяСистемаКлиент.СохранитьФайл(ОбработчикЗавершения, АдресВоВременномХранилище, ИмяФайла)`
      
      ---
      
      ## Антипаттерны
      
      | Не делать | Использовать |
      |---|---|
      | `Сообщить("текст")` | `ОбщегоНазначения.СообщитьПользователю(...)` |
      | `Ссылка.Реквизит` | `ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Ссылка, "Реквизит")` |
      | Несколько `Ссылка.Реквизит` | `ЗначенияРеквизитовОбъекта(Ссылка, "Реквизит1,Реквизит2")` |
      | `Структура.Свойство("Ключ", Знач)` для дефолта | `ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(Стр, "Ключ", Знач)` |
      | Ручное добавление массивов в цикле | `ОбщегоНазначенияКлиентСервер.ДополнитьМассив(...)` |
      | `ХранилищеОбщихНастроек.Сохранить(...)` напрямую | `ОбщегоНазначения.ХранилищеОбщихНастроекСохранить(...)` |
      | `ТекущаяДата()` | `ТекущаяДатаСеанса()` на сервере, `ОбщегоНазначенияКлиент.ДатаСеанса()` на клиенте |
      | `НачатьЗапускПриложения` | `ФайловаяСистемаКлиент.ЗапуститьПрограмму(...)` |
      | `ПредопределенноеЗначение("Справочник.Х.Имя")` | `ОбщегоНазначения.ПредопределенныйЭлемент("Справочник.Х.Имя")` |
      | `Выполнить(Код)` для внешнего кода | `ОбщегоНазначения.ВыполнитьВБезопасномРежиме(Алгоритм, ...)` |
      | Жёстко зашитые цвета на клиенте | `ОбщегоНазначенияКлиент.ЦветСтиля(...)` |
      | Ручная проверка ролей через `РольДоступна` для админа | `Пользователи.ЭтоПолноправныйПользователь()` |
      
      ## Что запрещено использовать
      
      - Методы из `#Область УстаревшиеПроцедурыИФункции`
      - Методы из `#Область СлужебныйПрограммныйИнтерфейс`
      - Модули с суффиксами `*Служебный`, `*ПовтИсп`, `*Переопределяемый`
      
    • checklist-code.md 18.5 KB
      # Чеклист ревью кода 1С (BSL)
      
      Иди по разделам, беря только те, что относятся к архетипу изменённого кода. Для каждого
      подозрения запроси текст стандарта по номеру через MCP `v8std` (`v8std_get_page("stdNNN")`),
      сверь дословное правило и пример, зафиксируй код диагностики.
      
      Тексты стандартов в плагине не хранятся — здесь только номера и указания, что проверять. Если
      MCP недоступен, раздел отмечается записью `skipped` с причиной, а не проверяется по памяти:
      формулировка стандарта, воспроизведённая по памяти, — источник ложных находок.
      
      ## 1. Структура и оформление модуля
      - [ ] Модуль структурирован по областям (#std455).
      - [ ] Тексты модуля оформлены по правилам: отступы, пустые строки (#std456).
      - [ ] Экспортные процедуры/функции имеют описание (#std453).
      - [ ] Ключевые слова написаны канонически (#std441 п.1).
      - [ ] Длинные строки/выражения перенесены по правилам (>120 симв.) (#std444).
      - [ ] Нет дублирующего кода; повторы вынесены (#std440).
      
      ## 2. Именование
      - [ ] Имена процедур/функций — от предметной области, самодокументирующиеся; функции — от возвращаемого значения, процедуры — от глагола (#std647).
      - [ ] Имена переменных по правилам, без префиксов-сокращений (масРеквизитов и т.п.) (#std454).
      - [ ] Нет лишних типов в именах методов (#std647 п.4).
      
      ## 3. Параметры и переменные
      - [ ] ≤7 параметров, ≤3 необязательных; необязательные — после обязательных (#std640).
      - [ ] Похожие параметры сгруппированы в Структуру (#std640 п.5, #std641).
      - [ ] Параметры передаются явно, без неявных модульных переменных (#std640 п.2).
      - [ ] Локальные переменные инициализированы до использования (#std494).
      - [ ] Переменные модуля используются обоснованно (#std639).
      
      ## 4. Конструкции языка
      - [ ] Булево не сравнивается с Истина/Ложь (#std441 п.4).
      - [ ] Системные наборы значений вместо магических литералов (Символы.ПС вместо Символ(10)) (#std441 п.6).
      - [ ] Директивы компиляции/препроцессор применены корректно (#std439).
      - [ ] Тип значения определяется корректно (#std442); метаданные получаются правильно (#std445).
      - [ ] Нет оператора Перейти (#std547).
      
      ## 5. Исключения и логирование
      - [ ] Исключения перехватываются корректно, без «глотания» (#std499).
      - [ ] Исключения вызываются осмысленно, с понятным текстом (#std790).
      - [ ] Используется Журнал регистрации, а не Сообщить для диагностики (#std498).
      - [ ] Корректно обработан параметр Отказ в обработчиках (#std686).
      
      ## 6. Запросы
      - [ ] Ключевые слова заглавными; псевдонимы у источников и полей выборки; запрос структурирован, не в одну строку (#std437).
      - [ ] Реквизиты ссылки читаются через `ОбщегоНазначения.ЗначениеРеквизитаОбъекта` / `ЗначенияРеквизитовОбъекта`, а не через точку: точка читает объект целиком (#std437, `qg:BSL-REF-DOT-ACCESS`, прогоняется `tools/bsl-lint.mjs`). Инструмент доказывает ссылочность присваиванием в том же методе или типом параметра из описания #std453 — это нижняя граница проверки: ссылку из чужой функции или недокументированного параметра ищи глазами.
      - [ ] Ни один псевдоним источника не совпадает с именем колонки, видимой в этом же запросе, — включая колонки временных таблиц пакета (`qg:QRY-ALIAS-SHADOWS-FIELD`, прогоняется `tools/query-lint.mjs`).
      - [ ] Источник-табличная-часть не назван именем своей табличной части, если её владелец соединён в той же ветке: имя занято полем владельца (`qg:QRY-ALIAS-SHADOWS-NESTED-TABLE`, прогоняется `tools/query-lint.mjs`). Коллизию псевдонима с именем реквизита реальной таблицы инструмент не ловит — это остаётся глазам.
      - [ ] Ни один псевдоним не является служебным словом языка запросов: платформа отвергает такой запрос при разборе целиком (`qg:QRY-ALIAS-RESERVED-WORD`, прогоняется `tools/query-lint.mjs`). У псевдонима источника список на два слова длиннее — `Ссылка` и `Представление` законны только в списке выборки.
      - [ ] Упорядочивание результата задано осознанно (#std412).
      - [ ] У каждого `ПЕРВЫЕ N` (N > 1) есть `УПОРЯДОЧИТЬ ПО`: без него неизвестно, какие именно строки вернутся (`qg:QRY-TOP-WITHOUT-ORDER`, прогоняется `tools/query-lint.mjs`).
      - [ ] ОБЪЕДИНИТЬ/ОБЪЕДИНИТЬ ВСЕ применены верно (#std434): совпадают количество и порядок полей, совместимы типы. Отсутствие псевдонимов в неголовной ветке — не дефект, имена берёт первая выборка.
      - [ ] Нет необоснованного ПОЛНОГО ВНЕШНЕГО СОЕДИНЕНИЯ (#std435).
      - [ ] Проверка на пустой результат корректна (#std438).
      - [ ] Однотипные запросы не выполняются в цикле (#std436).
      - [ ] Если в запросе стоит `РАЗРЕШЕННЫЕ` — проверен п. 14: в расчёте и проведении его быть не должно (#std415).
      - [ ] Строковые колонки таблицы, передаваемой в запрос параметром, объявлены с квалификатором длины: поле неограниченной длины роняет `РАЗЛИЧНЫЕ`, `СГРУППИРОВАТЬ ПО`, соединение и сравнение в `ГДЕ` (#std432 п.3.1, `qg:BSL-UNBOUNDED-STRING-COLUMN`, прогоняется `tools/bsl-lint.mjs`). Колонка с длинным текстом — законное исключение (#std432 п.2): ей длину назначает запрос через `ВЫРАЗИТЬ`, а не квалификатор, который её обрежет. Передачу таблицы в чужой метод инструмент не видит — эта половина за читателем (`qg:AI-16`).
      
      ## 7. Оптимизация запросов
      - [ ] Запрос оптимален: индексируемые условия, без лишних соединений (#std729, #std658).
      - [ ] Условия соответствуют индексам (#std652).
      - [ ] Разыменование составных ссылочных полей не убивает план (#std654).
      - [ ] Обращения к виртуальным таблицам эффективны; «Остатки» — с отбором (#std657, #std733).
      - [ ] Временные таблицы применены к месту (#std777).
      
      ## 8. Транзакции, блокировки, чтение
      - [ ] Транзакции открыты/закрыты по правилам, попытка-исключение (#std783).
      - [ ] В обработчиках записи, удаления и проведения нет собственной `НачатьТранзакцию`: платформа уже открыла свою, вложенные не поддерживаются (#std783 п.1.4, `qg:BSL-TXN-IN-HANDLER`, прогоняется `tools/bsl-lint.mjs`).
      - [ ] Управляемый режим блокировки применён корректно (#std460).
      - [ ] Блокировка объекта для редактирования из кода — где нужно (#std490).
      - [ ] «Ответственное чтение» соблюдено (#std648).
      - [ ] Чтение отдельных реквизитов вместо объекта целиком, где уместно (#std496).
      - [ ] Нет избыточных блокировок: область и длительность минимальны (#std659).
      - [ ] Остатки не читаются блокирующим чтением в начале транзакции (#std661).
      
      ## 9. Обработчики событий объекта
      - [ ] ПередЗаписью/ПриЗаписи используются по назначению, без лишней логики (#std464, #std465).
      - [ ] ОбработкаПроверкиЗаполнения и ОбработкаЗаполнения реализованы корректно (#std463, #std396).
      - [ ] Учтён признак ОбменДанными.Загрузка (пропуск бизнес-логики при загрузке) (#std773).
      - [ ] Движения записываются через свойство Движения с признаком Записывать, а не явным Записать() набора в обработке проведения (#std450, диагностика `acc:105`).
      - [ ] Активность движений используется по назначению, а не как способ «мягкого удаления» (#std633).
      
      ## 10. Клиент-серверное взаимодействие и поведение формы
      - [ ] Минимизированы серверные вызовы и трафик (#std487).
      - [ ] Минимум кода на клиенте (#std629).
      - [ ] Повторное использование возвращаемых значений применено осознанно (#std724).
      - [ ] Предопределённые значения на клиенте получаются правильно (#std443).
      
      Поведение формы — берётся при правке модуля формы, а не любого серверного кода:
      
      - [ ] Обработчики событий формы, подключаемые из кода, назначены по правилам (#std492).
      - [ ] Условное оформление задано настройкой, а не переписыванием оформления в обработчиках (#std710).
      
      ## 11. Прикладные объекты и коллекции
      - [ ] Поиск в коллекциях эффективен (#std452).
      - [ ] Структуры используются корректно (#std693).
      - [ ] Массовая склейка строк не растёт по квадрату: удвоение числа строк не должно давать вчетверо больше работы (#std782).
      - [ ] РегистрСведенийМенеджерЗаписи применён верно (#std447).
      - [ ] Разделение модуля объекта / менеджера / общих модулей соблюдено (#std486).
      - [ ] Экспортные процедуры/функции не избыточны (#std544).
      - [ ] Один и тот же набор регистра не перезаписывается многократно за одну операцию (#std792).
      - [ ] Набор однотипных записей получен из запроса через `Выгрузить()`, а не переложен циклом в массив структур; признак, которого в запросе нет, добавлен колонкой с указанием типа через `ОписаниеТипов` (`qg:AI-17`). Записи разного состава, отправка набора в JSON или XML и работа на клиенте — законные исключения.
      - [ ] Запись, которая опознаётся по нескольким полям, ищется через `НайтиСтроки` по этим колонкам, а не по ключу, склеенному из значений в одну строку, и не по такому же ключу, переложенному в отдельную колонку (`qg:AI-18`). Если по одному ключу обращаются многократно из часто выполняемого кода, `Соответствие` остаётся правильным выбором.
      
      Свойства метаданных — проверяются по XML объекта, а не по тексту модуля; в контуре кода они
      попадают в поле зрения только если правка их затронула:
      
      - [ ] Режим разделения итогов задан осознанно (#std664 — накопление, #std663 — бухгалтерия).
      - [ ] Разрешение итогов у периодического регистра сведений соответствует характеру обращений (#std708).
      - [ ] У документа предусмотрен реквизит «Комментарий» (#std531).
      
      ## 12. Регламентные и фоновые задания
      - [ ] Регламентное задание выполняется порциями и переносит повторный запуск без порчи данных, без бесконечных блокировок (#std540).
      - [ ] Ошибки задания фиксируются и не рвут весь процесс; предусмотрен рестарт (#std402, #std539).
      - [ ] Код задания вынесен в отдельный метод/модуль, не в модуль менеджера регламентного задания напрямую (#std760).
      
      ## 13. Безопасность
      - [ ] API сервера безопасен; нет небезопасного «Вызов сервера» и передачи исполняемого кода с клиента (#std678, #std679).
      - [ ] Пароли и секреты не хранятся/не передаются в открытом виде; используется безопасное хранилище (#std740).
      - [ ] Нет небезопасного выполнения внешнего кода / работы с внешними компонентами (#std669, #std770).
      - [ ] Безопасная работа с файлами/внешними ресурсами и инъекциями (#std774, #std775, #std794).
      
      ## 14. Права доступа
      - [ ] Права доступа проверяются в коде, где это необходимо (#std737).
      - [ ] Привилегированный режим — только где обоснован, с минимальным охватом (#std485).
      - [ ] Ограничения на уровне записей (RLS) и роли спроектированы корректно (#std689, #std488, #std532).
      - [ ] Доступ к объектам/реквизитам и проверка ролей выполнены по правилам (#std491).
      - [ ] `РАЗРЕШЕННЫЕ` не стоит в запросе, влияющем на расчёт или проведение: нехватка прав закрыта выдачей права либо явным прерыванием операции (#std415).
      
      ## 15. Обмен данными
      - [ ] Настройка обмена (в т.ч. классификаторов между ИБ) выполнена по правилам (#std637).
      - [ ] При загрузке данных бизнес-логика пропускается по ОбменДанными.Загрузка (#std773, #std701).
      - [ ] Регистрация изменений и планы обмена используются корректно (#std771).
      - [ ] При обращении к внешним ресурсам задан таймаут; поведение при его истечении определено (#std748).
      
      ## 16. Локализация и многоязычность
      - [ ] Все пользовательские строки через НСтр()/механизм перевода, без жёстко зашитых текстов (#std458, #std769).
      - [ ] Формат дат/чисел/разделителей не зависит от региональных настроек жёстко (#std761, #std762, #std763).
      - [ ] Многоязычные синонимы/представления заданы, склонения учтены (#std764, #std765, #std766, #std767, #std778, #std784).
      
      ## 17. Библиотеки и БСП
      - [ ] Общий код переиспользуется по правилам библиотек (#std551, #std552).
      - [ ] Программный интерфейс библиотеки и точки расширения использованы правильно (#std553, #std554, #std739, #std644, #std668).
      - [ ] Обработчики обновления ИБ оформлены корректно (#std690, #std705).
      
    • cold-reader.md 4 KB
      # Холодный читатель: зачем второй взгляд с противоположным входом
      
      Слой 2 контура — ревью логики моделью. Порядок действий в SKILL.md; здесь то, почему взглядов
      два и что ломается, если их перепутать.
      
      ---
      
      ## Два входа, а не два мнения
      
      `advisor()` видит **весь** транскрипт: задачу, шаги, написанный код. Его сила — расхождение
      «сказал одно, написал другое»: он помнит, что было обещано, и сверяет с тем, что получилось.
      
      Холодный читатель не видит **ничего**, кроме кода. Его сила ровно в этой слепоте: свой код
      читается доброжелательно, недостающий смысл достраивается из намерения — а намерения в тексте
      программы нет. Читатель, не знающий замысла, видит программу такой, какой её увидит
      сопровождение через год.
      
      Ценность даёт не количество ревьюеров, а противоположность входов. Два прогона с одинаковым
      контекстом дают одну и ту же слепую зону дважды.
      
      ## Что ломает независимость
      
      Достаточно передать формулировку задачи, свои выводы или названия уже найденных проблем — и
      читатель перестаёт быть холодным. Он начинает искать подтверждение чужой гипотезы, а
      получается второй `advisor()`, только с меньшим контекстом и потому слабее.
      
      Передаётся ровно две вещи: сравнение версий и содержимое изменённых файлов.
      
      ## Почему модель не дешёвая
      
      Верификатор, разведчик и исполнитель проверок собирают перечислимые факты — «есть ли метод»,
      «кто вызывает», «что напечатал валидатор», — и дешёвая модель делает это корректно.
      
      Здесь выносится суждение о логике: что код делает как написан, на каких данных ломается,
      какое ожидаемое поведение из него не следует. Ошибка такого суждения не видна по форме ответа
      — неверный вывод выглядит ровно так же, как верный. Уровень нужен не ниже основной модели
      сессии.
      
      ## Расхождение выводов — само по себе находка
      
      Если `advisor()` и холодный читатель сошлись на разном, это не «один из них ошибся». Это
      значит, что код допускает два прочтения: одно — при знании замысла, другое — без него.
      Разбирается вопрос до вердикта, а не усредняется голосованием.
      
      ## Когда запускать
      
      Дополнительно к `advisor()`, когда цена ошибки высока: класс C3 либо затронуты проведение,
      деньги, права, необратимые операции. На обычной правке второй взгляд не окупается — и хуже
      того, приучает к ритуалу, из-за которого его перестают запускать там, где он нужен.
      
    • platform-api.md 9.5 KB
      # Сверка со справочником платформы — что стоит за выводом
      
      Второй движок слоя 1а: `tools/platform-context-run.mjs`. Источник — сервер
      [`bsl-context`](https://github.com/Regsorm/bsl-context), читающий справку платформы
      `shcntx_ru.hbk`. Сервер движок заводит сам: ищет уже поднятый, а не найдя — ставит закреплённый
      релиз и поднимает свой по установленной на машине платформе. Порядок заведения и диагностика —
      [INSTALL.md](../../../docs/INSTALL.md), ключи настройки — [CONFIG.md](../../../docs/CONFIG.md),
      секция `platformContext`.
      
      ## Зачем он рядом со статическим анализатором
      
      Анализатор знает имена КОНФИГУРАЦИИ: общие модули, реквизиты, поля запросов
      (`UnresolvedMethodCall`, `UnresolvedField`). Про саму платформу он не знает ничего. Замер на
      одном файле, отданном обоим движкам (31.08.2026, `bsl-analyzer` 0.2.73 с конфигом гейта):
      
      | Дефект | анализатор | движок справки |
      |---|---|---|
      | `Массив.Сортировать()` — метода нет | молчит | `unknown_type_member` |
      | `ТипГруппыЭлементовОтбораКомпоновкиДанных.Группа` | молчит | `unknown_enum_value` + подсказка `ГруппаИ` |
      | `Новый ТаблицаЗначенийРасширенная` | молчит | `unknown_new_type` |
      | `ЗначениеЗаполненно(...)` — опечатка | молчит | `unknown_global_method` |
      | `ДиалогВыбораФайла.НачальныйКаталог` — свойства нет | молчит | `unknown_type_member` |
      | `СтрШаблон` с 12 аргументами | ловит | ловит |
      | `ИЛИ` в условии соединения | ловит | ловит |
      | несуществующая константа | ловит (`UnresolvedField`) | ловит |
      
      Всё, на чём анализатор молчит, компилируется и падает при выполнении. Виды находок из
      последних трёх строк движок отбрасывает сам: дублирующая находка удваивает строки следа по
      одному дефекту, и вердикт начинает зависеть от того, какой движок отчитался первым.
      
      ## Уверенность находки
      
      - **`major`** — высокая уверенность, доля ложных близка к нулю.
      - **`info` с пометкой «низкая уверенность»** — класс смешанный. Известное ложное
        срабатывание: переменная названа именем существующего платформенного типа
        (`ЗаписьДанных`, `Соединение`, `Блокировка`, `РезультатЗапроса`), и это имя перебивает
        вывод типа из присваивания. Пример: `ЗаписьДанных = Новый ЗаписьJSON` →
        «у типа `ЗаписьДанных` нет члена `УстановитьСтроку`», хотя у `ЗаписьJSON` он есть.
      
      Отбрасывать класс `low` целиком нельзя: там же приходят настоящие дефекты — на замере
      `ДиалогВыбораФайла.НачальныйКаталог` пришёл именно с низкой уверенностью, а присваивание
      несуществующему свойству падает при выполнении. Каждую находку смотри глазами.
      
      По той же причине профиль `strict` самого сервера здесь не используется: он форсирует
      уровень 1, а на нём не существует `unknown_type_member` — тот самый класс, ради которого
      движок и добавлен.
      
      ## Оговорки в следе
      
      - **`not_verified: reason=symbols_unavailable`** — имена конфигурации серверу были
        недоступны, применены только правила платформы. «Чисто» относится к платформенному слою и
        ничего не говорит про имена конфигурации.
      - **`not_verified: reason=tree_not_parsed`** — текст не разобрался в дерево, проверки по
        дереву не выполнялись.
      - **`not_verified: reason=request_failed, files=N`** — по N файлам ответа не получено
        (таймаут, отказ, недоступный сервер). Назови их в отчёте: вердикт «чисто» к ним не
        относится.
      - **`skipped: reason=no_bsl_files`** — в правке нет ни одного `.bsl`/`.os`. Законный исход, и
        именно пропуск, а не «чисто»: движку нечего было смотреть.
      - **`skipped: reason=no_platform_install`** — установленной платформы со справкой на машине
        нет, сверять не с чем. Тоже законный исход: требование контура снимается там, где выполнить
        его нечем. Остальные причины пропуска (`platform_version_absent`, `repo_required`,
        `server_unreachable`, `start_timeout`) означают, что контур выполним, но не заведён, — прогон
        останавливается, а инструмент печатает рядом со следом, что делать.
      
      ## Часовой и отметка версии
      
      `[qg sentinel: target=platform-api, id=unknown_enum_value, status=found,
      engine=bsl-context@0.16.0/8.3.27.1688]`. Фикстура содержит заведомо несуществующее значение
      системного перечисления: находка на ней доказывает, что справка платформы РАЗОБРАНА, а не
      что сервер ответил. Сервер, поднявшийся без справки, отвечает на всё и не находит ничего —
      пустой ответ читался бы как «замечаний нет».
      
      Через косую черту в отметке — **версия платформы**, чью справку сервер загрузил. Она важнее
      версии самого сервера: состав системных перечислений и сигнатуры между релизами платформы
      отличаются, поэтому сервер и не выбирает версию сам. Без этой отметки два прогона, сверявшие
      один и тот же код с 8.3.25 и с 8.3.27, дают побайтово одинаковый след. Версия читается из
      `/health`; если он недоступен, отметка остаётся `bsl-context`, а разбор модулей не страдает.
      
      Причины `not_found`:
      
      | Причина | Что значит |
      |---|---|
      | `finding_absent` | сервер отвечает, справка платформы не загружена |
      | `request_refused` | запрос отклонён настройкой — почти всегда неверный `repo`; список доступных алиасов сервер называет в тексте отказа |
      | `unreachable` / `timeout` | сервер не поднят либо не отвечает в срок |
      | `fixture_missing` | повреждён состав плагина |
      
      Отдельно от часового: `skipped ... reason=platform_version_mismatch` означает, что сервер
      отдаёт справку не той версии платформы, которую закрепил проект (`platformVersion`). Это не
      поломка сервера — на машине с несколькими проектами так выглядит попытка проверить код одной
      версии справкой другой. Прогон останавливается: находки чужой версии хуже их отсутствия.
      
      При `status=not_found` вердикт «чисто» по контуру запрещён — как и у анализатора.
      
      ## Модуль объекта и модуль формы
      
      Движок передаёт серверу путь модуля (`module_path`). Без него сервер не отличает модуль
      объекта от произвольного фрагмента и считает, что неявного контекста объекта нет: обращения
      к реквизитам и табличным частям (`Товары.Очистить()`) дают ложную находку «нет такого общего
      модуля». Путь берётся относительно корня проекта — отдельной настройки не требует.
      
  • SKILL.md 22 KB
    ---
    name: bsl-code-review
    description: >-
      Контур проверки кода BSL: диагностики статического анализатора, антипаттерны производительности
      и механики платформы, стандарты разработки #stdNNN, именование, верификация сигнатур API и
      существования общих модулей. Уровень «внутри тела метода» — то, что чинится заменой строк.
      Вызывается оркестратором quality-gate с готовым профилем изменения; напрямую — по запросу
      «проверь код», «отревьюй что я написал», «проверь на антипаттерны».
    license: MIT
    ---
    
    # bsl-code-review — контур кода
    
    Проверяет то, что чинится **внутри тела метода**: замена строк, без нового шва. Всё, что
    требует выделения метода, переноса в другой модуль, нового экспорта или изменения «кто кого
    вызывает», принадлежит контуру `bsl-architecture-review` — граница и правила отсева повторов
    находок в `shared/routing-contract.md`.
    
    <ЖЁСТКИЙ-ШЛЮЗ>
    Только проверка и отчёт. НЕ переписывай логику, запросы, транзакции и права по своей
    инициативе. В режиме `--fix` допустимы лишь безопасные категории (см. ниже).
    </ЖЁСТКИЙ-ШЛЮЗ>
    
    ## Инварианты контура
    
    Пять утверждений, без которых прогон контура недействителен.
    
    1. **Каталог антипаттернов проходится всегда** — читателем либо самостоятельно, но след
       печатает `catalog.mjs attest`.
    2. **Строку следа инструментальной проверки печатает инструмент** — переноси дословно,
       своих находок этого класса не добавляй: результат детерминирован.
    3. **Каждое замечание доказуемо**: номер стандарта, код диагностики или название
       антипаттерна плюс строка кода. «Так лучше» — не находка.
    4. **Пропуск фиксируется.** Недоступный инструмент или субагент даёт `skipped` с причиной;
       молчание неотличимо от выполнения.
    5. **Файл, который анализатор не разобрал, не проверен** — вердикт «чисто» по нему
       невозможен, и в отчёте он назван поимённо.
    
    ## Вход
    
    От оркестратора: класс изменения (C0…C3), сработавшие архетипы, список изменённых файлов.
    Глубину (Слой 1 / Слой 1+2 / Слой 1+2 с предложением Слоя 3) печатает план (`gate.mjs plan`,
    поле `resolved: code=...`) — архетип может поднять её сверх класса, понизить нельзя. При
    прямом вызове — определи профиль сам по правилам `quality-gate`.
    
    ---
    
    ## Слой 1а — статический анализ
    
    Строка плана для `analyzer-run.mjs` — одна команда: она находит корень конфигурации, прогоняет
    только изменённые файлы, проверяет часового и формирует записи следа. Вывод — находки по
    файлам и готовый блок `## quality evidence`. Перенеси его в отчёт как есть: записи следа по
    слою `code` сочинять руками не нужно и нельзя.
    
    **Твоя работа здесь — триаж, а не припоминание.** Список нарушений детерминирован. От тебя
    требуется отделить то, что надо чинить сейчас, от того, что является осознанной нормой этого
    проекта, и назвать последствие каждой оставленной находки. Коды расшифровывай через
    `v8std_explain_diagnostics` и привязывай к номеру стандарта.
    
    Четыре режима вывода, каждый из которых меняет то, что можно утверждать по результату:
    
    - **Информационные находки свёрнуты** в одну строку, полный список — флаг `--all`. В след
      коды попадают в любом случае.
    - **Проект без основной конфигурации** (репозиторий одного расширения): диагностики о
      неразрешённых именах понижены до информационных — обратно **не поднимай**, отличить их от
      настоящих ошибок в этом режиме нечем.
    - **«НЕ РАЗОБРАНО файлов»** — по этим файлам не проверено **ничего**. Назови их в отчёте
      поимённо: вердикт «чисто» по ним невозможен.
    - **Часовой `status=not_found`** — прогон недостоверен, вердикт «чисто» запрещён; разберись
      с анализатором и повтори.
    
    Что стоит за каждым режимом и известные случаи — `references/analyzer-output.md`. Гейтовый
    анализ идёт с конфигом из состава плагина: проектный `subsystemsFilter` вывести изменённые
    файлы из проверки не может.
    
    **Если анализатор недоступен** — команда сама запишет
    `[qg skipped: layer=code, scope=static-analysis, planned=[bslls:*], reason=analyzer_unavailable]`
    и вернёт код 1. Продолжай со Слоя 1б: он ловит другое и от анализатора не зависит.
    
    ### Второй движок — сверка со справочником платформы
    
    Строка плана для `platform-context-run.mjs`. Ловит то, чего анализатор не видит вовсе:
    несуществующий член платформенного типа, значение системного перечисления, конструктор,
    свойство объекта. Всё это компилируется и падает при выполнении. Сервер справки движок
    заводит сам: ищет поднятый, а не найдя — ставит закреплённый релиз и поднимает свой по
    установленной платформе. Где платформы на машине нет, пишет `skipped` с причиной и
    возвращает код 1 — это законный исход.
    
    Два правила: **`info` «низкая уверенность» не отбрасывать** (класс смешанный) и **часовой
    `not_found` — «чисто» запрещено**. Остальное — `references/platform-api.md`.
    
    ## Слой 1б — то, чего анализатор не видит
    
    ### 1. Каталог антипаттернов — субагент `antipattern-reader`
    
    Строка плана для `catalog.mjs index` печатает индекс триггеров; полная карточка читается по
    попаданию, не заранее. **Сначала сохрани `git diff HEAD --` по изменённым `.bsl` в файл** —
    признаку `qg:AI-11` нужно сравнение версий — у читателя нет оболочки, чтобы построить его
    самому, а `attest` вычисляет diff сам и без него карточку не пропустит; переданный файл лишь
    сверяется. Сравнивать не с чем (новый файл без истории) — `attest --no-diff-available`
    печатает честный `skipped` вместо молчания.
    
    **Делегируй субагенту `antipattern-reader`**: передай вывод `index`, список изменённых `.bsl`
    и путь к файлу диффа. Он не знает задачи и возвращает JSON — сохрани в файл: путь к нему,
    список файлов и путь к диффу — в строку плана `"$QG/tools/catalog.mjs" attest --diff <файл>`.
    
    Инструмент сверяет полноту списка проверенных признаков, состав файлов и цитату каждой
    находки с самим файлом, после чего печатает строки следа `ai-antipatterns` и
    `platform-antipatterns` и пишет журнал. Отвергнутый результат — повтори запуск читателя с
    его замечаниями, не правь JSON руками. Читателя в среде нет — прогони индекс сам по той же
    процедуре и аттестуй так же: строку следа в обоих случаях печатает инструмент.
    
    Признаки с инструментом (`bsl-lint`, `query-lint`, `rename-check`) в проход не входят — их
    строки печатают инструменты, а карточка нужна для «как чинить»:
    `node "$QG/tools/catalog.mjs" card <ID>`.
    
    ### 2. Проверки по тексту кода
    
    Команды — строки плана для `query-lint.mjs`, `bsl-lint.mjs`, `rename-check.mjs`: план
    печатает их только когда применимо, а полный список признаков с разбором каждого — в
    `references/catalog/INDEX.md` (`qg:BSL-UNBOUNDED-STRING-COLUMN` там же — механическая
    половина AI-16). XML идёт в `query-lint` наравне с `.bsl`: `<query>` СКД и `<QueryText>`
    динамического списка — тоже носитель запроса.
    
    **`attribute-access` покрыт инструментом лишь частично.** Доказать ссылочность в пределах
    одного файла удаётся не всегда: ссылка из чужой функции или из недокументированного параметра
    остаётся неопознанной. `clean` здесь означает «механическая часть чиста» и разбора #std437
    глазами не отменяет — инструмент задаёт нижнюю границу, а не верхнюю.
    
    Записи следа обоих — по инварианту 2, дословно, без своих находок.
    
    Граф вызовов оба не строят: запрос, собранный конкатенацией или `СтрШаблон`, виден им лишь
    частями, и вердикт «чисто» этого не закрывает — разбор приближений в справочнике правила.
    
    ### 3. Стандарты под архетип
    
    Не весь свод подряд — только релевантное: справочники и разделы `checklist-code.md`
    печатает план (`gate.mjs plan`) — общие разделы всегда, прочие под архетип; перечень —
    `BASE_CHECKLIST` и `ARCHETYPES` в `tools/profile.mjs`. Глубокая
    вложенность и длинные методы архетипом не считаются и в план не попадают — при такой правке
    открывай `bsl-refactoring.md` сам. Тексты самих стандартов запрашивай через MCP `v8std` по
    номеру.
    
    ### 4. Именование
    
    `#std454` — частая и легко пропускаемая ошибка: сокращения-префиксы, не-CamelCase,
    булево не в утвердительной форме. Детали и примеры — в `references/checklist-code.md`.
    
    ### 5. Символы в исходнике
    
    В коде и комментариях только ASCII-дефис. Длинное тире и его родственники дают у анализатора
    ошибку недопустимого символа. Кавычки-ёлочки допустимы.
    
    ### 6. Верификация API — субагент `bsl-verifier`
    
    Сигнатуры платформенных методов, существование и экспортность общих модулей, состав объектов
    метаданных. Процедура — `references/api-verification.md`.
    
    **Делегируй субагенту `bsl-verifier`**, передав ему список изменённых `.bsl`-файлов. Он
    дешёвый, работает по той же процедуре и возвращает вердикт, список нарушений с локациями и
    раздел «Не проверено». Вызов **один на весь список**: каждый лишний инстанс поднимает свою
    сессию индекса кода, а справочник платформы на stdio-транспорте вдобавок не переносит
    параллельных обращений.
    
    Если прогнан второй движок слоя 1а, платформенная часть уже закрыта: субагенту остаются
    общие модули, метаданные и контекст доступности.
    
    Субагента в среде может не быть — тогда прогоняй `api-verification.md` сам. **Результат
    обязан попасть в след одинаково в обоих случаях** (инвариант 4):
    
    ```
    [qg applied: layer=code, scope=api-verification, ids=[qg:API-SIGNATURE,qg:API-MODULE], verdict=clean]
    [qg skipped: layer=code, scope=api-verification, reason=platform_unavailable]
    ```
    
    > Для класса C1 на этом контур завершается — переходи к отчёту.
    
    ---
    
    ## Слой 2 — ревью логики моделью
    
    Вызови `advisor()`. Более сильная модель видит весь транскрипт: задачу, шаги, написанный код.
    Ловит то, что статика не видит в принципе — неверную бизнес-логику, упущенные сценарии,
    неучтённые состояния. Замечаниям давай весомый вес.
    
    ### Холодный читатель — второй взгляд с противоположным входом
    
    Дополнительно к `advisor()`, когда цена ошибки высока: класс C3 либо затронуты проведение,
    деньги, права, необратимые операции. Ценность даёт противоположность входов, а не второе
    мнение — почему, разбирает `references/cold-reader.md`.
    
    **Передавать:** только сравнение версий и содержимое изменённых файлов. **Не передавать:** формулировку
    задачи, свои выводы, названия найденных проблем — узнавший намерение читатель перестаёт
    быть холодным.
    
    Три вопроса, на которые он отвечает:
    
    1. Что этот код делает **как написан**, а не как задуман?
    2. На каких входных данных он ломается или ведёт себя неожиданно?
    3. Какое ожидаемое поведение из него не следует?
    
    **Модель не дешевле основной:** уровень не ниже модели сессии. Расхождение с `advisor()` —
    сигнал, а не шум: код допускает два прочтения.
    
    **Слой заканчивается записью следа** — иначе его пропуск на C3 неотличим от прогона; дефект
    без своего признака — `qg:LOGIC-CONTRACT` или `qg:LOGIC-CASE-LOSS`:
    
    ```
    [qg applied: layer=code, scope=logic-review, ids=[qg:LOGIC-CONTRACT], verdict=violation:qg:LOGIC-CONTRACT]
    [qg skipped: layer=code, scope=logic-review, reason=advisor_unavailable]
    ```
    
    ## Слой 3 — состязательный аудит (только по подтверждению)
    
    **Никогда не запускается сам** — контур лишь предлагает его в отчёте и ждёт явного согласия.
    
    Суть: веер независимых ревьюеров по измерениям, затем по каждой находке несколько
    проверяющих, которым поставлена задача её **опровергнуть**. Проходит только то, что
    опровергнуть не удалось.
    
    Состав измерений, пороги, правила голосования, асимметрия для находок 🔴 и порядок действий,
    когда оркестрация недоступна, — в `../quality-gate/references/adversarial-audit.md`.
    
    ---
    
    ## Автофикс (`--fix`)
    
    **Можно:** именование (через переименование символа анализатором, не текстовой заменой),
    форматирование и отступы, канонические ключевые слова, магические литералы на системные
    константы, очевидные quick-fix анализатора.
    
    **Нельзя без подтверждения:** любая правка логики, проведения, запросов; транзакции и
    блокировки; права и привилегированный режим; всё, помеченное 🔴; сигнатуры экспортных методов
    (ломает вызывающих).
    
    После автофикса прогони Слой 1 заново — правки могли внести новые диагностики.
    
    ---
    
    ## Выход
    
    ### Находки
    
    ```
    [🔴/🟠/🟡] <краткая суть>
    Где: <путь:строка>
    Правило: #stdNNN п.X | антипаттерн «<название>» | #bslls:<Код>
    Проблема: <что именно не так здесь>
    Как исправить: <конкретно; для 🔴 — со ссылкой на пример из справочника>
    Уверенность: средняя | требует проверки — опускается при высокой
    ```
    
    При не-высокой уверенности следом — проверка, которая находку закроет. Довод, снимающий
    находку без такой проверки, обязан опираться на прочитанный источник — правило и пример в
    `../quality-gate/references/evidence-format.md`.
    
    Ключ локации `<путь>::<Метод>:<строка>` обязателен — по нему оркестратор дедуплицирует
    находки с архитектурным контуром (правила — в `shared/routing-contract.md`).
    
    ### Записи следа
    
    Минимум одна на каждый слой — выполненный или пропущенный:
    
    ```
    [qg applied: layer=code, scope=query-in-loop, ids=[std436,bslls:QueryInLoop], verdict=clean]
    [qg applied: layer=code, scope=attribute-access, ids=[qg:BSL-REF-DOT-ACCESS,std437], verdict=violation:qg:BSL-REF-DOT-ACCESS]
    [qg skipped: layer=code, scope=static-analysis, planned=[bslls:*], reason=analyzer_unavailable]
    ```
    
    Вторую строку печатает инструмент: написанная руками, она валидатор не проходит.
    
    Формат — `../quality-gate/references/evidence-format.md`.
    
    Два измерения контур закрыть не может и обязан об этом сказать. **Компилируемость тел
    модулей** проверяет только платформа: без запуска проверки конфигурации нужна запись
    `[qg not_verified: dimension=compilation, reason=no_platform]`, иначе полностью чистый вердикт
    валидатор отклонит. **Выполнимость запроса** — то же самое при сработавшем архетипе «Запрос»:
    
    ```
    [qg applied: layer=code, scope=query-execution, ids=[qg:QRY-EXECUTED], verdict=clean]
    [qg not_verified: dimension=query-execution, reason=no_platform]
    ```
    
    Проверка по тексту кода (пункт 2 Слоя 1б) её не заменяет — «Поле не найдено» и
    несовместимость типов в `ОБЪЕДИНИТЬ` всплывают только при выполнении. Почему оба измерения
    устроены так — `../quality-gate/references/evidence-format.md`.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related