Модуль
1Пакеты: основы← вы здесь2Go модули3Тестирование4Инструменты Go
Урок 1~12 минут

Пакеты: основы

Пакет — единица организации кода

В 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 (внешнее тестирование)
go
// user/user.go
package user
 
// user/validator.go
package user  // обязан совпадать

Экспорт: заглавная буква решает всё

В других языках есть ключевые слова public, private, protected. В Go — только одно правило: начинается с заглавной буквы = экспортировано:

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 — видимость прямо в имени, не нужно искать модификатор.


Импорты

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.Get
  • import "encoding/json"json.Marshal
  • import "database/sql"sql.Open

Псевдонимы импортов

go
import (
    "fmt"
 
    // Псевдоним: когда имя конфликтует или слишком длинное
    mrand "math/rand"           // mrand.Intn(10)
    crand "crypto/rand"         // crand.Read(buf)
 
    // Точечный импорт: имена пакета в текущее пространство имён
    . "math"                    // Sqrt(4) вместо math.Sqrt(4)
    // Антипаттерн — затрудняет чтение, используй только в тестах
)

Точечный импорт (. "pkg") считается плохим тоном в обычном коде — сложно понять откуда пришло имя. Допустим в тестах для DSL-подобного синтаксиса.


Функция init()

init() — специальная функция, которая запускается автоматически при инициализации пакета:

go
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() — например, для регистрации драйвера БД:

go
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 не используется напрямую — только _.

Другие примеры:

go
_ "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 выдаст ошибку компиляции.

Это отличный способ скрыть детали реализации не создавая монолитный пакет.


Называй пакеты правильно

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.

В Go экспортируется всё что начинается с заглавной буквы. Имя пакета — последняя часть пути импорта: import "net/http" → используешь как http.Get. Один пакет = одна директория.
package user — идентификаторы
structUserEXPORTED
funcNewUserEXPORTED
funcvalidatePRIVATE
vardefaultTimeoutPRIVATE
constMaxRetriesEXPORTED
fieldagePRIVATE
fieldEmailEXPORTED
из другого пакета — доступность
user.Userдоступно
user.NewUserдоступно
user.validatecompile error
user.defaultTimeoutcompile error
user.MaxRetriesдоступно
user.agecompile error
user.Emailдоступно
user/user.go
package user const ( MaxRetries = 3 ) var defaultTimeout = 30 type User struct { age string Email string } func NewUser() {} func validate() {}
main/main.go
package main import "user" func main() { u := user.User{} // OK user.NewUser() // OK user.validate() // ERROR _ = user.defaultTimeout // ERROR _ = user.MaxRetries // OK _ = u.age // ERROR _ = u.Email // OK }
🎯
Миссия 1 из 4
Как в Go обозначить что функция или тип экспортируется из пакета?