Seleksi Kondisi

Seleksi Kondisi #

Setiap program yang berguna membuat keputusan — menjalankan kode ini jika kondisi itu terpenuhi, melewati blok itu jika tidak. Ruby menyediakan beragam cara untuk mengekspresikan keputusan ini: dari if-elsif-else yang klasik, unless yang membaca seperti kalimat bahasa Inggris, hingga case/when yang jauh lebih powerful dari switch di bahasa lain karena bisa mencocokkan berdasarkan kelas, regex, range, dan bahkan kondisi kustom. Yang membuat seleksi kondisi di Ruby menarik adalah semuanya adalah ekspresi — artinya if, unless, dan case semuanya mengembalikan nilai dan bisa digunakan langsung dalam assignment. Memahami nuansa ini membuat kode Ruby terasa lebih ringkas dan ekspresif.

if — Bentuk Dasar #

if adalah fondasi seleksi kondisi. Blok kode di dalamnya hanya dieksekusi jika kondisi bernilai truthy.

stok = 5

if stok > 0
  puts "Produk tersedia"
end

Karena if adalah ekspresi, ia mengembalikan nilai dari baris terakhir yang dieksekusi — atau nil jika kondisi tidak terpenuhi:

nilai = 85

pesan = if nilai >= 60
           "Lulus"
         end

puts pesan.inspect   # => "Lulus"

# Jika kondisi tidak terpenuhi, if mengembalikan nil
pesan2 = if nilai < 60
             "Tidak lulus"
           end

puts pesan2.inspect  # => nil

Karena if mengembalikan nilai, kamu bisa menggunakannya langsung dalam assignment — sebuah idiom yang sering terlihat di kode Ruby yang bersih:

# ANTI-PATTERN: assignment di setiap cabang — repetitif
grade = nil
if nilai >= 90
  grade = "A"
elsif nilai >= 80
  grade = "B"
elsif nilai >= 70
  grade = "C"
else
  grade = "D"
end

# BENAR: if sebagai ekspresi — satu assignment, bersih
grade = if nilai >= 90 then "A"
         elsif nilai >= 80 then "B"
         elsif nilai >= 70 then "C"
         else "D"
         end

if-elsif-else — Percabangan Bertingkat #

Gunakan elsif (perhatikan: bukan elseif atau else if) untuk menambahkan cabang kondisi tambahan:

def kategori_usia(umur)
  if umur < 0
    raise ArgumentError, "Umur tidak boleh negatif"
  elsif umur < 13
    "anak-anak"
  elsif umur < 18
    "remaja"
  elsif umur < 60
    "dewasa"
  else
    "lansia"
  end
end

puts kategori_usia(8)    # => "anak-anak"
puts kategori_usia(15)   # => "remaja"
puts kategori_usia(35)   # => "dewasa"
puts kategori_usia(65)   # => "lansia"

Ruby mengevaluasi setiap kondisi dari atas ke bawah dan berhenti di cabang pertama yang terpenuhi — cabang berikutnya tidak diperiksa lagi. Ini berarti urutan elsif penting:

# ANTI-PATTERN: urutan kondisi yang salah — kondisi lebih luas di atas
def level_diskon(total)
  if total > 100_000     # ini selalu true jika total > 500_000 juga true
    "Bronze - 5%"
  elsif total > 500_000  # tidak akan pernah tercapai untuk total > 500_000!
    "Gold - 15%"
  elsif total > 1_000_000
    "Platinum - 20%"
  end
end

# BENAR: kondisi paling spesifik (paling ketat) di atas
def level_diskon(total)
  if total > 1_000_000
    "Platinum - 20%"
  elsif total > 500_000
    "Gold - 15%"
  elsif total > 100_000
    "Bronze - 5%"
  else
    "Tanpa diskon"
  end
end

unless — Kebalikan if #

unless mengeksekusi blok kode ketika kondisinya tidak terpenuhi — ia adalah if not yang lebih mudah dibaca dalam banyak situasi.

