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.

Referensi

  1. 1Function declarations — The Go Programming Language Specification
  2. 2Functions — A Tour of Go
  3. 3Closures — A Tour of Go