Чеклист ревьюера PR¶
Пробегай этот список, когда смотришь чужой PR. Не всё применимо к каждому изменению — пропускай пункты, которые к PR не относятся. Но ни один пункт не пропускай «на доверии».
Scope¶
- PR сфокусирован. Одна логическая правка. Если в diff'е «заодно рефакторинг» — попроси вынести в отдельный PR.
- Связь с задачей. В описании PR есть ссылка на issue/ticket (если есть trackers) и ответы на «зачем / что сделано / как тестировать».
- Размер. Не более 400 изменённых строк (без сгенерированных). Больше — попроси декомпозицию, если только PR-описание не объясняет убедительно, почему неизбежно.
Тесты¶
- Новое поведение покрыто тестами. Service — unit, repository —
integration (testcontainers), handler — через
httptest, Watermill — черезgochannel. - Race detector. В Makefile и CI
go test -race -count=1 ./.... - Нет
time.Sleepв тестах. Для async используетсяeventuallyили канал. - Моки не over-specified. Тест не ломается от любого рефакторинга, проверяет только то, что действительно важно.
-
t.Parallel()применён там, где тест не зависит от shared state, и сtc := tcв table-driven.
Стиль кода¶
-
golangci-lintв CI зелёный. - Именование консистентно:
userID(неuserId),HTTPClient(неHttpClient), sentinel-ошибкиErrXxx, конструкторыNewXxx. - Нет
panicв прод-коде. Только вmain.go/initпри фатальной ошибке старта. -
context.Context— первый параметр у всех функций, делающих I/O. - Ошибки обёрнуты через
%w. Нетfmt.Errorf("error: %s", err). - Нет глобальных переменных кроме sentinel-ошибок.
- Нет
TODO/FIXMEбез ссылки на issue.
Безопасность¶
- Новые секреты в
.env.exampleкак placeholder, не в коде, не в тестах, не в compose. - Валидация input на handler-boundary (
validatorтеги,MaxBytesReader,DisallowUnknownFields). - Нет
fmt.Sprintfв SQL с user input. Только$1, $2через pgx. - PII не попадает в логи. Email/phone/token — замаскированы
(
pii.MaskEmailи т.п.) или не логируются. - Error-сообщения клиенту generic. Нет stack trace, нет DSN, нет
полного
err.Error()в ответ. - Новый
/internal/*endpoint — подInternalTokenmiddleware (subtle.ConstantTimeCompare). - Cookies/session —
SameSite=Strict; Secure; HttpOnly. Если применимо.
База данных¶
- Миграция имеет up + down файлы.
-
pg_advisory_xact_lockв начале up.sql (и down.sql если в транзакции). - Expand-contract при изменении существующего столбца/типа. Не combo «add + rename + drop» в одной миграции.
- Индексы под новые WHERE/ORDER BY.
CREATE INDEX CONCURRENTLYдля hot-таблиц (вне транзакции). -
EXPLAIN ANALYZEпрогнан для новых запросов с индексами — результат упомянут в PR-описании. - Таблицы во множественном числе, есть
created_at/updated_at. - Repository не возвращает
pgx.ErrNoRowsнаружу — транслирует вpkgdb.ErrNotFound.
События¶
- Publisher пишет в outbox, не напрямую в Kafka. Outbox-запись в той же транзакции, что и бизнес-write.
- Consumer идемпотентен. UPSERT / unique constraint / Deduplicator.
- Envelope заполнен через helper (
eventmeta.New) — все обязательные поля на месте (Event-Type,Schema-Version,Correlation-Id,Source-Service,Published-At,traceparent). - Middleware stack применён: CorrelationID → Recoverer → PoisonQueue → Retry → Deduplicator.
- Имя топика
kazmaps.<service>.<entity>.<action>, action в прошедшем времени. - Payload без PII в открытом виде.
HTTP API¶
- Версионирование: путь
/v1/*для публичного,/internal/*для сервис-к-сервису. - Response shape стандартный:
{"data":...}на успех,{"error":{"code","message","request_id"}}на ошибку. - Валидация на handler-уровне (
validate(req)послеdecodeJSON). - Маппинг sentinel в HTTP — через
mapServiceError, не хардкодом. - Content-Type проверен на
POST/PUT/PATCH(415 иначе). - Endpoint в OpenAPI (если сервис поддерживает
api/openapi.yaml). - Таймаут handler'а:
Timeoutmiddleware активен. - Пагинация cursor-based на публичных list endpoint'ах.
Docker¶
- Dockerfile не регрессировал до root:
USER app(uid 10001) остался. - Multistage build сохранён.
CGO_ENABLED=0если не нужен cgo. -
-ldflags="-s -w"на builder-стадии. - Healthcheck в compose для postgres/redis/kafka, и сервис с
depends_on: condition: service_healthy.
Конфиг¶
- Новые env добавлены в
.env.exampleс dev-placeholder. - Новые env загружаются в
internal/config/. - Валидация на старте: fail-fast, если обязательное поле пустое.
- README сервиса обновлён, если добавились env, которые надо знать разработчику.
Коммиты¶
- Conventional commits:
<type>(<scope>): <imperative>. - Нет
--no-verifyв истории. Если pre-commit hook падал — проблему починили. - Нет секретов в diff. Проверь глазами: JWT, DB-пароли, API-ключи. Даже закомментированные.
- Нет мусора:
bin/,.idea/,node_modules/, случайных файлов с локальной машины.
Документация¶
- Публичный API изменился → обновлён README сервиса и/или
api/openapi.yaml. - Новая доменная концепция → обновлён
../glossary.md. - Новый паттерн, которого нет в handbook → обсудить с автором,
не вынести ли в
conventions/. Не блокировать PR из-за этого, но записать задачу.
Поведение при отказе¶
- Graceful shutdown не сломан:
SIGINT/SIGTERM→ctx.Done()→srv.Shutdownс таймаутом → все long-running goroutine завершаются. - Таймаут на внешние вызовы: все HTTP-клиенты под
ctxили с явнымTimeout. Никаких «вечных» вызовов. - Retry там, где ошибка транзиентная. Без retry там, где ошибка бизнес-логическая (400, 409 — не retry).
- Circuit breaker для внешних paid провайдеров (SMS gateway, push provider). Чтобы один сбойный провайдер не съел всех goroutine.
- Kafka consumer при panic поднимает ошибку через Recoverer, а не валит процесс.
Как оставлять комментарии¶
Категоризируй комментарии — экономит всем время:
nit:— косметическая правка (пробел, порядок строк, стиль). Автор может игнорировать, reviewer может approve при остатках.suggestion:— альтернативный подход, который, возможно, лучше. Автор решает, применять или нет.question:— «я не понял, что тут происходит». Автор объясняет или правит код так, чтобы вопрос отпал.issue:— блокер. Без фикса не approve. Пиши что не так и почему это проблема (не «плохо написано», а «здесь возможен NPE, если repo вернёт nil»).
Комментарий — конструктивный: предлагай альтернативу, не просто
критикуй. Если не знаешь, как лучше — формулируй как question:.
Approve¶
Approve только если ты лично запустил бы этот код в прод. Иначе — request changes.
- Approve с
nit:комментариями — OK, если автор их явно увидит. - Approve с непомеченным
question:— не OK. Сначала ответ. - «Approve, но я не смотрел миграцию / Dockerfile / крипто» — не OK. Либо смотри всё, либо попроси второго ревьюера на те части, которые вне твоей экспертизы.
Что не проверять¶
- Не комментируй форматирование — это работа
gofmtиgolangci-lint. Если линтер зелёный, а код неудобен — issue на линтер, не на PR. - Не комментируй bikeshed: имена переменных длиной 3 vs 4 символа,
ifvsswitchна двух ветках. Автор знает контекст лучше. - Не проси тесты на тривиальные геттеры / wiring в
main.go. Тесты — на поведение, не на отсутствие опечаток.