- шаги PocketBase переехали из файла в пакет internal/adapter/repo/pocketbase/migrations, файл на шаг с именем зарегистрированного шага; туда же имена коллекций, срок сессии — в provider.go - ключ migrations в docs/.docs.json наведён на этот каталог: прежнее значение указывало на несуществующий migrations/, и шаг гейта проходил зелёным при всякой правке схемы - app.go подключает пакет шагов явным пустым импортом: пропавшая ссылка на константы унесла бы регистрацию, и хранилище поднялось бы без коллекций
74 lines
6.3 KiB
Markdown
74 lines
6.3 KiB
Markdown
# Конвенции кода
|
||
|
||
Как мы пишем код — в отличие от `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`), ни
|
||
архитектурные тесты-сканеры. Это следующий шаг переноса в правило: свойство,
|
||
оставшееся прозой, проверяет человек на каждом ревью заново.
|