Sebagian kegagalan memiliki makna yang tetap: data tidak ada, berkas sudah ditutup, kuota habis. Untuk kasus seperti ini, Go memakai pola sentinel error — satu nilai error yang dideklarasikan di level package sebagai titik acuan, lalu dibandingkan pemanggilnya dengan errors.Is. Pada chapter ini kita akan mendefinisikan sentinel sendiri, melihat contoh nyata io.EOF dari standard library, dan menimbang kapan pola ini layak dipakai di API publik.
Apa Itu Sentinel Error #
Sentinel error adalah nilai error yang dibuat sekali dan menjadi penanda kondisi tertentu:
var ErrTidakDitemukan = errors.New("data tidak ditemukan")Tiga ciri menentukan pola ini. Pertama, nilai dideklarasi sebagai variabel global dengan awalan Err sesuai konvensi penamaan Go. Kedua, nilai dialokasikan sekali saat package dimuat, sehingga setiap kejadian gagal berbagi objek yang identik. Ketiga, pemanggil tidak membandingkan teks pesannya, melainkan identitas nilainya — dan pemeriksaan identitas dilakukan lewat errors.Is, bukan operator ==.
Sentinel di Standard Library #
Contoh paling terkenal adalah io.EOF dari package io. Seluruh pembaca bertipe io.Reader menandai akhir data dengan mengembalikan io.EOF, bukan error buatan sendiri. Karena satu titik acuan itu disepakati, kode pembaca berkas, koneksi jaringan, dan buffer memori memakai pemeriksaan yang sama:
if errors.Is(err, io.EOF) {
fmt.Println("selesai membaca")
}Pola serupa tersebar di standard library: sql.ErrNoRows untuk query tanpa hasil dan fs.ErrNotExist untuk berkas yang tidak ada.
Kapan Sentinel Cocok #
Sentinel paling berguna pada batas API publik, ketika pemanggil butuh mengambil keputusan berbeda untuk kegagalan yang berbeda. Daftar berikut memetakan pertimbangannya:
- Cocok: kondisi yang pemanggil ingin tangani secara khusus, misalnya not found yang harus dijawab dengan HTTP 404.
- Cocok: nilai yang bisa dijamin stabil dalam jangka panjang karena sudah menjadi kontrak API.
- Tidak cocok: klasifikasi kegagalan yang granular dan terus bertambah — setiap sentinel baru adalah kontrak baru yang harus dirawat.
- Tidak cocok: detail yang hanya perlu dibaca manusia; cukup
fmt.Errorfdengan konteks.
Program Utuh #
Berkas sentinel-demo.go membandingkan sentinel buatan sendiri dan sentinel standard library dalam satu program:
package main
import (
"errors"
"fmt"
"io"
"strings"
)
// ErrTidakDitemukan adalah sentinel untuk data yang tidak ada.
var ErrTidakDitemukan = errors.New("data tidak ditemukan")
func cariPengguna(kunci string) (string, error) {
pengguna := map[string]string{
"budi": "Budi Santoso",
"siti": "Siti Aminah",
}
if nama, ok := pengguna[kunci]; ok {
return nama, nil
}
return "", ErrTidakDitemukan
}
func bacaPotongan(r io.Reader) {
buf := make([]byte, 4)
for {
n, err := r.Read(buf)
if n > 0 {
fmt.Printf("terbaca %d byte: %q\n", n, buf[:n])
}
if err != nil {
if errors.Is(err, io.EOF) {
fmt.Println("akhir data tercapai")
} else {
fmt.Println("error pembacaan lain:", err)
}
return
}
}
}
func main() {
nama, err := cariPengguna("budi")
fmt.Printf("nama=%q err=%v\n", nama, err)
nama, err = cariPengguna("rudi")
fmt.Printf("nama=%q err=%v\n", nama, err)
fmt.Println("errors.Is ErrTidakDitemukan:", errors.Is(err, ErrTidakDitemukan))
fmt.Println("errors.Is io.ErrUnexpectedEOF:", errors.Is(err, io.ErrUnexpectedEOF))
bacaPotongan(strings.NewReader("Golang"))
}Jalankan program dari direktori chapter dengan perintah berikut:
go run sentinel-demo.goOutput yang Dihasilkan:
nama="Budi Santoso" err=<nil>
nama="" err=data tidak ditemukan
errors.Is ErrTidakDitemukan: true
errors.Is io.ErrUnexpectedEOF: false
terbaca 4 byte: "Gola"
terbaca 2 byte: "ng"
akhir data tercapaiMari kita telaah baris demi baris program di atas:
- Baris 10-11 (
var ErrTidakDitemukan): sentinel dideklarasikan di level package sehingga fungsicariPenggunadan pemanggilnya memakai objek error yang identik. - Baris 18-21 (
if nama, ok := pengguna[kunci]): idiom comma ok pada akses map memisahkan jalur ketemu dan tidak ketemu; jalur gagal mengembalikan sentinel, bukan error baru. - Baris 27-30 (
n, err := r.Read(buf)): methodReadboleh mengembalikan data dan error sekaligus, sehingga program mencetak data yang berhasil dibaca lebih dulu sebelum memeriksaerr. - Baris 31-37 (
if err != nil ... errors.Is(err, io.EOF)): pemeriksaan akhir data memakaierrors.Isterhadap sentinel standard library; cabang lain menangani error pembacaan yang berbeda. - Baris 49-50 (
fmt.Println("errors.Is ...")): pemeriksaan terhadap sentinel sendiri menghasilkantrue, sedangkanio.ErrUnexpectedEOFyang berbeda menghasilkanfalse— sentinel tidak tertukar walaupun sama-sama berhubungan dengan pembacaan. - Baris 52 (
bacaPotongan(strings.NewReader("Golang"))): pembacaan string enam byte dengan buffer empat byte;strings.Readermengembalikan potongan terakhirngsekaligusio.EOF, sehingga output menampilkan dua baristerbacalalu berakhir.
Sentinel menjawab pertanyaan “kegagalan jenis apa ini?”, tetapi jawabannya terbatas pada satu bit: cocok atau tidak. Ketika pemanggil butuh detail — field mana yang salah, berapa nilainya — kita perlu error yang membawa data. Chapter berikutnya, Custom Error Type , membangun tipe error milik kita sendiri.
Referensi #
- Dokumentasi package io: EOF — contoh sentinel error paling terkenal di standard library.
- Dokumentasi errors: Is — aturan perbandingan error dalam rantai.
- Working with Errors in Go 1.13
— artikel resmi yang memperkenalkan pola pemeriksaan
errors.Is.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
sentinel-demo.go
Unduh berkaspackage main
import (
"errors"
"fmt"
"io"
"strings"
)
// ErrTidakDitemukan adalah sentinel untuk data yang tidak ada.
var ErrTidakDitemukan = errors.New("data tidak ditemukan")
func cariPengguna(kunci string) (string, error) {
pengguna := map[string]string{
"budi": "Budi Santoso",
"siti": "Siti Aminah",
}
if nama, ok := pengguna[kunci]; ok {
return nama, nil
}
return "", ErrTidakDitemukan
}
func bacaPotongan(r io.Reader) {
buf := make([]byte, 4)
for {
n, err := r.Read(buf)
if n > 0 {
fmt.Printf("terbaca %d byte: %q\n", n, buf[:n])
}
if err != nil {
if errors.Is(err, io.EOF) {
fmt.Println("akhir data tercapai")
} else {
fmt.Println("error pembacaan lain:", err)
}
return
}
}
}
func main() {
nama, err := cariPengguna("budi")
fmt.Printf("nama=%q err=%v\n", nama, err)
nama, err = cariPengguna("rudi")
fmt.Printf("nama=%q err=%v\n", nama, err)
fmt.Println("errors.Is ErrTidakDitemukan:", errors.Is(err, ErrTidakDitemukan))
fmt.Println("errors.Is io.ErrUnexpectedEOF:", errors.Is(err, io.ErrUnexpectedEOF))
bacaPotongan(strings.NewReader("Golang"))
}