stok_habis = false

unless stok_habis
  puts "Silakan tambahkan ke keranjang"
end

# Ekuivalen dengan:
if !stok_habis
  puts "Silakan tambahkan ke keranjang"
end

unless paling kuat ketika kondisi yang diperiksa lebih mudah dirumuskan secara negatif:

# unless membaca lebih natural dari if !
unless user.terblokir?
  izinkan_login(user)
end

unless saldo < minimum_saldo
  proses_penarikan(jumlah)
end

unless ENV["RAILS_ENV"] == "production"
  puts "[DEBUG] Mode development aktif"
end
Jangan gunakan unless dengan kondisi yang mengandung negasi atau operator || dan && — hasilnya sulit dibaca dan rawan salah baca. Aturan sederhana: jika kamu butuh unless dengan || atau &&, gunakan if saja dengan kondisi yang dirumuskan ulang secara positif.
# ANTI-PATTERN: unless dengan negasi — dua negasi dalam satu ekspresi
unless !user.aktif?          # artinya: "jika user aktif" — pakai if saja!
  proses(user)
end

# ANTI-PATTERN: unless dengan ||
unless a.nil? || b.nil?      # membingungkan — sulit diparsing di kepala
  gabungkan(a, b)
end

# BENAR: tulis ulang dengan if
if user.aktif?
  proses(user)
end

if a && b
  gabungkan(a, b)
end

Modifier Postfix — if dan unless Satu Baris #

Ruby memungkinkan penulisan if dan unless di akhir baris — setelah kode yang ingin dikondisikan. Ini disebut modifier postfix dan merupakan salah satu idiom Ruby yang paling ekspresif.

# Modifier if
puts "Akses diizinkan" if user.admin?
kirim_email(user) if user.email_terverifikasi?
log.warn("Saldo rendah") if saldo < batas_minimum

# Modifier unless
redirect_to(root_path) unless user.login?
raise ArgumentError, "Nama tidak boleh kosong" unless nama.present?

Modifier postfix paling tepat digunakan untuk:

Kapan gunakan modifier postfix:
  ✓ Guard clause — menolak kondisi tidak valid di awal method
  ✓ Ekspresi satu baris yang tidak perlu else
  ✓ Kode yang membaca natural dari kiri ke kanan
  ✓ Logging dan debugging yang opsional

  ✗ Kondisi yang butuh blok multi-baris
  ✗ Kondisi kompleks yang menyulitkan pembacaan kiri ke kanan
  ✗ Ketika ada else — gunakan if-else biasa
# ANTI-PATTERN: logika kompleks dalam modifier postfix
simpan_data(proses(transformasi(input))) if valid?(input) && !locked? && user.punya_izin?(:tulis)

# BENAR: pecah jika kondisi kompleks
return unless valid?(input)
return if locked?
return unless user.punya_izin?(:tulis)

simpan_data(proses(transformasi(input)))

Guard Clause dan Early Return #

Guard clause adalah pola penggunaan if/unless modifier di awal method untuk menangani kasus-kasus tepi (edge case) sesegera mungkin, sebelum masuk ke logika utama. Ini adalah salah satu pola yang paling dianjurkan di Ruby karena mengurangi nesting dan membuat logika utama tetap bersih.

# ANTI-PATTERN: nesting dalam yang membuat logika utama terkubur
def proses_pembayaran(user, order, kartu)
  if user
    if user.aktif?
      if order
        if order.belum_dibayar?
          if kartu && kartu.valid?
            # logika utama di sini — 5 level dalam!
            charge(kartu, order.total)
            order.tandai_lunas
            kirim_notifikasi(user, order)
          else
            "Kartu tidak valid"
          end
        else
          "Order sudah dibayar"
        end
      else
        "Order tidak ditemukan"
      end
    else
      "User tidak aktif"
    end
  else
    "User tidak ditemukan"
  end
end

