Məzmuna keç
Educora
Orta16 dəq8 / 10

Xətaların idarə olunması

Go-da xətaların niyə adi qiymət olduğunu anla, `errors.New`, `fmt.Errorf` və `%w`, `errors.Is` və `errors.As`, həmçinin `panic` və `recover` ilə işləməyi öyrən.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Xəta qaytaran funksiya yazmaq və if err != nil ilə yoxlamaq
  • Xətaları %w ilə bükmək və errors.Is, errors.As ilə yoxlamaq
  • panic və recover-in nə vaxt işlədildiyini izah etmək

Fayl tapılmaya bilər, internet kəsilə bilər, istifadəçi ədəd əvəzinə hərf yaza bilər. Bir çox dil belə hallarda istisna (exception) atır və onu try/catch ilə tutur. Go başqa yol seçib: xəta — funksiyanın qaytardığı adi qiymətdir. Bu, kodu bir az uzadır, amma hər xətanın harada yarana biləcəyini və necə emal olunduğunu açıq görürsən.

Xəta — adi qiymətdir

Go-da error sadə bir interfeysdir: Error() string metodu olan istənilən tip xətadır. Qayda belədir: uğursuz ola bilən funksiya xətanı son nəticə kimi qaytarır, uğurlu halda isə nil qaytarır. Çağıran tərəf dərhal yoxlayır: if err != nil { ... }. Ən sadə xətanı errors.New("mətn") yaradır.

Go
package main

import (
	"errors"
	"fmt"
)

func divide(a, b float64) (float64, error) {
	if b == 0 {
		return 0, errors.New("division by zero")
	}
	return a / b, nil
}

func main() {
	result, err := divide(10, 4)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println("result:", result)

	if _, err := divide(1, 0); err != nil {
		fmt.Println("error:", err)
	}
}
Gözlənilən nəticə
result: 2.5
error: division by zero

Diqqət et: xəta olanda nəticə kimi 0 qaytarılır, amma çağıran tərəf ona baxmır — əvvəlcə err yoxlanılır. Əsas kod sola yaxın qalır, xəta isə tez emal olunur və funksiyadan çıxılır.

Xətaları bükmək: %w və errors.Is

Xəta yuxarı ötürüləndə ona kontekst əlavə etmək faydalıdır: nə edərkən xəta baş verdi? fmt.Errorf("load profile %d: %w", id, err) yeni xəta yaradır və orijinalı içində bükür (wrap). %w işarəsi vacibdir: orijinal xətanı məhz o saxlayır. Sonra errors.Is(err, ErrNotFound) zənciri axıra qədər yoxlayır və içəridə axtarılan xətanın olub-olmadığını deyir. Əvvəlcədən elan olunmuş belə xətalara gözətçi xətalar (sentinel errors) deyilir, adları isə adətən Err ilə başlayır.

Go
package main

import (
	"errors"
	"fmt"
)

var ErrNotFound = errors.New("not found")

func findStudent(id int) (string, error) {
	students := map[int]string{1: "Aysel", 2: "Murad"}
	name, ok := students[id]
	if !ok {
		return "", ErrNotFound
	}
	return name, nil
}

func loadProfile(id int) (string, error) {
	name, err := findStudent(id)
	if err != nil {
		return "", fmt.Errorf("load profile %d: %w", id, err)
	}
	return "Profile of " + name, nil
}

func main() {
	p, err := loadProfile(2)
	fmt.Println(p, err)
	_, err = loadProfile(7)
	fmt.Println(err)
	fmt.Println(errors.Is(err, ErrNotFound), err == ErrNotFound)
}
Gözlənilən nəticə
Profile of Murad <nil>
load profile 7: not found
true false

Son sətrə bax: bükülmüş xəta artıq ErrNotFound-a bərabər deyil (== false verir), amma errors.Is onu zəncirin içində tapır. Ona görə xətaları həmişə errors.Is ilə yoxla.

