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 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:
- Module
Checker: aturan level berkas — di siniLineLengthmembatasi panjang baris ke 100 karakter agar teks tetap terbaca di layar apa pun. - Module
TreeWalker: aturan level sintaks yang bekerja pada pohon sintaks Java.TypeNamemenjaga nama class PascalCase,MethodNamemenjaga method camelCase,UnusedImportsmendeteksi import menganggur,NeedBracesmenolak blokiftanpa 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 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:
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:
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 spasiMari kita bedah aturan-aturan pada linter mini tersebut:
- Aturan 1 (
\\sclass\\s[a-z_]): nama class yang diawali huruf kecil atau garis bawah tertangkap —layanan_pelangganpada baris pertama cuplikan. Checkstyle menamai aturan iniTypeName. - Aturan 2 (
\\(.*\\s[A-Z]\\w*): parameter method yang diawali huruf kapital terdeteksi padaHarga_satuan— padananParameterNamedi Checkstyle. - Aturan 3 (
,\\S): koma yang tidak diikuti spasi (Harga_satuan,int) — padananWhitespaceAfter. - Aturan 4 (
\\S\\*\\S): operator perkalian yang diapit tanpa spasi pada dua baris berbeda — padanan aturan spasi operator. - 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:
java -jar checkstyle-<versi>-all.jar -c checkstyle.xml src/main/javaKeluarannya 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 berkasimport 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);
}
}
}