# BENAR: guard clause — tangani kasus tepi di atas, logika utama di bawah
def proses_pembayaran(user, order, kartu)
  return "User tidak ditemukan"  unless user
  return "User tidak aktif"     unless user.aktif?
  return "Order tidak ditemukan" unless order
  return "Order sudah dibayar"  unless order.belum_dibayar?
  return "Kartu tidak valid"    unless kartu&.valid?

  # logika utama — tidak ada nesting, mudah dibaca
  charge(kartu, order.total)
  order.tandai_lunas
  kirim_notifikasi(user, order)
  "Pembayaran berhasil"
end
flowchart TD
    A[Masuk ke method] --> B{user ada?}
    B -- Tidak --> R1["return: User tidak ditemukan"]
    B -- Ya --> C{user.aktif?}
    C -- Tidak --> R2["return: User tidak aktif"]
    C -- Ya --> D{order ada?}
    D -- Tidak --> R3["return: Order tidak ditemukan"]
    D -- Ya --> E{order belum dibayar?}
    E -- Tidak --> R4["return: Order sudah dibayar"]
    E -- Ya --> F{kartu valid?}
    F -- Tidak --> R5["return: Kartu tidak valid"]
    F -- Ya --> G[Logika utama\ncharge, tandai lunas, notifikasi]
    G --> H["return: Pembayaran berhasil"]

case/when — Pattern Matching yang Powerful #

case/when di Ruby jauh lebih powerful dari switch/case di kebanyakan bahasa lain. when menggunakan operator === (triple equals) untuk pencocokan — dan setiap kelas bisa mendefinisikan ===-nya sendiri. Artinya when bisa mencocokkan berdasarkan nilai, kelas, range, regex, Proc, dan lebih banyak lagi.

Pencocokan Nilai Dasar #

hari = "Rabu"

pesan = case hari
        when "Senin", "Selasa", "Rabu", "Kamis", "Jumat"
          "Hari kerja"
        when "Sabtu", "Minggu"
          "Hari libur"
        else
          "Hari tidak dikenal"
        end

puts pesan   # => "Hari kerja"

Pencocokan Berdasarkan Range #

nilai = 78

grade = case nilai
        when 90..100 then "A — Sangat Baik"
        when 80..89  then "B — Baik"
        when 70..79  then "C — Cukup"
        when 60..69  then "D — Kurang"
        else              "E — Tidak Lulus"
        end

puts grade   # => "C — Cukup"

Pencocokan Berdasarkan Kelas #

Ini adalah fitur case/when yang paling membedakan Ruby dari bahasa lain — when bisa mencocokkan berdasarkan tipe objek:

def deskripsikan(nilai)
  case nilai
  when Integer then "Integer: #{nilai}"
  when Float   then "Float: #{nilai}"
  when String  then "String: \"#{nilai}\""
  when Array   then "Array dengan #{nilai.length} elemen"
  when Hash    then "Hash dengan #{nilai.size} kunci"
  when Symbol  then "Symbol: :#{nilai}"
  when NilClass then "Nilai kosong (nil)"
  when TrueClass, FalseClass then "Boolean: #{nilai}"
  else "Tipe tidak dikenal: #{nilai.class}"
  end
end

puts deskripsikan(42)           # => Integer: 42
puts deskripsikan(3.14)         # => Float: 3.14
puts deskripsikan("halo")       # => String: "halo"
puts deskripsikan([1, 2, 3])    # => Array dengan 3 elemen
puts deskripsikan(nil)          # => Nilai kosong (nil)
puts deskripsikan(true)         # => Boolean: true

Pencocokan Berdasarkan Regex #

def klasifikasikan_input(teks)
  case teks
  when /\A\d+\z/
    "Angka bulat: #{teks.to_i}"
  when /\A\d+\.\d+\z/
    "Angka desimal: #{teks.to_f}"
  when /\A[\w.+-]+@[\w-]+\.[a-z]{2,}\z/i
    "Alamat email: #{teks}"
  when /\Ahttps?:\/\//
    "URL: #{teks}"
  when /\A(\+62|0)\d{9,12}\z/
    "Nomor telepon Indonesia: #{teks}"
  else
    "Teks biasa: #{teks}"
  end
