Belajar Golang dari Nol #3: Function, Multiple Return, dan Error

Solusi IT

Di Belajar Golang dari Nol #2 kamu sudah pegang variabel, tipe data, zero value, dan konversi tipe. Di sana kamu juga sempat ketemu sekilas dengan if err != nil waktu memakai strconv.Atoi. Waktu itu saya minta kamu terima dulu polanya tanpa banyak tanya. Sekarang saatnya membongkar asal-usulnya. Di bagian ini kita belajar function, multiple return, dan cara Go memperlakukan error.

Function: bungkus logika biar bisa dipakai ulang

Bayangkan function seperti mesin jus. Kamu masukkan buah, mesin bekerja, keluar jus. Kamu tidak perlu tahu isi mesinnya setiap kali mau bikin jus. Cukup tahu apa yang dimasukkan dan apa yang keluar.

Di Go, bentuk dasarnya seperti ini.

func sapa(nama string) string {
    return "Halo, " + nama
}

Baca pelan-pelan dari kiri ke kanan. func adalah kata kunci untuk membuat function. sapa adalah namanya. nama string artinya function ini menerima satu parameter bernama nama yang bertipe string. string di paling kanan adalah tipe nilai balikannya. Lalu return mengirim nilai itu keluar ke pemanggil.

Kalau kamu pernah lihat Java atau C, urutannya terasa terbalik, karena Go memang sengaja menaruh tipe setelah nama supaya deklarasi tetap enak dibaca saat mulai kompleks.

Begini cara memanggilnya dalam program utuh.

package main

import "fmt"

func sapa(nama string) string {
    return "Halo, " + nama
}

func main() {
    pesan := sapa("Sari")
    fmt.Println(pesan)
}

Jalankan dengan go run main.go, hasilnya:

Halo, Sari

Parameter dan return value

Function boleh menerima lebih dari satu parameter. Kita coba function hitung sederhana.

func luasPersegiPanjang(panjang int, lebar int) int {
    return panjang * lebar
}

Ada satu trik kecil yang sering kamu temui di kode Go orang lain. Kalau beberapa parameter bertipe sama, tipenya cukup ditulis sekali di belakang.

func luasPersegiPanjang(panjang, lebar int) int {
    return panjang * lebar
}

Keduanya sama persis artinya. Memanggilnya juga biasa saja: luas := luasPersegiPanjang(8, 5) akan mengisi luas dengan 40. Sampai sini belum ada yang aneh. Bagian menariknya baru mulai sekarang.

Multiple return: ciri khas Go

Di kebanyakan bahasa, function hanya bisa mengembalikan satu nilai. Go berbeda. Satu function boleh mengembalikan dua nilai atau lebih sekaligus. Ini bukan sekadar fitur gaya-gayaan. Ini fondasi dari cara Go menangani error.

Contoh paling klasik adalah pembagian. Membagi angka dengan nol itu operasi yang tidak sah. Jadi function pembagi kita sebaiknya menjawab dua hal: berapa hasilnya, dan apakah operasinya berhasil.

package main

import (
    "errors"
    "fmt"
)

func bagi(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("tidak bisa membagi dengan nol")
    }
    return a / b, nil
}

func main() {
    hasil, err := bagi(10, 2)
    if err != nil {
        fmt.Println("terjadi error:", err)
        return
    }
    fmt.Println("hasil:", hasil)
}

Perhatikan bagian (float64, error). Itu artinya function ini mengembalikan dua nilai: hasil pembagian bertipe float64, dan sebuah error. Kalau semuanya lancar, error yang dikembalikan adalah nil, yang artinya kosong, tidak ada masalah. Kalau ada masalah, kita kembalikan nilai error yang berisi pesan.

Di sisi pemanggil, dua nilai itu ditangkap sekaligus: hasil, err := bagi(10, 2). Ini pola yang akan kamu tulis ratusan kali selama menulis Go, jadi biasakan dari sekarang.

Konvensi error di Go

Di Go, error bukan sesuatu yang spesial. Error adalah nilai biasa, sama seperti angka atau string, hanya saja tipenya error. Karena dia nilai biasa, dia bisa dikembalikan dari function, disimpan di variabel, atau dibandingkan.

