İçeriğe geç
Educora
Orta16 dk8 / 10

Hata yönetimi

Go'da hataların neden sıradan değerler olduğunu anla; `errors.New`, `%w` ile `fmt.Errorf`, `errors.Is` ve `errors.As` ile `panic` ve `recover` kullanmayı öğren.

Kendini test et
Bu derste öğreneceklerin
  • Hata döndüren fonksiyonlar yazmak ve bunları if err != nil ile denetlemek
  • Hataları %w ile sarmalamak ve errors.Is, errors.As ile incelemek
  • panic ve recover'ın ne zaman kullanıldığını açıklamak

Bir dosya bulunamayabilir, internet kesilebilir, kullanıcı sayı yerine harf yazabilir. Birçok dil böyle durumlarda bir istisna fırlatır ve onu try/catch ile yakalar. Go farklı bir yol seçti: hata, fonksiyonun döndürdüğü sıradan bir değerdir. Kod biraz uzar ama her hatanın nerede oluşabileceğini ve nasıl ele alındığını açıkça görürsün.

Hata sıradan bir değerdir

Go'da error basit bir arayüzdür: Error() string metoduna sahip her tür bir hatadır. Kural şudur: başarısız olabilecek bir fonksiyon hatayı son sonuç olarak döndürür, her şey yolundaysa nil döndürür. Çağıran taraf hemen denetler: if err != nil { ... }. En basit hata errors.New("metin") ile oluşturulur.

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)
	}
}
Beklenen çıktı
result: 2.5
error: division by zero

Dikkat et: hata olduğunda sonuç olarak 0 döndürülür ama çağıran taraf ona bakmaz; önce err denetlenir. Ana kod sola yakın kalır, hata ise erkenden ele alınır ve fonksiyondan çıkılır.

Hataları sarmalamak: %w ve errors.Is

Bir hata yukarıya iletilirken ona bağlam eklemek yararlıdır: hata oluştuğunda ne yapıyorduk? fmt.Errorf("load profile %d: %w", id, err) yeni bir hata oluşturur ve asıl hatayı onun içine sarmalar (wrap). %w belirteci önemlidir: asıl hatayı koruyan odur. Ardından errors.Is(err, ErrNotFound) zincirin tamamını dolaşır ve aranan hatanın içeride olup olmadığını söyler. Önceden tanımlanan bu tür hatalara gözcü hatalar (sentinel errors) denir, adları da genellikle Err ile başlar.

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)
}
Beklenen çıktı
Profile of Murad <nil>
load profile 7: not found
true false

Son satıra bak: sarmalanmış hata artık ErrNotFound'a eşit değildir (== false verir), ama errors.Is onu zincirin içinde bulur. Bu yüzden hataları her zaman errors.Is ile denetle.

Kırılgan: metin karşılaştırma
if err != nil && err.Error() == "not found" {
	fmt.Println("no such student")
}
Güvenilir: errors.Is
if errors.Is(err, ErrNotFound) {
	fmt.Println("no such student")
}
Bağlam eklenir eklenmez metin değişir (load profile 7: not found) ve ilk sürüm artık çalışmaz. errors.Is ise sarmalanmış hataları da tanır.

Özel hata türleri ve errors.As

Bazen bir hata hakkında metninden fazlasını bilmek gerekir: hangi alan yanlış, hangi değer geldi? O zaman kendi hata türünü oluştur: Error() string metodu olan bir struct. Böyle bir hatayı sarmalanmış bir zincirden çıkarmak için errors.As kullanılır: zincirde uygun türde bir hata bulursa onu senin değişkenine yazar ve true döndürü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)
}
Beklenen çıktı
register user: invalid age: -3
field: age value: -3
true
AraçNe yapar
errors.New("...")basit bir hata oluşturur
fmt.Errorf("ctx: %w", err)bağlam ekler ve hatayı sarmalar
errors.Is(err, ErrX)zincirde belirli bir hata var mı?
errors.As(err, &target)zincirden belirli türde bir hatayı çıkarır

panic ve recover

panic, programın normal akışını durduran acil bir durumdur: dizinin sınırlarının dışına çıkmak, nil bir işaretçi, tam sayının sıfıra bölünmesi. Panic sırasında fonksiyonlardan tek tek çıkılır ama ertelenmiş (defer) çağrılar yine çalışır. Ertelenmiş bir fonksiyon recover() çağırırsa panic durur ve program çalışmaya devam eder.

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")
}
Beklenen çıktı
5 <nil>
0 recovered: runtime error: integer divide by zero
program continues

safeDivide(1, 0) bir panic'e yol açtı, ama ertelenmiş fonksiyon onu recover ile yakaladı ve adlandırılmış sonuç err'e sıradan bir hata yazdı. Adlandırılmış sonuç burada önemlidir: ertelenmiş fonksiyonun döndürülen değeri değiştirebilmesini sağlayan odur.

Önemli noktalar

  • Hata, Error() string metoduna sahip bir değerdir; fonksiyon onu son sonuç olarak, başarıda ise nil döndürür.
  • if err != nil denetimini hemen yap ve hatayı bağlamla yukarı ilet: fmt.Errorf("...: %w", err).
  • Sarmalanmış hataları == ya da metin karşılaştırmasıyla değil, errors.Is (belirli hata) ve errors.As (hata türü) ile denetle.
  • Kendi hata türün alan ve değer gibi ek bilgiler taşıyabilir.
  • panic yalnızca acil durumlar içindir; recover onu yalnızca ertelenmiş bir fonksiyonun içinde yakalayabilir.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
Başarısız olabilecek bir Go fonksiyonu hatayı genellikle nasıl bildirir?