Setelah mempelajari if, else if, switch, dan operator ? :, pertanyaan berikutnya adalah: bagaimana menyusun kondisi agar kode tetap mudah dibaca, mudah diuji, dan mudah dirawat di kemudian hari? Kondisi yang panjang, bertingkat, dan penuh negasi sering menjadi sumber bug. Pada chapter penutup Section percabangan ini kita akan menelaah tiga strategi konkret: penggunaan hukum De Morgan untuk menyederhanakan negasi, pola guard clause untuk meratakan nested if yang dalam, serta ekstraksi kondisi kompleks ke fungsi pembantu bernama deskriptif.

Hukum De Morgan untuk Menyederhanakan Negasi #

Hukum De Morgan adalah dua identitas logika yang sangat berguna saat menulis kondisi:

  1. !(A && B) ekuivalen dengan (!A) || (!B)
  2. !(A || B) ekuivalen dengan (!A) && (!B)

Saat kita menemukan kondisi !(A && B), sering kali kondisi (!A) || (!B) lebih mudah dibaca karena menghilangkan negasi luar. Begitu pun sebaliknya. Penghapusan negasi ganda membuat intent logika lebih jelas.

c
#include <stdio.h>

/* Demonstrasi De Morgan: negasi konjungsi */
int main(void) {
    int suhu = 38;
    int kelembapan = 90;

    /* Versi 1: negasi implisit */
    if (!(suhu < 30 && kelembapan < 80)) {
        printf("Cuaca tidak nyaman\n");
    }

    /* Versi 2: aplikasi hukum De Morgan */
    if (suhu >= 30 || kelembapan >= 80) {
        printf("Cuaca tidak nyaman\n");
    }

    return 0;
}

Output yang Dihasilkan:

text
Cuaca tidak nyaman
Cuaca tidak nyaman

Pada contoh di atas, kedua versi menghasilkan output yang sama. Versi pertama menulis !(suhu < 30 && kelembapan < 80)—membaca “bukan (suhu di bawah 30 dan kelembapan di bawah 80)”. Versi kedua menerapkan hukum De Morgan menjadi suhu >= 30 || kelembapan >= 80—membaca “suhu minimal 30 atau kelembapan minimal 80”. Versi kedua lebih jelas karena langsung menyatakan kondisi yang membuat cuaca tidak nyaman, tanpa lapisan negasi di luarnya.

Pola Guard Clause untuk Meratakan Nested If #

Nested if yang dalam—tiga atau lebih tingkat indentasi—sulit dibaca dan rawan bug. Pola guard clause mengatasi ini dengan memeriksa kondisi kegagalan lebih awal dan keluar segera (dengan return, continue, atau break), sehingga logika utama tetap berada di tingkat indentasi dangkal.

c
#include <stdio.h>
#include <stdbool.h>

/* Refactor: guard clause menggantikan nested if yang dalam */
static bool boleh_transfer(int saldo, int jumlah, bool terverifikasi) {
    /* Guard clauses: keluar lebih awal bila kondisi gagal */
    if (!terverifikasi) return false;
    if (jumlah <= 0) return false;
    if (jumlah > saldo) return false;

    /* Logika utama di sini, sudah pasti semua prasyarat terpenuhi */
    return true;
}

int main(void) {
    int saldo = 1000000;
    int jumlah = 500000;
    bool terverifikasi = true;

    if (boleh_transfer(saldo, jumlah, terverifikasi)) {
        printf("Transfer boleh diproses\n");
    } else {
        printf("Transfer ditolak\n");
    }

    /* Contoh negasi yang lebih mudah dibaca */
    int umur = 17;
    bool punya_izin_orang_tua = true;

    /* Hindari negasi ganda */
    if (!(umur < 17 || !punya_izin_orang_tua)) {
        printf("Boleh ikut acara\n");
    }
    /* Versi yang sama, lebih jelas */
    if (umur >= 17 && punya_izin_orang_tua) {
        printf("Boleh ikut acara\n");
    }
    return 0;
}

Output yang Dihasilkan:

text
Transfer boleh diproses
Boleh ikut acara
Boleh ikut acara