Ada beberapa konvensi tidak tertulis yang dipegang hampir semua programmer Go:

  • Kalau function bisa gagal, nilai balikan terakhirnya bertipe error. Bukan yang pertama, bukan di tengah. Selalu yang terakhir.
  • Untuk membuat error dengan pesan tetap, pakai errors.New("pesan errornya") dari package errors.
  • Untuk pesan yang butuh format, pakai fmt.Errorf. Misalnya fmt.Errorf("stok tinggal %d", sisa).

Satu hal lagi yang cukup kamu tahu namanya dulu. fmt.Errorf punya kata kerja format khusus, yaitu %w, yang dipakai untuk membungkus error lain di dalam error baru. Teknik ini namanya error wrapping, dan detailnya kita bahas di bagian lanjutan seri ini. Sekarang cukup ingat bahwa %w itu ada.

Pola if err != nil, dijelaskan tuntas

Sekarang kita jawab pertanyaan yang tertunda dari bagian 2: kenapa kode Go penuh dengan if err != nil?

Bahasa seperti Java, Python, atau JavaScript memakai try-catch. Kamu tulis kode di blok try, dan kalau ada yang meledak di mana pun di dalamnya, eksekusi melompat ke blok catch. Go mengambil jalan lain. Tidak ada try-catch. Function yang bisa gagal mengembalikan error sebagai nilai, dan kamu memeriksanya langsung setelah pemanggilan.

Alasannya soal kejujuran kode. Dengan try-catch, kamu bisa memanggil sepuluh function tanpa tahu mana yang bisa gagal. Di Go, kemungkinan gagal tertulis jelas di tanda tangan function. Pembaca kode langsung tahu titik mana saja yang bisa bermasalah, dan compiler akan protes kalau kamu menangkap err lalu tidak menyentuhnya sama sekali.

Supaya adil, pendekatan ini ada harganya. Kode Go jadi lebih panjang, dan mengetik if err != nil berulang-ulang memang terasa repetitif. Ini kritik paling umum terhadap Go, dan kritik itu valid. Sebagian orang menganggapnya bertele-tele, sebagian lagi justru menyukainya karena alur kegagalan terlihat gamblang tanpa lompatan tersembunyi. Setelah beberapa minggu menulis Go, kebanyakan orang berhenti menganggapnya masalah.

Pola pendampingnya bernama early return: begitu ketemu error, langsung return dan hentikan function. Kode sukses tidak perlu dibungkus blok else, jadi alur utamanya tetap lurus dari atas ke bawah. Contohnya sudah kamu lihat di function main tadi. Kalau err tidak nil, cetak pesannya lalu return. Baris di bawahnya hanya berjalan saat semuanya aman.

Named return: singkat saja

Go mengizinkan nilai balikan diberi nama di tanda tangan function.

func bagi(a, b float64) (hasil float64, err error) {
    if b == 0 {
        err = errors.New("tidak bisa membagi dengan nol")
        return
    }
    hasil = a / b
    return
}

hasil dan err otomatis menjadi variabel di dalam function, dan return polos akan mengembalikan nilai keduanya saat itu. Kelihatan praktis, tapi pada function yang panjang, pembaca harus menebak apa isi return polos itu. Saran saya untuk sekarang: tulis return secara eksplisit seperti contoh-contoh sebelumnya. Named return baru berguna di kasus khusus yang akan kamu temui jauh di depan.

Variadic function: parameter yang jumlahnya bebas

Kadang kamu tidak tahu berapa banyak nilai yang akan dikirim ke function. Menjumlahkan isi keranjang belanja, misalnya. Bisa dua barang, bisa sepuluh. Untuk itu Go punya variadic function, ditandai tiga titik sebelum tipe.

func totalBelanja(harga ...int) int {
    total := 0
    for _, h := range harga {
        total += h
    }
    return total
}

harga ...int artinya function ini menerima nol, satu, atau banyak nilai int. Di dalam function, harga berperilaku seperti kumpulan nilai yang bisa kita jelajahi dengan for range. Perulangan sendiri akan kita kupas dalam di bagian lain, untuk sekarang cukup pahami bahwa baris itu mengunjungi tiap harga satu per satu dan menambahkannya ke total.

