Пакеты: основы
Пакет — единица организации кода
В Go каждый файл принадлежит пакету. Пакет — это директория с .go файлами, все из которых объявляют одно и то же имя:
myapp/
├── main.go // package main
├── user/
│ ├── user.go // package user
│ └── validator.go // package user (тот же пакет!)
└── store/
└── store.go // package store
Правила:
- Одна директория = один пакет
- Все
.goфайлы в директории должны объявлять одинаковое имя пакета - Исключение: тесты могут использовать
package user_test(внешнее тестирование)
// user/user.go
package user
// user/validator.go
package user // обязан совпадатьЭкспорт: заглавная буква решает всё
В других языках есть ключевые слова public, private, protected. В Go — только одно правило: начинается с заглавной буквы = экспортировано:
package user
// Экспортировано — видно из других пакетов:
type User struct {
Name string // экспортированное поле
Email string // экспортированное поле
age int // НЕ экспортировано (строчная буква)
}
func NewUser(name, email string) *User { ... } // экспортировано
func validate(u *User) error { ... } // не экспортировано
var DefaultTimeout = 30 * time.Second // экспортировано
var maxRetries = 3 // не экспортированоЭто одно из самых элегантных решений в Go — видимость прямо в имени, не нужно искать модификатор.
Импорты
import "fmt" // стандартная библиотека
import "net/http" // пакет использовать как http.Get(...)
import "github.com/user/pkg" // внешняя зависимость
// Групповой импорт (идиоматично):
import (
"fmt"
"os"
"net/http"
"github.com/user/pkg" // внешние — обычно отдельная группа
)Имя пакета в коде — последняя часть пути:
import "net/http"→ используешь какhttp.Getimport "encoding/json"→json.Marshalimport "database/sql"→sql.Open
Псевдонимы импортов
import (
"fmt"
// Псевдоним: когда имя конфликтует или слишком длинное
mrand "math/rand" // mrand.Intn(10)
crand "crypto/rand" // crand.Read(buf)
// Точечный импорт: имена пакета в текущее пространство имён
. "math" // Sqrt(4) вместо math.Sqrt(4)
// Антипаттерн — затрудняет чтение, используй только в тестах
)Точечный импорт (. "pkg") считается плохим тоном в обычном коде — сложно понять откуда пришло имя. Допустим в тестах для DSL-подобного синтаксиса.
Функция init()
init() — специальная функция, которая запускается автоматически при инициализации пакета:
package config
var settings map[string]string
func init() {
settings = make(map[string]string)
settings["timeout"] = "30s"
settings["maxRetries"] = "3"
// Загружаем конфиг из файла при старте
if err := loadFromFile("config.json"); err != nil {
log.Printf("конфиг не найден, используем defaults: %v", err)
}
}Особенности init():
- Вызывается автоматически, вручную вызвать нельзя
- Запускается после инициализации всех переменных пакета
- В одном файле может быть несколько
init()— все выполнятся - Выполняется до
main()
Порядок инициализации:
1. Переменные пакета (var x = ...)
2. init() всех импортируемых пакетов (рекурсивно)
3. init() текущего пакета
4. main() (только для пакета main)
Blank import: побочные эффекты
Иногда пакет нужно импортировать только ради выполнения его init() — например, для регистрации драйвера БД:
import (
"database/sql"
_ "github.com/lib/pq" // blank import — только для init()
)
func main() {
// pq.init() зарегистрировал драйвер "postgres"
db, err := sql.Open("postgres", dsn)
}Без _ "github.com/lib/pq" драйвер не зарегистрируется и sql.Open("postgres", ...) вернёт ошибку. Пакет pq не используется напрямую — только _.
Другие примеры:
_ "image/png" // регистрирует PNG декодер
_ "image/jpeg" // регистрирует JPEG декодер
_ "net/http/pprof" // регистрирует /debug/pprof эндпоинтыВнутренние пакеты: internal
Директория internal ограничивает видимость пакета:
myapp/
├── main.go
├── api/
│ └── handler.go // может импортировать internal/
└── internal/
├── database/
│ └── db.go // виден только внутри myapp/
└── cache/
└── cache.go // виден только внутри myapp/
Пакеты внутри internal/ могут импортировать только родительский пакет и его дочерние пакеты. Внешние модули не смогут их импортировать — Go выдаст ошибку компиляции.
Это отличный способ скрыть детали реализации не создавая монолитный пакет.
Называй пакеты правильно
// Хорошо: короткое, строчное, без подчёркиваний
package user
package store
package config
package httputil
// Плохо:
package UserService // CamelCase — нет
package user_service // подчёркивания — нет
package utils // слишком общее, ни о чём не говорит
package common // то же самое
package helpers // и это тоже
// Имя пакета должно отражать что он делает:
// net/http — работа с HTTP
// encoding/json — кодирование в JSON
// database/sql — работа с SQLТипичная ошибка джуна — создать пакет utils или helpers и свалить туда всё подряд. Лучше дать функциям конкретный дом: stringutil, timeutil, mathutil — или ещё лучше, разбить по смыслу: format, parse, validate.