Error sebagai Nilai

Interface error serta pola mengembalikan dan memeriksa error.

Daftar isi

Go memperlakukan error sebagai nilai biasa yang dikembalikan oleh fungsi, bukan sebagai pengecualian yang menyela alur program. Topik ini membahas interface error, pola mengembalikan dan memeriksa error dengan if err != nil, serta error sentinel sebagai penanda kegagalan yang dikenal. Setelah membacanya, kegagalan dapat dilaporkan dan ditangani mengikuti konvensi bahasa Go, yaitu diperiksa secara eksplisit di tempat hasilnya diterima.

Interface error#

Tipe error adalah interface, yaitu tipe yang menuntut sekumpulan metode tanpa menetapkan implementasinya. Interface error menuntut satu metode saja, dan setiap tipe yang memilikinya otomatis memenuhi interface ini.

go Definisi interface error pada pustaka standar
// tidak-dijalankan
type error interface {
	Error() string
}

Metode Error() mengembalikan pesan kegagalan dalam bentuk string. Nilai nil berarti tidak ada kegagalan, sebab zero value (nilai bawaan sebuah tipe sebelum diisi) dari interface adalah nil. Konvensi Go menempatkan error sebagai nilai kembalian terakhir, sehingga pola (nilai, error) terbaca seragam di pustaka standar maupun kode Go pada umumnya.

Paket errors dan fmt menyediakan pembuat siap pakai untuk nilai error, yaitu errors.New untuk pesan tetap dan fmt.Errorf untuk pesan berformat. Keduanya menghasilkan nilai yang sudah memenuhi interface error, sehingga kebutuhan pesan sederhana tidak menuntut tipe baru. Tipe error buatan sendiri baru diperlukan apabila detail kegagalan perlu disimpan dalam bentuk terstruktur.

Pola if err != nil#

Fungsi yang dapat gagal mengembalikan hasilnya bersama error, dan pemanggil memeriksa error itu sebelum memakai hasilnya. Program berikut menguraikan teks pengaturan menjadi bilangan bulat dengan strconv.Atoi, lalu memeriksa hasilnya satu per satu.

go periksa-error.go
package main

import (
	"fmt"
	"strconv"
)

// ambilAmbang mengubah teks pengaturan menjadi bilangan bulat.
func ambilAmbang(teks string) (int, error) {
	n, err := strconv.Atoi(teks)
	if err != nil {
		return 0, err
	}
	return n, nil
}

func main() {
	daftar := []string{"80", "nol", "120"}
	for _, teks := range daftar {
		ambang, err := ambilAmbang(teks)
		if err != nil {
			fmt.Printf("%q ditolak: %v\n", teks, err)
			continue
		}
		fmt.Printf("%q diterima, ambang %d\n", teks, ambang)
	}
}
text Keluaran
"80" diterima, ambang 80
"nol" ditolak: strconv.Atoi: parsing "nol": invalid syntax
"120" diterima, ambang 120

Pola if err != nil membuat setiap jalur kegagalan tampak dalam kode, sehingga keputusan mengabaikan, melaporkan, atau meneruskan kegagalan menjadi pilihan yang tertulis. Ketika err != nil, fungsi mengembalikan nilai nol bersama errornya agar pemanggil tidak memakai hasil yang tidak sah. Ketika tidak ada kegagalan, nil dikembalikan bersama hasil utuhnya.

Filosofi di balik pola ini dijelaskan pada artikel resmi Errors are values, yaitu error diperlakukan sebagai nilai biasa yang dapat disimpan, dibandingkan, dan diteruskan sebagaimana nilai lain. Kegagalan menjadi bagian dari tipe kembalian fungsi, bukan jalur tersembunyi di luar kendali pemrogram. Akibatnya, pembaca kode dapat menemukan seluruh keputusan penanganan kegagalan dengan mencari pola pemeriksaannya.

Gaya yang lazim adalah memeriksa error segera setelah pemanggilan, lalu kembali lebih awal apabila gagal. Pola kembali awal ini menjaga jalur keberhasilan tetap lurus tanpa lekukan yang dalam, berbeda dengan gaya bersarang yang membungkus jalur keberhasilan di dalam blok else. Pembaca kode dapat mengikuti alur utamanya tanpa harus melacak tingkatan lekukan.

Error sentinel#

Error sentinel (sentinel error) adalah nilai error yang dideklarasikan sebagai variabel agar kegagalan tertentu dapat dikenali pemanggil. Fungsi mengembalikan nilai sentinel itu pada kondisi yang telah dirancang, sedangkan pemanggil membandingkannya dengan ==. Program berikut memvalidasi nama kategori dengan satu sentinel.

go sentinel.go
package main

import (
	"errors"
	"fmt"
)

// errNamaKosong adalah error sentinel, yaitu satu nilai error yang dikenal.
var errNamaKosong = errors.New("nama kategori tidak boleh kosong")

