Fungsi yang meneruskan error dari lapisan bawah sering perlu menambahkan konteks tanpa menghilangkan identitas kegagalannya. Topik ini membahas pembungkusan (wrapping) error menggunakan fmt.Errorf dengan verb %w, serta pemeriksaan error terbungkus menggunakan errors.Is dan errors.As. Setelah membacanya, error dapat dilaporkan dengan pesan yang kaya konteks sekaligus tetap dikenali berdasarkan sebab aslinya.
Menambah konteks dengan %w#
Verb %w pada fmt.Errorf membuat error baru yang memuat pesan baru dan error aslinya sekaligus. Rangkaian error yang terbentuk disebut rantai pembungkusan, dan pesannya berlapis dari konteks terluar sampai sebab terdalam. Program berikut membungkus error dari strconv.Atoi, lalu mengenali sebabnya kembali melalui errors.Is.
package main
import (
"errors"
"fmt"
"strconv"
)
// errAmbangKosong adalah error sentinel milik program ini.
var errAmbangKosong = errors.New("ambang kosong")
func ambilAmbang(teks string) (int, error) {
if teks == "" {
return 0, errAmbangKosong
}
n, err := strconv.Atoi(teks)
if err != nil {
return 0, fmt.Errorf("menguraikan ambang %q: %w", teks, err)
}
return n, nil
}
func main() {
for _, teks := range []string{"80", "nol", ""} {
_, err := ambilAmbang(teks)
if err == nil {
fmt.Printf("%q: ambang sah\n", teks)
continue
}
fmt.Printf("%q: %v\n", teks, err)
switch {
case errors.Is(err, errAmbangKosong):
fmt.Println(" sebab dikenal: pakai nilai bawaan 50")
case errors.Is(err, strconv.ErrSyntax):
fmt.Println(" sebab dikenal: teks bukan bilangan")
}
}
}"80": ambang sah
"nol": menguraikan ambang "nol": strconv.Atoi: parsing "nol": invalid syntax
sebab dikenal: teks bukan bilangan
"": ambang kosong
sebab dikenal: pakai nilai bawaan 50Pesan untuk "nol" menampilkan seluruh lapisan konteksnya, sedangkan errors.Is tetap menemukan strconv.ErrSyntax di ujung rantai. Cara kerjanya, errors.Is menelusuri rantai dengan memanggil metode Unwrap pada setiap lapisan dan membandingkan nilainya dengan target. Fungsi errors.Is juga menemukan errAmbangKosong yang dikembalikan tanpa pembungkusan, sehingga kedua pola, yaitu sentinel langsung dan sentinel terbungkus, diperiksa dengan satu fungsi yang sama.
Pesan pembungkusan mengikuti konvensi kecil yang menjaga keterbacaan rantai. Pesan error di Go lazimnya dimulai dengan huruf kecil dan tidak diakhiri tanda baca, karena lapisan di atasnya sering merangkai pesan itu ke dalam kalimatnya sendiri. Konteks yang ditambahkan juga berorientasi pada operasi, misalnya menyebut nama pengaturan yang sedang diuraikan, sedangkan detail teknisnya diserahkan kepada error aslinya. Rantai yang panjang tetap terbaca karena setiap lapisan hanya menyumbang satu kejadian atau satu konteks saja.
Mengambil tipe konkret dengan errors.As#
errors.Is mengenali nilai sentinel, sedangkan errors.As mengambil tipe konkret dari dalam rantai agar field-nya dapat dibaca. Target pencariannya adalah pointer ke variabel yang bertipe sama dengan tipe yang ingin diambil, dan errors.As mengisinya dengan error pertama yang cocok. Program berikut mengurai masukan yang salah, lalu membedah strconv.NumError, yaitu tipe error konkret dari paket strconv.
package main
import (
"errors"
"fmt"
"strconv"
)
func ambilAmbang(teks string) (int, error) {
return strconv.Atoi(teks)
}
func main() {
_, err := ambilAmbang("2x5")
var numErr *strconv.NumError
if errors.As(err, &numErr) {
fmt.Println("fungsi:", numErr.Func)
fmt.Println("masukan:", numErr.Num)
fmt.Println("sebab:", numErr.Err)
}
}fungsi: Atoi
masukan: 2x5
sebab: invalid syntaxField Func menyebut fungsi yang gagal, field Num menyebut masukannya, dan field Err menyebut sebabnya. Informasi terstruktur seperti ini tidak mungkin diperoleh dari pesan teks tanpa menguraikannya lagi, sehingga errors.As menjadi alat yang tepat ketika keputusan bergantung pada data error, bukan pada kalimatnya. Bila tidak ada lapisan mana pun dalam rantai yang bertipe cocok, errors.As mengembalikan false dan variabel target dibiarkan tidak terisi.
Target pencarian pada errors.As tidak terbatas pada tipe konkret, tetapi juga boleh berupa pointer menuju tipe interface, sehingga pencarian dapat menyasar kemampuan tertentu dari error tersebut. Bentuk target yang dituntut selalu pointer agar isinya dapat ditetapkan oleh fungsi. Pencarian berhenti pada lapisan pertama yang cocok, dan nilai kembalian fungsinya diperiksa sebelum target dipakai.
Metode Unwrap di balik rantai#
Rantai pembungkusan bekerja melalui kontrak yang sederhana, yaitu error pembungkus menyediakan metode Unwrap yang mengembalikan error di dalamnya. fmt.Errorf dengan %w membuat nilai yang menyediakan metode itu secara otomatis, sedangkan errors.Is dan errors.As menelusuri rantai dengan memanggil Unwrap berulang kali hingga lapisan terdalam. Tipe pembungkus buatan sendiri dapat ikut serta dalam rantai yang sama asalkan menyediakan metode yang sama.
// tidak-dijalankan
type ErrSumber struct {
Asal error
}
func (e *ErrSumber) Error() string {
return "sumber tidak terbaca: " + e.Asal.Error()
}
func (e *ErrSumber) Unwrap() error {
return e.Asal
}Dengan metode Unwrap seperti itu, errors.Is yang mencari error asli di dalam ErrSumber akan menemukannya, seolah-olah pembungkusannya dibuat oleh fmt.Errorf. Pengetahuan ini berguna ketika program perlu membungkus error dengan struktur sendiri, misalnya untuk menyimpan konteks tambahan yang tidak muat dalam pesan teks. Kontrak Unwrap inilah yang menjadikan seluruh mekanisme errors.Is dan errors.As bekerja seragam atas tipe pembungkus apa pun, termasuk tipe milik pustaka standar.
Kesalahan umum#
- Memakai
%valih-alih%w. Pesan yang dicetak tampak sama, tetapi perbandinganerrors.Isdanerrors.Asgagal menemukan sebabnya, dan program berjalan ke cabang yang salah tanpa pesan kesalahan apa pun. Perbaikannya, periksa setiapfmt.Errorfdan pakai%wbila identitas errornya masih dibutuhkan oleh lapisan pemanggil. - Menumpuk prefiks yang sama. Setiap lapisan yang menambahkan awalan seperti
gagal:menghasilkan pesangagal: gagal: gagal: ...bila lapisan di atasnya menambahkan awalan yang sama lagi. Perbaikannya, tulis konteks yang spesifik untuk lapisan itu saja, misalnya nama pengaturan atau langkah yang sedang berjalan. - Membandingkan dengan
==pada error terbungkus. Perbandingan==hanya mengenali kesamaan nilai langsung, sehingga sentinel yang dibungkus%wtidak pernah cocok. Perbaikannya, gunakanerrors.Isuntuk nilai danerrors.Asuntuk tipe, keduanya menelusuri rantai pembungkusan.
Ringkasan#
%wpadafmt.Errorfmembungkus error asli di dalam error baru yang pesannya lebih kaya konteks.errors.Ismenelusuri rantai pembungkusan untuk mencari nilai sentinel tertentu.errors.Asmengekstrak tipe konkret pertama yang cocok dari rantai agar field-nya dapat dibaca.- Rantai bekerja melalui metode
Unwrap, dan perbandingan==hanya bekerja pada error yang dikembalikan tanpa pembungkusan.
Referensi#
- Working with Errors in Go 1.13 — The Go Blog
— Perkenalan
%w,errors.Is, danerrors.As. - Package errors — The Go Authors
— Dokumentasi
Is,As, dan mekanisme pembukaan rantai error. - Effective Go — The Go Project — Bagian Errors tentang gaya penanganan error di Go.