Ревью кода на Go: что скажет senior и почему ваш код не пройдет

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Ваш Go-код: от «работает» до «принято»

Вы написали код на Go. Он компилируется. Тесты зелёные. Вы горды. Но вот приходит code review от senior-разработчика, и через пять минут ваш пул-реквест покрыт красными комментариями. Знакомо?

Go — язык, где «простота» обманчива. За кажущейся лёгкостью скрываются десятки подводных камней, которые опытный глаз видит сразу. В этой статье мы разберём самые частые замечания, которые senior-ы оставляют на ревью Go-кода. Вы узнаете не только что не так, но и как это исправить, чтобы ваш код проходил ревью с первого раза.

Поехали разбирать типичные ошибки на Golang code review.

1. Обработка ошибок: не просто if err != nil

Проблема: игнорирование ошибок или «голый» return

Самая частая проблема новичков — недостаточно информативная обработка ошибок. Просто вернуть ошибку наверх — это 50% дела. Senior всегда спросит: «А что пошло не так? Где?».

// Плохо: senior скажет "добавь контекст"
func ReadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, err // ❌ Никакого контекста
    }
    // ...
}

Правильный подход — оборачивать ошибки с помощью fmt.Errorf и плейсхолдера %w (для последующей проверки через errors.Is или errors.As).

// Хорошо: senior кивнёт
func ReadConfig(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, fmt.Errorf("read config file %s: %w", path, err) // ✅ Контекст + цепочка
    }
    // ...
}

Проблема: повторяющийся if err != nil

Go известен своей многословностью в обработке ошибок. Но это не значит, что нужно мириться с копипастой. Senior предложит вынести повторяющиеся проверки в хелперы или использовать паттерн «errgroup» для горутин.

  • Не делайте так: 10 раз подряд if err != nil { return ... } в одной функции — это нормально, но если паттерн повторяется в разных функциях — пора рефакторить.
  • Совет senior-а: Используйте defer для закрытия ресурсов и проверки ошибок закрытия (да, file.Close() тоже возвращает ошибку).

2. Конкурентность: гонки данных и неправильные каналы

Проблема: доступ к shared-переменной без синхронизации

Go — язык, где горутины дёшевы, а гонки данных — дороги. Senior сразу заметит, если вы пишете в map или слайс из нескольких горутин без мьютекса.

// Плохо: data race
var counter int
for i := 0; i < 100; i++ {
    go func() {
        counter++ // ❌ Гонка!
    }()
}

Решение: использовать sync.Mutex или атомики. Но senior скажет: «А не проще ли использовать каналы?».

// Хорошо: каналы для синхронизации
ch := make(chan int, 100)
for i := 0; i < 100; i++ {
    go func() {
        ch <- 1
    }()
}
var counter int
for i := 0; i < 100; i++ {
    counter += <-ch // ✅ Без гонок
}

Проблема: утечка горутин

Забыли закрыть канал? Горутина зависла навсегда. Senior на ревью кода на Go обязательно проверит, не «висит» ли у вас горутина без select с ctx.Done().

// Плохо: горутина может висеть вечно
go func() {
    for msg := range ch {
        process(msg)
    }
}()
// Если ch никогда не закроется — утечка памяти

Правильный паттерн — всегда передавать context.Context и слушать сигнал отмены.

// Хорошо: защита от утечки
go func(ctx context.Context) {
    for {
        select {
        case msg, ok := <-ch:
            if !ok {
                return // канал закрыт
            }
            process(msg)
        case <-ctx.Done():
            return // контекст отменён
        }
    }
}(ctx)

3. Интерфейсы: большие и ненужные

Проблема: «гигантские» интерфейсы

В Go принят принцип: «чем меньше интерфейс, тем лучше». Senior сразу заметит, если ваш интерфейс содержит 10 методов. Это не Java. Go-интерфейсы — это контракты, а не классы.

// Плохо: интерфейс-монстр
type Storage interface {
    Save(key string, value []byte) error
    Load(key string) ([]byte, error)
    Delete(key string) error
    List() ([]string, error)
    Connect() error
    Close() error
    Stats() (Stats, error)
}

Senior скажет: «Разбей на мелкие интерфейсы. Пользователь твоего кода не должен зависеть от методов, которые он не использует».

// Хорошо: интерфейсы по одной ответственности
type Reader interface {
    Load(key string) ([]byte, error)
}

type Writer interface {
    Save(key string, value []byte) error
}

type Closer interface {
    Close() error
}

Проблема: интерфейсы там, где они не нужны

Обратная сторона — интерфейсы ради интерфейсов. Если у вас только одна реализация — интерфейс не нужен. Senior спросит: «Ты правда планируешь заменять реализацию? Если нет — убери лишнюю абстракцию».

4. Именование и структура пакетов

Проблема: плохие имена переменных и функций

Go — язык, где читаемость кода ценится превыше всего. Senior на code review обязательно укажет на:

  • Слишком короткие имена: a, b, c — допустимо только в однострочных циклах.
  • Слишком длинные имена: GetUserProfileDataFromDatabase — в Go это называется UserProfile (пакет уже знает, что он про базу данных).
  • Венгерская нотация: strUserName, intCount — в Go типы и так известны компилятору.
// Плохо: шум в именах
func GetUserInfoByUserID(userID int) (*UserInfo, error) // ❌ UserID и UserInfo в одном неймсп                

Блоги

Рекомендаций по книгам