end

puts klasifikasikan_input("42")                    # => Angka bulat: 42
puts klasifikasikan_input("[email protected]")      # => Alamat email: ...
puts klasifikasikan_input("https://ruby-lang.org") # => URL: ...
puts klasifikasikan_input("08123456789")           # => Nomor telepon Indonesia: ...

case tanpa Ekspresi — Pengganti if-elsif #

case bisa digunakan tanpa ekspresi setelahnya, sehingga berperan sebagai pengganti if-elsif yang lebih bersih:

def periksa_kondisi(suhu, kelembaban, angin)
  case
  when suhu > 38 && kelembaban > 80
    "Bahaya: panas ekstrem dan lembab"
  when suhu > 35
    "Peringatan: suhu sangat tinggi"
  when angin > 80
    "Peringatan: angin kencang"
  when suhu < 10
    "Peringatan: suhu sangat rendah"
  else
    "Kondisi normal"
  end
end

Capture dengan then dan Variabel Lokal #

Sejak Ruby 3.x, case/in (pattern matching) memungkinkan ekstraksi nilai dari struktur data secara langsung:

# Pattern matching dengan case/in (Ruby 3.0+)
respons = { status: 200, data: { user: { nama: "Rani", role: :admin } } }

case respons
in { status: 200, data: { user: { nama: String => nama, role: :admin } } }
  puts "Admin login: #{nama}"
in { status: 200, data: { user: { nama: String => nama } } }
  puts "User login: #{nama}"
in { status: 401 }
  puts "Tidak terotorisasi"
in { status: 404 }
  puts "Tidak ditemukan"
in { status: (500..) }
  puts "Server error"
end
# => "Admin login: Rani"

# Pattern matching untuk Array
koordinat = [10, 20, 30]

case koordinat
in [x, y]
  puts "2D: (#{x}, #{y})"
in [x, y, z]
  puts "3D: (#{x}, #{y}, #{z})"
end
# => "3D: (10, 20, 30)"

Ternary Operator #

Operator ternary (? :) adalah bentuk ringkas if-else untuk ekspresi satu baris yang mengembalikan nilai:

umur   = 20
status = umur >= 18 ? "dewasa" : "anak-anak"
puts status   # => "dewasa"

# Penggunaan idiomatik
label  = saldo >= 0 ? "Kredit" : "Debit"
warna  = error? ? :merah : :hijau
prefix = jumlah == 1 ? "item" : "items"
# ANTI-PATTERN: ternary untuk efek samping
kondisi ? puts("ya") : puts("tidak")

# ANTI-PATTERN: ternary bertumpuk
hasil = a > 0 ? (b > 0 ? "++ positif" : "+- campuran") : "negatif"

# BENAR: ternary hanya untuk ekspresi yang mengembalikan nilai
label = aktif? ? "Aktif" : "Nonaktif"

# BENAR: if-elsif untuk kondisi bertingkat
hasil = if a > 0 && b > 0
           "keduanya positif"
         elsif a > 0
           "hanya a positif"
         else
           "a tidak positif"
         end

then — Penulisan Kompak #

Keyword then memungkinkan penulisan if dan when dalam satu baris tanpa harus menggunakan newline sebagai separator:

# Dengan then di if
if x > 0 then puts "positif" end

# Lebih sering digunakan di case/when untuk satu baris:
case status
when :aktif   then "Pengguna aktif"
when :pending then "Menunggu verifikasi"
when :suspend then "Akun ditangguhkan"
else               "Status tidak dikenal"
end

Seleksi Kondisi sebagai Ekspresi dalam Konteks Lain #

Karena if, unless, dan case adalah ekspresi, mereka bisa digunakan di mana saja yang menerima nilai — termasuk sebagai argumen method, nilai hash, dan elemen array:

