Ваш 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 в одном неймсп