Pada tutorial kali ini di JadiFullstack, kita akan mempelajari salah satu fitur paling powerful dan unik dari Golang: Functions. Functions di Go memiliki beberapa karakteristik khusus yang membedakannya dari bahasa pemrograman lain, seperti kemampuan mengembalikan multiple values dan named returns.
💡 Prereq: Jika Anda baru mulai belajar Golang, pastikan sudah membaca Tutorial Variabel di Golang dan Package Main di Golang terlebih dahulu.
📚 Daftar Isi Tutorial
- Pengantar Functions di Golang
- Sintaks Dasar Function
- Multiple Return Values
- Named Returns
- Variadic Functions
- Anonymous Functions dan Closures
- Functions sebagai First-Class Citizens
- Higher-Order Functions
- Best Practices
- Testing Functions
- Performance Tips
Pengantar Functions di Golang
Functions adalah blok kode yang dapat digunakan kembali untuk melakukan tugas tertentu. Di Golang, functions bukan hanya sekedar cara untuk mengorganisir kode, tetapi juga merupakan first-class citizens yang dapat diperlakukan seperti tipe data lainnya.
Mengapa Functions Penting?
Functions membantu kita untuk:
- Mengurangi duplikasi kode dengan menempatkan logika yang sama di satu tempat
- Meningkatkan readability dengan memecah program menjadi bagian-bagian kecil
- Memudahkan testing dengan mengisolasi logika tertentu
- Memungkinkan reusability dalam berbagai bagian aplikasi
Sintaks Dasar Function di Golang
Berikut adalah sintaks dasar untuk mendefinisikan function di Golang:
func functionName(parameter1 type1, parameter2 type2) returnType {
// function body
return value
}
Mari kita lihat contoh sederhana:
package main
import "fmt"
// Function untuk menghitung total belanja
// Parameter: hargaBarang1 dan hargaBarang2 (tipe int untuk Rupiah)
// Return: total harga (tipe int)
func hitungTotalBelanja(hargaBarang1 int, hargaBarang2 int) int {
total := hargaBarang1 + hargaBarang2
return total
}
func main() {
// Contoh: beli nasi gudeg Rp 15000 + es teh Rp 5000
hargaNasiGudeg := 15000
hargaEsTeh := 5000
totalBayar := hitungTotalBelanja(hargaNasiGudeg, hargaEsTeh)
fmt.Printf("Total yang harus dibayar: Rp %d\n", totalBayar)
// Output: Total yang harus dibayar: Rp 20000
}
Shorthand Parameter Type
Jika beberapa parameter memiliki tipe yang sama, kita bisa menggunakan shorthand untuk memperpendek penulisan:
// Cara panjang - kedua parameter ditulis tipenya
func hitungTotalBelanja(hargaBarang1 int, hargaBarang2 int) int {
return hargaBarang1 + hargaBarang2
}
// Cara singkat - tipe cukup ditulis sekali di akhir
func hitungTotalBelanja(hargaBarang1, hargaBarang2 int) int {
return hargaBarang1 + hargaBarang2
}
Multiple Return Values: Keunggulan Golang
Salah satu fitur paling distinctive dari Golang adalah kemampuan function untuk mengembalikan multiple values. Ini sangat berguna untuk error handling dan mengembalikan data kompleks.
Contoh Multiple Return Values
package main
import (
"errors"
"fmt"
)
// Function untuk membagi uang jajan
// Return: hasil bagi dan error (jika ada)
func bagiUangJajan(totalUang, jumlahAnak int) (int, error) {
// Cek jika jumlah anak = 0 (tidak bisa bagi dengan 0)
if jumlahAnak == 0 {
return 0, errors.New("tidak ada anak yang menerima uang jajan")
}
uangPerAnak := totalUang / jumlahAnak
return uangPerAnak, nil // nil artinya tidak ada error
}
// Function untuk menghitung statistik nilai ujian
// Return: tiga nilai sekaligus (total, jumlah siswa, rata-rata)
func hitungStatistikNilai(nilaiSiswa []int) (total, jumlahSiswa int, rataRata float64) {
total = 0
jumlahSiswa = len(nilaiSiswa)
// Hitung total nilai semua siswa
for _, nilai := range nilaiSiswa {
total += nilai
}
// Hitung rata-rata
if jumlahSiswa > 0 {
rataRata = float64(total) / float64(jumlahSiswa)
}
return total, jumlahSiswa, rataRata
}
func main() {
// Contoh 1: Bagi uang jajan
uangJajan := 50000 // Rp 50.000
jumlahAnak := 3
uangPerAnak, err := bagiUangJajan(uangJajan, jumlahAnak)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Printf("Setiap anak mendapat: Rp %d\n", uangPerAnak)
// Contoh 2: Coba bagi dengan 0 anak
_, err2 := bagiUangJajan(50000, 0)
if err2 != nil {
fmt.Println("Error:", err2)
}
// Contoh 3: Statistik nilai ujian siswa
nilaiUjian := []int{85, 90, 78, 92, 88, 76, 94, 82}
totalNilai, jumlahSiswa, rataRata := hitungStatistikNilai(nilaiUjian)
fmt.Printf("Total nilai: %d, Jumlah siswa: %d, Rata-rata: %.2f\n",
totalNilai, jumlahSiswa, rataRata)
}
Best Practice: Error Handling dengan Multiple Returns
Pattern yang sangat umum di Golang adalah mengembalikan error sebagai return value terakhir. Ini seperti memberikan informasi “berhasil atau tidak” dari sebuah function:
package main
import (
"fmt"
"strconv"
)
// Function untuk validasi dan konversi umur dari string
func validasiUmur(umurString string) (int, error) {
// Coba konversi string ke integer
umur, err := strconv.Atoi(umurString)
if err != nil {
return 0, fmt.Errorf("umur harus berupa angka, bukan: %s", umurString)
}
// Validasi range umur yang masuk akal
if umur < 0 || umur > 120 {
return 0, fmt.Errorf("umur tidak valid: %d tahun", umur)
}
return umur, nil // berhasil, tidak ada error
}
func main() {
// Test dengan input yang benar
umur1, err1 := validasiUmur("25")
if err1 != nil {
fmt.Println("Error:", err1)
} else {
fmt.Printf("Umur valid: %d tahun\n", umur1)
}
// Test dengan input yang salah
_, err2 := validasiUmur("abc")
if err2 != nil {
fmt.Println("Error:", err2)
}
// Test dengan umur yang tidak masuk akal
_, err3 := validasiUmur("150")
if err3 != nil {
fmt.Println("Error:", err3)
}
}
Named Returns: Clarity dan Efficiency
Named returns adalah fitur dimana kita bisa memberikan nama pada return values di function signature. Ini membuat kode lebih readable dan memungkinkan beberapa optimasi.
Contoh Named Returns
Named returns membuat kode lebih mudah dibaca karena kita sudah tahu apa yang dikembalikan function tanpa perlu melihat ke dalam function body:
package main
import "fmt"
// Function untuk menghitung luas dan keliling kebun
// Named returns: luas dan keliling sudah diberi nama di signature
func hitungKebun(panjang, lebar float64) (luas, keliling float64) {
// Langsung assign ke variable yang sudah didefinisikan
luas = panjang * lebar
keliling = 2 * (panjang + lebar)
return // "naked return" - tidak perlu tulis luas, keliling lagi
}
// Function untuk memproses data siswa
// Named returns membuat error handling lebih jelas
func prosesSiswa(nama string, nilai int) (dataSiswa map[string]interface{}, err error) {
// Inisialisasi map untuk data siswa
dataSiswa = make(map[string]interface{})
// Validasi nama tidak boleh kosong
if nama == "" {
err = fmt.Errorf("nama siswa tidak boleh kosong")
return // err sudah diset, dataSiswa akan zero value (empty map)
}
// Validasi nilai harus antara 0-100
if nilai < 0 || nilai > 100 {
err = fmt.Errorf("nilai harus antara 0-100, nilai yang diberikan: %d", nilai)
return
}
// Isi data siswa
dataSiswa["nama"] = nama
dataSiswa["nilai"] = nilai
// Tentukan grade berdasarkan nilai
if nilai >= 90 {
dataSiswa["grade"] = "A"
} else if nilai >= 80 {
dataSiswa["grade"] = "B"
} else if nilai >= 70 {
dataSiswa["grade"] = "C"
} else {
dataSiswa["grade"] = "D"
}
return // dataSiswa dan err sudah ready
}
func main() {
// Contoh 1: Hitung kebun
panjangKebun := 10.5
lebarKebun := 8.0
luas, keliling := hitungKebun(panjangKebun, lebarKebun)
fmt.Printf("Kebun %gm x %gm:\n", panjangKebun, lebarKebun)
fmt.Printf("- Luas: %.2f m²\n", luas)
fmt.Printf("- Keliling: %.2f m\n", keliling)
fmt.Println()
// Contoh 2: Proses data siswa (sukses)
siswa, err := prosesSiswa("Budi Santoso", 85)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Printf("Data Siswa: %+v\n", siswa)
}
// Contoh 3: Proses data siswa (error - nama kosong)
_, err2 := prosesSiswa("", 90)
if err2 != nil {
fmt.Println("Error:", err2)
}
// Contoh 4: Proses data siswa (error - nilai invalid)
_, err3 := prosesSiswa("Siti Aminah", 150)
if err3 != nil {
fmt.Println("Error:", err3)
}
}
Kapan Menggunakan Named Returns?
Named returns sangat berguna ketika:
- Function memiliki multiple return values yang kompleks
- Ada beberapa titik return dalam function
- Ingin membuat kode lebih self-documenting
- Perlu melakukan cleanup atau logging sebelum return
package main
import "fmt"
// Function untuk memproses daftar nilai siswa
// Named returns untuk tracking progress dan hasil
func prosesNilaiSiswa(daftarNilai []int) (rataRata float64, jumlahDiproses int, err error) {
// defer akan dijalankan sebelum function return
defer func() {
// Log hasil pemrosesan
if err != nil {
fmt.Printf("❌ Error terjadi setelah memproses %d dari %d nilai\n",
jumlahDiproses, len(daftarNilai))
} else {
fmt.Printf("✅ Berhasil memproses %d nilai, rata-rata: %.2f\n",
jumlahDiproses, rataRata)
}
}()
total := 0
// Proses setiap nilai
for i, nilai := range daftarNilai {
// Validasi nilai harus antara 0-100
if nilai < 0 || nilai > 100 {
err = fmt.Errorf("nilai tidak valid (%d) pada posisi ke-%d", nilai, i+1)
return // jumlahDiproses dan rataRata akan tetap zero value
}
total += nilai
jumlahDiproses = i + 1 // update jumlah yang sudah diproses
}
// Hitung rata-rata
if jumlahDiproses > 0 {
rataRata = float64(total) / float64(jumlahDiproses)
}
return
}
func main() {
// Test dengan data yang valid
fmt.Println("=== Test data valid ===")
nilaiKelas := []int{85, 90, 78, 88, 92, 76}
rata, jumlah, err := prosesNilaiSiswa(nilaiKelas)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Printf("Hasil: rata-rata %.2f dari %d siswa\n", rata, jumlah)
}
fmt.Println("\n=== Test data dengan error ===")
// Test dengan data yang ada error
nilaiError := []int{85, 90, 150, 88} // nilai 150 tidak valid
_, _, err2 := prosesNilaiSiswa(nilaiError)
if err2 != nil {
fmt.Println("Error:", err2)
}
}
Variadic Functions: Parameter yang Fleksibel
Variadic functions memungkinkan kita mengirim jumlah parameter yang berbeda-beda. Sangat berguna untuk situasi dimana kita tidak tahu pasti berapa banyak data yang akan dikirim:
package main
import "fmt"
// Function untuk menghitung total belanja (bisa beli banyak barang)
func hitungTotalBelanja(namaBarang ...int) int {
total := 0
fmt.Printf("📝 Daftar belanja:\n")
// Loop semua harga barang yang diberikan
for i, harga := range namaBarang {
fmt.Printf(" %d. Rp %d\n", i+1, harga)
total += harga
}
return total
}
// Function untuk menyapa banyak orang sekaligus
func sapaBanyakOrang(salam string, namaNama ...string) {
fmt.Printf("🎉 %s semuanya!\n", salam)
for i, nama := range namaNama {
fmt.Printf(" %d. %s %s!\n", i+1, salam, nama)
}
}
// Function untuk mencari nilai tertinggi dari sekumpulan nilai
func cariNilaiTertinggi(nilai ...int) (tertinggi int, posisi int) {
if len(nilai) == 0 {
return 0, -1 // tidak ada nilai yang diberikan
}
tertinggi = nilai[0] // asumsi nilai pertama yang tertinggi
posisi = 0
// Bandingkan dengan nilai-nilai lainnya
for i, n := range nilai {
if n > tertinggi {
tertinggi = n
posisi = i
}
}
return tertinggi, posisi
}
func main() {
// Contoh 1: Belanja sedikit barang
fmt.Println("=== Belanja Ringan ===")
total1 := hitungTotalBelanja(15000, 8000) // nasi gudeg + es teh
fmt.Printf("💰 Total: Rp %d\n\n", total1)
// Contoh 2: Belanja banyak barang
fmt.Println("=== Belanja Bulanan ===")
total2 := hitungTotalBelanja(50000, 25000, 15000, 30000, 12000)
fmt.Printf("💰 Total: Rp %d\n\n", total2)
// Contoh 3: Belanja dari slice
fmt.Println("=== Belanja dari Daftar ===")
daftarHarga := []int{20000, 35000, 18000}
total3 := hitungTotalBelanja(daftarHarga...) // gunakan ... untuk expand slice
fmt.Printf("💰 Total: Rp %d\n\n", total3)
// Contoh 4: Sapa banyak orang
fmt.Println("=== Salam ===")
sapaBanyakOrang("Selamat pagi", "Budi", "Siti", "Ahmad", "Dewi")
fmt.Println()
// Contoh 5: Cari nilai tertinggi
fmt.Println("=== Nilai Ujian ===")
nilaiUjian := []int{78, 85, 92, 88, 76, 94, 82}
tertinggi, posisi := cariNilaiTertinggi(nilaiUjian...)
fmt.Printf("📊 Nilai tertinggi: %d (siswa ke-%d)\n", tertinggi, posisi+1)
// Test dengan parameter langsung
tertinggi2, posisi2 := cariNilaiTertinggi(65, 78, 82, 91, 77)
fmt.Printf("📊 Nilai tertinggi: %d (posisi ke-%d)\n", tertinggi2, posisi2+1)
}
Anonymous Functions dan Closures
Anonymous functions adalah function tanpa nama yang bisa kita gunakan langsung. Closures adalah anonymous function yang bisa “mengingat” variabel dari tempat dia dibuat. Konsep ini sangat berguna untuk operasi cepat:
package main
import "fmt"
func main() {
// Anonymous function langsung dijalankan
// Contoh: hitung diskon langsung
func(hargaAsli int, diskonPersen int) {
diskon := hargaAsli * diskonPersen / 100
hargaSetelahDiskon := hargaAsli - diskon
fmt.Printf("🏷️ Harga asli: Rp %d\n", hargaAsli)
fmt.Printf("💸 Diskon %d%%: Rp %d\n", diskonPersen, diskon)
fmt.Printf("✅ Harga final: Rp %d\n\n", hargaSetelahDiskon)
}(100000, 20) // langsung dijalankan dengan parameter Rp 100.000, diskon 20%
// Anonymous function disimpan dalam variabel
// Contoh: kalkulator sederhana
hitungLuasPersegi := func(sisi int) int {
return sisi * sisi
}
fmt.Printf("📐 Luas persegi (sisi 5m): %d m²\n\n", hitungLuasPersegi(5))
// CLOSURE: function yang "mengingat" variabel dari luar
// Contoh: sistem poin reward
poinReward := 0 // variabel yang akan diingat oleh closure
tambahPoin := func(poin int) int {
poinReward += poin // mengakses dan mengubah variabel luar
fmt.Printf("🎯 Menambah %d poin. Total poin: %d\n", poin, poinReward)
return poinReward
}
fmt.Println("=== Sistem Poin Reward ===")
tambahPoin(50) // berbelanja
tambahPoin(25) // referral teman
tambahPoin(100) // review produk
fmt.Println()
// Closure untuk counter yang berbeda
fmt.Println("=== Counter Terpisah ===")
// Counter untuk pengunjung website
hitungPengunjung := 0
counterPengunjung := func() int {
hitungPengunjung++
return hitungPengunjung
}
// Counter untuk penjualan
hitungPenjualan := 0
counterPenjualan := func() int {
hitungPenjualan++
return hitungPenjualan
}
// Simulasi aktivitas
fmt.Printf("👥 Pengunjung ke-%d\n", counterPengunjung())
fmt.Printf("👥 Pengunjung ke-%d\n", counterPengunjung())
fmt.Printf("💰 Penjualan ke-%d\n", counterPenjualan())
fmt.Printf("👥 Pengunjung ke-%d\n", counterPengunjung())
fmt.Printf("💰 Penjualan ke-%d\n", counterPenjualan())
fmt.Println()
// Closure untuk kalkulator dengan operasi yang diingat
fmt.Println("=== Kalkulator dengan Memory ===")
hasil := 0
kalkulator := func(operasi string, angka int) int {
switch operasi {
case "tambah":
hasil += angka
case "kurang":
hasil -= angka
case "kali":
hasil *= angka
case "reset":
hasil = 0
}
fmt.Printf("🔢 %s %d = %d\n", operasi, angka, hasil)
return hasil
}
kalkulator("tambah", 10) // 0 + 10 = 10
kalkulator("kali", 3) // 10 * 3 = 30
kalkulator("kurang", 5) // 30 - 5 = 25
kalkulator("reset", 0) // reset ke 0
}
Functions sebagai First-Class Citizens
Di Golang, functions adalah first-class citizens, artinya functions dapat:
- Disimpan dalam variabel
- Diteruskan sebagai parameter
- Dikembalikan dari function lain
- Dibuat pada runtime
Ini membuat Go sangat fleksibel untuk programming patterns yang advanced:
package main
import "fmt"
// Definisi tipe function untuk operasi matematika
type OperasiMatematika func(int, int) int
// Function yang mengembalikan function lain (Function Factory)
func buatKalkulator(jenisOperasi string) OperasiMatematika {
switch jenisOperasi {
case "tambah":
return func(a, b int) int {
fmt.Printf("🔢 Menambah %d + %d = ", a, b)
return a + b
}
case "kurang":
return func(a, b int) int {
fmt.Printf("🔢 Mengurang %d - %d = ", a, b)
return a - b
}
case "kali":
return func(a, b int) int {
fmt.Printf("🔢 Mengalikan %d × %d = ", a, b)
return a * b
}
default:
return func(a, b int) int {
fmt.Printf("❌ Operasi tidak dikenal: ")
return 0
}
}
}
// Function yang menerima function sebagai parameter (Higher-Order Function)
func prosesTransaksi(jumlahBelanja int, hitungTotal OperasiMatematika) {
ongkir := 15000
total := hitungTotal(jumlahBelanja, ongkir)
fmt.Printf("%d\n", total)
fmt.Printf("💰 Total yang harus dibayar: Rp %d\n\n", total)
}
func main() {
fmt.Println("=== Function sebagai First-Class Citizens ===\n")
// 1. Menyimpan function dalam variabel
var operasi OperasiMatematika
operasi = func(a, b int) int {
fmt.Printf("🔢 Operasi custom %d + %d = ", a, b)
return a + b
}
hasil := operasi(25000, 10000)
fmt.Printf("%d\n\n", hasil)
// 2. Array/slice of functions - seperti menu kalkulator
menuKalkulator := []OperasiMatematika{
func(a, b int) int {
fmt.Printf("➕ Penjumlahan: %d + %d = ", a, b)
return a + b
},
func(a, b int) int {
fmt.Printf("➖ Pengurangan: %d - %d = ", a, b)
return a - b
},
func(a, b int) int {
fmt.Printf("✖️ Perkalian: %d × %d = ", a, b)
return a * b
},
}
fmt.Println("=== Menu Kalkulator ===")
for i, fungsiOperasi := range menuKalkulator {
hasil := fungsiOperasi(20, 5)
fmt.Printf("%d (menu ke-%d)\n", hasil, i+1)
}
fmt.Println()
// 3. Function factory - membuat function sesuai kebutuhan
fmt.Println("=== Function Factory ===")
kalkulatorTambah := buatKalkulator("tambah")
kalkulatorKurang := buatKalkulator("kurang")
kalkulatorKali := buatKalkulator("kali")
kalkulatorTambah(50000, 25000)
kalkulatorKurang(100000, 15000)
kalkulatorKali(1500, 3)
fmt.Println()
// 4. Higher-order function - function yang menerima function lain
fmt.Println("=== Proses Transaksi E-commerce ===")
// Simulasi checkout dengan berbagai jenis kalkulasi
hargaBarang := 85000
fmt.Println("Scenario 1: Hitung total + ongkir")
prosesTransaksi(hargaBarang, kalkulatorTambah)
fmt.Println("Scenario 2: Hitung setelah diskon")
diskon := 10000
prosesTransaksi(hargaBarang, func(harga, potongan int) int {
fmt.Printf("🎁 Mengurangi diskon %d - %d = ", harga, potongan)
return harga - potongan
})
}
Higher-Order Functions
Functions di Golang bisa menerima functions lain sebagai parameter atau mengembalikan functions:
package main
import "fmt"
// Function type definition
type Operation func(int, int) int
// Higher-order function yang menerima function sebagai parameter
func calculate(a, b int, operation Operation) int {
return operation(a, b)
}
// Function yang mengembalikan function
func makeMultiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}
func main() {
// Define operations sebagai functions
add := func(a, b int) int { return a + b }
multiply := func(a, b int) int { return a * b }
// Menggunakan higher-order function
fmt.Println("5 + 3 =", calculate(5, 3, add)) // 8
fmt.Println("5 * 3 =", calculate(5, 3, multiply)) // 15
// Function factory
double := makeMultiplier(2)
triple := makeMultiplier(3)
fmt.Println("Double 5:", double(5)) // 10
fmt.Println("Triple 5:", triple(5)) // 15
}
Method Values dan Method Expressions
Golang memiliki konsep method values dan method expressions yang powerful:
package main
import "fmt"
type Calculator struct {
brand string
}
func (c Calculator) Add(a, b int) int {
fmt.Printf("Using %s calculator\n", c.brand)
return a + b
}
func (c Calculator) Multiply(a, b int) int {
fmt.Printf("Using %s calculator\n", c.brand)
return a * b
}
func main() {
calc := Calculator{brand: "Casio"}
// Method value - method yang di-bind ke instance tertentu
addMethod := calc.Add
result1 := addMethod(5, 3) // Tidak perlu pass calc lagi
fmt.Println("Addition result:", result1)
// Method expression - method sebagai function
multiplyFunc := Calculator.Multiply
result2 := multiplyFunc(calc, 4, 6) // Perlu pass calc sebagai parameter pertama
fmt.Println("Multiplication result:", result2)
// Gunakan dalam slice of functions
operations := []func(int, int) int{
calc.Add,
calc.Multiply,
}
for i, op := range operations {
fmt.Printf("Operation %d result: %d\n", i, op(2, 3))
}
}
Best Practices untuk Functions di Golang
Sebagai pemula, ada 4 aturan emas yang harus diikuti ketika membuat functions di Golang:
1. 📝 Berikan Nama Function yang Jelas
Prinsip: Nama function harus menjelaskan apa yang dilakukan function tersebut.
// ✅ GOOD: Langsung paham apa yang dilakukan function
func hitungTotalBelanja(harga1, harga2 int) int {
return harga1 + harga2
}
func cekUmurValid(umur int) bool {
return umur >= 17 && umur <= 100
}
func tampilkanPesan(nama string) {
fmt.Printf("Halo, %s! Selamat datang!\n", nama)
}
// ❌ BAD: Tidak jelas apa yang dilakukan
func hitung(a, b int) int { } // hitung apa?
func cek(x int) bool { } // cek apa?
func tampil(s string) { } // tampil apa?
💡 Tips penamaan:
- Gunakan kata kerja untuk function yang melakukan aksi:
hitung
,kirim
,validasi
- Gunakan kata sifat untuk function yang mengembalikan boolean:
valid
,kosong
,aktif
- Gunakan bahasa Indonesia yang mudah dipahami
2. ⚠️ Selalu Handle Error dengan Benar
Prinsip: Error selalu dikembalikan sebagai return value terakhir.
// ✅ GOOD: Pattern error handling yang benar
func bagiAngka(pembagi, dibagi int) (int, error) {
if dibagi == 0 {
return 0, fmt.Errorf("tidak bisa membagi dengan nol")
}
return pembagi / dibagi, nil
}
func bacaFileKonfigurasi(namaFile string) (string, error) {
if namaFile == "" {
return "", fmt.Errorf("nama file tidak boleh kosong")
}
// Simulasi baca file
konten := "konfigurasi aplikasi"
return konten, nil // nil berarti tidak ada error
}
// Cara menggunakan function yang mengembalikan error
func main() {
hasil, err := bagiAngka(10, 2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Hasil:", hasil)
}
💡 Tips error handling:
- Selalu cek error sebelum menggunakan hasil
- Gunakan pesan error yang informatif dalam bahasa Indonesia
- Return
nil
jika tidak ada error
3. 🎯 Satu Function, Satu Tugas
Prinsip: Setiap function hanya boleh melakukan satu hal saja.
// ✅ GOOD: Function yang fokus pada satu tugas
func hitungPajak(harga int) int {
pajak := harga * 10 / 100 // PPN 10%
return pajak
}
func hitungOngkir(jarak int) int {
ongkir := jarak * 5000 // Rp 5000 per KM
return ongkir
}
func hitungTotalPembayaran(hargaBarang, jarak int) int {
pajak := hitungPajak(hargaBarang)
ongkir := hitungOngkir(jarak)
total := hargaBarang + pajak + ongkir
return total
}
// ❌ BAD: Function yang melakukan terlalu banyak hal
func prosesSemuaPembayaran(harga, jarak int, email string) int {
// Hitung pajak
pajak := harga * 10 / 100
// Hitung ongkir
ongkir := jarak * 5000
// Kirim email (tidak seharusnya di sini!)
fmt.Printf("Kirim email ke %s\n", email)
// Simpan ke database (juga tidak seharusnya di sini!)
fmt.Println("Simpan ke database")
return harga + pajak + ongkir
}
💡 Tips:
- Jika function Anda melakukan lebih dari satu hal, pecah menjadi beberapa function kecil
- Function kecil lebih mudah dipahami dan di-test
- Gunakan function kecil sebagai building blocks untuk function yang lebih besar
4. 📏 Jaga Function Tetap Pendek
Prinsip: Function yang pendek (maksimal 15-20 baris) lebih mudah dipahami.
// ✅ GOOD: Function pendek dan jelas
func validasiNama(nama string) error {
if nama == "" {
return fmt.Errorf("nama tidak boleh kosong")
}
if len(nama) < 2 {
return fmt.Errorf("nama minimal 2 karakter")
}
return nil
}
func validasiUmur(umur int) error {
if umur < 17 {
return fmt.Errorf("umur minimal 17 tahun")
}
if umur > 100 {
return fmt.Errorf("umur maksimal 100 tahun")
}
return nil
}
func daftarPengguna(nama string, umur int) error {
// Validasi nama
if err := validasiNama(nama); err != nil {
return err
}
// Validasi umur
if err := validasiUmur(umur); err != nil {
return err
}
// Proses pendaftaran
fmt.Printf("✅ Pengguna %s (%d tahun) berhasil didaftarkan\n", nama, umur)
return nil
}
💡 Tips:
- Jika function lebih dari 20 baris, pertimbangkan untuk memecahnya
- Gunakan helper functions untuk logic yang berulang
- Function pendek = lebih mudah di-debug
🎓 Contoh Penerapan Best Practices
Mari lihat contoh lengkap yang menerapkan semua best practices di atas:
package main
import "fmt"
// ✅ Nama jelas: validasi email
func validasiEmail(email string) error {
if email == "" {
return fmt.Errorf("email tidak boleh kosong")
}
if !strings.Contains(email, "@") {
return fmt.Errorf("format email tidak valid")
}
return nil
}
// ✅ Nama jelas: hitung diskon
func hitungDiskon(harga int, persenDiskon float64) int {
diskon := float64(harga) * persenDiskon / 100
return int(diskon)
}
// ✅ Nama jelas: hitung harga setelah diskon
func hitungHargaSetelahDiskon(harga int, persenDiskon float64) int {
diskon := hitungDiskon(harga, persenDiskon)
return harga - diskon
}
// ✅ Function utama yang menggunakan helper functions
func prosesPembelian(email string, harga int, diskon float64) error {
// Validasi email
if err := validasiEmail(email); err != nil {
return fmt.Errorf("validasi gagal: %w", err)
}
// Hitung harga final
hargaFinal := hitungHargaSetelahDiskon(harga, diskon)
// Tampilkan hasil
fmt.Printf("📧 Email: %s\n", email)
fmt.Printf("💰 Harga asli: Rp %d\n", harga)
fmt.Printf("🎁 Diskon: %.1f%%\n", diskon)
fmt.Printf("✅ Harga final: Rp %d\n", hargaFinal)
return nil
}
func main() {
err := prosesPembelian("[email protected]", 100000, 15.0)
if err != nil {
fmt.Println("❌ Error:", err)
}
}
🏆 Hasil yang baik:
- Setiap function punya nama yang jelas ✅
- Error handling yang konsisten ✅
- Satu function satu tugas ✅
- Function pendek dan mudah dipahami ✅
💡 Catatan Advanced: Setelah Anda mahir dengan best practices di atas, Anda bisa mempelajari konsep advanced seperti functional options pattern, dependency injection, dan clean architecture di tutorial terpisah.
Testing Functions di Golang
Testing adalah bagian penting dari development. Berikut cara testing functions dengan baik:
// math.go
package math
import "errors"
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func Sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
// math_test.go
package math
import (
"testing"
)
func TestDivide(t *testing.T) {
tests := []struct {
name string
a, b float64
want float64
wantErr bool
}{
{"positive numbers", 10, 2, 5, false},
{"negative numbers", -10, 2, -5, false},
{"division by zero", 10, 0, 0, true},
{"zero dividend", 0, 5, 0, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Divide(tt.a, tt.b)
if (err != nil) != tt.wantErr {
t.Errorf("Divide() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Divide() = %v, want %v", got, tt.want)
}
})
}
}
func TestSum(t *testing.T) {
tests := []struct {
name string
numbers []int
want int
}{
{"empty slice", []int{}, 0},
{"single number", []int{5}, 5},
{"multiple numbers", []int{1, 2, 3, 4, 5}, 15},
{"negative numbers", []int{-1, -2, -3}, -6},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Sum(tt.numbers...)
if got != tt.want {
t.Errorf("Sum() = %v, want %v", got, tt.want)
}
})
}
}
// Benchmark test
func BenchmarkSum(b *testing.B) {
numbers := make([]int, 1000)
for i := range numbers {
numbers[i] = i
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
Sum(numbers...)
}
}
Error Handling dengan Defer
defer
statement sangat berguna dalam functions untuk cleanup:
package main
import (
"fmt"
"os"
)
func processFile(filename string) error {
file, err := os.Open(filename)
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer file.Close() // Akan dijalankan sebelum function return
// Process file...
// Jika ada error di sini, file tetap akan ditutup
return nil
}
func complexOperation() (result string, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic occurred: %v", r)
}
}()
// Complex logic that might panic...
result = "success"
return
}
Performance Tips
1. Hindari Function Calls dalam Loop yang Tidak Perlu
// Bad
for i := 0; i < len(slice); i++ {
// len(slice) dipanggil setiap iterasi
}
// Good
length := len(slice)
for i := 0; i < length; i++ {
// len() hanya dipanggil sekali
}
// Best: gunakan range untuk slice iteration
for i, value := range slice {
// Lebih idiomatik dan biasanya lebih cepat
}
2. Gunakan Named Returns untuk Optimasi
// Compiler bisa melakukan optimasi lebih baik dengan named returns
func expensiveCalculation() (result int, err error) {
// Named returns memungkinkan optimasi tertentu
// dan menghindari alokasi memori tambahan untuk return values
return
}
3. Preallocate Slices dan Maps
// Bad: slice akan grow dan reallocate berkali-kali
func processItems(items []Item) []Result {
var results []Result
for _, item := range items {
results = append(results, processItem(item))
}
return results
}
// Good: preallocate dengan kapasitas yang diketahui
func processItems(items []Item) []Result {
results := make([]Result, 0, len(items))
for _, item := range items {
results = append(results, processItem(item))
}
return results
}
// Best: gunakan index jika possible
func processItems(items []Item) []Result {
results := make([]Result, len(items))
for i, item := range items {
results[i] = processItem(item)
}
return results
}
4. Pointer vs Value Receivers
type LargeStruct struct {
data [1000]int
// ... banyak field lainnya
}
// Bad: copy seluruh struct setiap kali function dipanggil
func (l LargeStruct) ProcessData() Result {
// ...
}
// Good: gunakan pointer untuk struct besar
func (l *LargeStruct) ProcessData() Result {
// ...
}
Kesimpulan
Functions di Golang menawarkan fleksibilitas dan kekuatan yang luar biasa dengan fitur-fitur unik seperti:
- Multiple return values untuk error handling yang elegant
- Named returns untuk kode yang lebih readable
- Variadic functions untuk parameter yang fleksibel
- Anonymous functions dan closures untuk programming yang functional
- Higher-order functions untuk abstraksi yang powerful
Memahami dan menguasai functions dengan baik adalah kunci untuk menulis kode Golang yang clean, maintainable, dan efficient. Functions yang dirancang dengan baik akan membuat aplikasi Anda lebih modular dan mudah untuk di-test.
FAQ (Frequently Asked Questions)
Q: Apa perbedaan functions di Golang dengan bahasa lain?
A: Functions di Golang unik karena mendukung multiple return values, named returns, dan merupakan first-class citizens. Berbeda dengan bahasa seperti Java atau C++ yang hanya bisa return satu value.
Q: Kapan harus menggunakan named returns?
A: Gunakan named returns ketika function memiliki multiple return values yang kompleks, ada beberapa titik return, atau ingin membuat kode lebih self-documenting.
Q: Apa itu variadic functions?
A: Variadic functions adalah function yang dapat menerima jumlah parameter yang bervariasi menggunakan syntax ...type
. Contoh: func sum(numbers ...int)
.
Q: Bagaimana cara testing functions di Golang?
A: Gunakan package testing
bawaan Go dengan convention function_test.go
. Buat test functions dengan nama TestFunctionName
dan gunakan table-driven tests untuk multiple scenarios.
Q: Apa performance tips terbaik untuk functions?
A: Hindari function calls dalam loop yang tidak perlu, gunakan named returns untuk optimasi, preallocate slices/maps, dan gunakan pointer receivers untuk struct besar.