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:
- Tes harus ada sebelum refactor. Tanpa test, kita tidak tahu apakah refactor memutus perilaku. Test menjadi safety net yang memvalidasi “output sebelum = output sesudah”.
- 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.
/* 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:
hitung_d(150, 60, 5) = 485
hitung_d(150, 40, -5) = 345
hitung_d(50, 60, 5) = 235
hitung_d(50, 40, 5) = 95Fungsi 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.
/* 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:
hitung_skor(150, 60, 5) = 485
hitung_skor(150, 40, -5) = 345
hitung_skor(50, 60, 5) = 235
hitung_skor(50, 40, 5) = 95Output identik dengan versi sebelum refactor—perilaku tidak berubah. Mari kita telaah perubahan yang dilakukan:
- Magic number menjadi konstanta:
100menjadiAMBANG_PREMIUM,50menjadiAMBANG_LOYALITI. Pembaca langsung tahu bahwa angka ini adalah threshold bisnis, bukan angka acak. - Ekstrak helper: Tiga fungsi
bobot_p,bobot_q,bobot_rmengkapalkan logika pemilihan bobot. Setiap fungsi 1 baris, dapat diuji secara independen. - 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. - Rename:
hitung_dmenjadihitung_skor—nama yang menjelaskan apa yang dihitung.dsebagai nama variabel hasil tidak ada maknanya;skorjelas.
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.
/* 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:
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=PlatinumOutput dari kedua fungsi (klasifikasi_buruk dan klasifikasi_baik) identik untuk semua 5 test case. Fungsi versi “baik” lebih pendek dan lebih mudah dibaca karena:
- 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. - Predicate function:
umur_dewasa(18),poin_platinum(1000)membaca seperti bahasa natural. Bandingkan denganif (umur >= 18)yang memaksa pembaca memetakan18ke “ambang dewasa” di kepala. - Rename
lamamenjadilama_member: namalamaambigu—lama waktu? lama pikiran?lama_memberspesifik: 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:
| Sebelum | Sesudah | Alasan |
|---|---|---|
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.