BAB 15: Function — Memecah Program Menjadi Bagian Kecil
Pelajari cara membuat dan menggunakan function di Go untuk program yang lebih modular dan mudah dipelihara. Bahas parameter, return value, multiple return, variadic, closure, dan fungsi sebagai first-class value.
Di bab-bab sebelumnya, semua kode kita tulis di dalam satu fungsi main. Itu masih oke ketika programnya pendek — beberapa baris untuk menghitung rata-rata atau mencari nilai di map. Tapi ketika program mulai tumbuh, menulis semuanya di main seperti memasukkan semua bahan masakan ke dalam satu panci besar: susah dipantau, susah diubah, dan susah digunakan ulang.
Function adalah solusinya. Dengan memecah logika ke dalam function yang lebih kecil, kamu bisa memberi nama pada setiap langkah, menggunakannya kembali di tempat lain, dan menguji setiap bagian secara independen. Go sangat mendukung gaya pemrograman ini — bahkan fungsi main itu sendiri adalah sebuah function.
Anatomi Function di Go
Semua function di Go mengikuti pola yang sama: kata kunci func, nama function, parameter dalam kurung, tipe return value, lalu blok kode.
// main.go
package main
import "fmt"
// function tanpa parameter dan return value
func salam() {
fmt.Println("Selamat belajar Go!")
}
// function dengan parameter
func sapa(nama string) {
fmt.Printf("Halo, %s!\n", nama)
}
// function dengan parameter dan return value
func tambah(a int, b int) int {
return a + b
}
func main() {
salam()
sapa("Budi")
hasil := tambah(10, 5)
fmt.Println("10 + 5 =", hasil)
}
Selamat belajar Go!
Halo, Budi!
10 + 5 = 15
Ketika beberapa parameter punya tipe yang sama, kamu bisa mempersingkatnya: func tambah(a, b int) int sama dengan func tambah(a int, b int) int.
Multiple Return Value
Ini salah satu fitur Go yang tidak ada di banyak bahasa lain: satu function bisa mengembalikan lebih dari satu nilai. Sangat berguna untuk mengembalikan hasil sekaligus informasi error — pola yang akan sangat sering kamu temui di ekosistem Go.
// main.go
package main
import "fmt"
func hitungStatistik(nilai []int) (int, int, float64) {
if len(nilai) == 0 {
return 0, 0, 0
}
maks := nilai[0]
min := nilai[0]
total := 0
for _, n := range nilai {
if n > maks {
maks = n
}
if n < min {
min = n
}
total += n
}
rataRata := float64(total) / float64(len(nilai))
return maks, min, rataRata
}
func main() {
nilai := []int{85, 92, 78, 95, 88, 71}
maks, min, avg := hitungStatistik(nilai)
fmt.Printf("Tertinggi : %d\n", maks)
fmt.Printf("Terendah : %d\n", min)
fmt.Printf("Rata-rata : %.1f\n", avg)
}
Tertinggi : 95
Terendah : 71
Rata-rata : 84.8
Jika kamu hanya butuh sebagian dari return value, gunakan _ untuk mengabaikan sisanya: maks, _, avg := hitungStatistik(nilai).
Named Return Value
Go juga memungkinkan return value diberi nama di deklarasi function. Ini bisa membuat kode lebih eksplisit, terutama untuk function dengan banyak return value.
// main.go
package main
import "fmt"
func bagi(a, b float64) (hasil float64, err string) {
if b == 0 {
err = "tidak bisa membagi dengan nol"
return // "naked return" — mengembalikan nilai dari named variables
}
hasil = a / b
return
}
func main() {
h, e := bagi(10, 3)
if e != "" {
fmt.Println("Error:", e)
} else {
fmt.Printf("10 / 3 = %.4f\n", h)
}
h, e = bagi(5, 0)
if e != "" {
fmt.Println("Error:", e)
}
}
10 / 3 = 3.3333
Error: tidak bisa membagi dengan nol
Gunakan named return value dengan hemat — hanya ketika nama-namanya benar-benar membuat kode lebih mudah dibaca. “Naked return” (return tanpa argumen) pada function yang panjang bisa membingungkan karena tidak jelas nilai apa yang dikembalikan.
Variadic Function
Variadic function bisa menerima jumlah argumen yang tidak ditentukan, selama bertipe sama. Tanda ... sebelum tipe parameter mengubah parameter menjadi variadic.
// main.go
package main
import "fmt"
func jumlahkan(angka ...int) int {
total := 0
for _, n := range angka {
total += n
}
return total
}
func main() {
fmt.Println(jumlahkan(1, 2, 3)) // 3 argumen
fmt.Println(jumlahkan(10, 20, 30, 40, 50)) // 5 argumen
// slice bisa dikirim sebagai argumen variadic dengan ...
data := []int{5, 10, 15, 20}
fmt.Println(jumlahkan(data...))
}
6
150
50
Di dalam function, parameter variadic diperlakukan seperti slice biasa — kamu bisa mengiterasi dengan for range. Parameter variadic harus selalu berada di posisi terakhir dalam daftar parameter.
Function sebagai Nilai
Di Go, function adalah first-class value — artinya function bisa disimpan di variabel, dikirim sebagai argumen, dan dikembalikan dari function lain. Ini membuka pola yang sangat powerful.
// main.go
package main
import (
"fmt"
"strings"
)
// function yang menerima function sebagai parameter
func filter(data []string, kondisi func(string) bool) []string {
var hasil []string
for _, item := range data {
if kondisi(item) {
hasil = append(hasil, item)
}
}
return hasil
}
func main() {
kota := []string{"Jakarta", "Bandung", "Surabaya", "Bali", "Bogor"}
// filter kota yang namanya mengandung huruf 'a'
adaA := filter(kota, func(s string) bool {
return strings.Contains(strings.ToLower(s), "a")
})
// filter kota dengan nama lebih dari 5 huruf
panjang := filter(kota, func(s string) bool {
return len(s) > 5
})
fmt.Println("mengandung 'a':", adaA)
fmt.Println("lebih dari 5 huruf:", panjang)
}
mengandung 'a': [Jakarta Bandung Surabaya Bali Bogor]
lebih dari 5 huruf: [Jakarta Bandung Surabaya]
Kalau skema function sering diulang, bisa disimpan sebagai type alias agar lebih rapi:
type KondisiFilter func(string) bool
func filter(data []string, kondisi KondisiFilter) []string { ... }
Closure
Closure adalah function yang “menangkap” variabel dari scope di sekitarnya. Function tersebut bisa mengakses dan memodifikasi variabel itu bahkan setelah scope asalnya sudah selesai dieksekusi.
// main.go
package main
import "fmt"
func buatCounter(mulaiDari int) func() int {
hitung := mulaiDari
return func() int {
hitung++
return hitung
}
}
func main() {
counter1 := buatCounter(0)
counter2 := buatCounter(10)
fmt.Println(counter1()) // 1
fmt.Println(counter1()) // 2
fmt.Println(counter2()) // 11 — counter2 punya state sendiri
fmt.Println(counter1()) // 3
}
1
2
11
3
counter1 dan counter2 adalah dua closure yang berbeda, masing-masing menyimpan state hitung yang independen. Ini berguna untuk membuat “generator” atau komponen yang punya state internal tanpa perlu struct.
Latihan
Latihan 1 — Refaktor dengan function:
Ambil program penghitung statistik dari bab-bab sebelumnya (nilai siswa dengan slice dan map) dan pecah menjadi tiga function terpisah: hitungRataRata, cariTertinggi, dan cariTerendah. Setiap function menerima []int dan mengembalikan satu nilai.
Latihan 2 — Filter generik:
Kembangkan function filter dari contoh di atas agar bekerja dengan []int. Gunakan fungsi ini untuk: (a) menyaring angka genap, (b) menyaring angka lebih dari 50, dan (c) menyaring angka yang habis dibagi 3.
Latihan 3 — Closure sebagai validator:
Buat function buatValidator(min, max int) func(int) bool yang mengembalikan closure. Closure tersebut mengembalikan true jika angka yang diberikan berada dalam rentang min hingga max. Gunakan untuk memvalidasi nilai ujian (0–100) dan usia (17–65).
Sejauh ini kamu telah bekerja dengan variabel yang nilainya tersimpan langsung di tempatnya. Tapi ada situasi di mana kamu ingin function mengubah nilai sebuah variabel yang ada di luar function — seperti yang kamu coba lakukan dengan hitung di closure, tapi untuk variabel biasa yang dikirim sebagai argumen. Untuk itu, kamu butuh cara untuk memberikan “alamat” variabel, bukan salinan nilainya. Itulah pointer, dan kita akan membahasnya di bab berikutnya.