Go / Өөрийн алдаа үүсгэх

Өөрийн алдаа үүсгэх

errors.New болон fmt.Errorf нь энгийн алдааны мессеж буцаахад хангалттай. Гэхдээ заримдаа алдаа дотор нэмэлт мэдээлэл (ямар файл, ямар мөр, ямар утга) хадгалах шаардлага гардаг. Тэр үед өөрийн алдааны тип үүсгэнэ.

Өөрийн алдааны struct үүсгэх

error interface-ийг хэрэгжүүлсэн ямар ч struct нь алдаа болж чадна. Зөвхөн Error() string метод байхад хангалттай:

go
package main

import "fmt"

// Өөрийн алдааны тип
type ValidationError struct {
    Field   string
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("баталгаажуулалтын алдаа [%s]: %s", e.Field, e.Message)
}

func validateAge(age int) error {
    if age < 0 {
        return &ValidationError{Field: "age", Message: "нас сөрөг байж болохгүй"}
    }
    if age > 150 {
        return &ValidationError{Field: "age", Message: "нас 150-аас их байж болохгүй"}
    }
    return nil
}

func main() {
    err := validateAge(-5)
    if err != nil {
        fmt.Println(err)
    }

    err = validateAge(200)
    if err != nil {
        fmt.Println(err)
    }

    err = validateAge(25)
    if err == nil {
        fmt.Println("Нас зөв байна")
    }
}

Алдааны тип шалгах — errors.As

Өөрийн алдааны тип буцаасан бол errors.As ашиглан тухайн алдааны нэмэлт мэдээллийг авч болно:

go
package main

import (
    "errors"
    "fmt"
)

type DatabaseError struct {
    Code    int
    Message string
}

func (e *DatabaseError) Error() string {
    return fmt.Sprintf("өгөгдлийн сангийн алдаа (код: %d): %s", e.Code, e.Message)
}

func queryUser(id int) error {
    if id == 0 {
        return &DatabaseError{Code: 404, Message: "хэрэглэгч олдсонгүй"}
    }
    if id < 0 {
        return &DatabaseError{Code: 500, Message: "серверийн дотоод алдаа"}
    }
    return nil
}

func main() {
    err := queryUser(0)
    if err != nil {
        var dbErr *DatabaseError
        if errors.As(err, &dbErr) {
            fmt.Printf("Алдааны код: %d\n", dbErr.Code)
            fmt.Printf("Алдааны мессеж: %s\n", dbErr.Message)
        } else {
            fmt.Println("Тодорхойгүй алдаа:", err)
        }
    }
}

errors.As нь type assertion-с хамаагүй аюулгүй — алдааны гинжийг дагаж шалгадаг.

Алдаа ороох — %w

fmt.Errorf дотор %w ашиглан алдааг ороож болно. Ингэснээр алдааны гинж үүснэ:

go
package main

import (
    "errors"
    "fmt"
)

type NotFoundError struct {
    Name string
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s олдсонгүй", e.Name)
}

func findProduct(name string) error {
    // Дотоод алдааг ороож гадна гаргаж байна
    inner := &NotFoundError{Name: name}
    return fmt.Errorf("бүтээгдэхүүн хайлт амжилтгүй: %w", inner)
}

func main() {
    err := findProduct("утас")
    if err != nil {
        fmt.Println(err)

        // Ороосон алдааг задлах
        var nfe *NotFoundError
        if errors.As(err, &nfe) {
            fmt.Printf("Олдоогүй зүйл: %s\n", nfe.Name)
        }
    }
}

%w ашиглан ороосон алдааг errors.As болон errors.Is ашиглан задлаж болдог.

errors.Is — тодорхой алдаа шалгах

Sentinel error (урьдчилан тодорхойлсон алдаа) ашиглах үед errors.Is хэрэглэнэ:

go
package main

import (
    "errors"
    "fmt"
)

// Sentinel errors — урьдчилан тодорхойлсон алдаанууд
var (
    ErrNotFound    = errors.New("олдсонгүй")
    ErrUnauthorized = errors.New("зөвшөөрөлгүй")
)

func getResource(id int, loggedIn bool) error {
    if !loggedIn {
        return fmt.Errorf("нөөц авахад алдаа: %w", ErrUnauthorized)
    }
    if id > 10 {
        return fmt.Errorf("нөөц #%d: %w", id, ErrNotFound)
    }
    return nil
}

func main() {
    err := getResource(99, true)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("Хуудас олдсонгүй — 404")
    }

    err = getResource(5, false)
    if errors.Is(err, ErrUnauthorized) {
        fmt.Println("Нэвтрэх шаардлагатай — 401")
    }
}

Дараагийн хичээлд:

Хүлээгдээгүй, сэргээх боломжгүй нөхцөл байдлыг зохицуулах panic болон recover механизмыг судална.