unisbadri.com » Python Java Golang Typescript Kotlin Ruby Rust Dart PHP

Seleksi Kondisi #

Seleksi kondisi di Go terlihat familiar — ada if dan switch seperti di hampir semua bahasa lain. Tapi Go membuat beberapa keputusan desain yang mengubah cara kamu menulis percabangan: if bisa punya initializer statement yang membatasi scope variabel, switch tidak punya fallthrough otomatis sehingga tidak butuh break di setiap case, dan komunitas Go punya preferensi kuat terhadap early return daripada nested if yang dalam. Keputusan-keputusan ini bukan kebetulan — semuanya mendorong kode yang lebih mudah dibaca dan di-maintain.

if Statement #

Sintaks if di Go tidak memerlukan tanda kurung di sekitar kondisi — ini berbeda dari C, Java, dan JavaScript:

umur := 20

// ✓ Idiomatic Go — tanpa kurung di kondisi
if umur >= 18 {
    fmt.Println("dewasa")
}

// ✗ Tidak idiomatic — kurung tidak diperlukan (tapi tidak error)
if (umur >= 18) {
    fmt.Println("dewasa")
}

Kurung kurawal { wajib selalu ada, bahkan untuk blok satu baris. Tidak ada one-liner if seperti di C:

// ✗ Compile error di Go — kurung kurawal wajib
if umur >= 18
    fmt.Println("dewasa")

// ✓ Harus selalu ada kurung kurawal
if umur >= 18 {
    fmt.Println("dewasa")
}

if dengan Initializer Statement #

Ini adalah fitur Go yang tidak ada di banyak bahasa lain — kamu bisa mendeklarasikan variabel tepat di dalam kondisi if, dan variabel itu hanya hidup dalam scope blok if tersebut:

// Tanpa initializer — variabel err bocor ke scope luar
err := doSomething()
if err != nil {
    return err
}
// err masih bisa diakses di sini (tidak selalu diinginkan)

// Dengan initializer — err ter-scope ke blok if saja
if err := doSomething(); err != nil {
    return err
}
// err tidak bisa diakses di sini — compiler error jika dicoba

Pola ini sangat idiomatik di Go dan digunakan di mana-mana untuk error handling:

func loadUserProfile(id int) (*Profile, error) {
    // Setiap variabel ter-scope ke blok if-nya masing-masing
    if id <= 0 {
        return nil, fmt.Errorf("id tidak valid: %d", id)
    }

    if user, err := db.FindUser(id); err != nil {
        return nil, fmt.Errorf("loadUserProfile: %w", err)
    } else if !user.Active {
        return nil, errors.New("akun tidak aktif")
    } else {
        return buildProfile(user), nil
    }
}

Initializer juga sangat berguna dengan type assertion:

var val interface{} = "Hello, Go!"

// Type assertion dengan initializer — s hanya ada di dalam blok if
if s, ok := val.(string); ok {
    fmt.Printf("string dengan panjang %d\n", len(s))
} else {
    fmt.Printf("bukan string, melainkan %T\n", val)
}

// s tidak bisa diakses di sini

if-else dan if-else if #

nilai := 78

if nilai >= 90 {
    fmt.Println("A")
} else if nilai >= 80 {
    fmt.Println("B")
} else if nilai >= 70 {
    fmt.Println("C")
} else if nilai >= 60 {
    fmt.Println("D")
} else {
    fmt.Println("F")
}

Aturan penting: else dan else if harus berada di baris yang sama dengan kurung kurawal penutup } dari blok sebelumnya. Ini berkaitan dengan aturan automatic semicolon insertion yang sudah dibahas di artikel Sintaks Utama:

// ✓ Benar
if kondisi {
    // ...
} else {
    // ...
}

// ✗ Compile error — else di baris baru
if kondisi {
    // ...
}
else {     // ← syntax error: unexpected else
    // ...
}

Early Return — Anti Pyramid of Doom #

