Tiga jenis error
Kode Go memakai tiga bentuk error, dari yang paling sederhana sampai yang paling kaya:
- Error ad hoc:
errors.New("...")ataufmt.Errorf("...")yang dibuat di tempat. Pemanggil hanya bisa membaca pesannya. - Sentinel error: variabel di level package seperti
io.EOF. Pemanggil bisa memeriksanya denganerrors.Is. - Tipe error: struct dengan method
Error()yang membawa field. Pemanggil bisa mengambilnya denganerrors.Asdan membaca field-nya.
Pilih yang paling sederhana yang tetap memungkinkan pemanggil melakukan apa yang mereka perlukan.
Sentinel error
Sentinel adalah nilai error yang dideklarasikan sekali dan dibandingkan berdasarkan identitasnya. Menurut konvensi, namanya diawali Err.
Output:
mug bought
cap is out of stock, notify me later
buy "hat": not found
Dua pemanggilan errors.New dengan teks yang sama menghasilkan error yang berbeda: errors.New("x") == errors.New("x") bernilai false. Karena itulah sentinel harus berupa satu variabel yang dipakai bersama.
Sentinel menjadi bagian dari API package-mu. Begitu pemanggil memeriksa ErrNotFound, kamu tidak bisa berhenti mengembalikannya tanpa merusak kode mereka. Ekspor hanya sentinel yang memang perlu dipakai pemanggil untuk menentukan percabangan.
Tipe error kustom
Saat pemanggil butuh detail (field yang mana, status code yang mana, jeda retry berapa lama), definisikan sebuah tipe.
Output:
register: age: must be between 0 and 150
bad field: age
Method-nya punya pointer receiver dan fungsinya mengembalikan &ValidationError{...}, jadi target untuk errors.As adalah *ValidationError, dan kamu mengirim alamatnya (&ve, sebuah **ValidationError). Salah di level ini adalah kesalahan klasik saat memakai errors.As: dengan value receiver, kamu akan mengembalikan ValidationError{...} dan mendeklarasikan var ve ValidationError. go vet menangkap kekeliruan yang paling umum, yaitu mengirim ve alih-alih &ve: second argument to errors.As must be a non-nil pointer to either a type that implements error, or to any interface type.
Membungkus dengan %w
fmt.Errorf dengan %w mengembalikan error yang mengingat error yang dibungkusnya. Setiap lapisan menambahkan konteks dan menjaga rantainya tetap utuh.
Output:
start: connect db: timeout
*fmt.wrapError: start: connect db: timeout
*fmt.wrapError: connect db: timeout
*errors.errorString: timeout
true
false
Dengan %v, pesannya sama tetapi rantainya terputus, jadi errors.Is mengembalikan false. Pilih %w saat pemanggil seharusnya bisa melihat penyebabnya, dan %v saat penyebabnya adalah detail implementasi yang tidak ingin kamu jadikan sandaran bagi mereka.
Sejak Go 1.20, satu pemanggilan bisa membungkus beberapa error: fmt.Errorf("%w; %w", err1, err2). errors.Is lalu cocok dengan salah satunya.
errors.Join: beberapa error sekaligus
Validasi dan pembersihan sering menghasilkan lebih dari satu error. errors.Join (Go 1.20) menggabungkannya.
Output:
name: required
email: required
age: negative
---
true
true
errors.Join mengembalikan nil saat setiap argumennya nil, jadi fungsi di atas tidak butuh penanganan khusus untuk kondisi "tidak ada error".
Method Unwrap dan Is
errors.Is dan errors.As menemukan error yang dibungkus dengan memanggil method Unwrap. Tipe kustom yang menyimpan penyebab sebaiknya menyediakannya:
Tipe yang membungkus beberapa error mengimplementasikan Unwrap() []error sebagai gantinya.
Sebuah tipe juga bisa mendefinisikan Is(target error) bool untuk menentukan kesamaannya sendiri, misalnya agar cocok dengan *HTTPError mana pun yang punya status code sama. Kamu jarang membutuhkannya; membungkus sentinel dan mengembalikannya dari Unwrap biasanya sudah cukup.
Jebakan typed nil
Jangan pernah mendeklarasikan variabel error dengan tipe konkretmu lalu mengembalikannya lewat error:
func check() error {
var err *ValidationError // nil pointer
// ... no problem found
return err // non-nil error! Its type is *ValidationError
}
if err != nil di sisi pemanggil bernilai true, karena interface yang berisi pointer nil bertipe tidaklah nil. Kembalikan nil literal saat berhasil, dan biarkan variabel error lokal bertipe error. Halaman interface menjelaskan alasannya.
Jenis mana yang dipilih
| Pemanggil perlu | Sediakan |
|---|---|
| hanya mencatat atau menampilkan kegagalan | fmt.Errorf("...: %w", err) |
| bercabang berdasarkan satu kondisi tertentu | sentinel var ErrX = errors.New(...) |
| membaca detail tentang kegagalan | tipe error dengan field |
| melihat beberapa kegagalan yang saling lepas | errors.Join |
Kesalahan umum
- Membandingkan error yang dibungkus dengan
==. Pakaierrors.Is. - Mengirim nilai yang bukan pointer ke
errors.As. Fungsi ini butuh pointer ke variabel bertipe target. - Membuat "sentinel" di dalam fungsi.
return errors.New("not found")membuat nilai baru di setiap pemanggilan; pemanggil tidak bisa membandingkan dengannya. - Mengekspor setiap error. Setiap sentinel atau tipe yang diekspor adalah janji API.
- Mencocokkan berdasarkan
err.Error(). String itu untuk dibaca manusia.
Pertanyaan yang sering diajukan
Bagaimana cara membuat error kustom di Go?
Untuk kondisi yang tetap, deklarasikan sentinel di level package: var ErrNotFound = errors.New("not found"). Untuk error yang membawa data, definisikan tipe dengan method Error() string: type ValidationError struct { Field string } dan func (e *ValidationError) Error() string { return e.Field + " is invalid" }.
Bagaimana cara membungkus (wrap) error di Go?
Pakai fmt.Errorf dengan verb %w: return fmt.Errorf("load user %d: %w", id, err). Pesan error baru menyertakan pesan yang lama, dan errors.Unwrap, errors.Is, serta errors.As bisa menjangkau error aslinya. Sejak Go 1.20, satu pemanggilan Errorf bisa membungkus beberapa error dengan beberapa verb %w.
Apa bedanya errors.Is dan errors.As?
errors.Is(err, target) menjawab "apakah nilai error tertentu ini ada di mana pun dalam rantai", untuk sentinel seperti io.EOF. errors.As(err, &target) menjawab "apakah ada error bertipe ini dalam rantai", dan kalau ada, menyimpannya di target supaya kamu bisa membaca field-nya.
Apa fungsi errors.Join?
errors.Join(errs...) (Go 1.20) menggabungkan beberapa error menjadi satu. Pesannya adalah pesan-pesan masing-masing error yang dipisahkan baris baru, error nil dibuang, dan fungsi ini mengembalikan nil jika semuanya nil. errors.Is dan errors.As cocok jika salah satu error yang digabungkan cocok.