Skip to content

Commit

Permalink
add: error handling section
Browse files Browse the repository at this point in the history
  • Loading branch information
Ja7ad committed Dec 13, 2022
1 parent a7800c0 commit 1c54f2d
Showing 1 changed file with 268 additions and 1 deletion.
269 changes: 268 additions & 1 deletion content/chapter 2/2.6-error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,271 @@ slug: error-handling
weight: 3006
---

مدیریت خطاها...
در این بخش قصد به مقوله مدیریت خطاها در زبان گو بپردازیم و اینکه چطور می توانید خیلی ساده خطاها را مدیریت کنید. مدیریت خطا در زبان گو با سایر زبان ها متفاوت هست و شما با چیزی به نام try-catch یا try-except سروکار ندارید.

مدیریت خطاها در زبان گو به دو روش صورت می گیرد :
- با استفاده از پیاده سازی اینترفیس error که یک روش مرسوم جهت مدیریت و نمایش خطا می باشد.
- با استفاده از panic/recover که در فصل اول توضیح دادیم


## مدیریت خطا با اینترفیس error

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


https://pkg.go.dev/builtin#error
```go
type error interface {
Error() string
}
```

به مثال زیر توجه کنید :

```go
package main

import (
"fmt"
"os"
)

func main() {
file, err := os.Open("non-existing.txt")
if err != nil {
fmt.Println(err)
} else {
fmt.Println(file.Name() + "opened succesfully")
}
}
```

```shell
$ go run main.go
open non-existing.txt: no such file or directory
```

در بالا ما از تابع Open در پکیج برای بازکردن فایل استفاده کردیم. اگر دقت کنید این تابع ۲ تا خروجی دارد یکی ساختار File هست و دیگری خطا هست. در ادامه ما با استفاده شرط آمدیم چک کردیم اینترفیس err آیا خالی است یا خیر؟ در بالا این اینترفیس خالی `nil` نیست و ما خطا را چاپ کردیم.

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

دقت کنید اینترفیس error یک متد دارد به نام ()Error که این متد متن خطا را بصورت رشته بر میگرداند.

**آیا همیشه نیاز است خطاها را مدیریت کنیم؟**

شاید بپرسید آیا واقعا نیاز هست ما همیشه خطاها را مدیریت کنیم؟ در جواب این سوال می توانیم بگیم هم آره و هم خیر

- علت اینکه میگیم آره از این بابت هست اگر خطاها را به دسترسی مدیریت نکنیم احتمال اینکه با panic در هرجا مواجه شویم خیلی زیاد می شود. بخصوص خطای `nil pointer` . پس بهتر است تا جایی که می توانید خطاها را به دسترسی مدیریت کنیم و همچنین اگر جایی احتمال می دهید panic پیش میاد بهتر است از recover استفاده کنید تا پایداری برنامه را بالا ببرید.
- علت اینکه میگیم خیر از این بابت هست در زبان گو هیچ اجباری برای مدیریت خطاها وجود ندارد و گاهی اوقات می توانید خطاها را نادیده بگیرید یا اینکه با استفاده از `ـ` نادیده بگیریدش.

## مزایای استفاده از error به عنوان یک تایپ در زبان گو

- به شما این امکان را می دهد کنترل بیشتری رو خطاها داشته باشید و تو هر قدم می توانید خطاها را بررسی کنید.
- جلوگیری از try-catch جهت مدیریت خطا (دقت کنید در سایر زبان ها باید تا جایی که ممکن است از try-catch کمتر استفاده کنید)

## روش های مختلف برای ایجاد یک خطا

در زبان گو شما می توانید در هرجای کد خود یک خطا با محتوای مناسب ایجاد کنید و یا اینکه برخی از خطاهای برخی از کتابخانه ها را {{< tooltip text="هم پوشانی" note="wrap" >}} کنید.

**1. با استفاده ("متن خطا")errors.New**

```go
package main

import (
"errors"
"fmt"
)

func main() {
sampleErr := errors.New("error occured")
fmt.Println(sampleErr)
}
```

```shell
$ go run main.go
error occured
```

در بالا ما با استفاده از تابع New پکیج errors یک خطا با متن مشخص ایجاد کردیم و متغیر sampleErr از نوع اینترفیس error می باشد که می توانید در هرجای کد خود مدیریتش کنید.

**2. با استفاده از ("error is %s", "some error message")fmt.Errorf**

شما با استفاده از تابع Errorf در پکیج fmt می توانید یک خطا ایجاد کنید و توجه کنید این متن خطا قابل فرمت هستش و حتی شما می توانید متن خطا را داینامیک کنید.

