Code review на Go: что проверять, чтобы код не бомбил
Code review — это не просто галочка в Jira или повод поругаться с коллегой в комментариях к пул-реквесту. Это, пожалуй, самый честный способ не выстрелить себе в ногу на проде. А когда речь заходит о Go — языке, который обожают за простоту и ненавидят за «а где здесь дженерики?» (спойлер: они уже есть), — ревью становится искусством. В этой статье мы разберем, на что именно смотреть при code review на Go, чтобы ваш код не только работал, но и радовал глаз. Поехали!
1. Обработка ошибок: не игнорируй, не бойся
В Go нет исключений. Тут всё честно: если функция может упасть, она возвращает error. Самая частая проблема новичков — игнорирование ошибок через подчеркивание. Помните: «Never ignore an error» — это не просто мантра, это закон.
Что проверять в коде:
- Проверка err != nil — она должна быть везде, где функция возвращает ошибку. Даже если вы уверены, что «здесь ошибки не будет».
- Логирование или проброс — ошибку нужно либо залогировать с контекстом, либо вернуть наверх. Глушить её — моветон.
- Сравнение с sentinel-ошибками — используйте
errors.Is()иerrors.As(), а не прямое сравнение==.
// Плохо: игнорируем ошибку
file, _ := os.Open("config.yaml")
// Хорошо: проверяем и логируем
file, err := os.Open("config.yaml")
if err != nil {
log.Printf("не удалось открыть конфиг: %v", err)
return err
}
defer file.Close()
2. Горутины и конкурентность: порядок в хаосе
Горутины — это суперсила Go. Но с великой силой приходит великая ответственность. На ревью мы часто видим утечки горутин, гонки данных и блокировки. Code review в Go без проверки конкурентности — это как ехать на спорткаре без ремня безопасности.
Чек-лист для горутин:
- Запуск горутин — всегда контролируйте время жизни. Используйте
sync.WaitGroupили контексты. - Гонки данных — проверяйте, что доступ к общим данным защищён мьютексом или каналом. Запустите
go test -race. - Паника в горутине — если горутина паникует, упадёт вся программа. В production-коде обязательно используйте
recover.
// Плохо: горутина без контроля
go func() {
// может упасть и убить весь процесс
doWork()
}()
// Хорошо: с WaitGroup и recover
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
log.Printf("горутина упала: %v", r)
}
}()
doWork()
}()
wg.Wait()
3. Интерфейсы: маленькие и по делу
В Go интерфейсы — это контракты. Но, в отличие от Java, они не обязаны быть огромными. В сообществе Go есть принцип: «Чем меньше интерфейс, тем лучше». Один метод — идеально. Два — нормально. Три — уже подозрительно.
На что смотреть:
- Размер интерфейса — если в нём больше 3-4 методов, задумайтесь о разделении.
- Принимайте интерфейсы, возвращайте структуры — классическая мудрость Go. Она делает код гибким и тестируемым.
- Не определяйте интерфейсы заранее — пусть их создаёт потребитель, а не производитель.
// Плохо: огромный интерфейс
type Storage interface {
Save(data []byte) error
Load(id string) ([]byte, error)
Delete(id string) error
List() ([]string, error)
Connect() error
Close() error
}
// Хорошо: маленькие интерфейсы
type Saver interface {
Save(data []byte) error
}
type Loader interface {
Load(id string) ([]byte, error)
}
4. Именование и стиль: код должен говорить
Go — язык с жёсткими правилами форматирования (gofmt — ваш друг). Но code review Go не про пробелы, а про семантику. Имена переменных, функций и пакетов должны быть понятны без комментариев.
Правила именования:
- Короткие имена для локальных переменных —
i,j,err— ок. Для глобальных — осмысленные. - Акронимов — пишите их одинаково:
HTTP,URL,ID. НеHttpилиUrl. - Пакеты — одно слово, нижний регистр, без подчеркиваний.
user,db,api.
// Плохо: непонятные имена
func f(x int) int {
return x * 2
}
// Хорошо: говорящие имена
func DoubleValue(value int) int {
return value * 2
}
5. Тесты: не количество, а качество
Go поставляется со встроенным тестовым фреймворком — и это прекрасно. Но на ревью мы часто видим тесты, которые тестируют только happy path. Грамотный code review в Go требует проверки тестов на полноту.
Что должно быть в тестах:
- Table-driven тесты — стандарт в Go. Позволяет легко добавлять кейсы.
- Edge cases — пустые строки, nil, нулевые значения, граничные условия.
- Тесты на ошибки — проверьте, что функция возвращает ошибку, когда должна.
func TestDivide(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
wantErr bool
}{
{"positive", 10, 2, 5, false},
{"by zero", 10, 0, 0, true},
{"negative", -6, 3, -2, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Divide(tt.a, tt.b)
if tt.wantErr && err == nil {
t.Error("expected error")
}
if !tt.wantErr && result != tt.expected {
t.Errorf("got %d, want %d", result, tt.expected