func validasi(nama string) error {
	if nama == "" {
		return errNamaKosong
	}
	return nil
}

func main() {
	for _, nama := range []string{"suhu", ""} {
		err := validasi(nama)
		if err != nil {
			fmt.Println("ditolak:", err)
			if err == errNamaKosong {
				fmt.Println("sebab dikenal: gunakan nilai bawaan")
			}
			continue
		}
		fmt.Println("diterima:", nama)
	}
}
text Keluaran
diterima: suhu
ditolak: nama kategori tidak boleh kosong
sebab dikenal: gunakan nilai bawaan

Fungsi errors.New membuat satu nilai error baru dengan pesannya, dan setiap pemanggilannya menghasilkan nilai yang berbeda. Perbandingan err == errNamaKosong berhasil karena fungsi mengembalikan nilai sentinel itu apa adanya. Apabila nilai error dibungkus dengan error baru, perbandingan == tidak lagi menemukannya, dan pemeriksaan pada rantai error terbungkus ditangani oleh errors.Is pada pembahasan pembungkusan error.

Tipe error buatan sendiri#

Tipe apa pun yang memiliki metode Error() string menjadi error yang sah, sehingga detail kegagalan dapat dikemas dalam struct milik program. Error sentinel cukup untuk kegagalan yang bentuknya satu, sedangkan tipe buatan sendiri berguna ketika konteksnya perlu bervariasi, misalnya nomor baris dan teks yang ditolak. Program berikut menguraikan pengaturan berformat nama=nilai dan melaporkan baris yang tidak sesuai format.

go tipe-error.go
package main

import (
	"fmt"
	"strings"
)

// ErrFormat adalah tipe error buatan sendiri yang menyimpan detail kegagalan.
type ErrFormat struct {
	Baris int
	Teks  string
}

func (e ErrFormat) Error() string {
	return fmt.Sprintf("baris %d tidak berformat nama=nilai: %q", e.Baris, e.Teks)
}

// urai membaca pengaturan sederhana dengan format nama=nilai.
func urai(baris []string) ([]string, error) {
	hasil := make([]string, 0, len(baris))
	for i, b := range baris {
		if !strings.Contains(b, "=") {
			return nil, ErrFormat{Baris: i + 1, Teks: b}
		}
		hasil = append(hasil, b)
	}
	return hasil, nil
}

func main() {
	for _, grup := range [][]string{
		{"ambang=80", "durasi=30"},
		{"ambang=80", "hanya teks"},
	} {
		daftar, err := urai(grup)
		if err != nil {
			fmt.Println("gagal:", err)
			continue
		}
		fmt.Println("sah:", daftar)
	}
}
text Keluaran
sah: [ambang=80 durasi=30]
gagal: baris 2 tidak berformat nama=nilai: "hanya teks"

Tipe ErrFormat menyimpan nomor baris dan teks barisnya, lalu metode Error() menyusun keduanya menjadi pesan. Fungsi urai mengembalikan ErrFormat tersebut sebagaimana error lain, dan pemanggil memeriksanya dengan pola if err != nil yang sama. Detail pada field-nya dapat dibaca setelah error dikenali tipenya, dan alat pemeriksaannya diperlihatkan pada pembahasan pembungkusan error.

Kesalahan umum#

  • Mengabaikan error dengan tanda garis bawah. Pernyataan ambang, _ := ambilAmbang(teks) meloloskan kegagalan tanpa jejak, sehingga program berlanjut memakai nilai nol yang tidak sah. Perbaikannya, periksa errornya dan putuskan langkahnya, atau teruskan error kepada pemanggil.
  • Mencetak lalu mengembalikan nil. Fungsi yang mencatat error ke layar tetapi mengembalikan nil membuat pemanggil mengira tidak ada kegagalan. Perbaikannya, kembalikan error yang sama kepada pemanggil, dan serahkan keputusan pencatatan kepada lapisan yang benar-benar memegang kendali keluaran.
  • Kehilangan jejak err karena lingkup if. Deklarasi if ambang, err := ambilAmbang(teks); err != nil membuat err hanya hidup di dalam blok if tersebut, sehingga pemakaian err di luar blok merujuk variabel lain yang mungkin masih nil. Perbaikannya, deklarasikan err di lingkup yang lebih luas bila errornya masih diperlukan setelah pemeriksaan.

Ringkasan#

  • error adalah interface dengan satu metode Error() string, dan nilainya nil ketika tidak ada kegagalan.
  • Konvensi Go menempatkan error sebagai nilai kembalian terakhir dari fungsi, dan pola if err != nil menjadikan setiap keputusan penanganan kegagalan tertulis secara eksplisit.
  • Error sentinel memungkinkan pemanggil mengenali kegagalan tertentu melalui perbandingan nilai.
  • Tipe error buatan sendiri mengemas detail kegagalan dalam field yang terstruktur.

Referensi#