Memanggilnya luwes sekali: totalBelanja(15000, 20000) sah, totalBelanja(5000, 7500, 12000, 3000) juga sah. fmt.Println yang kamu pakai sejak bagian 1 juga variadic. Itu sebabnya dia mau menerima berapa pun argumen.

Latihan: kalkulator diskon

Sekarang kita rangkai semuanya jadi satu program utuh. Skenarionya sederhana: kasir menjumlahkan keranjang belanja, lalu menerapkan diskon. Diskon di bawah 0 persen atau di atas 100 persen jelas tidak masuk akal, jadi harus ditolak lewat error.

package main

import "fmt"

func totalKeranjang(harga ...float64) float64 {
    total := 0.0
    for _, h := range harga {
        total += h
    }
    return total
}

func hitungDiskon(total, persen float64) (float64, error) {
    if persen < 0 {
        return 0, fmt.Errorf("persen diskon tidak boleh negatif, dapat %v", persen)
    }
    if persen > 100 {
        return 0, fmt.Errorf("persen diskon tidak boleh lebih dari 100, dapat %v", persen)
    }
    potongan := total * persen / 100
    return total - potongan, nil
}

func main() {
    total := totalKeranjang(15000, 42500, 8000)
    fmt.Println("Total keranjang:", total)

    bayar, err := hitungDiskon(total, 20)
    if err != nil {
        fmt.Println("Gagal menghitung diskon:", err)
        return
    }
    fmt.Println("Bayar setelah diskon 20 persen:", bayar)

    bayar, err = hitungDiskon(total, 150)
    if err != nil {
        fmt.Println("Gagal menghitung diskon:", err)
        return
    }
    fmt.Println("Bayar setelah diskon 150 persen:", bayar)
}

Jalankan, dan outputnya seperti ini.

Total keranjang: 65500
Bayar setelah diskon 20 persen: 52400
Gagal menghitung diskon: persen diskon tidak boleh lebih dari 100, dapat 150

Mari bedah sebentar apa yang terjadi:

  • totalKeranjang adalah variadic function. Dia menerima tiga harga dan menjumlahkannya jadi 65500.
  • hitungDiskon memakai multiple return: hasil bertipe float64 plus error di posisi terakhir, sesuai konvensi.
  • Validasi dilakukan di awal function dengan early return. Persen 20 lolos, jadi errornya nil dan program mencetak 52400.
  • Panggilan kedua mengirim persen 150. Validasi menolaknya, err terisi, dan blok if err != nil mencetak pesan lalu menghentikan main. Baris cetak “Bayar setelah diskon 150 persen” tidak pernah dijalankan.

Coba ubah sendiri: ganti persen jadi -5, tambah barang di keranjang, atau buat function baru hitungPajak dengan pola yang sama. Semakin sering tanganmu menulis pola ini, semakin cepat dia jadi refleks.

Rangkuman dan lanjut ke mana

Hari ini kamu sudah belajar cukup banyak:

  • Function dideklarasikan dengan func, dan tipe ditulis setelah nama.
  • Function di Go bisa mengembalikan lebih dari satu nilai.
  • Konvensi error: nilai balikan terakhir bertipe error, dibuat dengan errors.New atau fmt.Errorf.
  • Go memilih pengecekan error eksplisit lewat if err != nil plus early return, bukan try-catch.
  • Named return ada, tapi untuk sekarang lebih baik return eksplisit.
  • Variadic function menerima jumlah argumen yang bebas.

Dengan bekal ini, kamu sudah bisa memecah program jadi potongan-potongan kecil yang rapi dan tahan gagal. Di bagian berikutnya kita naik satu tingkat: “Belajar Golang dari Nol #4: Struct dan Method: Cara Go Menyusun Data”. Di sana function yang baru kamu kuasai akan menempel langsung ke data.

Pola function dan error yang kamu latih hari ini persis pola yang kami pakai sehari-hari saat membangun backend produksi dengan Go untuk klien Arrazy.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *