Ошибка — часть результата
В Go функция, которая может не справиться, обычно возвращает ошибку последним результатом. Успех обозначает nil; любое ненулевое значение нужно проверить до использования остальных результатов. Текст ошибки можно создать через errors.New, а контекст добавить форматированием.
При оборачивании используй verb %w: он сохраняет исходную ошибку внутри цепочки. errors.Is(err, target) отвечает, есть ли в цепочке конкретная ошибка или sentinel. errors.As(err, &target) ищет в цепочке ошибку нужного типа и при успехе записывает её в переменную. В As передают указатель на переменную подходящего типа.
Для собственного типа ошибки реализуй метод Error() string с получателем. Структурированная ошибка полезна, когда вызывающему коду нужны данные вроде неверного значения; для ветвления по стабильной категории подойдёт sentinel.
Пример
package main
import (
"errors"
"fmt"
"strconv"
)
var errDivisionByZero = errors.New("division by zero")
type portRangeError struct {
port int
}
// Error describes a port that is outside the accepted range.
func (err *portRangeError) Error() string {
return fmt.Sprintf("port %d is outside 1..65535", err.port)
}
func parsePort(input string) (int, error) {
port, err := strconv.Atoi(input)
if err != nil {
return 0, fmt.Errorf("parse port %q: %w", input, err)
}
if port < 1 || port > 65535 {
return 0, fmt.Errorf("validate port: %w", &portRangeError{port: port})
}
return port, nil
}
func divide(total, count int) (int, error) {
if count == 0 {
return 0, fmt.Errorf("divide %d by %d: %w", total, count, errDivisionByZero)
}
return total / count, nil
}
func main() {
port, err := parsePort("70000")
if err != nil {
var rangeErr *portRangeError
if errors.As(err, &rangeErr) {
fmt.Printf("неподходящий порт: %d\n", rangeErr.port)
} else {
fmt.Printf("ошибка настройки: %v\n", err)
}
} else {
fmt.Printf("порт: %d\n", port)
}
_, err = divide(10, 0)
if errors.Is(err, errDivisionByZero) {
fmt.Printf("деление отклонено: %v\n", err)
}
}
Порт 70000 приводит к пользовательской ошибке с полем port; обёртка добавляет контекст, а errors.As всё ещё находит исходный тип. Обработка этой ошибки не прерывает программу, поэтому в том же запуске видна проверка ошибки деления через errors.Is.
Не сравнивай произвольные ошибки по строке и не создавай заново один и тот же sentinel внутри каждой функции. Возвращай полезный контекст, сохраняй причинную ошибку через %w, а вызывающий код пусть выбирает действие по типу или категории.