- Hata döndüren fonksiyonlar yazmak ve bunları
if err != nilile denetlemek - Hataları
%wile sarmalamak veerrors.Is,errors.Asile incelemek panicverecover'ı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.
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)
}
}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.
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)
}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.
if err != nil && err.Error() == "not found" {
fmt.Println("no such student")
}if errors.Is(err, ErrNotFound) {
fmt.Println("no such student")
}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.
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)
}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.
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")
}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() stringmetoduna sahip bir değerdir; fonksiyon onu son sonuç olarak, başarıda isenildöndürür. if err != nildenetimini 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) veerrors.As(hata türü) ile denetle. - Kendi hata türün alan ve değer gibi ek bilgiler taşıyabilir.
panicyalnızca acil durumlar içindir;recoveronu yalnızca ertelenmiş bir fonksiyonun içinde yakalayabilir.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.