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:

go
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:

go
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:

bash
go run error-interface-demo.go

Output yang Dihasilkan:

text
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 12

Mari kita telaah baris demi baris program di atas:

  1. Baris 6-8 (type SisaStokError struct): struct biasa dengan satu field int; struct ini belum menjadi error sampai method Error dideklarasikan.
  2. Baris 10-12 (func (e SisaStokError) Error() string): method dengan receiver SisaStokError memenuhi kontrak interface error, sehingga SisaStokError otomatis bisa dipakai sebagai nilai error.
  3. Baris 16-21 (func ambilStok): fungsi mengembalikan pasangan (int, error); kegagalan diwakili error non-nil, sukses diwakili nil.
  4. Baris 20 (return tersedia - diminta, nil): jalur sukses mengembalikan error nil — nilai inilah yang nanti dibandingkan pemanggil.
  5. Baris 24-25 (sisa, err := ambilStok(5, 12)): pemanggilan sukses; fmt.Println mencetak nilai err yang masih nil sebagai <nil>.
  6. Baris 27-28 (sisa, err = ambilStok(20, 12)): pemanggilan gagal; nilai sisa menjadi 0 dan err berisi SisaStokError.
  7. Baris 30 (fmt.Printf dengan verb %v): verb %v memanggil method Error() pada nilai error non-nil, sehingga pesan lengkapnya ikut tercetak.
  8. Baris 32-36 (if err == nil ... else ...): pola pemeriksaan standar; cabang else memanggil err.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 #

Berkas Sumber

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

error-interface-demo.go

Unduh berkas
go
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())
	}
}