Ini bukan hanya soal gaya — ini adalah pola yang sangat didorong di komunitas Go. Alih-alih menumpuk kondisi sukses ke dalam nested if yang makin ke kanan, tangani kasus error atau edge case lebih dulu dan segera return, biarkan happy path mengalir lurus ke bawah tanpa indentasi berlebihan.

// ANTI-PATTERN: pyramid of doom
// Happy path terkubur di indentasi terdalam
func processPayment(order *Order, user *User) error {
    if order != nil {
        if user != nil {
            if user.Balance >= order.Total {
                if order.Items > 0 {
                    if !order.IsDuplicate() {
                        err := chargeUser(user, order.Total)
                        if err == nil {
                            return saveOrder(order)
                        } else {
                            return fmt.Errorf("gagal charge: %w", err)
                        }
                    } else {
                        return errors.New("order duplikat")
                    }
                } else {
                    return errors.New("order kosong")
                }
            } else {
                return errors.New("saldo tidak cukup")
            }
        } else {
            return errors.New("user tidak ditemukan")
        }
    } else {
        return errors.New("order tidak boleh nil")
    }
}

// BENAR: early return — bersih, linear, mudah dibaca
func processPayment(order *Order, user *User) error {
    // Guard clauses — tangani semua kondisi error di awal
    if order == nil {
        return errors.New("order tidak boleh nil")
    }
    if user == nil {
        return errors.New("user tidak ditemukan")
    }
    if order.Items == 0 {
        return errors.New("order kosong")
    }
    if order.IsDuplicate() {
        return errors.New("order duplikat")
    }
    if user.Balance < order.Total {
        return errors.New("saldo tidak cukup")
    }

    // Happy path — bersih di bagian bawah
    if err := chargeUser(user, order.Total); err != nil {
        return fmt.Errorf("gagal charge: %w", err)
    }
    return saveOrder(order)
}

Kedua fungsi di atas melakukan hal yang identik — tapi yang kedua jauh lebih mudah dibaca, diuji, dan dimodifikasi. Jumlah baris bahkan lebih sedikit.

