Menu

Penanganan Error di Golang: if err != nil, Wrapping, Pengecekan

Go menangani error sebagai nilai biasa yang dikembalikan oleh fungsi. Pelajari interface error, if err != nil, errors.New dan fmt.Errorf, mengembalikan error dengan konteks, memeriksanya dengan errors.Is dan errors.As, dan menangani setiap error sekali saja.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Error adalah nilai

Fungsi Go yang bisa gagal mengembalikan error sebagai hasil terakhirnya. Pemanggil langsung memeriksanya.

Output:

parsed: 42
could not parse: strconv.Atoi: parsing "forty-two": invalid syntax

Hanya itu mekanismenya. Tidak ada exception, tidak ada try atau catch, dan tidak ada alur kontrol yang tersembunyi: error hanya berpindah ke tempat yang dituju kodemu. Harganya adalah pengulangan if err != nil yang terlihat jelas. Manfaatnya, setiap titik kegagalan terlihat di halaman, dan kamu yang menentukan apa yang terjadi di setiap titik itu.

Tipe error

error adalah interface bawaan dengan satu method:

type error interface {
	Error() string
}

Tipe apa pun yang punya method Error() string adalah error. Error yang bernilai nil berarti berhasil. Mencetak error dengan fmt.Println(err) atau %v memanggil Error().

Membuat error

Dua fungsi sudah mencakup kebanyakan kasus.

errors.New membuat error dengan teks yang tetap. fmt.Errorf memformatnya, dengan verb yang sama seperti Printf. Menurut konvensi, string error diawali huruf kecil dan tidak diakhiri tanda baca, karena biasanya disisipkan ke dalam pesan yang lebih panjang: load config: open app.yaml: no such file or directory.

Pola if err != nil

Bentuk yang idiomatis adalah: panggil, periksa, kembalikan lebih awal. Jalur sukses tetap berada di margin kiri, dan setiap kegagalan langsung keluar begitu terjadi.

func loadUser(id int) (*User, error) {
	row, err := db.Query(id)
	if err != nil {
		return nil, err
	}
	u, err := parseUser(row)
	if err != nil {
		return nil, err
	}
	if err := u.Validate(); err != nil {
		return nil, err
	}
	return u, nil
}

Dua konvensi yang perlu diperhatikan:

  • Saat error, kembalikan zero value untuk hasil lainnya (nil, 0, ""). Pemanggil tidak boleh memakainya saat err != nil.
  • if err := f(); err != nil membatasi scope err hanya di dalam if saat fungsinya hanya mengembalikan error. Ini menjaga scope luar tetap bersih.

Hindari else setelah return karena error. if err != nil { return err } else { ... } hanya menambah indentasi pada jalur sukses tanpa alasan.

Menambahkan konteks saat mengembalikan error

Error yang diteruskan ke atas tanpa diubah kehilangan cerita tentang asal-usulnya. open config.yaml: no such file or directory tidak memberi tahu langkah startup mana yang gagal. Tambahkan konteks dengan fmt.Errorf dan verb %w:

Output:

start server: read config: open /etc/myapp/config.yaml: no such file or directory
true

Setiap lapisan menambahkan apa yang sedang dikerjakannya, dan pesan akhirnya terbaca seperti jejak dari puncak pemanggilan sampai ke penyebabnya. Konteks yang baik menyebut operasi dan input-nya: parse line 12, fetch user 42. Jangan tambahkan "error" atau "failed" di setiap level; pesannya sudah merupakan error.

%w membungkus: ia menyimpan error asli di dalam error yang baru, jadi errors.Is dan errors.As masih bisa menemukannya. %v hanya menyalin teksnya. Pakai %v saat kamu sengaja ingin menyembunyikan detail implementasi dari pemanggil, misalnya supaya mereka tidak sampai bergantung pada tipe error dari driver database.

Memeriksa error tertentu: errors.Is dan errors.As

Kadang pemanggil perlu bereaksi terhadap satu jenis kegagalan: file yang tidak ada berarti "pakai default", timeout berarti "coba lagi". Dua fungsi menjawab kebutuhan itu, dan keduanya menelusuri setiap lapisan pembungkus.

Aturan praktis:

  • Bandingkan dengan nilai error yang sudah didefinisikan (sentinel seperti io.EOF, os.ErrNotExist, sql.ErrNoRows) dengan errors.Is, bukan ==. == gagal begitu error-nya dibungkus.
  • Ambil error bertipe dengan errors.As, bukan type assertion, dengan alasan yang sama. errors.As menerima pointer ke variabel bertipe target.
  • Jangan pernah mencocokkan berdasarkan teks err.Error(). Pesan bisa berubah antarversi, dan pencocokan teks akan rusak diam-diam saat itu terjadi.

Mendefinisikan sentinel error dan tipe error milikmu sendiri, serta menggabungkan beberapa error dengan errors.Join, dibahas di halaman error kustom.

Tangani error sekali saja

Sebuah error seharusnya ditangani tepat satu kali. Menangani berarti salah satu dari: mengembalikannya (biasanya dibungkus), mencatatnya di log lalu melanjutkan, mencoba lagi, atau mengubahnya menjadi respons untuk pengguna. Melakukan dua di antaranya adalah bug error yang paling umum di kode Go.

// Wrong: logged here, and returned, so it is logged again by every caller.
if err != nil {
	log.Printf("could not fetch user: %v", err)
	return err
}

// Right: add context and return. The top of the program logs once.
if err != nil {
	return fmt.Errorf("fetch user %d: %w", id, err)
}

Mencatat di log sekaligus mengembalikan error membuat kegagalan yang sama muncul beberapa kali di log, masing-masing dengan konteks yang lebih sedikit dari pesan akhirnya. Biarkan error mengalir ke atas sampai ke tempat yang bisa memutuskan apa yang harus dilakukan (handler HTTP, main, loop worker), dan catat di sana.

