Введение: почему чистый код на Go — это не роскошь, а необходимость
Go (или Golang) — язык, который с первого взгляда кажется простым и даже скучным. В нём нет дженериков (до версии 1.18), нет классов, нет виртуальной машины. Но именно эта минималистичность делает его идеальным полем для битвы за чистый код. Если вы пишете на Go и ваш проект начинает напоминать спагетти из goroutine и нечитаемых switch — вы теряете главное преимущество языка: скорость разработки и простоту поддержки.
В этой статье мы разберём 7 практических правил, которые помогут вам писать идиоматический Go-код, который будет понятен не только компилятору, но и вашим коллегам (и вам самому через месяц). Поехали!
1. Правильные имена переменных: коротко и ясно
В Go принято использовать короткие, но осмысленные имена. В отличие от Java, где ListOfUserOrders — это норма, в Go вы увидите orders или даже os. Почему? Потому что Go — язык для чтения кода, а не для его написания.
Основные правила нейминга в Go
- Локальные переменные: 1-2 буквы для очевидных вещей (
i— счётчик,s— строка). - Пакеты: одно слово, строчные буквы, без подчёркиваний (
http,fmt). - Экспортируемые идентификаторы: начинаются с заглавной буквы (
GetUser). - Акроним: пишутся в одном регистре (
HTTP,URL, а неHttpилиUrl).
// Плохо
func GetUserDataFromDatabaseByID(userID string) (UserData, error) {
var userDataFromDb UserData
// ...
return userDataFromDb, nil
}
// Хорошо
func GetUser(id string) (User, error) {
var u User
// ...
return u, nil
}
Совет: если имя переменной занимает больше 2 слов — скорее всего, вы что-то усложняете.
2. Форматирование: доверьтесь gofmt
В Go нет «стилевых войн». Команда gofmt решает всё за вас. Это не рекомендация — это стандарт. Если вы не используете gofmt — вы пишете не на Go, а на «диалекте Go».
Что делает gofmt?
- Расставляет отступы (табуляция, а не пробелы).
- Убирает лишние переносы строк.
- Выравнивает комментарии.
- Удаляет неиспользуемые импорты (с флагом
-s).
// До gofmt
func add (a int,b int) int{
return a+b
}
// После gofmt
func add(a, b int) int {
return a + b
}
В любом современном редакторе (VS Code, GoLand) gofmt запускается автоматически при сохранении. Если нет — настройте. Это сэкономит часы код-ревью.
3. Обработка ошибок: не игнорируйте, не паникуйте
В Go нет исключений (try-catch). Ошибки — это просто значения, которые нужно проверять. Это одна из самых сильных сторон языка, если вы используете её правильно.
Правила чистой работы с ошибками
- Всегда проверяйте
error. Никаких_для пропуска. - Не используйте
panicв библиотечном коде. Только если программа не может продолжить работу (например, не открылся конфиг). - Оборачивайте ошибки с помощью
fmt.Errorf("context: %w", err)(Go 1.13+).
// Плохо
func ReadConfig(path string) *Config {
data, _ := ioutil.ReadFile(path) // игнорируем ошибку
var cfg Config
json.Unmarshal(data, &cfg)
return &cfg
}
// Хорошо
func ReadConfig(path string) (*Config, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config: %w", err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config: %w", err)
}
return &cfg, nil
}
Важно: никогда не пишите if err != nil { return nil, err } без контекста. Добавляйте хотя бы одно слово, чтобы понять, где произошла ошибка.
4. Интерфейсы: маленькие и только там, где нужны
В Go принято правило: «принимайте интерфейсы, возвращайте структуры». Это не догма, но хорошая практика. Интерфейсы в Go — это контракты, и они должны быть минимальными.
Как не перегрузить интерфейсы
- Один метод — один интерфейс (вспомните
io.Reader,io.Writer). - Не создавайте интерфейсы «на вырост». Только когда есть 2+ реализации.
- Не называйте интерфейсы с суффиксом
I(как в C#). В Go принято имя +er:Reader,Writer,Sender.
// Плохо: огромный интерфейс
type IUserService interface {
GetUser(id string) User
CreateUser(u User) error
UpdateUser(u User) error
DeleteUser(id string) error
SendEmail(email string) error
}
// Хорошо: разделяем на маленькие
type UserGetter interface {
GetUser(id string) (User, error)
}
type EmailSender interface {
Send(email string) error
}
Такой подход делает код гибким: вы можете легко подменить реализацию для тестов, не таща за собой лишние методы.
5. Структура проекта: стандартное расположение
Go — не Java, и структура папок здесь гораздо проще. Но даже в простоте можно запутаться. Придерживайтесь конвенции, описанной в официальном Go project layout.
Базовая структура для микросервиса
myapp/
├── cmd/
│ └── myapp/
│ └── main.go # точка входа
├── internal/
│ ├── handler/ # HTTP-хендлеры
│ ├── service/ # бизнес-логика
│ └── repository/ # работа с БД
├── pkg/ # публичные библиотеки
├── config/ # конфиги (YAML, env)
├── go.mod
└── Makefile
Поч