Aturan praktis early return: jika kamu menemukan dirinya menulis } else { setelah menangani kondisi error, itu tanda bahwa kamu seharusnya menggunakan early return. Kondisi error di-return lebih awal, sehingga tidak butuh else.

switch Statement #

switch di Go lebih bersih dan lebih ekspresif dari switch/case di C atau Java. Perbedaan terpenting: tidak ada fallthrough otomatis. Di C, kamu harus menulis break di setiap case — lupa satu break adalah bug klasik. Di Go, setiap case otomatis berhenti di akhir bloknya:

hari := "Senin"

switch hari {
case "Senin":
    fmt.Println("Awal minggu kerja")
    // tidak butuh break — otomatis berhenti di sini
case "Rabu":
    fmt.Println("Tengah minggu")
case "Jumat":
    fmt.Println("Akhir minggu kerja")
default:
    fmt.Println("Hari lainnya")
}

Multi-Value Per Case #

Satu case bisa menangani beberapa nilai sekaligus, dipisahkan koma:

hari := "Sabtu"

switch hari {
case "Senin", "Selasa", "Rabu", "Kamis", "Jumat":
    fmt.Println("Hari kerja")
case "Sabtu", "Minggu":
    fmt.Println("Akhir pekan")
default:
    fmt.Println("Bukan nama hari yang valid")
}

switch dengan Initializer #

Sama seperti if, switch juga bisa punya initializer statement:

switch os := runtime.GOOS; os {
case "darwin":
    fmt.Println("macOS")
case "linux":
    fmt.Println("Linux")
case "windows":
    fmt.Println("Windows")
default:
    fmt.Printf("platform lain: %s\n", os)
}
// os tidak bisa diakses di sini

switch Tanpa Ekspresi #

switch tanpa ekspresi bertindak seperti serangkaian if-else if — Go mengevaluasi setiap case dari atas ke bawah dan mengeksekusi case pertama yang kondisinya true. Ini jauh lebih bersih dari if-else if yang panjang:

suhu := 35

switch {
case suhu < 0:
    fmt.Println("Beku")
case suhu < 10:
    fmt.Println("Sangat dingin")
case suhu < 20:
    fmt.Println("Dingin")
case suhu < 30:
    fmt.Println("Sejuk")
case suhu < 38:
    fmt.Println("Panas")
default:
    fmt.Println("Sangat panas")
}

// Kondisi dalam case bisa kompleks
skor := 85
switch {
case skor >= 90 && skor <= 100:
    fmt.Println("A — Sangat Baik")
case skor >= 80:
    fmt.Println("B — Baik")
case skor >= 70:
    fmt.Println("C — Cukup")
case skor >= 60:
    fmt.Println("D — Kurang")
default:
    fmt.Println("F — Tidak Lulus")
}

fallthrough — Eksplisit dan Jarang Dipakai #

Jika kamu memang membutuhkan fallthrough ke case berikutnya, Go menyediakan keyword fallthrough yang harus ditulis secara eksplisit:

nilai := 2

switch nilai {
case 1:
    fmt.Println("satu")
    fallthrough  // lanjut ke case 2
case 2:
    fmt.Println("dua")
    fallthrough  // lanjut ke case 3
case 3:
    fmt.Println("tiga")
    // tidak ada fallthrough — berhenti di sini
case 4:
    fmt.Println("empat")  // tidak dieksekusi
}
// Output:
// dua
// tiga

Beberapa hal penting tentang fallthrough:

// 1. fallthrough TIDAK memeriksa kondisi case berikutnya
n := 5
switch {
case n > 3:
    fmt.Println("lebih dari 3")
    fallthrough
case n > 10:
    // Ini tetap dieksekusi meski n=5 tidak > 10!
    // fallthrough tidak mengecek kondisi — langsung eksekusi body
    fmt.Println("blok ini selalu dieksekusi setelah fallthrough")
}

// 2. fallthrough harus menjadi statement terakhir dalam case
switch x {
case 1:
    fmt.Println("satu")
    fallthrough
    fmt.Println("ini error")  // ← compile error: fallthrough harus statement terakhir
case 2:
    fmt.Println("dua")
}

// 3. fallthrough tidak bisa dipakai di case terakhir (default)
switch x {
case 1:
    fmt.Println("satu")
default:
    fallthrough  // ← compile error: tidak bisa fallthrough dari default
}
Gunakan fallthrough dengan sangat hati-hati. Karena ia melewati pengecekan kondisi case berikutnya, perilakunya sering mengejutkan. Dalam hampir semua kasus, alternatif yang lebih bersih adalah menggabungkan nilai dalam satu case (case 1, 2, 3:) atau memanggil fungsi helper yang sama dari beberapa case.

Type Switch #

Type switch adalah bentuk khusus switch untuk memeriksa tipe dinamis dari nilai interface. Ini adalah cara Go yang idiomatik untuk menangani nilai dengan berbagai kemungkinan tipe:

func describe(i interface{}) string {
    switch v := i.(type) {
    case nil:
        return "nilai nil"
    case int:
        return fmt.Sprintf("integer: %d", v)
    case int64:
        return fmt.Sprintf("integer64: %d", v)
    case float64:
        return fmt.Sprintf("float: %.2f", v)
    case string:
        return fmt.Sprintf("string: %q (len=%d)", v, len(v))
    case bool:
        if v {
            return "boolean: true"
        }
        return "boolean: false"
    case []int:
        return fmt.Sprintf("slice of int: %v (len=%d)", v, len(v))
    case error:
        return "error: " + v.Error()
    default:
        return fmt.Sprintf("tipe tidak dikenal: %T", v)
    }
}

func main() {
    fmt.Println(describe(42))
    fmt.Println(describe("Halo"))
    fmt.Println(describe(3.14))
    fmt.Println(describe(nil))
    fmt.Println(describe([]int{1, 2, 3}))
    fmt.Println(describe(errors.New("oops")))
}

Type Switch dengan Interface Tertentu #

Type switch tidak hanya untuk interface{} — bisa dipakai untuk interface apapun:

type Stringer interface {
    String() string
}

type JSONMarshaler interface {
    MarshalJSON() ([]byte, error)
}

func formatValue(v interface{}) string {
    switch val := v.(type) {
    case Stringer:
        // Tipe apapun yang punya method String()
        return val.String()
    case JSONMarshaler:
        // Tipe yang bisa di-marshal ke JSON
        b, err := val.MarshalJSON()
        if err != nil {
            return fmt.Sprintf("error: %v", err)
        }
        return string(b)
    case fmt.Stringer:
        // Interface bawaan dari package fmt
        return val.String()
    default:
        return fmt.Sprintf("%v", val)
    }
}

break dengan Label dalam switch #

Dalam situasi switch yang berada di dalam loop, kamu mungkin perlu keluar dari loop (bukan hanya dari switch) menggunakan break berlabel:

items := []string{"apel", "berhenti", "mangga", "jeruk"}

loop:
    for i, item := range items {
        switch item {
        case "berhenti":
            fmt.Printf("Berhenti di index %d\n", i)
            break loop  // keluar dari FOR loop, bukan dari switch
        default:
            fmt.Println("Proses:", item)
        }
    }
// Output:
// Proses: apel
// Berhenti di index 1

Tanpa label loop:, break dalam switch hanya keluar dari switch — bukan dari loop yang membungkusnya. Loop akan terus berjalan ke item berikutnya.


Pola Idiomatik #

Guard Clause — Validasi di Awal Fungsi #

Pattern guard clause adalah penerapan early return untuk memvalidasi semua precondition fungsi di awal, sebelum logika utama:

func transferDana(dari, ke *Rekening, jumlah float64) error {
    // Guard clauses — semua validasi di atas
    if dari == nil {
        return errors.New("rekening pengirim tidak boleh nil")
    }
    if ke == nil {
        return errors.New("rekening penerima tidak boleh nil")
    }
    if dari.ID == ke.ID {
        return errors.New("tidak bisa transfer ke rekening yang sama")
    }
    if jumlah <= 0 {
        return fmt.Errorf("jumlah transfer harus positif, got %.2f", jumlah)
    }
    if jumlah > dari.Saldo {
        return fmt.Errorf("saldo tidak cukup: punya %.2f, butuh %.2f",
            dari.Saldo, jumlah)
    }
    if !dari.Aktif || !ke.Aktif {
        return errors.New("rekening harus aktif untuk melakukan transfer")
    }

    // Logika utama — bersih, tanpa distraksi validasi
    dari.Saldo -= jumlah
    ke.Saldo += jumlah
    return simpanTransaksi(dari, ke, jumlah)
}

State Machine dengan switch #

switch sangat cocok untuk mengimplementasikan state machine — pola yang sangat umum di kode produksi:

type OrderStatus int

const (
    StatusDraft OrderStatus = iota
    StatusSubmitted
    StatusPaid
    StatusShipped
    StatusDelivered
    StatusCancelled
)

type Order struct {
    ID     int
    Status OrderStatus
}

func (o *Order) Transition(newStatus OrderStatus) error {
    switch o.Status {
    case StatusDraft:
        if newStatus != StatusSubmitted && newStatus != StatusCancelled {
            return fmt.Errorf("dari Draft hanya bisa ke Submitted atau Cancelled")
        }
    case StatusSubmitted:
        if newStatus != StatusPaid && newStatus != StatusCancelled {
            return fmt.Errorf("dari Submitted hanya bisa ke Paid atau Cancelled")
        }
    case StatusPaid:
        if newStatus != StatusShipped {
            return fmt.Errorf("dari Paid hanya bisa ke Shipped")
        }
    case StatusShipped:
        if newStatus != StatusDelivered {
            return fmt.Errorf("dari Shipped hanya bisa ke Delivered")
        }
    case StatusDelivered, StatusCancelled:
        return fmt.Errorf("status final tidak bisa diubah lagi")
    }

    o.Status = newStatus
    return nil
}

Lookup Table sebagai Alternatif switch #

Untuk mapping nilai sederhana, map seringkali lebih bersih dari switch:

// switch untuk mapping sederhana — OK tapi verbose
func namaHariSwitch(n int) string {
    switch n {
    case 0: return "Minggu"
    case 1: return "Senin"
    case 2: return "Selasa"
    case 3: return "Rabu"
    case 4: return "Kamis"
    case 5: return "Jumat"
    case 6: return "Sabtu"
    default: return "Tidak valid"
    }
}

// Map sebagai lookup table — lebih bersih untuk mapping statik
var namaHari = map[int]string{
    0: "Minggu", 1: "Senin", 2: "Selasa",
    3: "Rabu",   4: "Kamis", 5: "Jumat", 6: "Sabtu",
}

func namaHariMap(n int) string {
    if nama, ok := namaHari[n]; ok {
        return nama
    }
    return "Tidak valid"
}

Gunakan switch ketika ada logika berbeda di setiap case. Gunakan map/slice sebagai lookup table ketika hanya memetakan nilai ke nilai lain.


Contoh Program Lengkap #

Program berikut mensimulasikan sistem validasi dan pemrosesan order e-commerce menggunakan berbagai pola seleksi kondisi:

package main

import (
    "errors"
    "fmt"
    "strings"
    "time"
)

type PaymentMethod string

const (
    PaymentCash        PaymentMethod = "cash"
    PaymentCreditCard  PaymentMethod = "credit_card"
    PaymentTransfer    PaymentMethod = "transfer"
    PaymentEWallet     PaymentMethod = "ewallet"
)

type Item struct {
    Name     string
    Price    float64
    Qty      int
    Category string
}

type Order struct {
    ID        string
    Items     []Item
    Payment   PaymentMethod
    VoucherCode string
    CreatedAt time.Time
}

// Menghitung diskon berdasarkan metode pembayaran dan kategori
func hitungDiskon(item Item, payment PaymentMethod) float64 {
    baseDiscount := 0.0

    // Diskon berdasarkan kategori
    switch strings.ToLower(item.Category) {
    case "elektronik":
        baseDiscount = 0.05  // 5%
    case "fashion":
        baseDiscount = 0.10  // 10%
    case "makanan":
        baseDiscount = 0.0   // tidak ada diskon
    default:
        baseDiscount = 0.02  // 2% untuk kategori lain
    }

    // Tambahan diskon berdasarkan metode pembayaran
    switch payment {
    case PaymentCreditCard:
        baseDiscount += 0.03  // tambah 3%
    case PaymentEWallet:
        baseDiscount += 0.05  // tambah 5%
    case PaymentTransfer:
        baseDiscount += 0.02  // tambah 2%
    case PaymentCash:
        // tidak ada tambahan diskon
    }

    return baseDiscount
}

// Validasi order — menggunakan pola guard clause
func validasiOrder(order Order) error {
    if order.ID == "" {
        return errors.New("ID order tidak boleh kosong")
    }
    if len(order.Items) == 0 {
        return errors.New("order harus memiliki minimal 1 item")
    }

    // Validasi setiap item
    for i, item := range order.Items {
        if item.Name == "" {
            return fmt.Errorf("item ke-%d: nama tidak boleh kosong", i+1)
        }
        if item.Price <= 0 {
            return fmt.Errorf("item %q: harga harus positif", item.Name)
        }
        if item.Qty <= 0 {
            return fmt.Errorf("item %q: jumlah harus positif", item.Name)
        }
    }

    // Validasi metode pembayaran
    switch order.Payment {
    case PaymentCash, PaymentCreditCard, PaymentTransfer, PaymentEWallet:
        // valid
    default:
        return fmt.Errorf("metode pembayaran %q tidak dikenal", order.Payment)
    }

    return nil
}

// Hitung total order dengan diskon
func hitungTotal(order Order) (subtotal, totalDiskon, total float64) {
    for _, item := range order.Items {
        itemTotal := item.Price * float64(item.Qty)
        diskonRate := hitungDiskon(item, order.Payment)
        itemDiskon := itemTotal * diskonRate

        subtotal += itemTotal
        totalDiskon += itemDiskon
    }

    // Diskon tambahan dari voucher
    if order.VoucherCode != "" {
        switch order.VoucherCode {
        case "GOFIRST10":
            totalDiskon += subtotal * 0.10
        case "WEEKEND15":
            // Hanya berlaku akhir pekan
            weekday := order.CreatedAt.Weekday()
            if weekday == time.Saturday || weekday == time.Sunday {
                totalDiskon += subtotal * 0.15
            }
        case "FLAT50K":
            if subtotal >= 500000 {
                totalDiskon += 50000
            }
        }
    }

    // Pastikan diskon tidak melebihi subtotal
    if totalDiskon > subtotal {
        totalDiskon = subtotal
    }

    total = subtotal - totalDiskon
    return
}

// Tentukan metode pengiriman berdasarkan total
func rekomendasiPengiriman(total float64) string {
    switch {
    case total >= 1_000_000:
        return "Gratis Ongkir — Kurir Express"
    case total >= 500_000:
        return "Gratis Ongkir — Kurir Reguler"
    case total >= 200_000:
        return "Diskon Ongkir 50%"
    default:
        return "Ongkir Normal"
    }
}

func main() {
    order := Order{
        ID: "ORD-2024-001",
        Items: []Item{
            {Name: "Laptop UltraBook", Price: 12_000_000, Qty: 1, Category: "Elektronik"},
            {Name: "Kaos Polos",       Price: 85_000,     Qty: 3, Category: "Fashion"},
            {Name: "Kopi Arabica",     Price: 120_000,    Qty: 2, Category: "Makanan"},
        },
        Payment:     PaymentEWallet,
        VoucherCode: "GOFIRST10",
        CreatedAt:   time.Now(),
    }

    // Validasi order
    if err := validasiOrder(order); err != nil {
        fmt.Println("Order tidak valid:", err)
        return
    }

    subtotal, totalDiskon, total := hitungTotal(order)
    pengiriman := rekomendasiPengiriman(total)

    fmt.Printf("=== Ringkasan Order %s ===\n\n", order.ID)

    fmt.Println("Item:")
    for _, item := range order.Items {
        diskonRate := hitungDiskon(item, order.Payment)
        itemTotal := item.Price * float64(item.Qty)
        fmt.Printf("  %-20s %dx Rp%.0f = Rp%.0f (diskon %.0f%%)\n",
            item.Name, item.Qty, item.Price, itemTotal, diskonRate*100)
    }

    fmt.Printf("\nSubtotal      : Rp%.0f\n", subtotal)
    fmt.Printf("Total Diskon  : Rp%.0f\n", totalDiskon)
    fmt.Printf("Voucher       : %s\n", order.VoucherCode)
    fmt.Printf("Metode Bayar  : %s\n", order.Payment)
    fmt.Printf("\nTotal Bayar   : Rp%.0f\n", total)
    fmt.Printf("Pengiriman    : %s\n", pengiriman)
}

Ringkasan #

  • Tidak butuh kurung di kondisi ifif x > 0 {} bukan if (x > 0) {}, tapi kurung kurawal {} selalu wajib.
  • if dengan initializer (if err := f(); err != nil) membatasi scope variabel ke blok if — lebih bersih dari deklarasi terpisah.
  • Early return (guard clause) daripada nested if — tangani error dan edge case di atas, biarkan happy path mengalir bersih di bawah.
  • else dan else if harus di baris yang sama dengan } penutup blok sebelumnya.
  • switch tidak butuh break — tidak ada fallthrough otomatis seperti di C/Java.
  • Multi-value case (case "Sabtu", "Minggu":) untuk menangani beberapa nilai dalam satu case.
  • switch tanpa ekspresi bertindak sebagai if-else if — case dievaluasi dari atas, eksekusi yang pertama true.
  • fallthrough eksplisit dan tidak memeriksa kondisi case berikutnya — gunakan dengan sangat hati-hati.
  • Type switch (switch v := i.(type)) untuk menangani berbagai tipe interface secara elegan.
  • break berlabel untuk keluar dari loop yang membungkus switch, bukan hanya dari switch.

← Sebelumnya: Operator   Berikutnya: Perulangan →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact