Bug pasti muncul di setiap proyek, dan perbedaan antar-programmer bukan jumlah bug melainkan cara menanganinya. Debugging tanpa metode biasanya berubah menjadi “coba ubah sesuatu, jalankan lagi, berharap berhasil” — lambat, melelahkan, dan sering menambal gejala bukan penyebab. Chapter ini menyusun alur kerja sistematis tujuh langkah, membandingkan printf debugging dengan debugger delve, lalu memetakan alat diagnosis untuk setiap jenis bug.
Alur Kerja Tujuh Langkah #
Alur ini memaksa kita memahami bug sebelum menyentuh kodenya. Setiap langkah punya bukti selesai yang jelas sehingga tidak ada langkah yang dilewati diam-diam:
- Reproduksi stabil: temukan cara memunculkan bug secara konsisten, idealnya setiap kali dijalankan. Bug yang hanya muncul sesekali hampir mustahil diverifikasi selesai.
- Isolasi: kecilkan cakupan sampai satu unit test gagal yang mereproduksi bug — fungsi tunggal, input tunggal, assertion tunggal.
- Hipotesis tertulis: rumuskan kalimat “bug terjadi karena X”. Hipotesis yang tidak ditulis cenderung bergeser diam-diam saat pengujian berlangsung.
- Uji hipotesis: kumpulkan bukti dengan printf debugging, debugger, atau
git bisectuntuk menemukan commit pemuncul. - Perbaiki akar masalah: ubah penyebab, bukan gejalanya.
- Tulis test regresi: test dari langkah 2 dijadikan pengaman permanen.
- Refleksi: tanyakan mengapa bug bisa lolos — pertahanan apa yang layak ditambah.
Demonstrasi: printf Debugging #
Berkas debug-demo.go mencontohkan langkah 1 sampai 5 pada satu bug nyata: fungsi penjumlahan yang hasilnya meleset. Berikut isi lengkap berkas tersebut:
package main
import "fmt"
func main() {
nilai := []int{70, 80, 90, 100}
// Langkah 1 (reproduksi): hasil meleset dari dugaan manual 340.
fmt.Println("hasil awal :", totalBermasalah(nilai), "(dugaan 340)")
// Langkah 5 (perbaikan): akar masalah len(nilai)-1 dihapus.
fmt.Println("perbaikan :", total(nilai))
}
// totalBermasalah versi awal: pengulangan berhenti satu elemen terlalu cepat.
// printf debugging sengaja dibiarkan di dalam untuk mengamati jalannya loop.
func totalBermasalah(nilai []int) int {
total := 0
for i := 0; i < len(nilai)-1; i++ {
total += nilai[i]
fmt.Printf("iterasi i=%d nilai=%d total=%d\n", i, nilai[i], total)
}
return total
}
// total versi diperbaiki: batasan pengulangan i < len(nilai).
func total(nilai []int) int {
total := 0
for i := 0; i < len(nilai); i++ {
total += nilai[i]
}
return total
}Jalankan dari direktori chapter:
go run debug-demo.goOutput yang Dihasilkan:
iterasi i=0 nilai=70 total=70
iterasi i=1 nilai=80 total=150
iterasi i=2 nilai=90 total=240
hasil awal : 240 (dugaan 340)
perbaikan : 340Mari kita telaah bagian-bagian penting berkas tersebut:
- Baris 6 (
nilai := []int{70, 80, 90, 100}): data uji dengan jumlah yang mudah dihitung manual — 340 — sehingga penyimpangan langsung terlihat. - Baris 9 (
fmt.Println("hasil awal :", ...)): langkah reproduksi; hasil240terbukti meleset, bug dikonfirmasi nyata. - Baris 19 (
for i := 0; i < len(nilai)-1; i++): hipotesis setelah membaca output: hanya tiga iterasi terjadi untuk empat elemen, jadi batasanlen(nilai)-1mencurigakan. - Baris 21 (
fmt.Printf("iterasi i=%d ...")): printf debugging — cetak kondisi tiap iterasi membuktikan loop berhenti dii=2dan elemen keempat (100) tidak pernah dijumlahkan. - Baris 29 (
for i := 0; i < len(nilai); i++): perbaikan akar — hapus-1; hasil kembali340sesuai dugaan manual.
printf Debugging vs delve #
Dua teknik pada langkah 4 saling melengkapi. printf debugging menambah cetakan sementara ke kode; delve adalah debugger interaktif yang menjalankan program sambil kita memeriksa keadaan.
| Aspek | printf debugging | delve |
|---|---|---|
| Persiapan | Tambah fmt.Printf, jalankan ulang | dlv debug, pasang breakpoint |
| Kecepatan mulai | Sangat cepat, tanpa alat tambahan | Perlu instalasi dan latihan perintah |
| Mengubah kode | Ya, jejak harus dibersihkan | Tidak, program disusupi via binary |
| Inspeksi runtime | Hanya nilai yang sengaja dicetak | Semua variabel, stack, goroutine |
| Jeda eksekusi | Tidak bisa berhenti kondisional | Breakpoint, watchpoint, step per baris |
| Paling pas untuk | Bug alur data sederhana, log produksi | Bug dalam panggilan bersarang, panic misterius |
Aturan praktisnya: mulai dengan printf untuk konfirmasi cepat, naik ke delve ketika pertanyaan berubah menjadi “bagaimana program sampai ke baris ini”. Peralatan delve — breakpoint kondisional sampai inspeksi goroutine — kita bahas tuntas di chapter Delve Debugger .
Alat untuk Tiap Jenis Bug #
Jenis bug menentukan alat yang paling menghemat waktu. Tabel berikut merangkum pemetaannya:
| Jenis bug | Gejala umum | Alat utama | Rujukan chapter |
|---|---|---|---|
| Logika | Hasil salah, kondisi terbalik, off-by-one | printf debugging, delve, test | Panduan ini |
| Data | Nilai korup, isi struct tak sesuai dugaan | fmt.Printf("%#v"), watchpoint delve | Logging untuk Debugging |
| Concurrency | Race, deadlock, nilai hilang tanpa jejak | go test -race, trace, stack dump | Race Detector |
| Performa | Lambat, CPU atau memori membengkak | pprof, testing.B benchmark | CPU Profiling |
| Regresi perilaku | “Dulu jalan, sekarang tidak” | git bisect, test regresi | — |
Regresi, git bisect, dan Rubber Duck #
Langkah 6 mengubah pemahaman menjadi perlindungan permanen: test yang tadinya gagal kini menjadi penjaga agar bug yang sama tidak kembali:
func TestTotal(t *testing.T) {
got := total([]int{70, 80, 90, 100})
if got != 340 {
t.Errorf("total() = %d, ingin 340", got)
}
}Untuk bug “dulu jalan, sekarang tidak”, git bisect membagi dua riwayat commit secara otomatis sampai commit pemuncul ditemukan — kita hanya menandai tiap commit “baik” atau “buruk”. Teknik pelengkap yang mengejutkan efektifnya adalah rubber duck debugging: menjelaskan bug lisan baris demi baris ke objek apa pun sering membuat otak menemukan ketidaksesuaian sebelum kalimat selesai.
Debugging menjaga program yang sudah jalan; kesehatan jangka panjangnya justru ditentukan sebelum bug lahir — saat kode ditinjau ulang oleh rekan. Daftar periksa untuk momen itu kita susun di chapter Checklist Code Review .
Referensi #
- Diagnostics — pusat resmi alat diagnosis: profiling, tracing, debugger, runtime stats.
- Delve — debugger untuk bahasa Go.
- Data races detector — artikel resmi race detector.
- Package testing — fondasi test regresi dan benchmark.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
debug-demo.go
Unduh berkaspackage main
import "fmt"
func main() {
nilai := []int{70, 80, 90, 100}
// Langkah 1 (reproduksi): hasil meleset dari dugaan manual 340.
fmt.Println("hasil awal :", totalBermasalah(nilai), "(dugaan 340)")
// Langkah 5 (perbaikan): akar masalah len(nilai)-1 dihapus.
fmt.Println("perbaikan :", total(nilai))
}
// totalBermasalah versi awal: pengulangan berhenti satu elemen terlalu cepat.
// printf debugging sengaja dibiarkan di dalam untuk mengamati jalannya loop.
func totalBermasalah(nilai []int) int {
total := 0
for i := 0; i < len(nilai)-1; i++ {
total += nilai[i]
fmt.Printf("iterasi i=%d nilai=%d total=%d\n", i, nilai[i], total)
}
return total
}
// total versi diperbaiki: batasan pengulangan i < len(nilai).
func total(nilai []int) int {
total := 0
for i := 0; i < len(nilai); i++ {
total += nilai[i]
}
return total
}