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.
Ushbu bo‘lim mundarijasi
Vahima (panic) - dasturni to'xtatadigan holat. U xatodan butunlay boshqacha narsa: xato - kutilgan, vahima esa kutilmagan.
Vahima qanday ko'rinadi #
func main() {
sonlar := []int{1, 2, 3}
fmt.Println(sonlar[10])
}
Bu dastur kompilyatsiya bo'ladi, lekin ishga tushganda to'xtaydi:
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 vahimalar | Sabab |
|---|---|
index out of range | Chegaradan tashqaridagi indeks |
nil pointer dereference | nil ko'rsatkichdan o'qish |
assignment to entry in nil map | nil map ga yozish |
integer divide by zero | Butun sonni nolga bo'lish |
slice bounds out of range | Noto'g'ri kesish |
recover bilan ushlash #
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")
}
ushlandi: runtime error: index out of range [10] with length 3
dastur davom etmoqda
Uch shart birga bajarilishi kerak:
recover()faqatdeferichida ishlaydi.defervahima yuz berishidan oldin e'lon qilingan bo'lishi kerak.- Natijani o'zgartirish uchun nomlangan natija kerak.
recover() ni oddiy kodda chaqirsangiz, u hech narsa qilmaydi va
nil qaytaradi:
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 #
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")
}
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 #
package main
import "fmt"
func main() {
for i := 1; i <= 3; i++ {
defer fmt.Println("defer:", i)
}
fmt.Println("sikl tugadi")
}
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 #
Bu kod xavfli:
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:
for _, nom := range fayllar {
qaytaIshla(nom) // defer shu funksiya tugaganda ishlaydi
}
defer natijani o'zgartira oladi #
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())
}
10
20
Ikki funksiya deyarli bir xil, lekin natijalari boshqa.
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 #
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))
}
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 #
| Holat | To'g'ri yo'l |
|---|---|
| Fayl topilmadi | error qaytaring |
| Foydalanuvchi noto'g'ri kiritdi | error qaytaring |
| Tarmoq uzildi | error qaytaring |
| Dastur boshlanishida sozlama buzuq | panic mumkin |
| Dasturchining o'zi xato yozgan | panic mumkin |
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 #
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")
}
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.
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.
- Chegaradan tashqari indeksga murojaat qilib, vahimani ko'ring.
recoverbilan uni ushlang.recovernideferdan tashqarida chaqirib, farqni ko'ring.- O'zingiz
panicko'taring va uni ushlang. - Siklda uchta
deferyozib, tartibini kuzating. - Nomlangan va nomsiz natijada
deferta'sirini solishtiring. - Vahimani
errorga aylantiruvchi funksiya yozing. - Nolga bo'lishni ushlab, xato qaytaring.
- Vahimani ushlab, qayta ko'taring.
Mustprefiksli funksiya yozing.
Xulosa #
- Vahima kutilmagan holat uchun; xato kutilgan holat uchun.
- Vahima butun dasturni to'xtatadi va izlarni chiqaradi.
recover()faqatdeferichida ishlaydi va vahimadan oldin e'lon qilingan bo'lishi kerak.deferlar teskari tartibda bajariladi.- Siklda
deferyozmang - u funksiya tugaganda ishlaydi. deferfaqat nomlangan natijani o'zgartira oladi.- Eng foydali naqsh - vahimani ushlab, oddiy xatoga aylantirish.
Mustprefiksi "xato o'rniga vahima" degan belgi.recoverni hamma joyda ishlatmang.
Keyingi bo'limda paketlar va modullarni o'rganamiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
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.