Ответ
При разработке на Go можно столкнуться с рядом типичных ошибок:
-
Разыменовывание
nilуказателя Это приводит к панике во время выполнения. Классический пример:var p *int fmt.Println(*p) // panic: runtime error: invalid memory address or nil pointer dereference -
Выход за границы слайса или массива Попытка доступа к элементу по индексу, которого не существует.
arr := []int{1, 2, 3} fmt.Println(arr[3]) // panic: runtime error: index out of range [3] with length 3 -
Гонка данных (Data Race) Возникает, когда две или более горутины одновременно обращаются к одной и той же области памяти, и хотя бы одна из них выполняет запись. Для обнаружения используется флаг
-race(go run -race main.go).var counter int go func() { counter++ }() // Запись go func() { counter++ }() // Конкурирующая запись // Для безопасной работы используйте мьютексы (sync.Mutex) или атомарные операции (sync/atomic) -
Взаимная блокировка (Deadlock) Ситуация, когда горутины бесконечно ожидают друг друга. Например, при записи в небуферизованный канал без читателя или при циклической блокировке мьютексов.
// Пример 1: Блокировка канала ch := make(chan int) ch <- 42 // Deadlock: горутина заблокируется навсегда, если никто не читает из ch // Пример 2: Блокировка мьютексов var mu1, mu2 sync.Mutex go func() { mu1.Lock() mu2.Lock() // ... }() go func() { mu2.Lock() mu1.Lock() // Потенциальный deadlock, если горутины выполнятся одновременно // ... }() -
Утечки горутин (Goroutine Leaks) Горутина может "зависнуть" в ожидании данных из канала, который никогда не будет закрыт или в который никто не будет писать. Такие горутины продолжают существовать и потреблять память до конца работы программы.
func leak() { ch := make(chan int) go func() { val := <-ch // Эта горутина будет ждать вечно, если в канал ch никто не запишет fmt.Println(val) }() } -
Неправильная работа с
deferв циклах Переменные, используемые вdefer, фиксируются в момент выполненияdefer, а не в момент его вызова. В циклах это приводит к тому, что все отложенные вызовы будут использовать значение переменной из последней итерации.for i := 0; i < 3; i++ { defer func() { fmt.Println(i) // Выведет "3" три раза }() } // Правильно передавать значение как аргумент: for i := 0; i < 3; i++ { defer func(val int) { fmt.Println(val) // Выведет "2", "1", "0" }(i) }Важно: Распространенное заблуждение, что
deferне выполняется при панике. На самом деле,deferвыполняется всегда перед тем, как функция завершит свою работу, в том числе и во время паники. -
Переопределение переменных (Variable Shadowing) Объявление новой переменной с тем же именем во внутреннем блоке, что и во внешнем. Это частый источник багов.
val := 10 if true { val, err := someFunc() // Ошибка: создана новая переменная `val` if err != nil { /* ... */ } // Внешняя переменная `val` осталась без изменений } // Правильно: val, err = someFunc()
Ответ 18+ 🔞
А, вот ты решил на Go писать, да? Ну что ж, добро пожаловать в мир, где тебя ждёт не только статическая типизация и горутины, но и целый овердохуища подводных граблей, на которые можно наступить с таким треском, что в рот меня чих-пых!
Слушай сюда, я тебе сейчас про основные косяки расскажу, чтобы ты не наступал на те же грабли, что и мы все в своё время.
Первое — это, блядь, указатели. Ну, точнее, их отсутствие. Вот представь: объявляешь ты переменную, а она nil. А потом пытаешься к ней обратиться, как будто там что-то есть. Пиздец, а не паника.
var p *int
fmt.Println(*p) // panic: runtime error: invalid memory address or nil pointer dereference
Вот так вот, на ровном месте. Проверяй, не nil ли твой указатель, прежде чем лезть в него, как в свою жизнь.
Второе — выход за границы. Это классика, ёпта. Массив или слайс на три элемента, а ты лезешь за четвёртый. Ну и что ты там надеешься найти? Пиздюлину от рантайма.
arr := []int{1, 2, 3}
fmt.Println(arr[3]) // panic: runtime error: index out of range [3] with length 3
Третье — гонки данных. Вот это, блядь, настоящая головная боль. Две горутины одновременно пишут в одну переменную. Кто победит? А хуй его знает! Результат каждый раз разный, как погода в Питере. Для этого есть флаг -race, он как детектор лжи, только для твоего кода. Используй мьютексы (sync.Mutex) или атомарные операции, не будь распиздяем.
Четвёртое — взаимная блокировка, deadlock. Ситуация, когда две горутины сидят и смотрят друг на друга, как два идиота, и ждут, пока другая что-то сделает. А они обе ждут. Вечность. Например, пишешь в канал, а читать его некому. Или два мьютекса взялись в разном порядке — и привет, вечный сон.
ch := make(chan int)
ch <- 42 // Сиди и жди, пока кто-то прочитает. А никто не придёт.
Пятое — утечка горутин. Создал горутину, она пошла в канал за данными, а данных-то и нет. И не будет. А она сидит, ждёт, память жрёт. Как та собака из анекдота, которая ждёт хозяина, а он на курорте. Закрывай каналы, продумывай логику завершения, не плоди сущности без надобности.
Шестое — defer в циклах. Вот тут многие обжигаются, я сам чуть не обосрался. Переменная в defer фиксируется не тогда, когда ты её отложил, а когда функция, в которой defer, завершается. В цикле все defer'ы будут использовать значение из последней итерации. Чтобы этого не было, передавай значение как аргумент.
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i) // Напечатает "3" три раза! Пиздец!
}()
}
// Правильно — так:
for i := 0; i < 3; i++ {
defer func(val int) {
fmt.Println(val) // Вот теперь будет 2, 1, 0
}(i)
}
И да, запомни раз и навсегда: defer выполняется ВСЕГДА, даже если в функции случилась паника. Он как верный пёс, который приберётся за тобой в любом случае.
Седьмое — переопределение переменных (shadowing). Объявляешь переменную val снаружи, а внутри блока, по привычке, через := создаёшь новую с тем же именем. И потом удивляешься: "Почему моя внешняя val не изменилась, ёбта?!"
val := 10
if true {
val, err := someFunc() // Создалась НОВАЯ val, локальная!
// Внешняя val так и осталась 10
}
// Правильно: val, err = someFunc()
Вот такие пироги, дружище. Go — язык мощный, но требует внимательности, а то так и до "панки-дринки-встаньки" недалеко. Держи ухо востро и не повторяй чужих ошибок, их и своих хватит.