Diskusi paling panjang dalam code review sering bukan tentang logika, melainkan selera: tab atau spasi, kurung kurawal di baris baru atau di akhir baris, nama class boleh mengandung garis bawah atau tidak. Setiap menit yang habis untuk debat selera adalah menit yang tidak dipakai memeriksa kebenaran kode. Checkstyle menyelesaikan konflik selera ini secara teknis: seluruh aturan gaya dinyatakan dalam satu berkas konfigurasi, diperiksa otomatis oleh mesin, dan hasilnya seragam bagi semua anggota tim. Pada Chapter ini kita akan membaca konfigurasi checkstyle.xml yang lazim, mengatur pengecualian dengan suppressions, dan membangun linter mini untuk melihat bahwa pemeriksaan gaya sebenarnya sederhana.

Gaya Konsisten Membebaskan Review #

Manfaat gaya yang seragam bukan keindahan semata. Kode yang formatnya konsisten lebih cepat dibaca karena mata tidak perlu beradaptasi di setiap berkas; pola yang dikenal (kurung kurawal, indentasi, penamaan) membebaskan beban kognitif untuk hal yang lebih penting. Reviewer yang tidak lagi berkomentar soal spasi dapat mencurahkan perhatian pada kondisi batas, penanganan error, dan kebenaran algoritma.

Satu poin penting untuk disepakati tim: isi aturan tidak se penting keberadaannya. Memilih indentasi dua atau empat spasi tidak mengubah kualitas program; yang berdampak adalah semua orang menulis dengan pilihan yang sama. Karena itu diskusi konvensi cukup dilakukan satu kali, dibekukan dalam konfigurasi, lalu ditegakkan otomatis.

Konfigurasi Aturan Umum #

Checkstyle membaca berkas XML yang menyusun aturan sebagai module berjenjang. Bentuk konfigurasi yang umum dipakai tim Java tampak seperti berikut:

xml
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
    "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
    "https://checkstyle.sourceforge.io/dtds/configuration_1_3.dtd">
<module name="Checker">
  <property name="charset" value="UTF-8"/>
  <property name="severity" value="warning"/>

  <module name="LineLength">
    <property name="max" value="100"/>
  </module>

  <module name="TreeWalker">
    <module name="TypeName"/>          <!-- class/interface: PascalCase -->
    <module name="MethodName"/>        <!-- method: camelCase -->
    <module name="ParameterName"/>
    <module name="LocalVariableName"/>
    <module name="UnusedImports"/>
    <module name="WhitespaceAfter"/>
    <module name="NeedBraces"/>
  </module>
</module>

Struktur konfigurasi tersebut terdiri dari dua tingkat:

  1. Module Checker: aturan level berkas — di sini LineLength membatasi panjang baris ke 100 karakter agar teks tetap terbaca di layar apa pun.
  2. Module TreeWalker: aturan level sintaks yang bekerja pada pohon sintaks Java. TypeName menjaga nama class PascalCase, MethodName menjaga method camelCase, UnusedImports mendeteksi import menganggur, NeedBraces menolak blok if tanpa kurung kurawal — sumber bug klasik saat baris baru disisipkan tanpa sengaja.

Setiap aturan memiliki properti yang dapat disetel (misalnya max pada LineLength), dan nilai severity menentukan apakah pelanggaran dihitung sebagai warning atau error yang menggagalkan build.

Suppressions untuk Pengecualian #

Kode warisan hampir selalu melanggar sebagian aturan, dan memperbaiki semuanya sekaligus sering tidak realistis. Checkstyle menyediakan berkas suppressions agar pengecualian bersifat eksplisit dan terlihat:

xml
<?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
    "-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
    "https://checkstyle.sourceforge.io/dtds/suppressions_1_2.dtd">
<suppressions>
  <!-- Direktori kode warisan diberi kelonggaran sementara -->
  <suppress files="[\\/]legacy[\\/]" checks="LineLength"/>

  <!-- Berkas data yang memang memuat identifier panjang -->
  <suppress files="KodeData.java" checks="LineLength"/>

  <!-- Pengecualian menyeluruh untuk satu aturan: hindari kecuali terpaksa -->
  <suppress files=".*" checks="MagicNumber"/>
</suppressions>

Berkas tersebut dirujuk dari konfigurasi utama dengan <module name="SuppressionFilter"><property name="file" value="suppressions.xml"/></module>. Disiplin pemakaiannya penting: setiap baris suppress sebaiknya disertai alasan dan tanggal tinjau, sehingga pengecualian tidak berubah menjadi kebijakan permanen yang tak terbaca.

Linter Mini Buatan Sendiri #

Mesin di balik aturan sederhana sesungguhnya hanya pencocokan pola per baris. Program berikut memeriksa cuplikan kode dengan empat aturan gaya — penamaan class, huruf awal parameter, spasi setelah koma, dan spasi sekitar operator — lalu melaporkan pelanggarannya:

java LinterGaya.java
import java.util.ArrayList;
import java.util.List;

public class LinterGaya {

    // Cuplikan dengan beberapa pelanggaran gaya; beku agar deterministik
    static final String CUPLIKAN = """
            public class layanan_pelanggan {
                private int hitungTotal(int Harga_satuan,int Jumlah) {
                    int hasil = Harga_satuan*Jumlah;
                    return hasil;
                }
            }
            """;