Ke mana error berakhir

Di puncak program, harus ada yang bertindak atas error tersebut. Di main, biasanya itu berarti mencetaknya dan keluar dengan status bukan nol:

Menjalankan program ini tanpa argumen mencetak error: usage: app <name> ke stderr dan keluar dengan status 1 (ketik sebuah nama di panel Argumen untuk melihat jalur yang lain). Menjaga main tetap dalam bentuk ini, dengan pekerjaan sesungguhnya di run, membuat program bisa diuji dan memastikan pernyataan defer di dalam run tetap dijalankan, karena os.Exit melewati pemanggilan defer.

Di server HTTP, puncaknya adalah handler: handler memetakan error ke status code dan pesan yang aman untuk client, lalu mencatat pesan lengkapnya di log untukmu.

Error yang boleh diabaikan, dan yang tidak boleh

Mengabaikan error kadang memang benar, tetapi lakukan secara eksplisit dengan _ supaya pembaca tahu itu adalah keputusan:

_ = conn.SetDeadline(t) // best effort

Beberapa pemanggilan pada praktiknya tidak bisa gagal (strings.Builder.WriteString, bytes.Buffer.Write). Yang lain terlihat tidak berbahaya padahal tidak: Close pada file yang kamu tulis bisa melaporkan bahwa datanya tidak pernah sampai ke disk, dan json.Marshal gagal pada channel dan fungsi. Kalau ragu, periksa.

Linter errcheck (termasuk dalam golangci-lint) melaporkan error yang tidak diperiksa. go vet sendiri tidak menandainya.

Error dan panic

Go juga punya panic, tetapi itu bukan sistem exception. Pakai error untuk apa pun yang bisa salah dalam operasi normal: input yang buruk, file yang tidak ada, kegagalan jaringan. Simpan panic untuk bug (kondisi yang mustahil, invarian yang rusak) dan untuk kegagalan saat startup ketika melanjutkan program tidak masuk akal. Sebuah library hampir tidak boleh panic melewati API-nya. Lihat panic dan recover.

Mengurangi pengulangan

if err != nil memang panjang, dan usulan untuk menambahkan sintaks baru untuknya berkali-kali ditolak; tim Go mengumumkan pada 2025 bahwa mereka tidak lagi mengejar perubahan sintaks untuk penanganan error. Beberapa pola mengurangi kebisingan itu di dalam bahasa yang ada:

  • Kembalikan lebih awal dan jaga fungsi tetap kecil. Kebanyakan pengulangan berasal dari fungsi panjang yang melakukan banyak langkah.
  • Sticky error. Untuk serangkaian penulisan, simpan error pertama di field struct dan buat pemanggilan berikutnya tidak melakukan apa-apa begitu error itu terisi. bufio.Writer bekerja dengan cara ini: kamu memeriksa error sekali saja setelah Flush.
  • Bungkus sekali per fungsi. Closure yang di-defer atas hasil bernama bisa menambahkan konteks yang sama ke setiap error yang dikembalikan fungsi itu (lihat defer).

Kesalahan umum

  • Memakai nilai saat err tidak nil. Periksa dulu, baru pakai.
  • Mencatat di log dan mengembalikan. Pilih salah satu.
  • Membandingkan dengan == setelah dibungkus. Pakai errors.Is.
  • Kehilangan penyebab karena %v. Pakai %w kecuali menyembunyikannya memang tujuannya.
  • Mengembalikan pointer nil bertipe sebagai error. var e *MyErr; return e tidak nil di mata pemanggil. Kembalikan nil literal.
  • Pesan dengan huruf kapital atau tanda baca. errors.New("Failed to connect.") terbaca janggal begitu dibungkus. Tulis connect to db: ....

Pertanyaan yang sering diajukan

Bagaimana cara kerja penanganan error di Go?

Fungsi yang bisa gagal mengembalikan error sebagai hasil terakhirnya. Pemanggil langsung memeriksanya: v, err := f(); if err != nil { return err }. error adalah nilai interface biasa dengan satu method, Error() string, dan nil berarti berhasil. Tidak ada exception.

Apakah Go punya try/catch?

Tidak. Go tidak punya exception dan tidak punya try/catch. Kegagalan yang sudah diperkirakan dikembalikan sebagai nilai error dan diperiksa dengan if err != nil. panic dan recover memang ada, tetapi keduanya untuk bug pemrograman dan kondisi yang tidak bisa dipulihkan, bukan untuk alur error yang normal.

Bagaimana cara mengembalikan error di Go?

Deklarasikan error sebagai hasil terakhir dan kembalikan nil saat berhasil. Buat error dengan errors.New("message") untuk teks yang tetap, atau fmt.Errorf("reading %s: %w", name, err) untuk menambahkan konteks ke error yang kamu terima. Saat gagal, kembalikan zero value untuk hasil lainnya.

Apa bedanya %w dan %v di fmt.Errorf?

Keduanya memasukkan pesan error asli ke dalam error yang baru. %w juga membungkusnya, jadi errors.Is dan errors.As masih bisa menemukan error aslinya. %v menghasilkan error baru yang hanya berisi teksnya. Pakai %w saat pemanggil mungkin perlu memeriksa penyebabnya, dan %v saat kamu ingin menyembunyikannya.

Bagaimana cara memeriksa error mana yang dikembalikan di Go?

Pakai errors.Is(err, target) untuk membandingkan dengan sentinel error seperti io.EOF atau os.ErrNotExist, dan errors.As(err, &target) untuk mengambil tipe error tertentu seperti *fs.PathError. Keduanya menelusuri error yang dibungkus. Hindari membandingkan string err.Error().

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai