Coding standards menjaga konsistensi permukaan kode, dan nama yang baik menjadikan setiap simbol terbaca — tetapi kode bisa rapi bernama lalu tetap sulit diubah. Clean code adalah disiplin menyusun kode agar mudah dibaca, mudah diubah, dan jujur terhadap pembacanya: fungsi kecil yang mengerjakan satu hal, efek samping yang tidak menyelinap, error yang ditangani tanpa kebisingan, dan duplikasi yang terkendali. Pada chapter ini kita mempraktikkan prinsip-prinsipnya lewat serangkaian refactoring — setiap contoh “sesudah” menghasilkan output yang identik persis dengan contoh “sebelum”, karena perubahan murni pada struktur, bukan perilaku.

Fungsi Kecil dan Satu Level Abstraksi #

Fungsi besar biasanya mengerjakan tiga pekerjaan sekaligus: menghitung, memformat, dan mencetak. Pembaca dipaksa melompat-lompat antar level — dari perkalian rupiah ke format header laporan. Prinsip clean code: isi sebuah fungsi sebaiknya berada pada satu level abstraksi, sehingga membacanya terasa seperti membaca daftar langkah. Berikut versi “sebelum”:

php
<?php

function buatLaporanPenjualan(array $transaksi)
{
    $total = 0;
    foreach ($transaksi as $transaksiTunggal) {
        $total += $transaksiTunggal['subtotal'];
    }

    echo "Laporan Penjualan\n";
    echo "-----------------\n";
    foreach ($transaksi as $transaksiTunggal) {
        echo $transaksiTunggal['produk'] . ': Rp' . $transaksiTunggal['subtotal'] . "\n";
    }
    echo "-----------------\n";
    echo "Total: Rp" . $total . "\n";
}

buatLaporanPenjualan([
    ['produk' => 'Kopi', 'subtotal' => 25000],
    ['produk' => 'Teh', 'subtotal' => 15000],
]);

Output yang Dihasilkan:

text
Laporan Penjualan
-----------------
Kopi: Rp25000
Teh: Rp15000
-----------------
Total: Rp40000

Versi “sesudah” memecahnya menjadi tiga fungsi kecil, masing-masing pada levelnya sendiri:

php
<?php

function hitungTotalPenjualan(array $transaksi): int
{
    $total = 0;
    foreach ($transaksi as $transaksiTunggal) {
        $total += $transaksiTunggal['subtotal'];
    }

    return $total;
}

function cetakBarisLaporan(string $produk, int $subtotal): void
{
    echo $produk . ': Rp' . $subtotal . "\n";
}

function cetakLaporanPenjualan(array $transaksi): void
{
    $total = hitungTotalPenjualan($transaksi);

    echo "Laporan Penjualan\n";
    echo "-----------------\n";
    foreach ($transaksi as $transaksiTunggal) {
        cetakBarisLaporan($transaksiTunggal['produk'], $transaksiTunggal['subtotal']);
    }
    echo "-----------------\n";
    echo "Total: Rp" . $total . "\n";
}

cetakLaporanPenjualan([
    ['produk' => 'Kopi', 'subtotal' => 25000],
    ['produk' => 'Teh', 'subtotal' => 15000],
]);

Output yang Dihasilkan:

text
Laporan Penjualan
-----------------
Kopi: Rp25000
Teh: Rp15000
-----------------
Total: Rp40000

Mari kita bedah hasil pemecahannya:

  1. hitungTotalPenjualan() hanya menghitung — level aritmetika.
  2. cetakBarisLaporan() hanya tahu cara mencetak satu baris — level format.
  3. cetakLaporanPenjualan() menjadi orkestrator yang membaca seperti rencana: hitung total, cetak header, cetak tiap baris, cetak total.
  4. Output kedua versi identik — refactoring mengubah struktur tanpa mengubah perilaku.

Bonusnya nyata: hitungTotalPenjualan() kini bisa diuji dan dipakai ulang tanpa mencetak apa pun, dan mengubah format laporan tidak menyentuh logika perhitungan.

Nama yang Bermakna #

Sebagian besar prinsip clean code bertumpu pada penamaan, sehingga kita tidak mengulang materinya di sini — tabel konvensi, nama yang mengungkap niat, hingga kosakata domain yang konsisten sudah dibedah tuntas pada chapter Naming Conventions . Cukup ingat aturan praktisnya saat merefactor: jika sebuah blok kode butuh komentar untuk menjelaskan apa yang dilakukannya, coba beri nama terlebih dahulu — jadikan blok itu fungsi bernama validasiInputPemesanan() alih-alih komentar // validasi input. Nama yang tepat sering menggantikan dokumentasi; komentar yang menerangkan kode jelek adalah aroma bahwa kode tersebut layak ditulis ulang.

Efek Samping yang Mengejutkan #

Fungsi seharusnya melakukan persis apa yang dikatakan namanya — tidak lebih. Pelanggaran paling umum adalah efek samping tersembunyi, seperti fungsi “hitung” yang diam-diam menulis berkas:

php
<?php

