Go — не просто модный язык, а инструмент для быстрой разработки
Go (или Golang) за последние годы превратился из экзотического языка в стандарт индустрии для бэкенда, микросервисов и облачных приложений. Его создатели (Роб Пайк, Кен Томпсон) намеренно сделали язык простым, но дьявол, как всегда, в деталях. Многие новички застревают на этапе «я написал Hello World, а что дальше?».
В этой статье я собрал советы от senior-разработчиков, которые помогут вам не просто выучить синтаксис Go, а ускорить изучение Go и начать писать production-ready код. Мы разберем реальные кейсы, типичные ошибки и дадим конкретные action items. Поехали!
1. Забудьте про «ООП из Java» — думайте композицией
Самая частая ошибка новичков — пытаться натянуть классическое ООП (наследование, полиморфизм классов) на Go. В Go нет классов и наследования. Вместо этого — структуры и интерфейсы.
Senior-разработчики советуют: изучайте Go через композицию. Встраивайте одну структуру в другую, используйте интерфейсы для абстракции.
Пример: как НЕ надо
// Плохо: попытка сделать "наследование" через встраивание с переопределением методов
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return "I am an animal"
}
type Dog struct {
Animal
}
func (d Dog) Speak() string {
return "Woof!"
}
Пример: как надо (композиция + интерфейсы)
// Хорошо: используем интерфейс и композицию
type Speaker interface {
Speak() string
}
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return "Woof! I'm " + d.Name
}
func MakeSound(s Speaker) {
fmt.Println(s.Speak())
}
func main() {
dog := Dog{Name: "Rex"}
MakeSound(dog) // Работает с любым Speaker
}
Совет senior: «Начните с interface{} — это пустой интерфейс. Поймите, как Go проверяет типы через type assertion. Это сэкономит вам недели.»
2. Горутины — это не просто «go func()»
Горутины — киллер-фича Go. Но новички часто пишут:
go myFunc()
// ... и забывают про синхронизацию
Это приводит к гонкам данных (data races). Senior-разработчики советуют всегда использовать sync.WaitGroup или каналы для синхронизации.
Правильный паттерн: Worker Pool
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, j)
time.Sleep(time.Second) // имитация работы
results <- j * 2
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// Запускаем 3 воркера
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// Отправляем задачи
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// Ждем завершения всех воркеров
wg.Wait()
close(results)
// Собираем результаты
for r := range results {
fmt.Println("Result:", r)
}
}
Почему это ускоряет изучение Go? Вы сразу учитесь работать с горутинами безопасно, без утечек и паник.
3. Ошибки — это значения (errors are values)
В Go нет исключений (try-catch). Ошибки — это просто значения, которые нужно обрабатывать. Senior-разработчики вбивают это в голову на первых же код-ревью.
Типичная ошибка новичка:
result, _ := doSomething() // Игнорируем ошибку
Как надо:
result, err := doSomething()
if err != nil {
// Логируем, возвращаем, паникуем (но не игнорируем!)
log.Printf("ошибка в doSomething: %v", err)
return
}
// Работаем с result
Лайфхак от senior: Используйте пакет errors для создания ошибок с контекстом:
import "errors"
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("деление на ноль")
}
return a / b, nil
}
А для более сложных случаев — fmt.Errorf("ошибка при делении: %w", err) с обёртыванием ошибок.
4. Не пишите свой велосипед — используйте стандартную библиотеку
Go поставляется с мощнейшей стандартной библиотекой. Senior-разработчики знают её наизусть. Вам не нужно тянуть фреймворки для HTTP-сервера, JSON, тестирования или работы с файлами.
Пример: простой HTTP-сервер без фреймворков
package main
import (
"encoding/json"
"log"
"net/http"
)
type User struct {
Name string `json:"name"`
Age int `json:"age"`
}
func userHandler(w http.ResponseWriter, r *http.Request) {
user := User{Name: "Alice", Age: 30}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func main() {
http.HandleFunc("/user", userHandler)
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Совет senior: «Прочитайте документацию пакетов io, net/http, encoding/json, sync, context. Это 80% вашей работы в продакшене.»
5. Тестируйте сразу — это сэкономит время
Go имеет встроенный инструмент для тестирования (go test). Senior-разработчики пишут тесты параллельно с кодом. Это не только улучшает качество, но и помогает вам лучше понять, как работает ваш код.
Пример: табличный тест
package main
import "testing"
func TestDivide(t *testing.T) {
tests := []struct {
a, b float64
expected float64
hasError bool
}{
{10, 2, 5, false},
{5, 0, 0, true},
{0, 5, 0, false},
}
for _, tt := range tests {
result, err := Divide(tt.a, tt.b)
if tt.hasError && err == nil {
t.Errorf("expected error for %f/%f", tt.a, tt.b