```go
package main

import (
"fmt"
)

func main() {
msg := "database connection issue"
sampleErr := fmt.Errorf("Err is: %s", msg)
fmt.Println(sampleErr)
}
```

```shell
$ go run main.go
Err is: database connection issue
```

## ایجاد خطا پیشرفته

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

**ویژگی های خطای پیشرفته :**

- در زیر inputError یک نوع ساختار است که داخلش ۲ تا فیلد message و missingField دارد و همچنین دارای یک متد ()Error می باشد.
- شما می توانید به این ساختار خطای پیشرفته متدهای بیشتری اضافه کنید و همچنین گسترش دهید که به عنوان مثال ما متد getMissingFields را برای گرفتن محتوای missingField اضافه کردیم.
- ما با استفاده از type assertion می توانیم اینترفیس error را به inputError تبدیل کنیم.

```go
package main

import "fmt"

type inputError struct {
message string
missingField string
}

func (i *inputError) Error() string {
return i.message
}

func (i *inputError) getMissingField() string {
return i.missingField
}

func main() {
err := validate("", "")
if err != nil {
if err, ok := err.(*inputError); ok {
fmt.Println(err)
fmt.Printf("Missing Field is %s\n", err.getMissingField())
}
}
}

func validate(name, gender string) error {
if name == "" {
return &inputError{message: "Name is mandatory", missingField: "name"}
}
if gender == "" {
return &inputError{message: "Gender is mandatory", missingField: "gender"}
}
return nil
}
```

```shell
$ go run main.go
Name is mandatory
Missing Field is name
```

## نادیده گرفتن خطاها

شما در هرجای کد خود با استفاده از `_` می توانید متغیر خطا را نادیده بگیرید و آن را مدیریت نکنید. هر چند در بالا گفتیم نادیده گرفتن خطاها عوارضی در بر دارد و ما همیشه تاکید میکنیم تا جایی که ممکن است خطاها را مدیریت کنید.

```go
package main
import (
"fmt"
"os"
)
func main() {
file, _ := os.Open("non-existing.txt")
fmt.Println(file)
}
```

```shell
$ go run main.go
{nil}
```

در بالا ما خطای تابع Open را نادیده گرفتیم و مقدار file را چاپ کردیم و مقدار چاپ شده `nil` است چون تایپ خروجی با اشاره گر است و قطعا مقدار خالی بودش `nil` است و دقت کنید اگر این متغیر را به تابع دیگری پاس دهید قطعا با panic مواجه خواهید شد.

## هم پوشانی (Wrapping) خطا

در زبان گو شما می توانید خطا را با خطا و پیغام مشخصی {{< tooltip text="هم پوشانی" note="wrap" >}} کنید. حالا {{< tooltip text="هم پوشانی" note="wrap" >}} خطا چیست؟

بزارید با یک مثال ساده توضیح دهیم فرض کنید شما تو لایه دیتابیس خود یکسری خطاها از سمت دیتابیس دریافت می کنید به عنوان مثال اگر شما سندی را در دیتابیس monogdb پیدا نکنید با خطای `no documents found` مواجه خواهید شد. شما در اینجا نمی توانید همان متن خطا را به کاربر نمایش دهید بلکه باید آن خطا را با یک متن خطای مناسب {{< tooltip text="هم پوشانی" note="wrap" >}} کنید.

```go
package main

import (
"fmt"
)

type notPositive struct {
num int
}

func (e notPositive) Error() string {
return fmt.Sprintf("checkPositive: Given number %d is not a positive number", e.num)
}

type notEven struct {
num int
}

func (e notEven) Error() string {
return fmt.Sprintf("checkEven: Given number %d is not an even number", e.num)
}

func checkPositive(num int) error {
if num < 0 {
return notPositive{num: num}
}
return nil
}

func checkEven(num int) error {
if num%2 == 1 {
return notEven{num: num}
}
return nil
}

func checkPostiveAndEven(num int) error {
if num > 100 {
return fmt.Errorf("checkPostiveAndEven: Number %d is greater than 100", num)
}

err := checkPositive(num)
if err != nil {
return err
}

err = checkEven(num)
if err != nil {
return err
}

return nil
}

func main() {
num := 3
err := checkPostiveAndEven(num)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Givennnumber is positive and even")
}

}
```

```shell
$ go run main.go
checkEven: Given number 3 is not an even number
```

0 comments on commit 1c54f2d

Please sign in to comment.