function hitungTotalBelanja(array $itemBelanja): int
{
    $total = 0;
    foreach ($itemBelanja as $item) {
        $total += $item['hargaSatuan'] * $item['jumlah'];
    }

    file_put_contents('log-transaksi.txt', "total: {$total}\n", FILE_APPEND);

    return $total;
}

Pembaca yang memanggil hitungTotalBelanja() tidak menyangka keranjang belanjaannya meninggalkan jejak di filesystem; pemanggilan berulang dalam satu request pun membuat berkas log tumbuh diam-diam. Versi jujurnya memisahkan dua tanggung jawab itu: fungsi hitungTotalBelanja() menjadi murni — menerima input, mengembalikan output, tanpa menyentuh dunia luar — dan pencatatan dipindah ke fungsi tersendiri yang namanya eksplisit, misalnya catatTransaksi(int $total): void yang memang menulis log. Pemanggil lalu memutuskan sendiri kapan mencatat. Prinsipnya: jika sebuah fungsi melakukan I/O, ubah, atau menyentuh state global, pastikan namanya — atau pemisahan tanggung jawabnya — memberi tahu.

Komentar dan Kapan Komentar Membantu #

Komentar yang buruk biasanya menarasikan ulang kode — duplikasi yang lapuk begitu kode berubah:

php
<?php

// cek jika poin lebih dari 100
if ($poin > 100) {
    // set flag memenuhi syarat jadi true
    $memenuhiSyarat = true;
}

Dua komentar di atas menambahkan nol informasi: kondisi if ($poin > 100) dan assignment $memenuhiSyarat = true sudah membaca diri mereka sendiri. Komentar semacam ini cepat membusuk — ketika batas berubah menjadi 150, siapa yang ingat memperbarui komentarnya? Kode yang bersih berusaha menjelaskan dirinya lewat nama yang tepat, fungsi kecil, dan struktur yang jelas.

Namun jujur pada kenyataannya: komentar tetap punya tempat yang tak tergantikan, yaitu menjelaskan mengapa — sesuatu yang tidak bisa diungkapkan kode. Komentar yang bernilai menjelaskan alasan di balik keputusan: regex yang terlihat aneh karena menangani edge case tertentu, workaround menunggu perbaikan upstream (lengkap dengan tautan issue), atau pilihan algoritma yang kontraintuitif. PHPDoc pada API publik juga tetap penting karena mendokumentasikan kontrak bagi pengguna yang tidak membaca isi. Aturan cepatnya: komentar yang menjawab “apa” biasanya bau kode; komentar yang menjawab “mengapa” biasanya berharga.

Penanganan Error yang Rapi #

Logika bisnis yang sehat sering tenggelam oleh try/catch yang berulang di setiap pemanggil. Clean code menanganinya dengan dua langkah: kumpulkan validasi dan pelemparan exception di satu tempat, lalu tangani kegagalan di batas sistem — bukan di setiap baris:

php
<?php

function bacaKonfigurasi(string $berkas): array
{
    if (!is_file($berkas)) {
        throw new RuntimeException("Berkas konfigurasi tidak ditemukan: {$berkas}");
    }

    $isiJson = file_get_contents($berkas);
    $konfigurasi = json_decode($isiJson, true);
    if (!is_array($konfigurasi)) {
        throw new RuntimeException("Berkas konfigurasi bukan JSON valid");
    }

    return $konfigurasi;
}

function muatKonfigurasiAman(string $berkas): array
{
    try {
        return bacaKonfigurasi($berkas);
    } catch (RuntimeException $pengecualian) {
        return [];
    }
}

var_dump(muatKonfigurasiAman('tidak-ada.json'));

Output yang Dihasilkan:

text
array(0) {
}

Mari kita lacak jalannya:

  1. bacaKonfigurasi() memvalidasi dua kondisi gagal — berkas hilang dan JSON tidak valid — lalu melempar RuntimeException dengan pesan yang menjelaskan penyebabnya.
  2. muatKonfigurasiAman() adalah pembungkus di batas: menangkap exception dan memutuskan kebijakan untuk kasus ini, yaitu mengembalikan array kosong sebagai nilai default.
  3. Pemanggil seperti var_dump() di baris terakhir tinggal memanggil fungsi tanpa tahu detail penanganan error — array(0) {} menunjukkan kegagalan sudah tertangani.

Dengan pola ini, pesan error berkumpul di satu tempat yang mudah dirawat, dan pemanggil yang butuh kebijakan berbeda bisa memanggil bacaKonfigurasi() langsung dan menangani exception-nya sendiri.

Jumlah Argumen yang Ideal #

Fungsi paling mudah dipahami saat argumennya nol, mudah saat satu atau dua, dan mulai perlu alasan kuat saat tiga. Enam argumen seperti berikut memaksa pemanggil menjaga urutan dan tipe enam nilai sekaligus:

php
<?php

function buatPesanan($pelangganId, $produkId, $jumlah, $alamat, $kodePos, $metodePembayaran)
{
    // proses pesanan
}

Solusi standarnya mengumpulkan data yang selalu berjalan bersama menjadi satu object:

php
<?php

final readonly class DataPesanan
{
    public function __construct(
        public string $pelangganId,
        public string $produkId,
        public int $jumlah,
        public string $alamat,
        public string $metodePembayaran,
    ) {
    }
}

function buatPesanan(DataPesanan $pesanan): string
{
    return "Pesanan dibuat untuk pelanggan {$pesanan->pelangganId}";
}

Dengan object DataPesanan, daftar parameter fungsi menyusut jadi satu, data pesanan dapat dilempar antar lapisan aplikasi sebagai satu kesatuan, dan pemanggil memakai named arguments (new DataPesanan(pelangganId: 'PLG-01', ...)) sehingga tiap nilai terlihat untuk apa. Satu bau tambahan yang layak diwaspadai: parameter boolean penanda perilaku seperti simpan($data, true) — dua perilaku dalam satu fungsi lebih baik dipecah menjadi dua fungsi bernama jelas.

Duplikasi DRY versus WET #

Duplikasi adalah sumber bug yang paling sabar menunggu: aturan berubah, salah satu salinan terbarui, dan dua bagian kode yang seharusnya identik kini berbeda diam-diam. Prinsip DRY (Don’t Repeat Yourself) menyarankan setiap pengetahuan punya satu representasi; kebalikannya, WET (Write Everything Twice), membiarkan salinan tumbuh. Berikut kode WET:

php
<?php

$subtotalKopi = 50000 * 2;
$diskonKopi = $subtotalKopi * 0.1;
$totalKopi = $subtotalKopi - $diskonKopi;

$subtotalTeh = 30000 * 3;
$diskonTeh = $subtotalTeh * 0.1;
$totalTeh = $subtotalTeh - $diskonTeh;

echo "Total kopi: {$totalKopi}, total teh: {$totalTeh}\n";

Output yang Dihasilkan:

text
Total kopi: 90000, total teh: 81000

Versi DRY mengekstrak pengetahuan “cara menghitung total setelah diskon” ke satu fungsi:

php
<?php

function hitungTotalSetelahDiskon(int $hargaSatuan, int $jumlah)
{
    $subtotal = $hargaSatuan * $jumlah;
    $potonganDiskon = $subtotal * 0.1;

    return $subtotal - $potonganDiskon;
}

$totalKopi = hitungTotalSetelahDiskon(50000, 2);
$totalTeh = hitungTotalSetelahDiskon(30000, 3);

echo "Total kopi: {$totalKopi}, total teh: {$totalTeh}\n";

Output yang Dihasilkan:

text
Total kopi: 90000, total teh: 81000

Ketika aturan diskon berubah menjadi 15%, versi WET harus diubah di dua tempat — dan bug lahir jika satu terlewat — sementara versi DRY cukup menyentuh satu baris. Meski begitu, jangan buru-buru mengabstraksi: dua kemiripan pertama boleh dibiarkan, dan ekstraksi paling aman dilakukan saat pola itu muncul untuk ketiga kalinya (rule of three). Abstraksi prematur justru melahirkan fungsi parameter penuh selera yang menyatukan hal yang hanya kebetulan mirip — jebakan ini dibahas tuntas pada chapter Menghindari Overengineering .

Boy Scout Rule #

Prinsip penutup yang merangkai semuanya dinamai dari motto kepanduan: tinggalkan perkemahan lebih bersih daripada saat kamu menemukannya. Dalam kode, setiap kali Anda menyentuh sebuah berkas untuk perbaikan bug atau fitur kecil, tinggalkan sedikit lebih baik: ganti satu nama yang menyesatkan, pecah satu fungsi yang kepanjangan, hapus komentar yang basi. Perbaikan kecil ini tidak perlu menjadi misi besar — cukup melebihi nol.

Daya pikirnya ada pada komposisi: seratus perubahan kecil dalam sebulan mengubah arah kualitas kode tanpa pernah meminta waktu “cleanup besar” yang sulit dinegosiasikan. Kode yang terus memburuk dan kode yang terus membaik dibedakan bukan oleh satu refactoring raksasa, melainkan oleh arah perubahan harian yang konsisten. Boy scout rule menghubungkan prinsip-prinsip chapter ini dengan praktik nyata — dan ketika perbaikan sudah terencana sebagai aktivitas tersendiri dengan langkah aman dan terukur, kita memasuki wilayah chapter Refactoring .

Ringkasan #

Clean code bekerja pada level struktur: fungsi kecil yang berada di satu level abstraksi, nama yang membuat komentar naratif tidak diperlukan, efek samping yang jujur, penanganan error yang dikumpulkan di batas sistem, jumlah argumen yang hemat, dan duplikasi yang diekstraksi setelah polanya mantap. Semua prinsip ini dijaga tetap hidup oleh boy scout rule — sedikit lebih bersih setiap kali menyentuh kode. Dari sini, langkah berikutnya adalah membekali diri dengan teknik refactoring yang aman untuk menjalankan prinsip-prinsip tersebut pada kode nyata yang sudah berjalan.

Referensi #