# Sebagai argumen method langsung
log.info(if debug? then "Mode debug aktif" else "Mode produksi" end)

# Dalam hash
config = {
  timeout: if production? then 30 else 5 end,
  log_level: (debug? ? :debug : :info)
}

# Return value method — Ruby otomatis mengembalikan ekspresi terakhir
def status_label(kode)
  case kode
  when 200 then "OK"
  when 201 then "Created"
  when 400 then "Bad Request"
  when 401 then "Unauthorized"
  when 404 then "Not Found"
  when 500 then "Internal Server Error"
  else "Unknown (#{kode})"
  end
  # tidak perlu 'return' — case mengembalikan nilai terakhirnya
end

puts status_label(404)   # => "Not Found"
puts status_label(999)   # => "Unknown (999)"

Memilih Konstruk yang Tepat #

flowchart TD
    A[Perlu seleksi kondisi] --> B{Berapa banyak cabang?}
    B --> C["Satu cabang\n(hanya jika true)"]
    B --> D["Dua cabang\n(if dan else)"]
    B --> E["Banyak cabang"]
    C --> F{Kondisi positif\natau negatif?}
    F --> G["Positif → if modifier\nputs x if kondisi"]
    F --> H["Negatif → unless modifier\nputs x unless kondisi"]
    D --> I{Satu baris\natau multi-baris?}
    I --> J["Satu baris → ternary\nkondisi ? a : b"]
    I --> K["Multi-baris → if-else"]
    E --> L{Cocokkan nilai,\nkelas, atau range?}
    L --> M["Ya → case/when"]
    L --> N["Tidak → if-elsif-else"]
    E --> O["Guard clauses di awal\nmethod → early return"]
Panduan cepat memilih konstruk:
  if modifier postfix     → satu cabang, kondisi sederhana, satu baris
  unless modifier postfix → satu cabang, kondisi negatif, satu baris
  if-else                 → dua cabang, ekspresi atau multi-baris
  unless-else             → dua cabang, ketika kondisi negatif lebih natural
  ternary ? :             → dua cabang, satu baris, mengembalikan nilai
  if-elsif-else           → banyak cabang, kondisi bebas
  case/when nilai         → banyak cabang, cocokkan nilai, kelas, range, regex
  Guard clause + return   → validasi awal sebelum logika utama

Ringkasan #

  • if, unless, dan case adalah ekspresi — semuanya mengembalikan nilai dan bisa digunakan langsung dalam assignment, bukan hanya sebagai statement.
  • Urutan elsif menentukan hasilnya — letakkan kondisi paling spesifik (paling ketat) di atas untuk menghindari kondisi yang lebih luas “memakan” kasus yang seharusnya jatuh ke kondisi di bawahnya.
  • unless untuk kondisi negatif yang natural — tapi hindari unless dengan ||, &&, atau negasi di dalamnya karena membingungkan.
  • Modifier postfix untuk satu baris tanpa elseputs x if kondisi dan return unless valid? adalah idiom Ruby yang sangat umum dan terbaca natural.
  • Guard clause + early return mengurangi nesting — tangani semua kasus tepi di awal method dengan return unless, biarkan logika utama tidak bersarang.
  • case/when menggunakan === — bukan ==, sehingga bisa mencocokkan berdasarkan kelas (Integer), range (1..10), regex (/\d+/), dan nilai sekaligus.
  • case tanpa ekspresi — bisa digunakan sebagai pengganti if-elsif yang lebih bersih untuk kondisi yang tidak cocok dengan satu variabel.
  • Pattern matching case/in — fitur Ruby 3.x yang memungkinkan ekstraksi (destructuring) data dari Hash dan Array langsung dalam when.
  • Ternary hanya untuk dua cabang satu baris — jangan tumpuk ternary, jangan gunakan untuk efek samping tanpa nilai kembalian.

← Sebelumnya: Operator   Berikutnya: Perulangan →

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