29 lines
12 KiB
Markdown
29 lines
12 KiB
Markdown
# Правила общности движка + Go-ответы по больным местам (12)
|
||
|
||
> Короткий норматив для бэкенд-сессий (24.07.2026). Только неочевидное/больное — общий Go-стиль сессии и так соблюдают, его тут нет. Источники Go-части: Google **Go Style Guide** (guide/decisions/best-practices) + Effective Go + Go Wiki CodeReviewComments.
|
||
|
||
## §0. Общность движка (директива владельца 24.07 — жёсткая)
|
||
|
||
1. **Движок language/book-agnostic.** Go-ЛОГИКА не ветвится по паре языков или книге. Языко-специфичное — ДАННЫМИ в `internal/lang` + `configs/langpacks/`, и КЛЮЧ каталога называет ПРЕДМЕТ данных: `<src>/` — факты исходного языка · `<src>-<tgt>/` — факты ПАРЫ (транслитерация, DC-чекеры, заголовки) · **`<tgt>/` — то, что принадлежит ЦЕЛЕВОМУ языку независимо от источника** (появился 30.08 с `reader.txt` писателя книги, D39.175). ⚠ **Два режима, и путать их дорого:** данные, которые видит МОДЕЛЬ или гейт, читает `lang.Load` — их байты фолдятся в `pack.Version()` → снапшот, и правка = громкий `--resnapshot`; данные ЧИСТО РЕНДЕРА (читательские слова файла книги) читает отдельный загрузчик и в `Version()` НЕ фолдятся — иначе добавление слова в словарь перекупало бы каждую книгу. Отказ калибруется объявленностью: объявленный файл канала отсутствует ⇒ ГРОМКИЙ отказ; опциональный ⇒ фича инертна молча и версия байт-стабильна. Книго-специфичное — данными КНИГИ: сид-глоссарий / brief / book.yaml. Книжный канон в общем пар-слое = утечка (прецедент: 古月 в surnames-compound.txt).
|
||
2. **Пар-скоупнутые модули** (checkers_zh_ru и т.п.) легитимны как код за pair-гейтом, но их ДАННЫЕ (словники, руны, таблицы) подлежат выносу в langpack.
|
||
3. **Тест-данные пары легитимны**: zh/ru-строки в `*_test.go`/golden упражняют общую логику. Не путать с логикой.
|
||
4. **Промпты резолвятся КОНВЕНЦИЕЙ ПУТИ, а не ключом конфига** (форма пака-15): пара-пак — `configs/pairs/<пара>.yaml`, роль — `prompts/<пара>/<роль>.md`, и пара без пакета падает ГРОМКО на загрузке с именем пути, который искали (`internal/config/pair.go`; пины `config.TestPromptResolvesByPairAndRoleConvention` и `config.TestPromptMissingPairPackFailsLoud`). ⚠ Прежний ключ не просто снят — он ОТВЕРГАЕТСЯ загрузкой (`config.TestRetiredPromptKeysAreRejected`), то есть конфиг, написанный по старой редакции этого норматива, не поднимется вовсе. Конвенции пары (меры, стихи, дискурс) живут в теле промпта СВОЕЙ пары — это данные. Дальний план — извлечение в структурированный пакет конвенций пары (10-prompt-architecture, гейтится формой пакета).
|
||
5. Ревью-вопрос по умолчанию: **«заработает ли это на паре, которой в репо ещё НЕТ, без правки Go?»** Если нет — данные наружу.
|
||
6. **Контент-свойства — тем же правилом (владелец 25.07, D39.25):** движок НЕ ветвится по типу контента (18+/архаика/жанр). Механизм generic: content-label книги/глав = данные книги · capability провайдера = данные models.yaml · сопоставление и политика цепочки = движок/конфиг. «adult» — значение данных, не Go-идентификатор. Refusal-обработка причино-агностична (D12: детектим ФАКТ отказа; причина — операторская диагностика). Ревью-вопрос: «добавится ли второй лейбл (archaic-register) без правки Go?»
|
||
|
||
## §1. Go-ответы (только то, о чём реально спорим)
|
||
|
||
- **Язык кода (владелец 20.07): английский** — комменты, логи, операторские дашборды/error-msg, detail-флаги. **Русским (целевым языком) ОСТАЮТСЯ и не переводятся:** wire-строки инъекции, которые читает МОДЕЛЬ, — сегодня они живут ДАННЫМИ, а не идентификаторами Go (`backend/internal/lang/data/injection.txt`, ключи `glossary_header` · `editor_header` · `gender_*`; загрузчик `backend/internal/lang/embedded.go`, греп `case "glossary_header"`); lang-data таблицы гейтов; тест-данные перевода. Почему: перевод wire-строки = тихая смена инструкций модели → сдвиг вердиктов и `RequestHash` → `--resnapshot`, то есть перекупка (D30.9); перевод lang-data ломает чекеры (матчат литералом). Проверка нейтральности правки: golden байт-идентичен по content_hash/final_hash.
|
||
⚠ **ИСПР. 05.09: прежняя редакция называла примером `glossaryBlockHeader` и маркер `⟨проверить⟩`** — символа в коде нет вовсе, а маркер СНЯТ С ПРОВОДА (D39.104 п.2, вайр-батч; `backend/internal/lang/embedded.go`, греп `RETIRED wire text` — ключ жив только затем, чтобы тесты предъявляли его ОТСУТСТВИЕ). Норматив, охранявший снятое, отправлял сессию возвращать провод назад — то есть покупать пере-снапшот ради ошибки дока. Пример той же формы брать из кода, а не отсюда.
|
||
|
||
⚠ **ТРЕТЬЯ КОРЗИНА, которой правило не знало (внесена 04.09):** ЧИТАТЕЛЬСКИЕ слова целевого языка живут ДАННЫМИ и вне снапшота — `configs/langpacks/<цель>/reader.txt` (`D39.175`), и туда же относятся фразы пользователя ВСЕХ зон, которые адресуются кодом причины и локалью, а не строкой в Go (`D39.176` п.4, строка бэклога 189). То есть корзин не две, а три, и различает их не язык, а то, КТО читает: провод модели · данные пары · слова читателя и пользователя.
|
||
|
||
- **Имена без аббревиатур этапов (владелец 19.07):** ни `W0`/`W1`/`W1.5`/`W2` в идентификаторах, логах и комментариях — пишем `waveDraft`/`waveEdit`, «draft wave started». Аббревиатуры волн попали в глоссарий как ИСТОРИЯ и оттуда не возвращаются.
|
||
- **Комментарий режется по ВОДЕ, а не по длине (владелец 26.07, формулировка уточнена им же 21.08).** ⚠ Счёт строк («пиши одну-две») — негодный гейт и был снят: комментарий на три строки может быть нужен, на одну — достаточен, длиной это не решается. Режем то, что не помогает читателю КОДА: пересказ решений («владелец решил… и поехали»), провенанс, сага о том, как шли, изложение исследования вместо краткой ссылки на него. Остаётся столько, сколько нужно, всё, что из одной функции НЕ выводится: порядок блокировок, инварианты между таблицами, цена забывания, вендор-квирк. Проверочный вопрос — «поможет ли это тому, кто через полгода правит именно эту функцию», а не «сколько тут строк».
|
||
- **PlantUML: две ловушки activity-синтаксиса, каждая уже стоила реального бага (25.07).** Точка с запятой ВНУТРИ многострочной метки обрывает её; строка метки, начинающаяся с `/ | < > ] }`, читается парсером как спец-терминатор. ⚠ Диаграммы НЕ рендерить в svg/png — владелец смотрит расширением VS Code (гардрейл `CLAUDE.md`); дом диаграмм — `backend/docs/`, правятся одним коммитом с кодом.
|
||
- **Код группируется логическими папками по назначению, а не свалкой `archive/` (владелец, про КОД).** Архив-метафора законна для доков и промтов; для кода каталог обязан называть предмет.
|
||
- **`_test.go` рядом с кодом — не бардак, а требование тулчейна**: `go test` собирает тесты только из каталога пакета; отдельных `tests/`-деревьев в Go не существует.
|
||
- **Число/размер файлов — НЕ критерий сплита** (Google: «maintainers can move code between files without affecting callers»; эталон — stdlib `bytes`). Легитимный триггер — концептуально отдельная подсистема со своим узким API. **Этот тест прогнан против фактуры (пак-14, прил. A: связность измерена) — его проходят miner/checks/membank/chunk → сплит РАТИФИЦИРОВАН (D39.23, пак-15): сначала хойст общего субстрата (`internal/text` + value-типы), затем подсистемы; драйвер остаётся композит-корнем `internal/pipeline`.** Дробление «ради размера» по-прежнему запрещено (util-пакеты, течь деталей в API); crush-масштаб (~71 пакет) не эмулируем — сплит только по измеренному узкому API.
|
||
- **Никаких пакетов `util`/`common`/`helpers`/`types`** — имя пакета обязано нести домен (Decisions/CodeReviewComments).
|
||
- **«Least mechanism»** (Guide): сперва slice/map/struct, потом stdlib, только потом своё/зависимость; это же — мера сдержанности к дженерикам и абстракциям «на вырост».
|
||
- **Интерфейс объявляет потребитель, не реализация**; не заводить интерфейсы заранее «для моков» (CodeReviewComments) — наш `BuildClient`/адаптеры уже так устроены, держать линию.
|