Code review adalah pertahanan kualitas terakhir sebelum kode masuk ke main branch. Reviewer yang baik tidak hanya menemukan bug, tetapi juga membangun budaya teknis tim. Sayangnya banyak code review berubah menjadi perdebatan style atau komentar yang tidak actionable. Pada chapter ini kita akan membahas praktik code review yang efektif: ukuran PR, single concern, self-review, checklist otomatis, dan cara memberi umpan balik yang konstruktif. Kode demo akan menunjukkan checklist otomatis sederhana yang dapat memandu reviewer.

Prinsip Dasar Code Review #

  • PR kecil: penambahan dan penghapusan di bawah ~400 baris; lebih dari itu sulit ditinjau.
  • Single concern: satu PR untuk satu fitur atau satu perbaikan; jangan campur refactor dengan fitur baru.
  • Self-review dulu: penulis PR harus mereview sendiri sebelum mengajukan ke orang lain.
  • Checklist: gunakan checklist konsisten untuk hal-hal yang sering terlewat.
  • Umpan balik konstruktif: jelaskan masalah dan saranannya; jangan hanya mengkritik.

Demo Checklist Otomatis #

Berikut implementasi sederhana checklist code review yang dapat dipakai sebagai starting point.

kotlin
// Demo code review practices: checklist otomatis sederhana.

data class Pr(val url: String, val judul: String, val tambahan: Int, val hapus: Int, val file: Int, val deskripsi: String)

class HasilReview(val pr: Pr, val temuan: List<String>, val lulus: Boolean) {
    fun cetak() {
        println("=== Code Review: ${pr.judul} ===")
        println("URL: ${pr.url}")
        println("Statistik: +${pr.tambahan} -${pr.hapus} (${pr.file} file)")
        println("Deskripsi: ${pr.deskripsi}")
        println()
        if (temuan.isEmpty()) {
            println("Status: APPROVED")
        } else {
            println("Status: REQUEST CHANGES (${temuan.size} temuan)")
            temuan.forEach { println("  - $it") }
        }
    }
}

class CodeReviewChecklist {
    fun review(pr: Pr, isiFile: Map<String, String>): HasilReview {
        val temuan = mutableListOf<String>()

        // Aturan 1: PR kecil (< 400 baris perubahan)
        val totalPerubahan = pr.tambahan + pr.hapus
        if (totalPerubahan > 400) {
            temuan.add("PR terlalu besar ($totalPerubahan baris); pecah menjadi PR lebih kecil")
        }

        // Aturan 2: Single concern (judul tidak mengandung kata "dan" / "&")
        if (Regex("""\b(dan|&)\b""", RegexOption.IGNORE_CASE).containsMatchIn(pr.judul)) {
            temuan.add("Judul mengandung lebih dari satu concern; pisahkan")
        }

        // Aturan 3: Deskripsi memadai (>50 karakter)
        if (pr.deskripsi.length < 50) {
            temuan.add("Deskripsi PR terlalu singkat (${pr.deskripsi.length} karakter)")
        }

        // Aturan 4: Cek konten file: tidak ada println, TODO, FIXME, hardcoded credential
        isiFile.forEach { (nama, isi) ->
            if ("println" in isi) {
                temuan.add("$nama: hindari println; gunakan logger")
            }
            if ("TODO" in isi || "FIXME" in isi) {
                temuan.add("$nama: terdapat TODO/FIXME; selesaikan atau buat issue terpisah")
            }
            if (Regex("""(?i)(password|api_key|secret)\s*=\s*"[^"]+"""").containsMatchIn(isi)) {
                temuan.add("$nama: hardcoded credential terdeteksi")
            }
            // Aturan 5: tidak ada catch Exception kosong
            if (Regex("""catch\s*\([^)]+\)\s*\{\s*}""").containsMatchIn(isi)) {
                temuan.add("$nama: blok catch kosong; setidaknya log error")
            }
        }

        val lulus = temuan.isEmpty()
        return HasilReview(pr, temuan, lulus)
    }
}

fun main() {
    println("=== Demo Code Review Practices ===")

    val checker = CodeReviewChecklist()

    // PR baik: kecil, satu concern, deskripsi jelas
    val prBaik = Pr(
        url = "https://example.com/pr/101",
        judul = "Tambah validasi email pada registrasi",
        tambahan = 25,
        hapus = 3,
        file = 2,
        deskripsi = "Menambahkan regex validasi format email saat pendaftaran pengguna baru."
    )
    val isiBaik = mapOf(
        "Registrasi.kt" to """
            fun validasi(email: String): Boolean {
                return email.matches(Regex("^[^@]+@[^@]+\$"))
            }
        """.trimIndent()
    )
    checker.review(prBaik, isiBaik).cetak()

    println()
    // PR buruk: besar, dua concern, deskripsi pendek
    val prBuruk = Pr(
        url = "https://example.com/pr/102",
        judul = "Refactor auth dan tambah fitur export PDF",
        tambahan = 350,
        hapus = 280,
        file = 18,
        deskripsi = "Refactor dan fitur baru"
    )
    val isiBuruk = mapOf(
        "Auth.kt" to """
            fun login(u: String, p: String) {
                println("login")
                try { /* ... */ } catch (e: Exception) {}
                // TODO: hapus hardcoded
                val password = "rahasia123"
            }
        """.trimIndent()
    )
    checker.review(prBuruk, isiBuruk).cetak()
}

Output yang Dihasilkan:

text
=== Demo Code Review Practices ===
=== Code Review: Tambah validasi email pada registrasi ===
URL: https://example.com/pr/101
Statistik: +25 -3 (2 file)
Deskripsi: Menambahkan regex validasi format email saat pendaftaran pengguna baru.

Status: APPROVED

=== Code Review: Refactor auth dan tambah fitur export PDF ===
URL: https://example.com/pr/102
Statistik: +350 -280 (18 file)
Deskripsi: Refactor dan fitur baru

Status: REQUEST CHANGES (7 temuan)
  - PR terlalu besar (630 baris); pecah menjadi PR lebih kecil
  - Judul mengandung lebih dari satu concern; pisahkan
  - Deskripsi PR terlalu singkat (23 karakter)
  - Auth.kt: hindari println; gunakan logger
  - Auth.kt: terdapat TODO/FIXME; selesaikan atau buat issue terpisah
  - Auth.kt: hardcoded credential terdeteksi
  - Auth.kt: blok catch kosong; setidaknya log error

Self-Review Sebelum Mengajukan #

Sebelum PR dikirim ke reviewer, penulis PR sebaiknya membaca sendiri setiap file yang diubah. Pertanyaan self-review:

  • Apakah setiap perubahan berkaitan dengan judul PR?
  • Apakah ada kode debug (println, commented-out) yang tertinggal?
  • Apakah ada credential atau data sensitif yang terlewat?
  • Apakah saya menjalankan test lokal dan semuanya lulus?
  • Apakah deskripsi PR cukup jelas untuk reviewer yang tidak familiar dengan konteks?

Umpan Balik Konstruktif #

Cara memberi umpan balik menentukan apakah reviewer diterima atau ditolak. Beberapa pedoman:

  • Spesifik: tunjukkan file, baris, dan masalah; jangan komentar “ini salah”.
  • Sarankan solusi: bila memungkinkan, beri contoh kode perbaikan.
  • Pisahkan style dari substansi: style sebaiknya di-handle oleh ktlint otomatis, bukan dibahas di review manual.
  • Tanyakan, jangan memerintah: “Bagaimana kalau kita pakai sealed class di sini?” lebih baik daripada “Ganti jadi sealed class”.
  • Bedakan blocker dari nice-to-have: gunakan label “blocking” dan “nit” agar penulis tahu prioritas.

Checklist Reviewer #

Checklist berikut adalah kerangka umum yang dapat disesuaikan tiap tim:

  1. Kebenaran: apakah kode melakukan apa yang seharusnya?
  2. Keamanan: ada input validation, escape, dan tidak ada credential?
  3. Test: apakah ada test baru untuk fitur baru? Apakah test lama masih lulus?
  4. Performa: ada query N+1, loop dalam loop, atau alokasi objek berlebihan?
  5. Readability: nama jelas, method tidak terlalu panjang, struktur logis?
  6. API design: apakah API publik konsisten dengan pola yang sudah ada?

Referensi Lanjutan #

Penutup #

Code review yang baik membutuhkan waktu dan latihan, tetapi memberi dampak besar pada kualitas dan budaya tim. PR kecil dengan single concern, self-review yang disiplin, dan umpan balik yang konstruktif membuat code review menjadi aktivitas pembelajaran, bukan hambatan. Section debugging dan code quality sekarang sudah lengkap — berikutnya kita akan masuk ke section software design dan architecture, dimulai dari prinsip clean code.