Bab 4 dari 10
Fungsi, Pointer, Error, dan Defer
Tujuan Pembelajaran
Setelah bab ini, Anda mampu:
- mendefinisikan fungsi dengan parameter dan beberapa hasil;
- memahami kapan pointer diperlukan;
- membuat, membungkus, dan memeriksa error;
- memakai
deferuntuk cleanup yang dapat diandalkan.
1. Fungsi dan Nilai Kembalian
Fungsi Go menyatakan tipe setiap parameter dan hasil. Parameter bertipe sama dapat diringkas.
package main
import "fmt"
func tambah(a, b int) int {
return a + b
}
func bagi(a, b float64) (float64, bool) {
if b == 0 {
return 0, false
}
return a / b, true
}
func main() {
fmt.Println(tambah(2, 3))
hasil, ok := bagi(10, 2)
fmt.Println(hasil, ok)
}
Beberapa hasil umum dipakai untuk nilai beserta status atau error. Named result tersedia, tetapi hasil eksplisit biasanya lebih jelas pada fungsi pendek. Fungsi variadik menerima nol atau lebih argumen:
func jumlah(angka ...int) int {
total := 0
for _, n := range angka {
total += n
}
return total
}
Fungsi adalah nilai dan dapat disimpan atau dikirim sebagai argumen:
func terapkan(a, b int, operasi func(int, int) int) int {
return operasi(a, b)
}
2. Pointer dan Semantik Nilai
Argumen selalu diteruskan by value. Jika argumennya pointer, nilai alamat itulah yang disalin, sehingga fungsi dapat mengubah objek tujuan.
package main
import "fmt"
func naikkan(n *int) {
*n++
}
func main() {
nilai := 10
naikkan(&nilai)
fmt.Println(nilai)
}
&nilai mengambil alamat; *n membaca atau menulis nilai yang ditunjuk. Go tidak mendukung aritmetika pointer. Pointer memiliki zero value nil; dereference pointer nil menyebabkan panic.
Gunakan pointer ketika fungsi perlu memutasi nilai pemanggil, ketika penyalinan struct besar memang mahal, atau ketika nil bermakna. Jangan memakai pointer hanya agar kode terlihat efisien. Slice dan map sudah memiliki descriptor/header yang merujuk data internal; fungsi biasanya tidak memerlukan pointer ke slice atau map untuk mengubah elemennya.
3. Error sebagai Nilai
error adalah interface standar:
type error interface {
Error() string
}
Fungsi yang dapat gagal lazimnya mengembalikan error terakhir. Pemeriksaan dilakukan segera.
package main
import (
"errors"
"fmt"
)
func akarSederhana(n float64) (float64, error) {
if n < 0 {
return 0, errors.New("nilai tidak boleh negatif")
}
if n == 0 {
return 0, nil
}
x := n
for i := 0; i < 10; i++ {
x = (x + n/x) / 2
}
return x, nil
}
func main() {
hasil, err := akarSederhana(9)
if err != nil {
fmt.Println("gagal:", err)
return
}
fmt.Println(hasil)
}
Tambahkan konteks tanpa menghilangkan identitas error memakai %w:
return fmt.Errorf("membaca konfigurasi %q: %w", nama, err)
Periksa rantai error dengan errors.Is, bukan membandingkan teks. Gunakan errors.As untuk mengambil tipe error tertentu.
package main
import (
"errors"
"fmt"
)
var ErrTidakDitemukan = errors.New("data tidak ditemukan")
func cari(id int) (string, error) {
if id != 1 {
return "", fmt.Errorf("id %d: %w", id, ErrTidakDitemukan)
}
return "data", nil
}
func main() {
_, err := cari(2)
if errors.Is(err, ErrTidakDitemukan) {
fmt.Println("gunakan hasil kosong")
}
}
Alur penanganannya:
4. Cleanup dengan defer
defer menjadwalkan pemanggilan sampai fungsi di sekitarnya selesai, baik melalui return normal maupun panic. Argumen deferred call dievaluasi saat defer dijalankan. Banyak defer dieksekusi LIFO.
package main
import "fmt"
func urutan() {
defer fmt.Println("pertama dijadwalkan, terakhir berjalan")
defer fmt.Println("kedua dijadwalkan, pertama berjalan")
fmt.Println("body")
}
func main() {
urutan()
}
Pola cleanup file:
func bacaAwal(nama string) ([]byte, error) {
f, err := os.Open(nama)
if err != nil {
return nil, fmt.Errorf("membuka %q: %w", nama, err)
}
defer f.Close()
data := make([]byte, 64)
n, err := f.Read(data)
if err != nil && !errors.Is(err, io.EOF) {
return nil, fmt.Errorf("membaca %q: %w", nama, err)
}
return data[:n], nil
}
Import yang diperlukan adalah errors, fmt, io, dan os. Untuk operasi tulis, error dari Close dapat penting; jangan selalu mengabaikannya. Hindari defer di loop panjang jika resource harus dilepas pada setiap iterasi; pindahkan satu iterasi ke fungsi kecil.
Kesalahan Umum
- Mengabaikan error dengan
_. Abaikan hanya bila kegagalan benar-benar tidak relevan dan kontraknya dipahami. - Membandingkan
err.Error()sebagai teks. Gunakanerrors.Isatauerrors.As. - Membungkus tanpa
%w.%vmemutus kemampuan pemeriksaan rantai. - Dereference pointer nil. Validasi
nilbila termasuk input yang mungkin. - Menggunakan pointer tanpa kebutuhan. Nilai kecil lebih sederhana dan aman disalin.
- Menaruh
defersebelum memastikan resource berhasil dibuka. Jadwalkan cleanup setelah pemeriksaan error. - Salah memahami argumen defer. Nilai argumen ditangkap saat pernyataan
defer, bukan saat eksekusi akhir.
Latihan
- Buat fungsi variadik yang mengembalikan minimum dan error jika tidak ada argumen.
- Buat fungsi
tukar(a, b *int)dan uji perubahan pada pemanggil. - Definisikan sentinel error untuk saldo tidak cukup; bungkus dengan konteks dan periksa memakai
errors.Is. - Prediksi keluaran tiga
defer, lalu jalankan untuk memverifikasi urutan LIFO. - Buat fungsi pembaca file yang selalu menutup file dan menambahkan nama file pada error.
Rangkuman
Fungsi Go mendukung beberapa hasil, nilai fungsi, dan parameter variadik. Semua argumen disalin; pointer memungkinkan mutasi terhadap nilai tujuan, tetapi harus digunakan dengan alasan jelas. Error adalah nilai yang diperiksa, diberi konteks dengan %w, dan dikenali melalui errors.Is/errors.As. defer menempatkan cleanup dekat akuisisi resource dan berjalan dalam urutan LIFO.