Files
transcriber/docs/conventions/README.md
T
av 4a052ec99b Шаги схемы вынесены в свой каталог, и сверка их снова видит
- шаги PocketBase переехали из файла в пакет
  internal/adapter/repo/pocketbase/migrations, файл на шаг с именем
  зарегистрированного шага; туда же имена коллекций, срок сессии — в provider.go
- ключ migrations в docs/.docs.json наведён на этот каталог: прежнее значение
  указывало на несуществующий migrations/, и шаг гейта проходил зелёным при
  всякой правке схемы
- app.go подключает пакет шагов явным пустым импортом: пропавшая ссылка на
  константы унесла бы регистрацию, и хранилище поднялось бы без коллекций
2026-08-12 22:03:48 +03:00

74 lines
6.3 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.
# Конвенции кода
Как мы пишем код — в отличие от `openspec/specs/`, который описывает, что
система делает, и от [../architecture.md](../architecture.md), который описывает,
как она сложена.
**Прозой остаётся только то, что не выражается правилом.** Свойство, ставшее
правилом линтера или тестом-сканером, отсюда **удаляется** и переезжает в
перечень «Механизировано» ниже. Причина: файл на несколько сотен строк
размазывает внимание по тривиальному — и модель, и человек добросовестно
проверят именование и не дойдут до формы решения.
Обоснование «почему именно так» живёт в [../adr/](../adr/README.md); инварианты с
severity — в [CLAUDE.md](../../CLAUDE.md).
## Откуда взяты и что с расхождениями
Четыре записи перенесены из проекта jellybit — тот же Go, тот же автор, те же
задачи. Код transcriber написан раньше и **части правил не следует**: ключи —
UUID вместо ULID, время берётся `time.Now()` по месту, лог пишется на каждом
шаге и дублируется воркером.
Из этого перечня одно уже закрыто: доменные ошибки проверялись приведением типа
до 2026-08-11, задача `errors-as-instead-of-typecast`. Приведение типа на этом
месте больше не долг, а регрессия.
Пятая, `web-ui.md`, тоже пришла оттуда, но не прижилась: jellybit работает на
htmx, а здесь решено делать SPA — и перенесённый текст снят целиком.
Каждое такое место названо в своей записи строкой «*Расхождение:*». Читается оно
как **долг, а не как нарушение**: правила действуют на новый код, переписывание
существующего — отдельная работа. Проходу ревью строка «Расхождение» говорит, что
находка на этом месте уже известна и новой не считается.
## Записи
- [logging.md](logging.md) — логирование: уровень по адресату, единая логирующая
точка на доменной границе, словарь полей, `ext.*`, что не логируем.
- [errors.md](errors.md) — ошибки: stdlib, обёртка `%w`, `errors.Is` и
`errors.As`, трансляция доменной ошибки на внешней границе, sentinel против
типизированной.
- [config.md](config.md) — конфигурация: TOML, секреты рендерит выкладка в файл
`0600`, самодокументируемый `config.dist.toml`, проверка на старте.
- [database.md](database.md) — БД и идентификаторы: время в UTC RFC 3339, TEXT
ULID, разбор на входной границе, естественные ключи у деталей.
- [web-ui.md](web-ui.md) — веб-UI: Vue 3 с Vite и статикой в бинарнике,
однофайловые компоненты, таблица маршрутов, состояние в экране, одна обёртка
над `fetch`, показ ошибок и состояний списка.
## Механизировано
Проверяется командами из [CLAUDE.md](../../CLAUDE.md); прозой не дублируется и в
промптах ревью не пересказывается.
| Правило | Где механизировано |
| --- | --- |
| Сравнение ошибок через `errors.Is` и `errors.As`, не `==` и не приведением типа | `.golangci.yml``errorlint` |
| Непроверенное возвращаемое значение ошибки | `.golangci.yml``errcheck`, включая присваивание в `_` (`check-blank`). Отказ, который решено не проверять, объявляют в `exclude-functions` поимённо — там сегодня `defer Close` и `send` |
| Форматирование исходников | `.golangci.yml``gofmt` |
| Подозрительные конструкции языка | `.golangci.yml``govet`, `staticcheck`, `ineffassign`, `unused` |
| Секреты в коммите | `lefthook.yml``gitleaks git --staged` |
| Раскладка документов, битые ссылки, изменённый шаг схемы без правки `database.md` | `docs.py check`; каталог шагов задаёт ключ `migrations` в `docs/.docs.json` |
| Одно число версии Go в `go.mod`, `Dockerfile`, `CLAUDE.md` и `README.md` | `Taskfile.yml` → шаг `go-version` (`scripts/check-go-version.sh`) |
Не названное здесь место механизации означает, что проход по конвенциям будет
добросовестно проверять уже проверенное.
**Из перечисленного в записях правилом выражено одно** — сравнение ошибок через
`errors.Is` и `errors.As` (`errorlint`, строка таблицы выше). Прозой остаётся всё
прочее: ни константный `msg` лога (`sloglint`), ни запрет `fmt.Print*` и
`os.Getenv` (`forbidigo`), ни запрет сторонних пакетов ошибок (`depguard`), ни
архитектурные тесты-сканеры. Это следующий шаг переноса в правило: свойство,
оставшееся прозой, проверяет человек на каждом ревью заново.