Kövrək: mətn müqayisəsi
if err != nil && err.Error() == "not found" {
	fmt.Println("no such student")
}
Etibarlı: errors.Is
if errors.Is(err, ErrNotFound) {
	fmt.Println("no such student")
}
Kontekst əlavə olunan kimi mətn dəyişir (load profile 7: not found) və birinci variant artıq işləmir. errors.Is isə bükülmüş xətaları da tanıyır.

Öz xəta tipin və errors.As

Bəzən xəta haqqında mətndən çox məlumat lazımdır: hansı sahə səhvdir, hansı qiymət gəlib? Onda öz xəta tipini yarat — Error() string metodu olan struktur. Bükülmüş zəncirdən belə xətanı çıxarmaq üçün errors.As işlədilir: o, zəncirdə uyğun tipli xəta tapırsa, onu sənin dəyişəninə yazır və true qaytarır.

Go
package main

import (
	"errors"
	"fmt"
)

type ValidationError struct {
	Field string
	Value int
}

func (e *ValidationError) Error() string {
	return fmt.Sprintf("invalid %s: %d", e.Field, e.Value)
}

func setAge(age int) error {
	if age < 0 || age > 120 {
		return &ValidationError{Field: "age", Value: age}
	}
	return nil
}

func main() {
	err := fmt.Errorf("register user: %w", setAge(-3))
	fmt.Println(err)
	var ve *ValidationError
	if errors.As(err, &ve) {
		fmt.Println("field:", ve.Field, "value:", ve.Value)
	}
	fmt.Println(setAge(30) == nil)
}
Gözlənilən nəticə
register user: invalid age: -3
field: age value: -3
true
AlətNə edir
errors.New("...")sadə xəta yaradır
fmt.Errorf("ctx: %w", err)kontekst əlavə edib xətanı bükür
errors.Is(err, ErrX)zəncirdə konkret xəta varmı?
errors.As(err, &target)zəncirdən müəyyən tipli xətanı çıxarır

panic və recover

panic — proqramın normal işini dayandıran fövqəladə vəziyyətdir: massivin hüdudundan kənara çıxmaq, nil göstərici, tam ədədin sıfıra bölünməsi. Panic zamanı funksiyalar bir-bir tərk edilir, təxirə salınmış (defer) çağırışlar isə icra olunur. Əgər təxirə salınmış funksiyada recover() çağırılsa, panic dayanır və proqram işini davam etdirir.

Go
package main

import "fmt"

func safeDivide(a, b int) (result int, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("recovered: %v", r)
		}
	}()
	result = a / b
	return result, nil
}

func main() {
	fmt.Println(safeDivide(10, 2))
	fmt.Println(safeDivide(1, 0))
	fmt.Println("program continues")
}
Gözlənilən nəticə
5 <nil>
0 recovered: runtime error: integer divide by zero
program continues

safeDivide(1, 0) panic yaratdı, amma təxirə salınmış funksiya onu recover ilə tutdu və adlı nəticəyə — err-ə adi xəta yazdı. Adlı nəticə burada vacibdir: məhz onun sayəsində defer funksiyası qaytarılan qiyməti dəyişə bilir.

Əsas fikirlər

  • Xəta — Error() string metodu olan qiymətdir; funksiya onu son nəticə kimi qaytarır, uğurda isə nil.
  • if err != nil yoxlamasını dərhal et və xətanı kontekstlə yuxarı ötür: fmt.Errorf("...: %w", err).
  • Bükülmüş xətaları == və mətn müqayisəsi ilə yox, errors.Is (konkret xəta) və errors.As (xəta tipi) ilə yoxla.
  • Öz xəta tipin əlavə məlumat (sahə, qiymət) daşıya bilər.
  • panic yalnız fövqəladə hallar üçündür; recover onu yalnız təxirə salınmış funksiyada tuta bilər.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
Go-da uğursuz ola bilən funksiya xətanı adətən necə bildirir?