textmachine/docs/architecture/12-go-style-notes.md

29 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Правила общности движка + 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`/адаптеры уже так устроены, держать линию.