Топ-5 ошибок начинающих Go разработчиков и как их избежать

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

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

Начать курс

Введение: Почему Go кажется простым, но таит ловушки

Go (Golang) — это компилируемый, статически типизированный язык, созданный в недрах Google. Его часто хвалят за простоту синтаксиса, встроенную поддержку конкурентности и высокую производительность. Однако именно эта кажущаяся простота часто играет злую шутку с новичками, приходящими из динамических языков (Python, JavaScript) или даже из C++.

Многие разработчики, начав писать на Go, совершают одни и те же типичные ошибки. В этой статье мы разберем топ-5 самых частых проблем, с которыми сталкиваются начинающие Go-разработчики, и покажем, как их исправить с помощью наглядных примеров кода.

1. Игнорирование возвращаемых ошибок (Error Handling)

Проблема

В Go нет исключений (try-catch). Ошибка — это просто значение, которое функция возвращает как последний аргумент. Самая распространенная ошибка новичка — присвоить результат переменной, но проигнорировать ошибку с помощью подчеркивания _.

// ПЛОХО: ошибка проглочена
result, _ := doSomething()
fmt.Println(result)

Почему это плохо?

Программа продолжит работу с некорректными данными, что может привести к панике, утечке памяти или логическим багам, которые сложно отловить.

Как правильно

Всегда проверяйте ошибку. Используйте идиоматический паттерн:

// ХОРОШО: ошибка обработана
result, err := doSomething()
if err != nil {
    log.Printf("Ошибка при выполнении doSomething: %v", err)
    return // или panic, или другая логика
}
fmt.Println(result)

Совет: Пишите свой код так, чтобы if err != nil стало вашим рефлексом. Не бойтесь раннего возврата (early return) — это делает код чище.

2. Путаница с nil и пустыми срезами/картами

Проблема

В Go nil — это не "пустой объект". Это нулевое значение для указателей, срезов, карт, каналов и интерфейсов. Новички часто пытаются записать данные в nil карту или срез, получая панику.

// ПЛОХО: паника при записи в nil map
var m map[string]int
m["key"] = 42 // panic: assignment to entry in nil map

Как правильно

Всегда инициализируйте карты и срезы перед использованием:

// ХОРОШО: инициализация через make
m := make(map[string]int)
m["key"] = 42

// Или через литерал
m2 := map[string]int{}
m2["key"] = 42

Со срезами ситуация хитрее: в nil срез можно читать (он вернет пустой срез), но нельзя записывать по индексу. Используйте append:

var s []int
s = append(s, 1) // OK, создаст новый срез
// s[0] = 1 // panic: runtime error: index out of range [0] with length 0

3. Неправильное использование горутин и гонка данных (Data Race)

Проблема

Горутины — это легковесные потоки. Новички часто запускают горутины, забывая синхронизировать доступ к общим данным. Это приводит к гонке данных (data race) — неопределенному поведению программы.

// ПЛОХО: гонка данных
var counter int
for i := 0; i < 1000; i++ {
    go func() {
        counter++ // небезопасно!
    }()
}
time.Sleep(time.Second)
fmt.Println(counter) // результат непредсказуем

Как правильно

Используйте мьютексы (sync.Mutex) или каналы для синхронизации:

// ХОРОШО: использование мьютекса
var (
    counter int
    mu      sync.Mutex
)
for i := 0; i < 1000; i++ {
    go func() {
        mu.Lock()
        counter++
        mu.Unlock()
    }()
}
// Ждем завершения (лучше использовать sync.WaitGroup)
time.Sleep(time.Second)
fmt.Println(counter) // 1000

Важно: Всегда проверяйте код на гонки с помощью флага -race: go run -race main.go.

4. Ошибки с замыканиями и переменными цикла

Проблема

Классическая ловушка для новичков. В Go переменная цикла i переиспользуется на каждой итерации. Если вы запускаете горутину внутри цикла, она захватывает ссылку на одну и ту же переменную.

// ПЛОХО: все горутины увидят последнее значение i
for i := 1; i <= 3; i++ {
    go func() {
        fmt.Println(i) // напечатает 3,3,3 (или 4,4,4)
    }()
}
time.Sleep(time.Second)

Как правильно

Создавайте локальную копию переменной на каждой итерации (до Go 1.22) или используйте новое поведение цикла (Go 1.22+):

// ХОРОШО (Go < 1.22): копирование переменной
for i := 1; i <= 3; i++ {
    i := i // создаем новую переменную в области видимости горутины
    go func() {
        fmt.Println(i)
    }()
}

// ХОРОШО (Go 1.22+): переменная цикла теперь уникальна на каждой итерации
for i := 1; i <= 3; i++ {
    go func() {
        fmt.Println(i) // 1,2,3 (порядок не гарантирован)
    }()
}

5. Злоупотребление интерфейсами и путаница с пустым интерфейсом

Проблема

Пустой интерфейс interface{} (или any в Go 1.18+) — это мощный, но опасный инструмент. Новички часто используют его как "универсальный тип", чтобы избежать дженериков (до Go

Блоги

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