Go / panic ба recover

panic ба recover

Go-д алдааг ихэвчлэн утга болгон буцаадаг гэж сурсан. Гэхдээ зарим нэг нөхцөл байдал бий — программ үргэлжлэн ажиллах боломжгүй болох гэнэтийн, хүнд нөхцөл. Тэр үед panic ба recover ажилладаг.

panic гэж юу вэ?

panic нь программыг нэн даруй зогсоодог функц юм. Суурилагдсан Go функцүүд заримдаа panic үүсгэдэг — жишээ нь slice-ын хязгаараас гарах үед:

go
package main

import "fmt"

func main() {
    s := []int{1, 2, 3}

    fmt.Println(s[0]) // Зөв — 1
    fmt.Println(s[5]) // panic: runtime error: index out of range [5] with length 3
}

Программ дараах мессежтэйгээр зогсоно:

код
panic: runtime error: index out of range [5] with length 3

goroutine 1 [running]:
main.main()
        main.go:8 +0x...

panic дуудах

Та өөрөө panic дуудаж болно — гэхдээ маш хязгаарлагдмал нөхцөлд:

go
package main

import "fmt"

func mustPositive(n int) int {
    if n <= 0 {
        panic(fmt.Sprintf("n тэгээс их байх ёстой, харин %d ирлээ", n))
    }
    return n
}

func main() {
    fmt.Println(mustPositive(5))  // 5
    fmt.Println(mustPositive(-1)) // panic!
}

panic нь алдаа буцааж болохгүй нөхцөлд — жишээ нь программ эхлэхэд шаардлагатай тохиргоо дутсан үед — ашигладаг. Энгийн алдаа зохицуулалтад panic ашиглах нь буруу хэв маяг.

recover — panic-аас сэргээх

panic болоход программ зогсохоос өмнө defer функцүүд ажилладаг. Тэр defer дотор recover дуудаж panic-аас "аврах" боломжтой:

go
package main

import "fmt"

func safeDiv(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic-аас сэргэлээ: %v", r)
        }
    }()

    result = a / b // b == 0 бол panic!
    return result, nil
}

func main() {
    result, err := safeDiv(10, 2)
    if err != nil {
        fmt.Println("Алдаа:", err)
    } else {
        fmt.Println("Үр дүн:", result)
    }

    result, err = safeDiv(10, 0)
    if err != nil {
        fmt.Println("Алдаа:", err)
    } else {
        fmt.Println("Үр дүн:", result)
    }
}

recover() нь nil бус утга буцаавал panic болсон гэсэн үг. Тэр утга нь panic(...) руу дамжуулсан аргумент байна.

defer ба panic-ийн гүйцэтгэлийн дараалал

Panic болоход defer-үүд LIFO (сүүлийнх эхлээд) дарааллаар ажилладаг:

go
package main

import "fmt"

func example() {
    defer fmt.Println("defer 1 — эхлээд бүртгэгдсэн, хамгийн сүүлд ажиллана")
    defer fmt.Println("defer 2")
    defer fmt.Println("defer 3 — хамгийн сүүлд бүртгэгдсэн, эхлээд ажиллана")

    panic("туршилтын panic")
}

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Panic барилаа:", r)
        }
    }()

    example()
    fmt.Println("Энэ мөр хэзээ ч хэвлэгдэхгүй")
}

Гаралт:

код
defer 3 — хамгийн сүүлд бүртгэгдсэн, эхлээд ажиллана
defer 2
defer 1 — эхлээд бүртгэгдсэн, хамгийн сүүлд ажиллана
Panic барилаа: туршилтын panic

Хэзээ panic ашиглах вэ?

Go-ийн олон нийтийн зөвлөмж:

код
Энгийн алдаа   → error утга буцаах
Хөгжүүлэлтийн алдаа  → panic (жишээ нь nil pointer-ийг дамжуулах)
Программ эхлэх тохиргооны алдаа → panic

Ихэнх тохиолдолд panic ашиглах шаардлагагүй. Алдааг утга болгон буцаах нь Go-ийн гол зарчим юм.

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

Go-ийн хамгийн ялгарах онцлог болох goroutine — нэгэн зэрэг олон ажил гүйцэтгэх хөнгөн механизмыг судална. Энэ нь Go-г backend болон systems programming-д тэргүүлэгч болгосон гол шалтгаан юм.