Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Назначение defer в Go
Определение
Defer — это оператор в Go, который откладывает выполнение функции до тех пор, пока окружающая функция не завершится. Это мощный инструмент для гарантированной очистки ресурсов и обработки ошибок.
func main() {
defer fmt.Println("Я выполнюсь в конце")
fmt.Println("Я выполнюсь первым")
fmt.Println("Я выполнюсь вторым")
}
// Вывод:
// Я выполнюсь первым
// Я выполнюсь вторым
// Я выполнюсь в конце
Основное назначение: Cleanup и Resource Management
1. Закрытие файлов
func ReadFile(filename string) (string, error) {
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close() // Гарантирует закрытие файла в любом случае
// Даже если произойдёт паника, файл будет закрыт
data, err := io.ReadAll(file)
return string(data), err
}
2. Возврат соединения в пул (Connection Pool)
func QueryDatabase(db *sql.DB, id int) (*User, error) {
// Получить соединение из пула
conn, err := db.Conn(context.Background())
if err != nil {
return nil, err
}
defer conn.Close() // Вернуть в пул
var user User
err = conn.QueryRowContext(
context.Background(),
"SELECT id, name FROM users WHERE id = $1",
id,
).Scan(&user.ID, &user.Name)
return &user, err
}
3. Блокировки (Mutexes)
var mu sync.Mutex
var count int
func Increment() {
mu.Lock()
defer mu.Unlock() // Гарантированно разблокирует mutex
count++
// Даже если здесь произойдёт паника, mutex будет разблокирован
}
4. Транзакции БД
func TransferMoney(db *sql.DB, fromID, toID int, amount float64) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback() // На случай ошибки
// Если всё хорошо, будет явный Commit
// Если ошибка — Rollback выполнится автоматически
if _, err := tx.Exec("UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, fromID); err != nil {
return err
}
if _, err := tx.Exec("UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, toID); err != nil {
return err
}
return tx.Commit().Err()
}
LIFO (Last In, First Out) порядок исполнения
func Order() {
defer fmt.Println("1. Первый defer (последний)")
defer fmt.Println("2. Второй defer")
defer fmt.Println("3. Третий defer (первый)")
fmt.Println("Основной код")
}
// Вывод:
// Основной код
// 3. Третий defer (первый)
// 2. Второй defer
// 1. Первый defer (последний)
// defer работает как LIFO стек
Обработка паник
Defer выполняется даже при панике:
func SafeOperation() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Поймана паника:", r)
// Логирование, очистка и т.д.
}
}()
// Некоторый код
panic("Что-то пошло не так") // Паника!
}
// Вывод: Поймана паника: Что-то пошло не так
Практический пример: HTTP обработчик
func HandleRequest(w http.ResponseWriter, r *http.Request) {
// Логирование начала
startTime := time.Now()
defer func() {
duration := time.Since(startTime)
log.Printf("Запрос обработан за %v\n", duration)
}()
// Открыть БД соединение
conn, err := db.Conn(r.Context())
if err != nil {
http.Error(w, "DB error", 500)
return
}
defer conn.Close()
// Получить данные
data, err := conn.QueryContext(r.Context(), "SELECT * FROM users")
if err != nil {
http.Error(w, "Query error", 500)
return
}
defer data.Close()
// Обработка успешна
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(data)
}
Defer с функциями и замыканиями
func DeferWithClosure() {
x := 10
defer func() {
fmt.Println("x:", x) // Напечатает 20 (значение при выполнении)
}()
x = 20
}
// Вывод: x: 20
// ❌ Неправильно: значение захватывается по ссылке
func WrongDefer() {
for i := 0; i < 3; i++ {
defer fmt.Println(i) // Напечатает 2, 1, 0 (последнее значение)
}
}
// Вывод: 2, 1, 0
// ✅ Правильно: использовать параметры функции
func RightDefer() {
for i := 0; i < 3; i++ {
defer func(index int) {
fmt.Println(index)
}(i) // Передаём i как параметр
}
}
// Вывод: 2, 1, 0 (но логика ясна)
Ошибки при использовании defer
// ❌ ОШИБКА 1: Забыли вызвать функцию в defer
defer fmt.Println // Это запланирует функцию, но не выполнит её
// ✅ Правильно:
defer fmt.Println("Сообщение")
// ❌ ОШИБКА 2: Игнорирование ошибок
defer file.Close() // Ошибка закрытия может быть потеряна
// ✅ Правильно:
defer func() {
if err := file.Close(); err != nil {
log.Println("Close error:", err)
}
}()
// ❌ ОШИБКА 3: Defer в цикле (memory leak)
for _, url := range urls {
resp, _ := http.Get(url)
defer resp.Body.Close() // Не выполнится до конца функции!
}
// ✅ Правильно: вынесите в отдельную функцию
for _, url := range urls {
if err := fetchURL(url); err != nil {
log.Println(err)
}
}
func fetchURL(url string) error {
resp, _ := http.Get(url)
defer resp.Body.Close() // Выполнится после fetchURL
return nil
}
Best Practices
✅ Правильно:
// Defer сразу после получения ресурса
resource, err := acquireResource()
if err != nil {
return err
}
defer releaseResource(resource)
// Используйте именованные возвращаемые значения
func Process() (result string, err error) {
defer func() {
if err != nil {
// Логирование
log.Println(err)
}
}()
// ...
}
Заключение
Defer используется для:
- Гарантированной очистки ресурсов (файлы, соединения, блокировки)
- Откладывания выполнения кода до конца функции
- Обработки ошибок и паник
- Логирования и профилирования
Deferзаписывает функции в стек и выполняет их в обратном порядке (LIFO) при выходе из функции, что делает код безопаснее и чище.