    public static void main(String[] args) {
        String[] baris = CUPLIKAN.split("\n", -1);
        List<String> temuan = new ArrayList<>();

        for (int n = 0; n < baris.length; n++) {
            String b = baris[n];

            // Aturan 1: nama class harus PascalCase
            if (b.matches(".*\\sclass\\s[a-z_].*")) {
                temuan.add("baris " + (n + 1) + ": nama class harus PascalCase");
            }

            // Aturan 2: parameter method diawali huruf kecil
            if (b.matches(".*\\(.*\\s[A-Z]\\w*.*\\).*")) {
                temuan.add("baris " + (n + 1) + ": parameter diawali huruf kapital");
            }

            // Aturan 3: koma diikuti satu spasi
            if (b.matches(".*,\\S.*")) {
                temuan.add("baris " + (n + 1) + ": koma tanpa spasi setelahnya");
            }

            // Aturan 4: operator * diberi spasi di kedua sisi
            if (b.matches(".*\\S\\*\\S.*")) {
                temuan.add("baris " + (n + 1) + ": operator * tanpa spasi");
            }
        }

        System.out.println("Jumlah temuan: " + temuan.size());
        for (String t : temuan) {
            System.out.println("- " + t);
        }
    }
}

Output yang Dihasilkan:

text
Jumlah temuan: 4
- baris 1: nama class harus PascalCase
- baris 2: parameter diawali huruf kapital
- baris 2: koma tanpa spasi setelahnya
- baris 3: operator * tanpa spasi

Mari kita bedah aturan-aturan pada linter mini tersebut:

  1. Aturan 1 (\\sclass\\s[a-z_]): nama class yang diawali huruf kecil atau garis bawah tertangkap — layanan_pelanggan pada baris pertama cuplikan. Checkstyle menamai aturan ini TypeName.
  2. Aturan 2 (\\(.*\\s[A-Z]\\w*): parameter method yang diawali huruf kapital terdeteksi pada Harga_satuan — padanan ParameterName di Checkstyle.
  3. Aturan 3 (,\\S): koma yang tidak diikuti spasi (Harga_satuan,int) — padanan WhitespaceAfter.
  4. Aturan 4 (\\S\\*\\S): operator perkalian yang diapit tanpa spasi pada dua baris berbeda — padanan aturan spasi operator.
  5. Perhatikan keluarannya: empat temuan terkonsentrasi pada baris yang memang melanggar; baris return hasil; yang bersih tidak ikut ditandai — linter yang baik tidak menandai kode yang sudah sesuai aturan.

Perbandingan dengan Checkstyle sungguhan terletak pada kedalaman: Checkstyle memakai parser Java sehingga mengerti konteks (nama class versus pemanggilan method, scope variabel), sedangkan linter mini kita hanya melihat teks per baris. Untuk aturan format murni, keduanya cukup setara.

Menjalankan Checkstyle #

Checkstyle tersedia sebagai berkas JAR mandiri maupun plugin build. Dari terminal, bentuk pemanggilannya:

bash
java -jar checkstyle-<versi>-all.jar -c checkstyle.xml src/main/java

Keluarannya berupa daftar temuan dengan pola [ERROR] Berkas.java:12:5: pesan aturan plus ringkasan jumlah pelanggaran di akhir. Dalam pipeline CI, exit code bukan nol saat ada temuan ber-severity error — mekanisme gate yang sama dengan yang kita bahas pada Chapter static analysis.

Checkstyle menjaga lapisan gaya — temuan yang menjengkelkan namun tidak membahayakan runtime. Alat berikutnya bekerja pada lapisan yang lebih dalam: SpotBugs membedah bytecode untuk menemukan pola bug sungguhan seperti perbandingan string dengan ==. Chapter berikutnya membahas alat tersebut beserta kategori temuannya.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

LinterGaya.java

Unduh berkas
java
import java.util.ArrayList;
import java.util.List;

public class LinterGaya {

    // Cuplikan dengan beberapa pelanggaran gaya; beku agar deterministik
    static final String CUPLIKAN = """
            public class layanan_pelanggan {
                private int hitungTotal(int Harga_satuan,int Jumlah) {
                    int hasil = Harga_satuan*Jumlah;
                    return hasil;
                }
            }
            """;

    public static void main(String[] args) {
        String[] baris = CUPLIKAN.split("\n", -1);
        List<String> temuan = new ArrayList<>();

        for (int n = 0; n < baris.length; n++) {
            String b = baris[n];

            // Aturan 1: nama class harus PascalCase
            if (b.matches(".*\\sclass\\s[a-z_].*")) {
                temuan.add("baris " + (n + 1) + ": nama class harus PascalCase");
            }

            // Aturan 2: parameter method diawali huruf kecil
            if (b.matches(".*\\(.*\\s[A-Z]\\w*.*\\).*")) {
                temuan.add("baris " + (n + 1) + ": parameter diawali huruf kapital");
            }

            // Aturan 3: koma diikuti satu spasi
            if (b.matches(".*,\\S.*")) {
                temuan.add("baris " + (n + 1) + ": koma tanpa spasi setelahnya");
            }

            // Aturan 4: operator * diberi spasi di kedua sisi
            if (b.matches(".*\\S\\*\\S.*")) {
                temuan.add("baris " + (n + 1) + ": operator * tanpa spasi");
            }
        }

        System.out.println("Jumlah temuan: " + temuan.size());
        for (String t : temuan) {
            System.out.println("- " + t);
        }
    }
}