1.13 defer, panic, recovery

1.13 defer, panic, recovery

1.13.1 تعویق (defer) #

کلمه کلیدی defer یکی از کاربردی ترین امکانات زبان گو می باشد که شما می توانید اجرای یک تابع را به تعویق بندازید و در آخر تابع فعلی اجرا کنید. عموما defer برای توابعی کاربرد دارد که قصد دارد پاکسازی یا بستن عملیات های صورت گرفته را انجام دهد نظیر توابع Close در برخی از جاها.

defer

 1package main
 2
 3import (
 4	"fmt"
 5)
 6
 7func main() {
 8	defer fmt.Println("world")
 9	fmt.Println("hello")
10}
1$ go run main.go
2hello
3world

1.13.1.1 تعویق (defer) تابع درون خطی (inline) #

شما خیلی ساده می توانید توابع درون خطی را تعویق بندازید.

1package main
2
3import "fmt"
4
5func main() {
6    defer func() { fmt.Println("In inline defer") }()
7    fmt.Println("Executed")
8}
1$ go run main.go
2Executed
3In inline defer

1.13.1.2 تعویق (defer) چند تابع در یک تابع #

در کد زیر ما داخل یک تابع چند تابع را تعویق (defer) کردیم.

 1package main
 2import "fmt"
 3func main() {
 4    i := 0
 5    i = 1
 6    defer fmt.Println(i)
 7    i = 2
 8    defer fmt.Println(i)
 9    i = 3
10    defer fmt.Println(i)
11}
1$ go run main.go
23
32
41

1.13.2 پانیک (panic) #

در زبان گو panic همانند exception به معنای خروج از برنامه در شرایط غیر عادی می باشد. panic در ۲ حالت زیر پیش می آید:

  • خطاهای در زمان اجرا برنامه
  • فراخوانی تابع panic توسط برنامه نویس در بخش های مختلف برنامه
1func panic(v interface{})

از تابع فوق شما می توانید برای ایجاد panic استفاده کنید و به عنوان ورودی دلیل panic را می توانید در قالب خطا یه یک متن مشخص کنید.

تایپ {}interface یک تایپ خیلی کاربردی می باشد برای مواقعی نمی دانیم ورودی یا خروجی تابع یا تایپ متغیر چی میخواهد باشد از اینترفیس استفاده می کنیم.

 1package main
 2
 3import "fmt"
 4
 5var (
 6	a interface{} = 123
 7	b interface{} = "abcd"
 8	c interface{} = 2.5
 9)
10
11func main() {
12	fmt.Printf("%v, %T\n", a, a)
13	fmt.Printf("%v, %T\n", b, b)
14	fmt.Printf("%v, %T\n", c, c)
15}
1$ go run main.go
2123, int
3abcd, string
42.5, float64

و به عنوان یک تایپ ضمنی می باشد که در ادامه فصل بعدی بیشتر آشنا خواهیم شد.

1.13.2.1 خطای panic در زمان اجرا (runtime) #

خطاهای panic در زمان اجرا به دلایل زیر می تواند رخ دهد :

  • خطای Out of bounds/range array/slice
  • فراخوانی تابع که nil pointer باشد
  • ارسال داده برروی کانال های بسته شده
  • type assertion نادرست
 1package main
 2
 3import "fmt"
 4
 5func main() {
 6
 7	a := []string{"a", "b"}
 8	print(a, 2)
 9}
10
11func print(a []string, index int) {
12	fmt.Println(a[index])
13}
1$ go run main.go
2panic: runtime error: index out of range [2] with length 2
3
4goroutine 1 [running]:
5main.checkAndPrint(...)
6        main.go:12
7main.main()
8        /main.go:8 +0x1b
9exit status 2

در تابع فوق ما یک تابع جهت چاپ یک المنت در داخل slice نوشتیم و به عنوان ورودی a و اندیس ۲ را می دهیم در صورتیکه slice ما فقط ۲ تا المنت بیشتر ندارد 0 و 1 ما به اندیس ۲ اشاره کردیم که باعث بروز panic شده است.

  • پانیک رخ داده شامل متن خطا
  • محل رخ دادن panic در قالب stacktrace می باشد

1.13.2.2 خطای panic از قبل تعیین شده توسط برنامه نویس #

همانطور که گفتیم شما می توانید هرجایی از بدنه توابع خود تابع panic را فراخوانی که تا برنامه در آن محل خطایی را نمایش و متوقف شود.

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6
 7	a := []string{"a", "b"}
 8	checkAndPrint(a, 2)
 9}
