Formatting Standards — Clean Code

Format kode itu bukan soal estetika — ia mempengaruhi kecepatan membaca dan kualitas diff saat review. Tim yang konsisten formatnya menghabiskan lebih sedikit…

Formatting adalah tata letak kode: indentasi, spasi, baris kosong, tanda kutip, titik koma, panjang baris. Ia gak mengubah apa yang dikerjakan program, tapi menentukan seberapa cepat orang membacanya dan seberapa bersih diff (daftar baris yang berubah) saat review. Bayangin tiga kasir Toko Kaos Raka yang menulis nota dengan tangan: Sari menulis Rp75.000, Budi 75rb, Dewi 75.000,-. Semuanya benar, tapi satu angka yang salah tenggelam di antara perbedaan gaya itu. Begitu toko memakai mesin struk, semua nota formatnya sama, dan mata Raka langsung jatuh ke angka yang beda. Formatter adalah mesin struk itu untuk kode.

🧪 Tebakan cepat: PR #16 berjudul "Rapikan format kasir.js", dan git diff --stat bilang 14 baris berubah. Berapa baris yang mengubah perilaku kasir: nol, satu, atau empat belas? Tebak dulu, jawabannya ada di bagian "Satu baris di antara 14".

PR #16: "cuma format"

Deskripsi PR: "Rapikan format kasir.js, gak ada perubahan logika." Editor penulisnya diatur 4 spasi dan kutip ganda, dan waktu file disimpan, editornya menulis ulang semuanya. Potongan diff-nya (- baris lama, + baris baru):

 function addShipping(amount) {
-  if (amount < FREE_SHIPPING_MIN) return amount + SHIPPING_FEE;
-  return amount;
+    if (amount <= FREE_SHIPPING_MIN) return amount + SHIPPING_FEE;
+    return amount;
 }

 function formatRupiah(amount) {
-  return 'Rp ' + amount.toLocaleString('id-ID');
+    return "Rp " + amount.toLocaleString("id-ID");
 }

Begitu terus dari atas sampai bawah: 1 file changed, 14 insertions(+), 14 deletions(-). Tiap baris di dalam fungsi berubah karena indentasinya, tiap teks karena tanda kutipnya. Reviewer yang melihat 28 baris merah-hijau dengan pola sama biasanya menyimpulkan "cuma format", lalu approve.

Satu baris di antara 14

Format file PR itu dengan aturan tim (caranya di bagian berikut), lalu bandingkan lagi dengan main:

 function addShipping(amount) {
-  if (amount < FREE_SHIPPING_MIN) return amount + SHIPPING_FEE;
+  if (amount <= FREE_SHIPPING_MIN) return amount + SHIPPING_FEE;
   return amount;
 }

Tinggal satu baris: < jadi <=. Itu jawaban tebakannya. Keempat keranjang test tetap OK, karena gak ada yang totalnya pas Rp 200.000. Keranjang E, 5 Topi Raka (5 × 40.000), yang membongkarnya:

OK   Total: Rp 220.500
OK   Total: Rp 245.000
OK   Total: Rp 90.000
OK   Total: Rp 204.000
BEDA Total: Rp 215.000, harusnya Total: Rp 200.000

Belanja pas Rp 200.000 dulu gratis ongkir, sekarang kena Rp 15.000. Disengaja atau salah ketik, ia harus kelihatan saat review, dan perubahan aturan ongkir layak punya PR sendiri dengan test. Format yang gak seragam bukan cuma jelek: ia menyembunyikan perubahan yang nyata.

Prettier: format diputuskan alat

Prettier adalah formatter: ia membaca kode jadi struktur (pohon sintaks), membuang tata letak lamanya, lalu mencetak ulang dengan aturannya sendiri. Ia sengaja opinionated: opsinya sedikit, supaya gak ada yang perlu diperdebatkan. Tim Raka cuma menulis satu keputusan di .prettierrc: { "singleQuote": true }. Sisanya bawaan: indentasi 2 spasi, titik koma, lebar baris 80 karakter (printWidth). Kode berantakan ini (baris let diawali tab):

const cartD = [{ name: "Kaos Polos Hitam", price: 75000, qty: 1 }, { name: "Kaos Sablon Kopi", price: 95000, qty: 1 }, { name: "Topi Raka", price: 40000, qty: 1 }]


function calculateTotal(cart,voucherCode){
	let total=calculateSubtotal(cart)
    total = applyVoucher(total,voucherCode);
  total=addShipping( total )

  return "Total: "+formatRupiah(total)}

Setelah npx prettier --write kasir.js, persis seperti yang dicetak Prettier 3:

const cartD = [
  { name: 'Kaos Polos Hitam', price: 75000, qty: 1 },
  { name: 'Kaos Sablon Kopi', price: 95000, qty: 1 },
  { name: 'Topi Raka', price: 40000, qty: 1 },
];

