Aplikasi biasanya tersusun berlapis: handler memanggil service, service memanggil repository. Ketika kegagalan muncul di lapisan terdalam dan setiap lapisan hanya membuat error baru dengan pesan sendiri, penyebab aslinya hilang — pemanggil paling atas menerima teks tanpa jejak kejadian pertama. Go 1.13 memperkenalkan error wrapping melalui verb %w pada fmt.Errorf: error lama disimpan sebagai penyebab, bukan dibuang. Pada chapter ini kita akan membungkus error berlapis, membedakan %v dengan %w, lalu menggabungkan beberapa error sekaligus dengan errors.Join.

Membungkus Error dengan %w #

Pembungkusannya sendiri sederhana — satu baris fmt.Errorf dengan verb %w di posisi error:

go
return fmt.Errorf("baca nilai dari %q: %w", teks, err)

Nilai yang dikembalikan tetap bertipe error, tetapi di dalamnya tersimpan rujukan ke err sebagai penyebab (cause). Pemeriksa errors.Is dan errors.As memakai method Unwrap() untuk menuruni rantai tersebut, sehingga identitas error lama tidak hilang meski teks pesannya kini bertambah konteks.

Pembungkusan Bertumpuk #

Setiap lapisan boleh membungkus lagi error yang diterimanya. Lapisan bawah membungkus hasil strconv.Atoi, lalu lapisan atas membungkus hasil lapisan bawah:

go
return fmt.Errorf("muat pengaturan: %w", err)

Hasil akhirnya satu error dengan dua lapis konteks dan satu penyebab paling dalam. Pesan tercetak berurutan dari lapisan terluar ke terdalam, seperti susunan muat pengaturan: baca nilai dari ...: <penyebab>. Pola baca “layar depan menyebut lapisan, layar belakang menyebut sebab” ini menjadikan log mudah ditelusuri manusia.

Perbedaan %v dan %w #

Verb %v juga bisa mencetak error di dalam format, tetapi hanya teksnya yang diambil — error aslinya tidak disimpan. Konsekuensinya besar:

  • %w mengikat penyebab ke rantai: errors.Is dan errors.As masih bisa menemukannya.
  • %v meratakan error menjadi teks: rantai terputus, identitas hilang, pemeriksaan selalu false.

Menggabungkan Banyak Error dengan errors.Join #

Sejak Go 1.20, errors.Join menggabungkan beberapa error menjadi satu nilai — berguna saat operasi paralel menghasilkan lebih dari satu kegagalan:

go
gabungan := errors.Join(errKafka, errRedis)

Hasil errors.Join tetap mengimplementasikan error; pesannya mencetak semua anggota dipisah baris baru, dan errors.Is maupun errors.As menjelajahi setiap anggotanya. Bila seluruh argumen errors.Join bernilai nil, hasilnya juga nil, sehingga penggabungan error tidak pernah menciptakan error palsu.

Program Utuh #

Berkas wrapping-demo.go mendemokan pembungkusan dua lapis, perbandingan %v melawan %w, dan penggabungan dua error:

go
package main

import (
	"errors"
	"fmt"
	"strconv"
)

func bacaNilai(teks string) error {
	_, err := strconv.Atoi(teks)
	if err != nil {
		return fmt.Errorf("baca nilai dari %q: %w", teks, err)
	}
	return nil
}

func muatPengaturan(teks string) error {
	if err := bacaNilai(teks); err != nil {
		return fmt.Errorf("muat pengaturan: %w", err)
	}
	return nil
}

func main() {
	err := muatPengaturan("sembilan")
	fmt.Println("pesan :", err)
	fmt.Println("syntax:", errors.Is(err, strconv.ErrSyntax))

	asli := errors.New("berkas rusak")
	denganV := fmt.Errorf("baca berkas: %v", asli)
	denganW := fmt.Errorf("baca berkas: %w", asli)
	fmt.Println("verb v, errors.Is:", errors.Is(denganV, asli))
	fmt.Println("verb w, errors.Is:", errors.Is(denganW, asli))

	errKafka := errors.New("koneksi kafka gagal")
	errRedis := errors.New("koneksi redis gagal")
	gabungan := errors.Join(errKafka, errRedis)
	fmt.Println("gabungan:")
	fmt.Println(gabungan)
	fmt.Println("errors.Is errRedis:", errors.Is(gabungan, errRedis))
}

