- беклог и план переехали в docs/tasks (38 задач, 11 целей), слаги переименованы с транслита на английские, 85 ссылок поправлены - conventions.md разобран в docs/conventions/, local-research.md — в docs/research/, review-journal.md — в docs/review.md с разделом настройки конвейера; заведены security.md, adr/ и .pm.json - шаг docs.py check добавлен в task gate; поведение в architecture.md помечено девятью маркерами долга, database.md получил настройки с числовым значением
25 lines
2.3 KiB
Markdown
25 lines
2.3 KiB
Markdown
# Ошибки
|
|
|
|
- Только стандартный `errors` + `fmt.Errorf`. Сторонних пакетов ошибок нет:
|
|
контекст несёт `slog`, стек-трейсы для домашнего сервиса избыточны.
|
|
- Контекст добавляем обёрткой `%w` — это дефолт, чтобы `errors.Is`/`As`
|
|
работали сквозь слои. `%v` — только когда причину сознательно не
|
|
раскрываем.
|
|
- Стиль сообщения: со строчной, без точки, без «failed to». Контекст —
|
|
операция или субъект (`"open archive: %w"`), каждый слой добавляет **свой**
|
|
смысл, не повторяя нижний.
|
|
- Граничные ошибки транслируем в доменные у источника: `sql.ErrNoRows` →
|
|
`store.ErrNotFound` внутри `store`, чтобы выше не торчал `database/sql`.
|
|
- **Sentinel** (`var ErrNotFound = errors.New(...)`) — для условий, на которые
|
|
ветвится код. **Типизированная ошибка** — когда вызывающему нужны данные
|
|
ошибки. Не плодим типы там, где хватает sentinel.
|
|
- Наружу (HTTP) отдаём человекочитаемое сообщение по доменной ошибке, не
|
|
сырой `err.Error()`. Маппинг доменная ошибка → статус живёт в одной точке
|
|
в `httpapi`; новая штатная ветвь отказа заводится sentinel'ом и
|
|
добавляется туда, иначе `default` отдаст 500 на нормальный конфликт.
|
|
- Собрать независимые ошибки (валидация конфига — все проблемы разом) —
|
|
`errors.Join`.
|
|
- `panic` — только невосстановимое: нарушенный инвариант, сбой инициализации.
|
|
`recover` — на верхней границе HTTP-обработчика.
|
|
- Глушить ошибку без лога — только с однострочным комментарием «почему».
|