Golang Dasar: Dari Nol hingga CLI Production-Ready4/10

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 defer untuk cleanup yang dapat diandalkan.

1. Fungsi dan Nilai Kembalian

Fungsi Go menyatakan tipe setiap parameter dan hasil. Parameter bertipe sama dapat diringkas.

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

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

Code
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.

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

Code
type error interface {
	Error() string
}

Fungsi yang dapat gagal lazimnya mengembalikan error terakhir. Pemeriksaan dilakukan segera.

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

Code
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.

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

Memuat diagram…

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.

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

Code
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. Gunakan errors.Is atau errors.As.
  • Membungkus tanpa %w. %v memutus kemampuan pemeriksaan rantai.
  • Dereference pointer nil. Validasi nil bila termasuk input yang mungkin.
  • Menggunakan pointer tanpa kebutuhan. Nilai kecil lebih sederhana dan aman disalin.
  • Menaruh defer sebelum memastikan resource berhasil dibuka. Jadwalkan cleanup setelah pemeriksaan error.
  • Salah memahami argumen defer. Nilai argumen ditangkap saat pernyataan defer, bukan saat eksekusi akhir.

Latihan

  1. Buat fungsi variadik yang mengembalikan minimum dan error jika tidak ada argumen.
  2. Buat fungsi tukar(a, b *int) dan uji perubahan pada pemanggil.
  3. Definisikan sentinel error untuk saldo tidak cukup; bungkus dengan konteks dan periksa memakai errors.Is.
  4. Prediksi keluaran tiga defer, lalu jalankan untuk memverifikasi urutan LIFO.
  5. 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.

Fungsi, Pointer, Error, dan Defer · Golang Dasar: Dari Nol hingga CLI Production-Ready