Cara Menyelesaikan Bug: Reproduksi, Persempit, Uji Hipotesis
Prosedur debugging dari reproduksi, timeline, hipotesis, git bisect, minimal reproduction, regression test, hingga verifikasi production.
0xNN · · 4 min read
Cara Menyelesaikan Bug: Reproduksi, Persempit, Uji Hipotesis
Debugging bukan menebak baris yang terlihat mencurigakan. Tujuannya adalah mengubah laporan samar menjadi reproduksi yang stabil, mempersempit penyebab, lalu membuktikan perbaikan tanpa menambah regresi.
1. Tulis Gejala Secara Terukur
Hindari deskripsi “halaman kadang error”. Catat:
• hasil yang diharapkan;
• hasil aktual;
• waktu dan environment;
• versi aplikasi atau commit;
• akun/role tanpa menyimpan data sensitif;
• request ID atau trace ID;
• frekuensi;
• langkah reproduksi terkecil.
Bedakan gejala dan kesimpulan. “HTTP 504 setelah 30 detik” adalah observasi. “Database lambat” masih hipotesis.
2. Pastikan Anda Bisa Mereproduksi
Buat kasus gagal yang sekecil mungkin. Kurangi input, dependency, dan langkah sampai satu perubahan kecil membuat bug muncul atau hilang.
Jika bug hanya terjadi di production, bandingkan:
• configuration dan environment variable;
• versi runtime/dependency;
• data shape dan volume;
• timezone/locale;
• network serta proxy;
• concurrency dan permission.
Jangan menyalin data pengguna mentah ke laptop. Gunakan data sintetis atau snapshot yang telah disanitasi sesuai kebijakan.
3. Bangun Timeline
Untuk request lintas service:
12:01:02.100 gateway menerima request
12:01:02.140 service memulai query
12:01:04.142 query timeout
12:01:04.150 service mengembalikan 503
Timestamp, request ID, metric, dan trace membantu membedakan penyebab dari efek. Log yang sangat banyak tanpa korelasi sering kurang berguna daripada beberapa event terstruktur.
Pastikan clock service tersinkronisasi dan jangan log password, token, cookie, atau payload pribadi.
4. Buat Hipotesis yang Bisa Dibantah
Hipotesis yang baik menghasilkan prediksi:
Jika pool database habis, waitingCount naik sebelum latency request meningkat.
Kemudian cari bukti yang dapat membantah. Jika antrean pool tetap nol, hipotesis melemah.
Ubah satu variabel pada satu waktu. Meng-upgrade dependency, menambah timeout, dan mengubah query sekaligus mungkin menghilangkan gejala, tetapi tidak memberi pengetahuan tentang penyebab.
5. Persempit dengan Binary Search
Jika ada rentang commit baik dan buruk:
git bisect start
git bisect bad
git bisect good v1.4.0
Git memilih commit tengah. Jalankan reproduksi lalu tandai:
git bisect good
atau
git bisect bad
Setelah penyebab ditemukan:
git bisect reset
Bisect paling efektif jika test deterministik. Jika hasil flaky, ulangi test atau buat script yang mengembalikan exit code konsisten.
6. Gunakan Minimal Reproduction
Hapus bagian yang tidak diperlukan:
• ganti API eksternal dengan response tetap;
• kurangi dataset;
• hapus middleware satu per satu;
• jalankan satu query tanpa UI;
• buat satu file dengan dependency minimum.
Minimal reproduction sering menunjukkan bahwa bug berasal dari asumsi boundary—encoding, null, timezone, retry, atau urutan event—bukan komponen yang pertama dicurigai.
7. Perbaiki Penyebab, Bukan Gejala
Menaikkan timeout dapat mengurangi error tetapi juga menambah antrean. Menambahkan retry dapat menggandakan write jika operasi tidak idempotent. Menangkap semua exception dapat menyembunyikan kerusakan.
Perbaikan harus menjelaskan:
• invariant yang dilanggar;
• mengapa bug terjadi;
• mengapa perubahan mencegahnya;
• risiko perubahan;
• cara rollback.
Jika root cause belum diketahui tetapi mitigasi darurat diperlukan, dokumentasikan sebagai mitigasi sementara dan buat pekerjaan lanjutan.
8. Tambahkan Regression Test
Test sebaiknya gagal pada versi lama dan lulus setelah fix:
it('does not charge twice when webhook is retried', async () => {
await handleWebhook(event);
await handleWebhook(event);
expect(await countCharges(event.id)).toBe(1);
});
Test ini lebih bernilai daripada test yang hanya menyalin implementasi. Gunakan input yang merepresentasikan kondisi pemicu.
Bedakan Flaky Test dari Bug Deterministik
Test flaky kadang lulus tanpa perubahan kode. Jangan menekan tombol retry sampai hijau lalu menganggap masalah selesai. Catat seed, urutan test, load, jumlah worker, timezone, dan resource yang dipakai.
Jalankan kasus berkali-kali:
for i in $(seq 1 100); do
npm test -- payment-retry.test.js || break
done
Jika hanya gagal paralel, cari shared state, port yang sama, database yang tidak di-reset, race condition, atau ketergantungan waktu. Fake timer dapat membantu, tetapi jangan memalsukan komponen yang justru sedang diuji.
Debugging Performa Membutuhkan Baseline
“Terasa lambat” perlu diubah menjadi metric. Catat p50, p95, p99, throughput, CPU, memory, query time, dan ukuran input pada versi baik serta buruk.
Pisahkan waktu:
DNS → TCP → TLS → antrean → aplikasi → database → response body
Profiler menunjukkan tempat waktu dihabiskan; ia tidak otomatis menjelaskan mengapa. Korelasikan flame graph, trace, metric, dan perubahan terbaru.
Hindari mengoptimalkan berdasarkan satu request lokal dengan cache hangat. Gunakan beban representatif dan ulangi pengukuran.
Jangan Debug dengan Mengubah Production Sembarangan
Menambahkan log mentah atau menjalankan query berat dapat memperparah insiden. Sebelum tindakan:
• tentukan risiko dan rollback;
• gunakan read-only bila cukup;
• batasi waktu dan scope;
• hindari data sensitif;
• catat perubahan manual;
• komunikasikan kepada operator terkait.
Untuk masalah kritis, stabilkan layanan lebih dulu melalui rollback, traffic reduction, atau feature flag. Root-cause analysis dapat dilanjutkan setelah dampak terkendali.
9. Verifikasi di Lingkungan Nyata
Setelah deploy:
• lakukan smoke test;
• pantau error rate dan latency;
• periksa metric yang terkait hipotesis;
• pastikan versi yang aktif benar;
• bandingkan sebelum dan sesudah;
• siapkan rollback.
“Tidak ada laporan baru” belum tentu bukti fix jika traffic pemicu belum terjadi.
10. Tulis Catatan Singkat
Catat dampak, timeline, root cause, detection gap, fix, dan pencegahan. Hindari menyalahkan individu. Bug biasanya lolos karena sistem tidak memiliki validation, test, review, atau observability yang tepat.
Debugging yang baik menghasilkan dua hal: bug selesai dan sistem lebih mudah didiagnosis saat masalah berikutnya muncul.
Referensi
• Git: git bisect
• Google SRE Book: Monitoring Distributed Systems
• Node.js: Diagnostics
• Chrome DevTools: Performance