Mari kita telaah baris-baris kode di atas:

  1. Baris 7-11 (fungsi boleh_transfer): tiga guard clause berturut-turut. Setiap clause mengembalikan false bila satu syarat gagal. Setelah ketiga clause terlewati, fungsi bisa yakin semua prasyarat terpenuhi dan langsung mengembalikan true.
  2. Baris 14-18 (main): pemanggil fungsi boleh_transfer. Karena semua kondisi terpenuhi, true dikembalikan dan pesan “Transfer boleh diproses” tercetak.
  3. Baris 27-29 (negasi ganda): !(umur < 17 || !punya_izin_orang_tua) sulit dibaca. Menerapkan hukum De Morgan menghasilkan umur >= 17 && punya_izin_orang_tua yang jauh lebih jelas.

Ekstraksi Kondisi ke Fungsi Pembantu #

Kondisi yang panjang seperti if (total > 500000 && tier == 3 && item >= 3 && !is_blacklisted) sulit dibaca dan rawan salah ketik. Solusinya adalah membungkus kondisi ke fungsi atau predicate function dengan nama deskriptif. Pemanggilan fungsi layak_diskon_premium() jauh lebih jelas daripada rantai &&.

c
#include <stdio.h>

/* Strategi: ekstrak kondisi kompleks menjadi fungsi pembantu */
static int hitung_diskon(int total_belanja, int jumlah_item, int tier_member) {
    /* Kondisi terpisah, masing-masing diberi nama jelas */
    if (total_belanja > 500000 && tier_member == 3) {
        return 20;  /* diskon 20% */
    }
    if (total_belanja > 200000 && jumlah_item >= 5) {
        return 10;  /* diskon 10% */
    }
    if (total_belanja > 100000) {
        return 5;   /* diskon 5% */
    }
    return 0;       /* tanpa diskon */
}

int main(void) {
    int total = 750000;
    int item = 3;
    int tier = 3;

    int diskon = hitung_diskon(total, item, tier);
    printf("Diskon yang didapat: %d%%\n", diskon);
    printf("Total bayar: %d\n", total - (total * diskon / 100));
    return 0;
}

Output yang Dihasilkan:

text
Diskon yang didapat: 20%
Total bayar: 600000

Pada contoh di atas, fungsi hitung_diskon menerima tiga parameter dan mengembalikan persentase diskon yang sesuai. Logika percabangan dipisahkan dari main, sehingga main hanya mencetak hasil. Bila aturan diskon berubah di kemudian hari, kita hanya mengubah isi fungsi hitung_diskon tanpa menyentuh kode main.

Prinsip Umum Menyusun Kondisi #

Beberapa prinsip umum yang membantu kode tetap jelas:

  1. Positif lebih baik daripada negatif. if (sudah_lunas) lebih jelas daripada if (!belum_lunas).
  2. Urutan kondisi dari yang paling mungkin. Letakkan kondisi yang paling sering dipenuhi di atas agar short-circuit bekerja optimal.
  3. Hindari magic number. Pakai #define atau enum untuk konstanta, misalnya #define BATAS_LULUS 60 lebih baik daripada angka 60 yang bertebaran.
  4. Hindari efek samping di kondisi. if ((n = read(fd, buf, size)) > 0) valid tetapi membingungkan—pisahkan menjadi n = read(...); if (n > 0) ....
  5. Konsisten dengan gaya. Bila kode sudah memakai switch, jangan bercampur dengan else if untuk kasus yang sejenis.

Rangkuman Section Percabangan #

Sepanjang Section percabangan ini kita telah mempelajari kerangka pengambilan keputusan di C:

  • Boolean di C: nilai 0 adalah false, selainnya true. Tipe _Bool (alias bool) dari <stdbool.h> untuk boolean eksplisit.
  • if: satu cabang bersyarat.
  • if-else: dua cabang saling eksklusif.
  • else if: rantai kondisi berurutan untuk banyak cabang berbasis rentang atau ekspresi kompleks.
  • nested if: logika berlapis, dengan peringatan dangling else.
  • switch: percabangan berbasis nilai konstan integer atau char, lebih cepat dan ringkas untuk kasus diskrit.
  • case dan default: label tujuan lompatan di dalam switch.
  • break dan fallthrough: kontrol keluar dari switch, dengan anotasi /* fall through */ untuk fallthrough sengaja.
  • Operator ? :: bentuk ekspresi percabangan, alternatif ringkas untuk if-else sederhana.
  • Strategi menyusun kondisi: hukum De Morgan, guard clause, dan ekstraksi fungsi pembantu.

Dengan perangkat ini, kita siap melangkah ke Section berikutnya: perulangan—while, do-while, dan for—yang memungkinkan kita mengeksekusi blok kode berulang kali selama kondisi tertentu terpenuhi.