function calculateTotal(cart, voucherCode) {
  let total = calculateSubtotal(cart);
  total = applyVoucher(total, voucherCode);
  total = addShipping(total);

  return 'Total: ' + formatRupiah(total);
}

Prettier gak bisa mengubah <= balik jadi <: ia cuma mencetak ulang struktur yang sama, arti kode gak ia sentuh. Di CI (Continuous Integration, pemeriksaan otomatis tiap push), npx prettier --check . gagal selama ada file yang belum rapi:

Checking formatting...
[warn] kasir.js
[warn] Code style issues found in the above file. Run Prettier with --write to fix.

ESLint: yang bukan soal tampilan

ESLint adalah linter: ia gak mengurus tata letak, tapi mencari pola yang kemungkinan besar bug. Potongan kasir-lama.js dari lesson sebelumnya, diperiksa dengan js.configs.recommended plus aturan eqeqeq di eslint.config.js:

export function applyVoucher(t, v) {
  if (v == 'HEMAT10') {
    t = t - t * 0.1;
  }
  return t;
}

function hitungDiskonLama(t) {
  return t * 0.15;
}
$ npx eslint kasir-lama.js

/home/raka/toko-raka/kasir-lama.js
  2:9   error  Expected '===' and instead saw '=='           eqeqeq
  8:10  error  'hitungDiskonLama' is defined but never used  no-unused-vars

✖ 2 problems (2 errors, 0 warnings)

.editorconfig: kesepakatan untuk editor

Prettier jalan saat file disimpan atau di-commit. .editorconfig bekerja lebih awal: ia memberi tahu editor cara menulis selagi kamu mengetik, di editor apa pun (JetBrains membacanya bawaan, VS Code lewat ekstensi EditorConfig):

root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

root = true berhenti mencari ke folder di atasnya, [*] berarti semua file. Tombol Tab menulis 2 spasi, spasi di ujung baris dibuang saat disimpan, file diakhiri baris baru, dan akhir baris sama di Windows dan Mac. Prettier ikut membaca indent_style, indent_size, max_line_length dan end_of_line dari sini, jadi "2 spasi" cukup ditulis sekali.

Kesepakatan tim, dijaga otomatis

  1. .editorconfig, .prettierrc dan eslint.config.js di-commit ke repo: aturan format jadi kesepakatan tertulis, bukan kebiasaan tiap orang.
  2. Di project lama, format seluruh repo dalam satu commit tersendiri ("format only, gak ada perubahan logika"). Hash-nya bisa dicatat di .git-blame-ignore-revs supaya git blame melewatinya.
  3. CI menjalankan prettier --check . dan eslint .; banyak tim juga memasang pre-commit hook (misalnya husky dan lint-staged).

Hasilnya, debat kutip tunggal atau ganda di PR berhenti karena alat yang memutuskan, dan diff cuma berisi perubahan yang berarti.

Latihan: formatCode

formatCode(src) adalah formatter mini:

  1. Pecah per baris dengan src.split('\n').
  2. Per baris, buang spasi di ujung dengan .replace(/\s+$/, ''), lalu hitung lebar indentasi di awal (tab 4 kolom, spasi 1 kolom) dan tulis ulang sebagai ' '.repeat(Math.round(kolom / 4)). Baris yang isinya kosong jadi ''.
  3. Rapatkan baris kosong beruntun jadi satu, buang baris kosong di awal dan akhir, lalu gabung dengan join('\n').

Output dua console.log:

  function a() {
    return 1;
  }
function a() {}

function b() {}

Di src1, satu tab jadi 2 spasi, dua tab jadi 4 spasi, dan spasi di ujung { dan 1; hilang. Di src2, tiga baris kosong di antara dua fungsi tinggal satu.

⚠️ Jebakan yang sering ketemu

TL;DR: Formatting adalah tata letak kode; ia gak mengubah perilaku, tapi format yang gak seragam bikin diff penuh noise. PR #16 "cuma format" mengubah 14 baris, dan setelah diformat dengan aturan tim tinggal satu: < jadi <=, yang lolos keempat keranjang tapi bikin belanja pas Rp 200.000 jadi Rp 215.000. Prettier adalah formatter opinionated yang mencetak ulang kode tanpa mengubah artinya (2 spasi, titik koma, printWidth 80, singleQuote dari .prettierrc); prettier --check di CI menolak file yang belum rapi. Baris kosong di antara fase tetap keputusanmu, seperti paragraf. ESLint mencari pola bug (eqeqeq, no-unused-vars), bukan tampilan. .editorconfig mengatur editor saat mengetik. Semua config di-commit sebagai kesepakatan tim, format legacy dikerjakan di satu commit tersendiri, dan debat gaya di PR berhenti. Di latihan, formatCode menyeragamkan indentasi (tab dan 4 spasi jadi 2 spasi), membuang spasi di ujung baris dengan .replace, merapatkan baris kosong, dan mencetak function a() { sampai }, lalu function a() {}, baris kosong, function b() {}.

Yang akan kamu pelajari