Что это 🧩
Этот навык — справочник и код-ревьюер по идиоматическому Go. Он помогает писать код, который будет легко читать, тестировать и поддерживать, следуя общепринятым практикам сообщества Golang. Навык охватывает ключевые паттерны: функциональные опции, маленькие интерфейсы, внедрение зависимостей, параллелизм, обработку ошибок и организацию пакетов.
Как работает 🛠️
Функциональные опции (Functional Options)
Паттерн для гибкой конфигурации конструкторов без раздувания сигнатуры. Вы определяете тип Option как функцию, меняющую структуру, и применяете вариативный список опций внутри New*.
Пример:
type Option func(*Server)
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
func NewServer(opts ...Option) *Server {
s := &Server{port: 8080} // значение по умолчанию
for _, opt := range opts {
opt(s)
}
return s
}
Плюсы:
- Обратно совместимое расширение API (можно добавить новую опцию, не ломая старые вызовы).
- Параметры со значениями по умолчанию.
- Самодокументируемая настройка (название функции-опции говорит само за себя).
Маленькие интерфейсы 🗂️
Главный принцип: принимай интерфейсы, возвращай структуры. Интерфейсы определяются там, где они нужны (на стороне потребителя), а не там, где они реализуются. Это даёт слабую связность и лёгкое тестирование.
Пример:
// Маленький интерфейс определён в пакете, который его использует
type UserStore interface {
GetUser(id string) (*User, error)
}
func ProcessUser(store UserStore, id string) error {
user, err := store.GetUser(id)
// ...
}
Внедрение зависимостей (Dependency Injection) 💉
Зависимости явно передаются в конструктор с префиксом New*. Конструктор принимает интерфейсы (или конкретные типы), возвращает конкретную структуру, часто с валидацией зависимостей на месте.
Пример:
func NewUserService(repo UserRepository, logger Logger) *UserService {
if repo == nil || logger == nil {
panic("dependencies must not be nil")
}
return &UserService{repo: repo, logger: logger}
}
Паттерны параллелизма ⚡
Навык предлагает две ключевые техники:
- Worker Pool — фиксированное количество горутин параллельно обрабатывают задания из канала
jobs и пишут результаты в results. Используется sync.WaitGroup для синхронизации.
- Контекст — всегда передаётся первым аргументом (
ctx context.Context). Перед началом долгой операции проверяется ctx.Done(), чтобы корректно отменять работу.
Обработка ошибок ⚠️
Три основных подхода:
- Оборачивание ошибок через
fmt.Errorf("...: %w", err) — сохраняет исходную ошибку для errors.Is и errors.As.
- Кастомные типы ошибок — структура с полями (например,
ValidationError), реализующая интерфейс error.
- Sentinel-ошибки — глобальные переменные-ошибки (например,
ErrNotFound), которые сравниваются через errors.Is.
Организация пакетов 📁
Типовая структура Go-проекта:
project/
├── cmd/ # Точки входа (main)
├── internal/ # Приватный код приложения
│ ├── domain/ # Бизнес-логика
│ ├── handler/ # HTTP-обработчики
│ └── repository/ # Доступ к данным
└── pkg/ # Публичные библиотеки
Правила именования:
- Имена пакетов — одно слово, нижний регистр.
- Без статтера:
user.User, а не user.UserModel.
internal/ — для кода, который не должен быть импортирован извне.
main — минимальный, только запуск сервера.
Тестирование 🧪
Два основных шаблона:
- Table-driven tests — слайс структур с полями
name, input, wantErr (или expected), запускаемый через t.Run.
- Test helpers — функции, создающие окружение (например, тестовую БД) с вызовом
t.Helper() и t.Cleanup() для автоматической очистки.
Когда использовать 🔍
- Ты проектируешь новое Go-API или библиотеку.
- Нужно написать надёжную параллельную обработку.
- Рефакторишь большую кодовую базу на Go, чтобы привести её к идиоматичному стилю.
- Готовишься к код-ревью и хочешь следовать best practices.
- Создаёшь структуру многопакетного проекта.
Важно знать ⚡
- Навык не заменяет полную документацию по пакету (например, не описывает все нюансы
context или errors). Это именно практические шаблоны.
internal/ — не просто соглашение: на уровне компилятора Go запрещает импорт из внешних пакетов.
- Sentinel-ошибки лучше объявлять как
var, а не const, и использовать только для ошибок без динамических данных. Для ошибок с контекстом (поля, параметры) подходят кастомные типы.
- В тестах
t.Helper() помечает функцию как вспомогательную: при падении стектрейс укажет на строку вызова, а не на тело helper'а.
- Комментарии к экспортируемым функциям, интерфейсам и типам обязательны (godoc). Навык этого не показывает, но подразумевает.
Комментарии
Комментариев пока нет. Будьте первым.