Почему даже гениальный код может провалить проект?
Представьте: вы пишете на Go. Код работает быстро, тесты проходят, а продакшен радует стабильностью. Но в понедельник приходит новый разработчик, открывает ваш модуль и… впадает в ступор. Переменные называны x1, data2, tmp. Функции — func1, process. Импорты разбросаны хаотично. Код скомпилируется, но понять его — задача со звёздочкой.
Знакомая ситуация? Если вы работаете в команде, code style и конвенции для Go — это не прихоть линтера, а ваш общий язык. Без него даже самый талантливый разработчик превращается в «соло-игрока», который тормозит всю команду. В этой статье мы разберём, как писать на Go так, чтобы ваш код читали, понимали и любили коллеги. А заодно — как не попасть в ловушку «быстро, но грязно».
Go — это про простоту. Но простоту нужно договаривать
Язык Go был создан с мыслью о командной работе. Не зря Роб Пайк и Кен Томпсон закладывали в него идею «читаемости превыше всего». Однако даже в Go можно написать код, который будет работать, но вызывать желание переписать его с нуля.
Давайте разберём ключевые блоки, которые превращают хаос в стройную систему.
1. gofmt — ваш первый друг и соратник
В Go есть встроенный инструмент форматирования — gofmt. Это не просто «красивости». Это стандарт. Если вы не используете gofmt — вы нарушаете конвенцию. Точка.
Пример: представьте, что один разработчик ставит отступы табами, другой — пробелами. В Go эта проблема решена раз и навсегда. gofmt приводит код к единому виду. Запускайте его перед каждым коммитом. Лучше — настройте pre-commit hook.
// Плохо (без gofmt)
func add(a int,b int) int {
return a+b
}
// Хорошо (после gofmt)
func add(a, b int) int {
return a + b
}
Совет: В современных IDE (VS Code, GoLand) gofmt запускается автоматически при сохранении. Проверьте настройки — это сэкономит часы code review.
2. Нейминг: говорите на языке предметной области
Go поощряет короткие имена. Но короткие — не значит бессмысленные. Правило простое: чем больше область видимости, тем длиннее имя.
- Локальные переменные (внутри функции) — 1-2 символа:
i,j,s,err. - Параметры функции — осмысленные, но краткие:
ctx,req,db. - Экспортируемые функции и типы — полные имена:
CreateUser,UserService.
Плохой пример:
func p(u string, pwd string) bool {
return u == "admin" && pwd == "12345"
}
Хороший пример:
func IsValidCredentials(username, password string) bool {
return username == "admin" && password == "12345"
}
Обратите внимание: IsValidCredentials — это не просто имя, это документация. Другой разработчик сразу поймёт, что делает функция, не заглядывая в тело.
3. Импорты: порядок — залог спокойствия
В Go импорты делятся на три группы:
- Стандартная библиотека (fmt, net/http, os).
- Внешние пакеты (github.com/xxx).
- Внутренние пакеты (ваш проект).
Между группами — пустая строка. Это негласное, но строгое правило. gofmt его не проверяет, но code review — обязательно.
import (
"fmt"
"os"
"github.com/gorilla/mux"
"github.com/lib/pq"
"yourproject/internal/config"
"yourproject/internal/handler"
)
Почему это важно? Когда импорты разбросаны, вы тратите время на поиск пакета. Единый порядок ускоряет чтение кода.
4. Комментарии: не пишите очевидное
Хороший код говорит сам за себя. Комментарии нужны для объяснения «почему», а не «что». Если вы пишете // прибавляем 1 к i — вы зря тратите время. Лучше назовите переменную counter.
В Go есть золотое правило: каждый экспортируемый элемент (функция, тип, константа) должен иметь комментарий. И он должен начинаться с имени этого элемента.
// UserService управляет операциями с пользователями.
type UserService struct {
repo UserRepository
}
// CreateUser создаёт нового пользователя и возвращает его ID.
func (s *UserService) CreateUser(ctx context.Context, name, email string) (int, error) {
// ...
}
Такой комментарий — это документация, которую видит godoc. Коллеги скажут вам спасибо.
Конвенции, которые спасают от багов
5. Обработка ошибок: не игнорируйте, а оборачивайте
В Go нет исключений. Ошибки — это значения. И их нужно обрабатывать явно. Плохая практика — игнорировать ошибку с помощью _.
Правильный подход: если функция может вернуть ошибку, проверяйте её. Если ошибку нужно передать выше — оборачивайте её с контекстом.
// Плохо
data, _ := ioutil.ReadFile("config.json")
// Хорошо
data, err := ioutil.ReadFile("config.json")
if err != nil {
return fmt.Errorf("read config: %w", err)
}
Оборачивание через %w позволяет использовать errors.Is и errors.As — это мощный механизм для проверки типов ошибок на верхних уровнях.
6. Паника — это не ошибка
Паника в Go предназначена для truly exceptional ситуаций: разыменование нулевого указателя, выход за границы массива. Не используйте панику для обработки бизнес-логики. Если что-то пошло не так — возвращайте ошибку.
// Плохо
func Divide(a, b int) int {
if b == 0 {
panic("division by zero")
}
return a / b
}
// Хорошо
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
Помните: паника в горутине может обрушить всю программу. Ошибки — безопаснее и предсказу