Refactoring adalah proses mengubah struktur internal kode tanpa mengubah perilaku eksternalnya. Tujuannya bukan menambah fitur baru, melainkan membuat kode lebih mudah dipelihara, lebih mudah dibaca, dan lebih mudah diuji—sehingga fitur baru di masa depan menjadi murah untuk ditambahkan. Bab ini mendemonstrasikan tiga refactor paling umum di C: extract function (memecah fungsi panjang menjadi fungsi-fungsi kecil), rename variable (mengganti nama samar dengan nama deskriptif), dan simplify conditional (mengganti nested if-else dengan guard clause). Setiap contoh disertai versi “sebelum” dan “sesudah” yang menghasilkan output identik—bukti bahwa perilaku tidak berubah.

Aturan Emas Refactoring #

Sebelum mulai refactor, dua prinsip wajib dipegang:

  1. Tes harus ada sebelum refactor. Tanpa test, kita tidak tahu apakah refactor memutus perilaku. Test menjadi safety net yang memvalidasi “output sebelum = output sesudah”.
  2. Refactor dalam commit kecil-kecil. Satu commit = satu jenis perubahan. Jangan mencampur extract function dengan ganti algoritma dalam satu commit—jika ada bug, sulit dilokalisasi.

Dalam contoh-contoh berikut, kita memakai verifikasi output sebagai pengganti unit test formal: versi sebelum dan sesudah harus menghasilkan output byte-for-byte identik untuk input yang sama.

Refactor 1: Extract Function dan Eliminasi Magic Number #

Berikut fungsi yang mengandung tiga anti-pola sekaligus: nama variabel satu huruf (p, q, r), magic number (100, 50, koefisien 2, 3, 1), dan nested conditional 3-level.

c
/* File: test_refactor1.c - Sebelum refactor: fungsi panjang + magic number */
#include <stdio.h>

/* Versi BURUK: magic number + nested conditional + naming tidak jelas */
static int hitung_d(int p, int q, int r)
{
    int d = 0;
    if (p > 100) {
        if (q > 50) {
            if (r > 0) {
                d = (p * 2) + (q * 3) + (r * 1);
            } else {
                d = (p * 2) + (q * 3) - (r * 1);
            }
        } else {
            if (r > 0) {
                d = (p * 2) + (q * 1) + (r * 1);
            } else {
                d = (p * 2) + (q * 1) - (r * 1);
            }
        }
    } else {
        if (q > 50) {
            d = (p * 1) + (q * 3) + (r * 1);
        } else {
            d = (p * 1) + (q * 1) + (r * 1);
        }
    }
    return d;
}

int main(void)
{
    printf("hitung_d(150, 60, 5) = %d\n", hitung_d(150, 60, 5));
    printf("hitung_d(150, 40, -5) = %d\n", hitung_d(150, 40, -5));
    printf("hitung_d(50,  60, 5) = %d\n", hitung_d(50, 60, 5));
    printf("hitung_d(50,  40, 5) = %d\n", hitung_d(50, 40, 5));
    return 0;
}

Output yang Dihasilkan:

text
hitung_d(150, 60, 5) = 485
hitung_d(150, 40, -5) = 345
hitung_d(50,  60, 5) = 235
hitung_d(50,  40, 5) = 95

Fungsi di atas sulit dipelihara karena: nama p, q, r tidak menjelaskan apa yang dihitung; angka 100, 50, 2, 3, 1 tidak punya makna; nested if membuat 8 cabang yang harus dipahami satu per satu. Mari kita refactor.

Hasil Refactor: Extract Function + Named Constant #

Berikut versi setelah refactor. Fungsi panjang dipecah menjadi helper kecil, magic number diganti konstanta bernama, dan nested if disederhanakan menjadi rumus tunggal.

c
/* File: test_refactor2.c - Setelah refactor: extract function + named constant */
#include <stdio.h>

/* Threshold bisnis sebagai konstanta bernama, bukan magic number */
#define AMBANG_PREMIUM   100
#define AMBANG_LOYALITI  50

/* Helper: bobot untuk produk premium vs standar */
static int bobot_p(int p) { return (p > AMBANG_PREMIUM) ? 2 : 1; }
static int bobot_q(int q) { return (q > AMBANG_LOYALITI) ? 3 : 1; }
static int bobot_r(int r) { return (r >= 0) ? 1 : -1; }

static int hitung_skor(int p, int q, int r)
{
    /* Skor = bobot_p * p + bobot_q * q + bobot_r * r */
    return bobot_p(p) * p + bobot_q(q) * q + bobot_r(r) * r;
}

int main(void)
{
    int tests[][3] = {
        {150, 60, 5},    /* premium, loyalti, positif */
        {150, 40, -5},   /* premium, baru, negatif */
        {50,  60, 5},    /* standar, loyalti, positif */
        {50,  40, 5}     /* standar, baru, positif */
    };
    for (int i = 0; i < 4; i++) {
        int p = tests[i][0], q = tests[i][1], r = tests[i][2];
        printf("hitung_skor(%d, %d, %d) = %d\n",
               p, q, r, hitung_skor(p, q, r));
    }
    return 0;
}

Output yang Dihasilkan:

text
hitung_skor(150, 60, 5) = 485
hitung_skor(150, 40, -5) = 345
hitung_skor(50, 60, 5) = 235
hitung_skor(50, 40, 5) = 95

