15-bo‘lim

Panic, recover va defer

Vahima qachon yuz beradi, recover bilan uni ushlash, defer ning aniq tartibi, nomlangan natijani o'zgartirish va qachon panic yozish mumkin.

🕑 12 daqiqa o‘qish 📄 765 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Vahima qanday ko'rinadi
  2. recover bilan ushlash
  3. O'z vahimangiz
  4. defer ning aniq tartibi
  5. Siklda defer yozmang
  6. defer natijani o'zgartira oladi
  7. Xatoga aylantirish
  8. Qachon panic yozish mumkin
  9. Vahimani qayta ko'tarish
  10. Xulosa

Vahima (panic) - dasturni to'xtatadigan holat. U xatodan butunlay boshqacha narsa: xato - kutilgan, vahima esa kutilmagan.

Vahima qanday ko'rinadi #

Go
func main() {
    sonlar := []int{1, 2, 3}
    fmt.Println(sonlar[10])
}

Bu dastur kompilyatsiya bo'ladi, lekin ishga tushganda to'xtaydi:

Natija
panic: runtime error: index out of range [10] with length 3

goroutine 1 [running]:
main.main()
    /tmp/misol/main.go:8 +0x1d
exit status 2

Vahima butun dasturni to'xtatadi va chiqishga izlar (stack trace) chiqaradi.

Eng ko'p uchraydigan vahimalarSabab
index out of rangeChegaradan tashqaridagi indeks
nil pointer dereferencenil ko'rsatkichdan o'qish
assignment to entry in nil mapnil map ga yozish
integer divide by zeroButun sonni nolga bo'lish
slice bounds out of rangeNoto'g'ri kesish

recover bilan ushlash #

Go
package main

import "fmt"

func xavfsiz() (natija string) {
    defer func() {
        if r := recover(); r != nil {
            natija = fmt.Sprintf("ushlandi: %v", r)
        }
    }()

    sonlar := []int{1, 2, 3}
    _ = sonlar[10]
    return "bu yerga yetib bormaydi"
}

func main() {
    fmt.Println(xavfsiz())
    fmt.Println("dastur davom etmoqda")
}
Natija
ushlandi: runtime error: index out of range [10] with length 3
dastur davom etmoqda

Uch shart birga bajarilishi kerak:

  1. recover() faqat defer ichida ishlaydi.
  2. defer vahima yuz berishidan oldin e'lon qilingan bo'lishi kerak.
  3. Natijani o'zgartirish uchun nomlangan natija kerak.
recover boshqa joyda nil qaytaradi

recover() ni oddiy kodda chaqirsangiz, u hech narsa qilmaydi va nil qaytaradi:

Go
func notogri() {
    recover()          // foydasiz
    panic("baribir to'xtaydi")
}

U faqat vahima yoyilayotgan paytda, defer qilingan funksiya ichida ma'noga ega bo'ladi.

O'z vahimangiz #

Go
package main

import "fmt"

func sozlamaniOqi(port int) {
    if port < 1 || port > 65535 {
        panic(fmt.Sprintf("noto'g'ri port: %d", port))
    }
    fmt.Println("port qabul qilindi:", port)
}

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("xato:", r)
        }
    }()

    sozlamaniOqi(8080)
    sozlamaniOqi(99999)
    fmt.Println("bu chiqmaydi")
}
Natija
port qabul qilindi: 8080
xato: noto'g'ri port: 99999

panic istalgan qiymatni qabul qiladi - satr, xato yoki struktura.

Vahima yuz berganda, Go joriy funksiyani to'xtatadi, defer larni bajaradi, keyin chaqiruvchiga ko'tariladi - toki kimdir uni ushlagunicha yoki dastur to'xtaguncha.

defer ning aniq tartibi #

Go
package main

import "fmt"

func main() {
    for i := 1; i <= 3; i++ {
        defer fmt.Println("defer:", i)
    }

    fmt.Println("sikl tugadi")
}
Natija
sikl tugadi
defer: 3
defer: 2
defer: 1

defer lar stek kabi yig'iladi: oxirgi qo'yilgani birinchi bajariladi.

E'tibor bering: i ning qiymati defer yozilgan paytda saqlangan (7-bo'lim).

Siklda defer yozmang #

Siklda defer - fayllar tugab qoladi

Bu kod xavfli:

Go
for _, nom := range fayllar {
    f, _ := os.Open(nom)
    defer f.Close()        // funksiya tugaguncha yopilmaydi
    // ...
}

defer funksiya tugaganda ishlaydi, sikl aylanishi tugaganda emas. Ming ta fayl bo'lsa, ming tasi ham ochiq turadi va operatsion tizimning chegarasiga urilib qolasiz.

To'g'ri yechim - tanani alohida funksiyaga chiqarish:

Go
for _, nom := range fayllar {
    qaytaIshla(nom)      // defer shu funksiya tugaganda ishlaydi
}

defer natijani o'zgartira oladi #

Go
package main

import "fmt"

func oddiy() int {
    natija := 10
    defer func() { natija *= 2 }()
    return natija
}

func nomlangan() (natija int) {
    natija = 10
    defer func() { natija *= 2 }()
    return natija
}

func main() {
    fmt.Println(oddiy())
    fmt.Println(nomlangan())
}
Natija
10
20

Ikki funksiya deyarli bir xil, lekin natijalari boshqa.

Nega farq bor

return Go da ikki qadamdan iborat: avval natija o'zgaruvchisiga qiymat yoziladi, keyin defer lar ishlaydi, keyin funksiya chiqadi.

