Documentation as code artinya dokumentasi diperlakukan seperti kode: ditulis sebagai teks biasa (Markdown) di repo yang sama, diubah di commit dan pull request yang sama dengan kodenya, di-review bersama, dan kalau bisa dicek oleh mesin. Bayangin petunjuk cara pakai mesin kasir Toko Kaos Raka. Yang ditempel di mesinnya ikut diganti waktu mesinnya diganti. Yang disimpan di laci kantor tetap menjelaskan mesin lama, dan gak ada yang sadar sampai karyawan baru mengikutinya.
🧪 Tebakan cepat: Raka menaikkan batas gratis ongkir jadi Rp 250.000. Kode dan test diubah di commit yang sama, semua cek
OK. README masih bilangcalculateTotal(cartA, 'HEMAT10')menghasilkanTotal: Rp 220.500. Apa yang memberitahumu README-nya basi: error, test yang gagal, atau gak ada? Tebak dulu, jawabannya ada di bagian "Dokumentasi basi gak bikin error".
README kasir
README adalah pintu depan repo: apa ini, cara menjalankannya, aturan pentingnya, dan contoh. Punya kasir Raka, README.md di folder yang sama dengan kasir.js:
# Kasir Toko Kaos Raka
Menghitung total belanja: subtotal, potongan voucher, lalu ongkir.
## Aturan
- Voucher HEMAT10 memotong 10%.
- Ongkir Rp 15.000, gratis kalau total setelah potongan minimal Rp 200.000.
## Contoh
calculateTotal(cartA, 'HEMAT10') // Total: Rp 220.500
calculateTotal(cartC, '') // Total: Rp 90.000
Karena ia file Markdown di repo, perubahan README kelihatan di diff pull request, persis seperti kode. Dokumen di Word, Google Drive atau wiki terpisah gak ikut diff itu, jadi gak ada reviewer yang menagihnya.
Dokumentasi basi gak bikin error
Raka minta gratis ongkir mulai Rp 250.000. Kamu mengubah FREE_SHIPPING_MIN = 250000 dan harapan di test. Kodenya sekarang:
> calculateTotal(cartA, 'HEMAT10')
Total: Rp 235.500
220.500 sekarang di bawah 250.000, jadi kena ongkir. Test lulus, karena test sudah diubah. README masih bilang Total: Rp 220.500 dan minimal Rp 200.000. Gak ada yang memberitahumu. Itu jawaban tebakannya: README cuma teks, gak ada yang menjalankannya. Dokumentasi yang salah lebih buruk daripada gak ada, karena orang percaya dan mengikutinya.
Contoh yang dijalankan
Contoh di README ditulis dengan pola yang bisa dibaca mesin: panggilan, lalu // dan hasil yang diharapkan. Script kecil ini menjalankan tiap contoh persis seperti tertulis:
const carts = { cartA, cartC, cartD };
function checkExamples(doc) {
const pattern = /calculateTotal\((\w+), '(\w*)'\);?\s*\/\/ (.+)/g;
for (const [, cartName, voucherCode, expected] of doc.matchAll(pattern)) {
check(calculateTotal(carts[cartName], voucherCode), expected);
}
}
checkExamples(README); // README: isi README.md sebagai string
BEDA Total: Rp 235.500, harusnya Total: Rp 220.500
OK Total: Rp 90.000
patternmenangkap tiga bagian: nama keranjang, kode voucher, dan teks setelah//.matchAllmengulang untuk tiap contoh;checkdari lesson pertama membandingkannya.- Jalankan bareng test di tiap commit, dan README basi gagal seperti test yang gagal.
Ide yang sama ada di alat sungguhan: cargo test di Rust menjalankan contoh di komentar dokumentasi, dan Python punya modul doctest. Tapi mesin cuma mengecek contohnya. Kalimat "minimal Rp 200.000" tetap diubah manusia, dan reviewer menagihnya di pull request yang sama.
JSDoc untuk calculateTotal
JSDoc adalah komentar /** ... */ tepat di atas fungsi, dengan tag yang diawali @. Editor seperti VS Code menampilkannya saat kursor diarahkan ke nama fungsi:
/**
* Menghitung total belanja: subtotal, potongan voucher, lalu ongkir.
* @param {Array} cart Barang di keranjang, tiap barang punya price dan qty.
* @param {string} voucherCode Kode voucher, '' kalau tanpa voucher.
* @returns {string} Teks total untuk struk.
* @example
* calculateTotal(cartA, 'HEMAT10'); // Total: Rp 220.500
*/
function calculateTotal(cart, voucherCode) { /* ... */ }
@param {tipe} nama keterangan: satu baris per parameter.@returns {tipe}: apa yang dikembalikan.@example: contoh pemakaian, dengan pola yang sama, jadicheckExamplesbisa menjalankannya juga.
Tulis JSDoc untuk fungsi yang dipanggil orang lain. Fungsi kecil di dalam modul yang namanya sudah jelas gak perlu.
ADR: mencatat kenapa
ADR (Architecture Decision Record) adalah satu file Markdown pendek per keputusan penting, di docs/adr/. Formatnya dari Michael Nygard ("Documenting Architecture Decisions", 2011): judul, status, konteks, keputusan, konsekuensi. Keputusan tabel voucher dari lesson Refactoring Techniques:
# ADR-0002: Voucher disimpan di tabel VOUCHERS
Status: accepted
## Context
Tiap voucher baru menambah if di applyVoucher, dan PR #14 (HEMAT20) mengubah fungsi yang sudah dites.
## Decision
Tarif voucher disimpan di tabel VOUCHERS; applyVoucher membaca VOUCHERS[voucherCode] ?? 0.
## Consequences
Voucher persen baru cukup satu baris. Voucher yang bukan persen, misalnya potongan Rp 25.000, gak muat di tabel tarif dan butuh keputusan baru.
Status-nya salah satu dari proposed, accepted, deprecated atau superseded (diganti ADR yang lebih baru). ADR gak diedit setelah diterima; keputusan baru jadi ADR baru yang menggantikannya. Setahun lagi, yang bertanya "kenapa tabel, bukan class?" menemukan jawabannya di repo.
Tiap jenis dokumentasi punya tugas
- Nama menjelaskan apa:
calculateTotal,FREE_SHIPPING_MIN(lesson Penamaan). - Komentar menjelaskan kenapa yang gak kelihatan dari kode, seperti di lesson Code Comments:
// Pakai polling 5 detik karena WebSocket diblokir firewall kantor klienmencegah orang "memperbaikinya" jadi WebSocket. - JSDoc: cara memanggil fungsi publik. README: cara menjalankan dan aturan pentingnya. ADR: kenapa sebuah keputusan besar diambil.
Latihan: parseJsDoc dan validateAdr
parseJsDoc(comment) mengubah teks JSDoc jadi data. Pola /@param\s*\{(\w+)\}\s*(\w+)([^@]*)/g dipakai berulang dengan exec dalam while: grup 1 tipe, grup 2 nama, grup 3 keterangan sampai @ berikutnya. /@returns?\s*\{(\w+)\}/ mengambil tipe kembalian; kalau gak ada @returns, returns tetap null, jangan dikarang. Untuk '/** @param {string} name User name @param {number} age @returns {boolean} */': 2 param, yang pertama name bertipe string, return boolean.
validateAdr(adr) mendorong satu error per field wajib yang kosong (title, status, context, decision, consequences) dan satu lagi kalau status bukan salah satu dari empat status di atas. valid adalah errors.length === 0. ADR lengkap: true dan 0. { title: 'Test', status: 'unknown' } dapat 4 error (tiga field kosong, satu status), jadi false dan errors.length >= 2 bernilai true. Output-nya:
2
name
string
boolean
true
0
false
true
⚠️ Jebakan yang sering ketemu
- Dokumen di luar repo. Wiki atau Drive gak ikut diff pull request, jadi gak ada yang menagihnya saat kode berubah.
- Contoh yang gak pernah dijalankan. README bilang
Rp 220.500, kodenyaRp 235.500, dan gak ada yang gagal. Jalankan contohnya bareng test. - Mengira cek contoh menangkap semuanya. "Minimal Rp 200.000" di kalimat tetap harus diubah manusia; reviewer menagihnya di PR yang sama.
- ADR ditulis berbulan-bulan kemudian. Konteks dan alternatifnya sudah lupa. Tulis saat memutuskan, dan buat ADR baru kalau keputusannya berubah.
- Di latihan, mengarang
returns. Tanpa@returns, hasilnyanull, bukan{ type: undefined }.
TL;DR: Documentation as code: dokumentasi ditulis sebagai Markdown di repo yang sama, diubah di pull request yang sama, di-review bersama kodenya, dan contohnya dijalankan mesin. Dokumentasi basi gak bikin error: setelah batas gratis ongkir jadi Rp 250.000, README yang masih bilang Total: Rp 220.500 cuma ketahuan kalau contohnya dijalankan (checkExamples mencetak BEDA Total: Rp 235.500). README berisi apa, cara menjalankan, aturan dan contoh; JSDoc (@param {tipe} nama, @returns, @example) menjelaskan cara memanggil fungsi publik; ADR mencatat konteks, keputusan dan konsekuensi sebuah keputusan arsitektur, dengan status proposed, accepted, deprecated atau superseded; komentar menjelaskan kenapa. Di latihan: parseJsDoc mengembalikan params { name, type, description } dan returns null kalau gak ada @returns; validateAdr mendorong error untuk tiap field kosong dan status tidak valid, dan output-nya 2, name, string, boolean, true, 0, false, true.