10
11func checkAndPrint(a []string, index int) {
12	if index > (len(a) - 1) {
13		panic("Out of bound access for slice")
14	}
15	fmt.Println(a[index])
16}
1$ go run main.go
2panic: Out of bound access for slice
3
4goroutine 1 [running]:
5main.checkAndPrint(0xc000104f58, 0x2, 0x2, 0x2)
6      main.go:13 +0xe2
7main.main()
8        main.go:8 +0x7d
9exit status 2
توجه کنید استفاده از تابع panic در برخی مواقع مفید می باشد به عنوان مثال قصد دارید هنگام اجرا یکسری تنظیمات از سمت کاربر دریافت کنید و در صورتیکه تنظیمات دارای مشکل باشد می توانید با استفاده panic جلو ادامه روند برنامه را بگیرید تا کاربر خطا را رفع کند.

1.13.3 بازیابی (recovery) #

برخی اوقات panic ها غیرقابل پیش بینی می باشند و ممکن است در حال حاضر برنامه شما بدون هیچ خطایی اجرا شوند و به روند خود ادامه دهد اما ممکن است به دلیل اشتباه panic رخ دهد و برنامه شما کاملا متوقف و باعث از دست دادن وضعیت استیبل برنامه شود.

به همین منظور یک تابع به نام recover وجود دارد که پس از رخ دادن panic برنامه مجدد بتواند به وضعیت قبلی خود بازگردد تا بعدا خطای panic رخ داده را بررسی و رفع کنیم.

1func recover() interface{}

یک نمونه کد استفاده از recover :

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6
 7	a := []string{"a", "b"}
 8	checkAndPrint(a, 2)
 9	fmt.Println("Exiting normally")
10}
11
12func checkAndPrint(a []string, index int) {
13	defer handleOutOfBounds()
14	if index > (len(a) - 1) {
15		panic("Out of bound access for slice")
16	}
17	fmt.Println(a[index])
18}
19
20func handleOutOfBounds() {
21	if r := recover(); r != nil {
22		fmt.Println("Recovering from panic:", r)
23	}
24}
1$ go run main.go
2Recovering from panic: Out of bound access for slice
3Exiting normally

در کد فوق ما یک تابع داریم که این تابع یک اندیسی از یک slice را چاپ می کند اما اگر این اندیس خارج از تعداد المنت های slice باشد یک خطای panic رخ می دهد. ما برای جلوگیری از خطای panic تابع handleOutOfBounds با استفاده defer درون تابع checkAndPrint قرار دادیم که پس از رخ دادن panic بصورت خودکار بازیابی صورت بگیرد تا برنامه ما متوقف نشود.

1.13.4 چاپ اطلاعات stacktrace پس از بازیابی #

شما می توانید پس از اینکه بازیابی کردید جزئیات بیشتر در خصوص خطای panic رخ داده بدست آوردید.

 1package main
 2import (
 3    "fmt"
 4    "runtime/debug"
 5)
 6func main() {
 7    a := []string{"a", "b"}
 8    checkAndPrint(a, 2)
 9    fmt.Println("Exiting normally")
10}
11func checkAndPrint(a []string, index int) {
12    defer handleOutOfBounds()
13    if index > (len(a) - 1) {
14        panic("Out of bound access for slice")
15    }
16    fmt.Println(a[index])
17}
18func handleOutOfBounds() {
19    if r := recover(); r != nil {
20        fmt.Println("Recovering from panic:", r)
21        fmt.Println("Stack Trace:")
22        debug.PrintStack()
23    }
24}
 1$ go run main.go
 2Recovering from panic: Out of bound access for slice
 3Stack Trace:
 4goroutine 1 [running]:
 5runtime/debug.Stack(0xd, 0x0, 0x0)
 6        stack.go:24 +0x9d
 7runtime/debug.PrintStack()
 8        stack.go:16 +0x22
 9main.handleOutOfBounds()
10        main.go:27 +0x10f
11panic(0x10ab8c0, 0x10e8f60)
12        /Users/slohia/Documents/goversion/go1.14.1/src/runtime/panic.go:967 +0x166
13main.checkAndPrint(0xc000104f58, 0x2, 0x2, 0x2)
14        main.go:18 +0x111
15main.main()
16        main.go:11 +0x81
17Exiting normally
comments powered by Disqus