Go tidak memiliki mekanisme try/catch seperti banyak bahasa pemrograman lain. Sebagai gantinya, setiap fungsi yang berpotensi gagal mengembalikan error sebagai nilai balikan biasa, tepat di sebelah data hasil. Nilai error tersebut bukan tipe primitif, melainkan nilai yang mengimplementasikan sebuah interface kecil bernama error dan tersedia di semua program Go tanpa import khusus. Pada chapter ini kita akan membedah bentuk interface error, memahami makna error nil sebagai penanda sukses, lalu membuat tipe error pertama kita sendiri.
Bentuk Interface error #
Definisi error di package builtin hanya memuat satu method:
type error interface {
Error() string
}Karena kontraknya sekecil itu, semua tipe yang memiliki method Error() string secara otomatis mengimplementasikan interface error. Tipe tersebut bisa berupa struct, pointer ke struct, bahkan tipe dasar seperti int asalkan method-nya ada. Dua nilai yang berbeda bisa sama-sama menjadi error: satu membawa sekadar teks pesan, satu lagi membawa struct berisi data pendukung. Bagi pemanggil, keduanya adalah error.
Pola ini membuat error menjadi nilai kelas satu (first-class value): error bisa disimpan di variabel, dikirim lewat channel, dibandingkan, atau dibungkus. Tidak ada jalur rahasia yang melompati kode kita seperti yang terjadi pada exception yang tidak ditangkap.
error nil Berarti Sukses #
Konvensi Go menyatakan fungsi yang mengembalikan (hasil, error) wajib mengisi error dengan nil ketika operasi berhasil. Karena itu pemeriksaan pertama pemanggil selalu sama: bandingkan err dengan nil sebelum memakai hasil. Mari kita lihat program utuh berikut:
package main
import "fmt"
// SisaStokError membawa informasi stok yang masih tersedia.
type SisaStokError struct {
Sisa int
}
// Error mengimplementasikan interface error.
func (e SisaStokError) Error() string {
return fmt.Sprintf("stok tidak cukup, sisa %d", e.Sisa)
}
// ambilStok mengembalikan sisa stok dan error.
func ambilStok(diminta, tersedia int) (int, error) {
if diminta > tersedia {
return 0, SisaStokError{Sisa: tersedia}
}
return tersedia - diminta, nil
}
func main() {
sisa, err := ambilStok(5, 12)
fmt.Println("sisa:", sisa, "| err:", err)
sisa, err = ambilStok(20, 12)
fmt.Println("sisa:", sisa, "| err:", err)
fmt.Printf("cetak dengan %%v: %v\n", err)
if err == nil {
fmt.Println("status: sukses")
} else {
fmt.Println("status: gagal, pesan:", err.Error())
}
}Jalankan program dari direktori chapter dengan perintah berikut:
go run error-interface-demo.goOutput yang Dihasilkan:
sisa: 7 | err: <nil>
sisa: 0 | err: stok tidak cukup, sisa 12
cetak dengan %v: stok tidak cukup, sisa 12
status: gagal, pesan: stok tidak cukup, sisa 12Mari kita telaah baris demi baris program di atas:
- Baris 6-8 (
type SisaStokError struct): struct biasa dengan satu fieldint; struct ini belum menjadi error sampai methodErrordideklarasikan. - Baris 10-12 (
func (e SisaStokError) Error() string): method dengan receiverSisaStokErrormemenuhi kontrak interfaceerror, sehinggaSisaStokErrorotomatis bisa dipakai sebagai nilaierror. - Baris 16-21 (
func ambilStok): fungsi mengembalikan pasangan(int, error); kegagalan diwakili error non-nil, sukses diwakilinil. - Baris 20 (
return tersedia - diminta, nil): jalur sukses mengembalikan errornil— nilai inilah yang nanti dibandingkan pemanggil. - Baris 24-25 (
sisa, err := ambilStok(5, 12)): pemanggilan sukses;fmt.Printlnmencetak nilaierryang masihnilsebagai<nil>. - Baris 27-28 (
sisa, err = ambilStok(20, 12)): pemanggilan gagal; nilaisisamenjadi0danerrberisiSisaStokError. - Baris 30 (
fmt.Printfdengan verb%v): verb%vmemanggil methodError()pada nilai error non-nil, sehingga pesan lengkapnya ikut tercetak. - Baris 32-36 (
if err == nil ... else ...): pola pemeriksaan standar; cabangelsememanggilerr.Error()secara eksplisit untuk mengambil teks pesan.
error Bukan panic #
Pesan runtime seperti divide by zero tidak datang sebagai nilai balikan error, melainkan lewat mekanisme panic yang menghentikan alur normal. Nilai yang dibawa panic tersebut bertipe runtime.Error, sebuah interface yang menempatkan error di dalamnya, tetapi jalurnya berbeda: panic melepas (unwind) stack dan mencari fungsi defer-recover, bukan dikembalikan ke pemanggil. Chapter panic dan recover
membahas jalur tersebut secara khusus.
Dengan pemahaman ini, kita sudah tahu error hanyalah nilai yang memenuhi satu method. Pertanyaan berikutnya adalah bagaimana cara membuat nilai tersebut: dengan errors.New atau fmt.Errorf? Chapter berikutnya, Membuat Error
, membedah kedua cara tersebut beserta konvensi penulisan pesannya.
Referensi #
- Spesifikasi Go: Errors
— definisi resmi tipe
errordan perilakunya di bahasa. - Error handling and Go — artikel blog resmi tentang filosofi error sebagai nilai.
- Tutorial: Handle errors — tutorial resmi memeriksa error hasil pemanggilan fungsi.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
error-interface-demo.go
Unduh berkaspackage main
import "fmt"
// SisaStokError membawa informasi stok yang masih tersedia.
type SisaStokError struct {
Sisa int
}
// Error mengimplementasikan interface error.
func (e SisaStokError) Error() string {
return fmt.Sprintf("stok tidak cukup, sisa %d", e.Sisa)
}
// ambilStok mengembalikan sisa stok dan error.
func ambilStok(diminta, tersedia int) (int, error) {
if diminta > tersedia {
return 0, SisaStokError{Sisa: tersedia}
}
return tersedia - diminta, nil
}
func main() {
sisa, err := ambilStok(5, 12)
fmt.Println("sisa:", sisa, "| err:", err)
sisa, err = ambilStok(20, 12)
fmt.Println("sisa:", sisa, "| err:", err)
fmt.Printf("cetak dengan %%v: %v\n", err)
if err == nil {
fmt.Println("status: sukses")
} else {
fmt.Println("status: gagal, pesan:", err.Error())
}
}