oddiy() da natija nomsiz, shuning uchun return natija qiymatni yashirin joyga nusxalaydi. defer esa mahalliy natija ni o'zgartiradi - qaytadigan qiymatga ta'siri yo'q.

nomlangan() da esa natija - aynan qaytariladigan o'zgaruvchining o'zi. defer uni o'zgartirsa, qaytadigan qiymat ham o'zgaradi.

Aynan shu sababdan recover bilan natijani to'g'rilash uchun nomlangan natija kerak bo'ladi.

Xatoga aylantirish #

Go
package main

import (
    "errors"
    "fmt"
    "strings"
)

func xavfsizHisobla(ifoda string) (natija int, xato error) {
    defer func() {
        if r := recover(); r != nil {
            xato = fmt.Errorf("hisoblab bo'lmadi: %v", r)
        }
    }()

    qismlar := strings.Split(ifoda, "/")
    a, b := len(qismlar[0]), len(qismlar[1])

    return a / (b - 1), nil
}

func main() {
    fmt.Println(xavfsizHisobla("abc/xy"))

    natija, xato := xavfsizHisobla("abc/z")
    fmt.Println(natija, xato)
    fmt.Println(errors.Is(xato, nil))
}
Natija
3 <nil>
0 hisoblab bo'lmadi: runtime error: integer divide by zero
false

Bu eng foydali naqsh: kutubxona ichida vahima yuz bersa, uni ushlab, oddiy xatoga aylantirish.

Shunda kutubxonadan foydalanuvchi butun dasturning to'xtashiga duch kelmaydi.

Qachon panic yozish mumkin #

HolatTo'g'ri yo'l
Fayl topilmadierror qaytaring
Foydalanuvchi noto'g'ri kiritdierror qaytaring
Tarmoq uzildierror qaytaring
Dastur boshlanishida sozlama buzuqpanic mumkin
Dasturchining o'zi xato yozganpanic mumkin
Qoida: panic - dasturchi uchun, error - foydalanuvchi uchun

panic ni faqat hech qachon bo'lmasligi kerak bo'lgan holatda ishlating.

Yaxshi misol - dastur ishga tushayotganda majburiy sozlama topilmasa. Bunday holatda davom etishning ma'nosi yo'q va darhol to'xtagan ma'qul.

Yomon misol - foydalanuvchi noto'g'ri sana kiritdi. Bu butunlay kutilgan holat va u error bo'lishi kerak.

Standart kutubxonada ham shu qoida: strconv.Atoi xato qaytaradi, regexp.MustCompile esa vahima ko'taradi - chunki naqshni dasturchi yozadi va u kompilyatsiya paytida ma'lum.

Must prefiksi ana shu belgidir: "bu funksiya xato o'rniga vahima ko'taradi".

Vahimani qayta ko'tarish #

Go
package main

import "fmt"

func ichki() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("ichkarida ko'rildi:", r)
            panic(r)
        }
    }()
    panic("asl muammo")
}

func main() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("tashqarida ushlandi:", r)
        }
    }()

    ichki()
    fmt.Println("bu chiqmaydi")
}
Natija
ichkarida ko'rildi: asl muammo
tashqarida ushlandi: asl muammo

Vahimani ushlab, jurnalga yozib, keyin qayta ko'tarish mumkin.

Bu ba'zan foydali: pastki qatlam kontekst qo'shadi, yuqori qatlam esa qaror qabul qiladi.

Har bir joyda recover yozmang

Yangi boshlovchilar ba'zan har bir funksiyaga recover qo'yib chiqadi - "dastur qulamasin" deb.

Bu xavfli: vahima odatda dasturda haqiqiy nosozlik borligini bildiradi. Uni jimgina yutib yuborsangiz, dastur buzuq holatda ishlashda davom etadi va keyingi xato yanada chalkashroq bo'ladi.

recover ning o'rni kam: server so'rovini qayta ishlovchi eng yuqori daraja (bitta so'rov butun serverni yiqitmasin uchun) va kutubxona chegarasi.

Amaliy topshiriq
  1. Chegaradan tashqari indeksga murojaat qilib, vahimani ko'ring.
  2. recover bilan uni ushlang.
  3. recover ni defer dan tashqarida chaqirib, farqni ko'ring.
  4. O'zingiz panic ko'taring va uni ushlang.
  5. Siklda uchta defer yozib, tartibini kuzating.
  6. Nomlangan va nomsiz natijada defer ta'sirini solishtiring.
  7. Vahimani error ga aylantiruvchi funksiya yozing.
  8. Nolga bo'lishni ushlab, xato qaytaring.
  9. Vahimani ushlab, qayta ko'taring.
  10. Must prefiksli funksiya yozing.

Xulosa #

  • Vahima kutilmagan holat uchun; xato kutilgan holat uchun.
  • Vahima butun dasturni to'xtatadi va izlarni chiqaradi.
  • recover() faqat defer ichida ishlaydi va vahimadan oldin e'lon qilingan bo'lishi kerak.
  • defer lar teskari tartibda bajariladi.
  • Siklda defer yozmang - u funksiya tugaganda ishlaydi.
  • defer faqat nomlangan natijani o'zgartira oladi.
  • Eng foydali naqsh - vahimani ushlab, oddiy xatoga aylantirish.
  • Must prefiksi "xato o'rniga vahima" degan belgi.
  • recover ni hamma joyda ishlatmang.

Keyingi bo'limda paketlar va modullarni o'rganamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.