Jalankan program dari direktori chapter dengan perintah berikut:

bash
go run wrapping-demo.go

Output yang Dihasilkan:

text
pesan : muat pengaturan: baca nilai dari "sembilan": strconv.Atoi: parsing "sembilan": invalid syntax
syntax: true
verb v, errors.Is: false
verb w, errors.Is: true
gabungan:
koneksi kafka gagal
koneksi redis gagal
errors.Is errRedis: true

Mari kita telaah baris demi baris program di atas:

  1. Baris 12 (fmt.Errorf("baca nilai dari %q: %w", teks, err)): lapisan bawah membungkus error strconv.Atoi — verb %w menyimpan err sebagai penyebab, verb %q mencetak masukan yang bermasalah.
  2. Baris 19 (fmt.Errorf("muat pengaturan: %w", err)): lapisan atas membungkus hasil lapisan bawah; pembungkusan bertumpuk seperti ini menghasilkan rantai tiga tingkat tanpa struct tambahan.
  3. Baris 25-27 (err := muatPengaturan("sembilan")): pesan tercetak dari lapisan terluar sampai terdalam, dan errors.Is menemukan strconv.ErrSyntax di kedalaman rantai — bukti identitas penyebab tetap hidup.
  4. Baris 30-33 (denganV dan denganW): dua error dibuat dari sumber sama tetapi dengan verb berbeda; hanya pembungkus %w yang lulus pemeriksaan errors.Is.
  5. Baris 37 (errors.Join(errKafka, errRedis)): dua kegagalan independen digabung menjadi satu nilai error yang mencakup keduanya.
  6. Baris 38-40 (fmt.Println(gabungan)): pesan gabungan tercetak dua baris karena errors.Join memisahkan anggota dengan baris baru, dan errors.Is tetap menemukan errRedis di dalamnya.

Rantai error kini bisa terbentuk dan utuh — tinggal satu pertanyaan: bagaimana cara memeriksa rantai itu secara sistematis, baik mencari nilai tertentu maupun mengambil tipe tertentu? Chapter berikutnya, errors.Is dan errors.As , membedah kedua fungsi tersebut sampai ke perbedaannya dengan type assertion.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

wrapping-demo.go

Unduh berkas
go
package main

import (
	"errors"
	"fmt"
	"strconv"
)

func bacaNilai(teks string) error {
	_, err := strconv.Atoi(teks)
	if err != nil {
		return fmt.Errorf("baca nilai dari %q: %w", teks, err)
	}
	return nil
}

func muatPengaturan(teks string) error {
	if err := bacaNilai(teks); err != nil {
		return fmt.Errorf("muat pengaturan: %w", err)
	}
	return nil
}

func main() {
	err := muatPengaturan("sembilan")
	fmt.Println("pesan :", err)
	fmt.Println("syntax:", errors.Is(err, strconv.ErrSyntax))

	asli := errors.New("berkas rusak")
	denganV := fmt.Errorf("baca berkas: %v", asli)
	denganW := fmt.Errorf("baca berkas: %w", asli)
	fmt.Println("verb v, errors.Is:", errors.Is(denganV, asli))
	fmt.Println("verb w, errors.Is:", errors.Is(denganW, asli))

	errKafka := errors.New("koneksi kafka gagal")
	errRedis := errors.New("koneksi redis gagal")
	gabungan := errors.Join(errKafka, errRedis)
	fmt.Println("gabungan:")
	fmt.Println(gabungan)
	fmt.Println("errors.Is errRedis:", errors.Is(gabungan, errRedis))
}