Как в Go реализована обработка ошибок? Расскажите про `error`, `panic` и `recover`.

Ответ

В Go отсутствует традиционный механизм исключений (try-catch-finally), как в Java или C++. Вместо этого используется подход, основанный на явной обработке ошибок как значений.

1. error — основной механизм

Идиоматический способ обработки ожидаемых ошибок — это возврат значения типа error последним из функции. error — это встроенный интерфейс:

 type error interface {
    Error() string
}

Если функция выполнилась успешно, она возвращает nil в качестве ошибки. В противном случае — объект ошибки.

import "os"

func readFile(path string) ([]byte, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        // Ошибка обрабатывается сразу на месте вызова
        return nil, err 
    }
    return data, nil
}

Современные практики: Оборачивание ошибок (Error Wrapping)

Начиная с Go 1.13, появилась возможность оборачивать ошибки, чтобы сохранить контекст. Это делается с помощью fmt.Errorf и директивы %w.

import (
    "fmt"
    "os"
)

func processFile(path string) error {
    data, err := os.ReadFile(path)
    if err != nil {
        // Оборачиваем исходную ошибку, добавляя контекст
        return fmt.Errorf("failed to process file: %w", err)
    }
    // ...
    return nil
}

Для проверки цепочки ошибок используются функции errors.Is() (проверка на конкретное значение ошибки) и errors.As() (проверка на соответствие типа ошибки).

2. panic — для исключительных ситуаций

panic — это встроенная функция, которая останавливает обычный поток выполнения и начинает "панику". Программа завершается, если паника не будет обработана.

Когда использовать panic?

  • При возникновении действительно невосстановимой ошибки, когда программа не может продолжать безопасное выполнение (например, не удалось инициализировать критически важный компонент при старте).
  • В коде, который не должен падать (например, ошибки программиста, такие как выход за пределы массива).
if configPath == "" {
    panic("Critical: configuration path is not set")
}

3. recover — для перехвата паники

recover — это встроенная функция, которая позволяет восстановить контроль над программой после паники. recover работает только внутри defer-функций.

Её основное применение — предотвратить падение всего приложения из-за ошибки в одной из подпрограмм (например, в обработчике HTTP-запроса, чтобы сервер не упал из-за одного сбойного запроса).

func safeHandler() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
            // Здесь можно залогировать ошибку и отдать корректный ответ клиенту
        }
    }()

    // Код, который может вызвать панику
    potentiallyPanicFunction()
}

Важно: panic/recover не является заменой error. Это механизм для обработки непредвиденных, катастрофических сбоев, а не для обычной логики обработки ошибок.