Kode ditulis sekali tetapi dibaca puluhan — sering kali oleh orang yang tidak menulisnya, termasuk diri kita enam bulan kemudian. Clean Code adalah disiplin menulis kode yang meminimalkan beban pembaca: nama yang jujur, method pendek, argumen sedikit, dan perilaku tanpa kejutan tersembunyi. Istilah ini dipopulerkan Robert C. Martin melalui buku berjudul sama, tetapi intinya telah menjadi kesepakatan industri jauh lebih luas. Chapter ini membandingkan versi “sebelum” dan “sesudah” dari satu program nyata dengan output identik — bukti bahwa kerapian tidak mengubah perilaku, hanya menurunkan biaya pemahaman.
Nama adalah Dokumentasi Termurah #
Nama variabel d, t, dan x menghemat enam detik saat menulis dan memboroskan enam menit setiap kali dibaca. Nama yang baik menjawab tiga hal: apa isinya, satuannya apa, dan dalam konteks apa dipakai. jumlah, hargaSatuan, dan yangDibayar tidak membutuhkan komentar; x[0] * x[1] membutuhkan investigasi. Aturan praktisnya: bila Anda ingin menaruh komentar untuk menjelaskan sebaris kode, coba ganti nama variabel dan method di baris tersebut lebih dulu — biasanya kebutuhan komentar hilang begitu nama berbenah.
Method juga membawa nama sebagai janji. Method bernama hitungTotal berjanji menghitung dan mengembalikan — tidak mencetak, tidak menyimpan, tidak mengubah state lain. Janji yang dilanggar inilah yang disebut efek samping tersembunyi:
// Method ini menghitung, tapi diam-diam juga mencetak: pembaca tertipu
long hitungTotal(List<ItemPesanan> pesanan) {
long total = 0;
for (ItemPesanan item : pesanan) {
total += item.hargaBaris();
}
System.out.println("Memproses " + pesanan.size() + " item"); // efek samping
return total;
}Pemanggil yang hanya ingin angka mendapatkan baris log yang tidak diminta; pengujian yang membandingkan output ikut gagal karena log ikut tercetak. Pisahkan: method hitung murni menghitung; pencetakan menjadi urusan pemanggil.
Sebelum dan Sesudah dengan Output Identik #
Program “sebelum” berikut fungsional dan benar, tetapi membaca seperti sandi: array int[] memaksakan makna posisi, magic number 100000 dan 10 tidak berasal, dan dua komentar menceritakan apa yang seharusnya terbaca dari kode itu sendiri:
import java.util.List;
public class SebelumRapi {
public static void main(String[] args) {
List<int[]> d = List.of(
new int[]{2, 18000},
new int[]{1, 25000},
new int[]{3, 15000});
int t = 0;
// hitung total
for (int[] x : d) {
t += x[0] * x[1];
}
// kalau lebih dari 100rb dapat diskon 10 persen
if (t > 100000) {
t = t - t / 10;
}
System.out.println("Total belanja: Rp" + t);
System.out.println("Jumlah item: " + d.size());
}
}Output yang Dihasilkan:
Total belanja: Rp95400
Jumlah item: 3Versi “sesudah” memakai record untuk data berarti, method pendek dengan satu tingkat abstraksi, serta konstanta bernama untuk angka bisnis:
import java.util.List;
public class SesudahRapi {
public static void main(String[] args) {
List<ItemPesanan> pesanan = List.of(
new ItemPesanan("Kopi susu", 2, 18_000),
new ItemPesanan("Croissant", 1, 25_000),
new ItemPesanan("Roti bakar", 3, 15_000));
long total = hitungTotal(pesanan);
long yangDibayar = terapkanDiskon(total);
System.out.println("Total belanja: Rp" + yangDibayar);
System.out.println("Jumlah item: " + pesanan.size());
}
static long hitungTotal(List<ItemPesanan> pesanan) {
long total = 0;
for (ItemPesanan item : pesanan) {
total += item.hargaBaris();
}
return total;
}
static long terapkanDiskon(long total) {
final long batasDiskon = 100_000;
final int persentaseDiskon = 10;
if (total <= batasDiskon) {
return total;
}
return total - total * persentaseDiskon / 100;
}
}
record ItemPesanan(String nama, int jumlah, long hargaSatuan) {
long hargaBaris() {
return jumlah * hargaSatuan;
}
}Output yang Dihasilkan:
Total belanja: Rp95400
Jumlah item: 3Kedua program mencetak baris yang persis sama — Rp95400 dan 3 — sehingga perapian ini terbukti tidak mengubah perilaku. Mari kita bedah pembedanya:
- Baris 22-27 (
record ItemPesanan):new int[]{2, 18000}berganti menjadinew ItemPesanan("Kopi susu", 2, 18_000). Compiler kini menjaga tipe;x[0]yang bermakna ganti menjadiitem.hargaBaris()yang bermakna tunggal. - Baris 12-13 (
hitungTotal,terapkanDiskon):mainkini terbaca sebagai ringkasan niat — hitung, terapkan diskon, cetak. Setiap method memiliki satu tingkat abstraksi:mainberbicara kebijakan, method kecil berbicara mekanik. - Baris 20-21 (
batasDiskon,persentaseDiskon): dua magic number menjadi konstanta bernama. Ketika manajemen menaikkan batas diskon, satu tempat berubah dan riwayatnya terbaca jelas pada diff. - Argumen minimal:
hitungTotalmenerima satu argumen — daftar pesanan. Method dengan nol-satu argumen mudah dipahami; tiga argumen mulai perlu usaha; lima argumen layak dijadikan record.
Komentar sebagai Sinyal #
Komentar // hitung total pada versi sebelum bukan dokumentasi — komentar itu adalah pengakuan bahwa kode gagal berbicara. Komentar semacam ini basi seketika saat kode berubah. Komentar yang bernilai justru menjelaskan mengapa: alasan bisnis di balik rumus, penanganan khusus sistem pihak ketiga, atau pembatasan yang disengaja. Untuk kontrak publik, gunakan Javadoc (/** ... */) yang menjelaskan prasyarat dan jaminan method — topik yang kita bahas tuntas pada chapter Design by Contract.
Kerapian tingkat method dan class adalah lapisan pertama. Lapisan berikutnya menuntut ketegasan yang lebih eksplisit: setiap method menyatakan prasyarat, jaminan, dan invarian yang dijaganya. Chapter selanjutnya, Design by Contract, mengajarkan cara menuliskannya.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
SebelumRapi.java
Unduh berkasimport java.util.List;
public class SebelumRapi {
public static void main(String[] args) {
List<int[]> d = List.of(
new int[]{2, 18000},
new int[]{1, 25000},
new int[]{3, 15000});
int t = 0;
// hitung total
for (int[] x : d) {
t += x[0] * x[1];
}
// kalau lebih dari 100rb dapat diskon 10 persen
if (t > 100000) {
t = t - t / 10;
}
System.out.println("Total belanja: Rp" + t);
System.out.println("Jumlah item: " + d.size());
}
}
SesudahRapi.java
Unduh berkasimport java.util.List;
public class SesudahRapi {
public static void main(String[] args) {
List<ItemPesanan> pesanan = List.of(
new ItemPesanan("Kopi susu", 2, 18_000),
new ItemPesanan("Croissant", 1, 25_000),
new ItemPesanan("Roti bakar", 3, 15_000));
long total = hitungTotal(pesanan);
long yangDibayar = terapkanDiskon(total);
System.out.println("Total belanja: Rp" + yangDibayar);
System.out.println("Jumlah item: " + pesanan.size());
}
static long hitungTotal(List<ItemPesanan> pesanan) {
long total = 0;
for (ItemPesanan item : pesanan) {
total += item.hargaBaris();
}
return total;
}
static long terapkanDiskon(long total) {
final long batasDiskon = 100_000;
final int persentaseDiskon = 10;
if (total <= batasDiskon) {
return total;
}
return total - total * persentaseDiskon / 100;
}
}
record ItemPesanan(String nama, int jumlah, long hargaSatuan) {
long hargaBaris() {
return jumlah * hargaSatuan;
}
}