Output identik dengan versi sebelum refactor—perilaku tidak berubah. Mari kita telaah perubahan yang dilakukan:

  1. Magic number menjadi konstanta: 100 menjadi AMBANG_PREMIUM, 50 menjadi AMBANG_LOYALITI. Pembaca langsung tahu bahwa angka ini adalah threshold bisnis, bukan angka acak.
  2. Ekstrak helper: Tiga fungsi bobot_p, bobot_q, bobot_r mengkapalkan logika pemilihan bobot. Setiap fungsi 1 baris, dapat diuji secara independen.
  3. Rumus tunggal menggantikan nested if: Alih-alih 8 cabang if/else, sekarang hanya satu baris bobot_p(p) * p + bobot_q(q) * q + bobot_r(r) * r. Logika menjadi jelas matematis: skor adalah jumlah tertimbang.
  4. Rename: hitung_d menjadi hitung_skor—nama yang menjelaskan apa yang dihitung. d sebagai nama variabel hasil tidak ada maknanya; skor jelas.

Refactor 2: Simplify Conditional dengan Guard Clause #

Berikut refactor kedua: mengganti nested if-else dengan guard clause—return awal jika kondisi tidak terpenuhi. Pola ini disebut flatten conditional atau replace nested with guard.

c
/* File: test_refactor3.c - Refactor: simplify conditional + rename variable */
#include <stdio.h>
#include <stdbool.h>

/* Sebelum: nested if-else untuk klasifikasi user */
static const char *klasifikasi_buruk(int umur, int poin, int lama)
{
    if (umur >= 18) {
        if (poin >= 1000) {
            if (lama >= 12) {
                return "Platinum";
            } else {
                return "Gold";
            }
        } else {
            if (poin >= 500) {
                return "Silver";
            } else {
                return "Bronze";
            }
        }
    } else {
        return "Anak";
    }
}

/* Setelah: guard clause + kondisi flat */
static bool umur_dewasa(int u)        { return u >= 18; }
static bool poin_platinum(int p)      { return p >= 1000; }
static bool poin_gold(int p)          { return p >= 500; }
static bool lama_setahun(int l)       { return l >= 12; }

static const char *klasifikasi_baik(int umur, int poin, int lama_member)
{
    if (!umur_dewasa(umur))     return "Anak";
    if (!poin_platinum(poin)) {
        return poin_gold(poin) ? "Silver" : "Bronze";
    }
    return lama_setahun(lama_member) ? "Platinum" : "Gold";
}

int main(void)
{
    int tests[][3] = {
        {17, 200, 0},     /* Anak */
        {25, 100, 0},     /* Bronze */
        {25, 600, 0},     /* Silver */
        {25, 1500, 6},    /* Gold */
        {25, 1500, 18},   /* Platinum */
    };
    for (int i = 0; i < 5; i++) {
        int u = tests[i][0], p = tests[i][1], l = tests[i][2];
        printf("umur=%d poin=%d lama=%d -> buruk=%-8s baik=%-8s\n",
               u, p, l,
               klasifikasi_buruk(u, p, l),
               klasifikasi_baik(u, p, l));
    }
    return 0;
}

Output yang Dihasilkan:

text
umur=17 poin=200 lama=0 -> buruk=Anak     baik=Anak    
umur=25 poin=100 lama=0 -> buruk=Bronze   baik=Bronze  
umur=25 poin=600 lama=0 -> buruk=Silver   baik=Silver  
umur=25 poin=1500 lama=6 -> buruk=Gold     baik=Gold    
umur=25 poin=1500 lama=18 -> buruk=Platinum baik=Platinum

Output dari kedua fungsi (klasifikasi_buruk dan klasifikasi_baik) identik untuk semua 5 test case. Fungsi versi “baik” lebih pendek dan lebih mudah dibaca karena:

  1. Guard clause lebih dulu: if (!umur_dewasa(umur)) return "Anak"; menangani kasus anak di awal. Pembaca yang peduli kasus dewasa bisa langsung skip baris ini.
  2. Predicate function: umur_dewasa(18), poin_platinum(1000) membaca seperti bahasa natural. Bandingkan dengan if (umur >= 18) yang memaksa pembaca memetakan 18 ke “ambang dewasa” di kepala.
  3. Rename lama menjadi lama_member: nama lama ambigu—lama waktu? lama pikiran? lama_member spesifik: durasi keanggotaan dalam bulan.

Refactor 3: Rename Variable Step-by-Step #

Rename variabel adalah refactor termurah tetapi paling sering diabaikan. Aturan praktis: jika pembaca harus melihat konteks 5 baris di atas untuk memahami apa yang diwakili variabel, nama tersebut tidak cukup jelas. Tabel berikut menunjukkan contoh rename yang umum:

SebelumSesudahAlasan
int d;int skor;d tanpa makna; skor menjelaskan peran
char *s;char *path;s generik; path menjelaskan data
int i, j;int baris, kolom;i, j idiomatik untuk loop, tetapi baris, kolom jelas untuk matriks
int n;int jumlah_pegawai;n konvensional untuk count generik, tetapi jumlah_pegawai spesifik domain
int flg;int is_admin;flg tidak menjelaskan apa flag-nya; is_ prefix menandakan boolean

Proses rename aman jika dilakukan dengan bantuan compiler: hapus nama lama, kompilasi, dan perbaiki setiap error “undeclared identifier”. Editor modern (VS Code, CLion) memiliki fitur rename symbol yang otomatis memperbarui semua referensi.