Ketika dua puluh orang menyunting satu basis kode, dua puluh kebiasaan format ikut masuk: tab di sini, spasi di sana, indentasi lompat dua-tiga tingkat di berkas warisan. Perbedaan itu terlihat sepele sampai kita menyadari harganya di git diff — perubahan satu baris logika tenggelam di antara ratusan baris perubahan spasi, dan git blame menunjuk komit formatisasi alih-alih komit logika yang kita cari. Formatter menyelesaikan masalah ini dengan satu keputusan sederhana: bentuk kode dihasilkan mesin, bukan diperdebatkan manusia. Pada Chapter ini kita akan melihat cara kerja formatter, berkas .editorconfig yang menjaga pengaturan lintas editor, dua standar populer di dunia Java, dan normalisasi sederhana yang bisa ditulis tangan.
Format sebagai Keputusan Otomatis #
Prinsip pertama yang mengubah budaya tim: format adalah keluaran mesin, bukan bahan diskusi review. Formatter diberi satu konfigurasi, dijalankan pada seluruh berkas (atau hanya berkas yang berubah), dan hasilnya final. Reviewer dilarang berkomentar soal gaya karena gaya sudah dijaga sistem — komentar review dialihkan ke arsitektur, penamaan, dan kebenaran.
Dua manfaat mengikuti secara otomatis. Pertama, diff bersih: karena format tidak berubah tanpa alasan, setiap baris di perbandingan adalah perubahan makna sungguhan. Kedua, blame akurat: riwayat per baris menunjuk perubahan logika terakhir, bukan sesi formatisasi massal — keduanya mempercepat investigasi bug bertahun-tahun ke depan.
.editorconfig untuk Semua Editor #
Sebelum bicara formatter penuh, ada berkas kecil yang diakui hampir semua editor dan IDE: .editorconfig di akar repositori. Berkas ini menjaga pengaturan dasar — jenis indentasi, akhir baris, penyandian — tanpa perlu pengaturan manual per anggota:
; .editorconfig — diletakkan di akar repositori
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.java]
indent_style = space
indent_size = 4
[*.{json,yml,yaml}]
indent_size = 2Makna kuncinya: root = true menandai berkas ini sebagai acuan tertinggi; end_of_line = lf menyeragamkan akhir baris lintas sistem operasi; insert_final_newline = true memastikan setiap berkas ditutup baris kosong — kebiasaan POSIX yang menyelamatkan banyak alat baris perintah; trim_trailing_whitespace membuang spasi menganggur di ujung baris. Bagian [*.java] dan [*.{json,yml,yaml}] menunjukkan pengaturan dapat berbeda per jenis berkas dalam satu repositori.
Yang perlu ditegaskan: .editorconfig menjaga pengaturan editor, bukan memformat kode. Ia memastikan tombol Tab menghasilkan empat spasi pada berkas Java, tetapi tidak menyusun ulang indentasi yang sudah salah. Untuk penyusunan ulang itulah kita naik ke formatter.
Dua Standar Populer #
Dunia Java memiliki dua pembawa acara formatter. google-java-format memakai pedoman gaya Google: dua spasi indent, kurung kurawal bergaya K&R, lebar baris 100, dan pengurutan import yang deterministik. Filosofinya tegas: hampir tanpa opsi — bentuk keluaran ditetapkan penuh agar tidak ada ruang perdebatan, dan hasilnya stabil di seluruh pengguna. Palantir Java Format mempertahankan gaya klasik Java empat spasi dengan opsi sedikit lebih longgar, lahir dari tim yang mengingat kompatibilitas dengan basis kode lawas.
Keduanya bekerja pada pohon sintaks, bukan teks, sehingga pemformatan tidak merusak makna kode — string dan komentar tetap utuh. Pemakaiannya dari terminal memakai bentuk java -jar formatter.jar --replace NamaBerkas.java, atau lewat plugin resmi untuk IntelliJ IDEA, VS Code, dan build Maven/Gradle.
Normalisasi Sederhana Buatan Sendiri #
Bagaimana sebenarnya formatter bekerja? Untuk percaya bahwa pemformatan aman, mari kita tulis versi mini yang hanya menormalkan indentasi berbasis kedalaman kurung kurawal:
public class NormalisasiKode {
public static void main(String[] args) {
// Cuplikan dengan indentasi campuran: 2 spasi, 6 spasi, 8 spasi
String kotor = "class Demo {\n"
+ " int total;\n"
+ "\n"
+ " void hitung(int a, int b) {\n"
+ " total = a + b;\n"
+ " }\n"
+ "}\n";
System.out.println("Sebelum (indentasi campuran):");
System.out.print(kotor);
System.out.println("(akhir cuplikan)");
String bersih = normalisasi(kotor);
System.out.println("Sesudah (indentasi 4 spasi):");
System.out.print(bersih);
System.out.println("(akhir cuplikan)");
}
static String normalisasi(String kode) {
String[] baris = kode.split("\n", -1);
StringBuilder hasil = new StringBuilder();
int kedalaman = 0;
boolean kosongSebelumnya = false;
for (String mentah : baris) {
String b = mentah.trim();
// Baris kosong berurutan diringkas menjadi satu
if (b.isEmpty()) {
if (!kosongSebelumnya && hasil.length() > 0) {
hasil.append('\n');
kosongSebelumnya = true;
}
continue;
}
kosongSebelumnya = false;
// Kurung tutup menutup satu tingkat sebelum baris dicetak
if (b.startsWith("}")) {
kedalaman = Math.max(0, kedalaman - 1);
}
hasil.append(" ".repeat(kedalaman)).append(b).append('\n');
// Hitung perubahan kedalaman dari kurung pada baris ini
int buka = b.length() - b.replace("{", "").length();
int tutup = b.length() - b.replace("}", "").length();
kedalaman += buka - tutup;
}
return hasil.toString().stripTrailing() + "\n";
}
}Output yang Dihasilkan:
Sebelum (indentasi campuran):
class Demo {
int total;
void hitung(int a, int b) {
total = a + b;
}
}
(akhir cuplikan)
Sesudah (indentasi 4 spasi):
class Demo {
int total;
void hitung(int a, int b) {
total = a + b;
}
}
(akhir cuplikan)Mari kita bedah algoritma normalisasi tersebut:
mentah.trim(): spasi awal dan akhir setiap baris dibuang — dari indentasi lama yang salah tidak tersisa apa pun.- Pengurangan kedalaman sebelum cetak (
b.startsWith("}")): baris penutup}dicetak pada kedalaman blok yang ditutupnya, bukan blok yang baru saja dibuka. Karena itu baris}pada keluaran sejajar denganvoid hitung. " ".repeat(kedalaman): indentasi baru dibangun murni dari kedalaman bersarang — empat spasi per tingkat — sehingga hasilnya konsisten apa pun format asalnya.- Perhitungan
buka - tutup: jumlah kurung{dan}per baris memutakhirkan kedalaman untuk baris berikutnya. Method satu baris pun dihitung benar. - Baris kosong diringkas: dua baris kosong berurutan menjadi satu;
stripTrailing()menjamin tepat satu baris baru di akhir — perilaku yang sama denganinsert_final_newlinepada.editorconfig.
Batas normalisasi mini ini juga instruktif: ia tidak mengerti string yang mengandung kurung, komentar multi-baris, atau lambda bersarang — situasi yang hanya aman ditangani lewat parser sungguhan seperti yang dipakai google-java-format. Itulah bedanya manipulasi teks dengan pemformatan berbasis AST.
Dengan format dijaga mesin dan konfigurasi berbagi lewat .editorconfig, tim bergerak lebih ringan: review membahas makna, diff membaca cepat, dan blame bisa dipercaya. Namun struktur kode yang rapi secara format belum tentu rapi secara desain — method raksasa tetap raksasa walau indentasinya sempurna. Chapter berikutnya membahas refactoring: teknik mengubah struktur internal tanpa mengubah perilaku.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
NormalisasiKode.java
Unduh berkaspublic class NormalisasiKode {
public static void main(String[] args) {
// Cuplikan dengan indentasi campuran: 2 spasi, 6 spasi, 8 spasi
String kotor = "class Demo {\n"
+ " int total;\n"
+ "\n"
+ " void hitung(int a, int b) {\n"
+ " total = a + b;\n"
+ " }\n"
+ "}\n";
System.out.println("Sebelum (indentasi campuran):");
System.out.print(kotor);
System.out.println("(akhir cuplikan)");
String bersih = normalisasi(kotor);
System.out.println("Sesudah (indentasi 4 spasi):");
System.out.print(bersih);
System.out.println("(akhir cuplikan)");
}
static String normalisasi(String kode) {
String[] baris = kode.split("\n", -1);
StringBuilder hasil = new StringBuilder();
int kedalaman = 0;
boolean kosongSebelumnya = false;
for (String mentah : baris) {
String b = mentah.trim();
// Baris kosong berurutan diringkas menjadi satu
if (b.isEmpty()) {
if (!kosongSebelumnya && hasil.length() > 0) {
hasil.append('\n');
kosongSebelumnya = true;
}
continue;
}
kosongSebelumnya = false;
// Kurung tutup menutup satu tingkat sebelum baris dicetak
if (b.startsWith("}")) {
kedalaman = Math.max(0, kedalaman - 1);
}
hasil.append(" ".repeat(kedalaman)).append(b).append('\n');
// Hitung perubahan kedalaman dari kurung pada baris ini
int buka = b.length() - b.replace("{", "").length();
int tutup = b.length() - b.replace("}", "").length();
kedalaman += buka - tutup;
}
return hasil.toString().stripTrailing() + "\n";
}
}