Menu

Error di Golang: Wrap, errors.Is, errors.As, dan Tipe Kustom

Definisikan sentinel error dan tipe error kustom, bungkus error dengan %w, periksa dengan errors.Is dan errors.As, gabungkan beberapa error dengan errors.Join, dan tulis method Unwrap dan Is saat kamu membutuhkannya.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Tiga jenis error

Kode Go memakai tiga bentuk error, dari yang paling sederhana sampai yang paling kaya:

  1. Error ad hoc: errors.New("...") atau fmt.Errorf("...") yang dibuat di tempat. Pemanggil hanya bisa membaca pesannya.
  2. Sentinel error: variabel di level package seperti io.EOF. Pemanggil bisa memeriksanya dengan errors.Is.
  3. Tipe error: struct dengan method Error() yang membawa field. Pemanggil bisa mengambilnya dengan errors.As dan 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 perluSediakan
hanya mencatat atau menampilkan kegagalanfmt.Errorf("...: %w", err)
bercabang berdasarkan satu kondisi tertentusentinel var ErrX = errors.New(...)
membaca detail tentang kegagalantipe error dengan field
melihat beberapa kegagalan yang saling lepaserrors.Join

Kesalahan umum

  • Membandingkan error yang dibungkus dengan ==. Pakai errors.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.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai