# Конвенции кода Как мы пишем код — в отличие от `openspec/specs/`, который описывает, что система делает, и [architecture.md](../architecture.md), который описывает, как она сложена. **Прозой остаётся только то, что не выражается правилом.** Свойство, ставшее правилом линтера, отсюда удаляется и переезжает в перечень «Механизировано». Язык документации и кода — в [CLAUDE.md](../../CLAUDE.md): это правило шире кода, оно касается и коммитов, и документов. ## Записи - [errors.md](errors.md) — ошибки: обёртка, sentinel против типа, трансляция на границе, что глушим. - [logging.md](logging.md) — логи: уровень по адресату, единственный логирующий чекпоинт, что не попадает в лог никогда. - [config.md](config.md) — конфигурация: TOML, валидация на старте, самодокументируемый образец. - [storage.md](storage.md) — база и идентификаторы: ULID и естественные ключи, время в UTC, правило выбора между версиями, отпечаток витрины, миграции. - [testing.md](testing.md) — тесты: реальные пакеты в `testdata`, идемпотентность, перебор версий, замер на живом архиве. ## Механизировано Проверяет `task lint` по [.golangci.yml](../../.golangci.yml). Пересказывать эти правила прозой не нужно — линтер скажет точнее и всегда актуальнее. | Правило | Где механизировано | | --- | --- | | `msg` лога — константная категория, данные атрибутами | `sloglint` | | Без `fmt.Print*` — логируем через `slog` | `forbidigo` | | Конфигурация только из TOML, без `os.Getenv` | `forbidigo` | | Время генерирует `store.Now()`, не `time.Now()` | `forbidigo` | | Сравнение ошибок через `errors.Is`/`As`, не `==` | `errorlint` | | Ошибки только stdlib `errors` + `fmt.Errorf` | `depguard` | | Стек-трейсы избыточны — контекст несёт `slog` | `depguard` | Плюс шаги [`task gate`](../../Taskfile.yml): сборка, `go vet`, `gofmt`, тесты, гонки, покрытие изменённых строк, миграции, образцы конфига, секреты в индексе, данные о здоровье в индексе. Непойманное место механизации означает, что проход по конвенциям будет добросовестно проверять уже проверенное.