git bisect untuk Regresi — Debugging

Bug muncul, tapi dulu tidak ada. Di antara commit mana pecahnya? git bisect pakai binary search di history commit — cek commit di tengah, tanyakan "baik atau…

Regression adalah fitur yang dulu jalan, lalu rusak setelah ada perubahan. git bisect mencari commit pertama yang merusaknya dengan cara membelah dua riwayat commit berulang kali, dan tiap belahan dicek pakai tes. Bayangin kamu memasak kuah soto sambil menambah bumbu sepuluh kali, dan tiap habis menambah bumbu kamu menyisihkan sesendok kuah ke gelas kecil. Di awal rasanya pas, di akhir keasinan, dan begitu garamnya kebanyakan, kuahnya gak akan pernah pas lagi. Daripada mencicipi sepuluh gelas berurutan, kamu cicipi gelas tengah dulu: kalau masih pas, masalahnya ada di gelas-gelas sesudahnya.

🧪 Tebakan cepat: ada 10 commit, index 0 sampai 9, dan bug-nya mulai di index 7. findFirstBad di latihan memanggil isBad berapa kali: 4, 7, atau 10? Jawabannya ada di bagian "Menelusuri findFirstBad".

Regression dan sifat monoton

Toko kopi online punya kotak pencarian menu. Di rilis v1.0, mencari kopi menemukan "Kopi Susu" dan "Es Kopi". Sekarang hasilnya kosong. Ini riwayatnya, dari yang paling lama, dengan hasil tes pencarian di tiap commit:

index commit tes cariMenu(menu, 'kopi')
0 rilis v1.0 OK: 2 hasil
1 sampai 6 tambah menu teh, rapikan tampilan menu, tambah harga di kartu menu, perbaiki typo footer, tambah filter kategori, ganti ikon keranjang OK: 2 hasil
7 optimasi pencarian GAGAL: 0 hasil
8 tambah halaman promo GAGAL: 0 hasil
9 perbarui README (HEAD) GAGAL: 0 hasil

Polanya OK OK OK OK OK OK OK GAGAL GAGAL GAGAL. Begitu satu commit rusak, semua commit sesudahnya juga rusak. Sifat ini disebut monoton, dan cuma karena sifat inilah membelah dua boleh dipakai: cek satu commit di tengah, dan separuh riwayat langsung bisa dibuang. Kalau tesnya kadang lulus kadang gagal di commit yang sama (flaky), atau bug-nya sempat hilang lalu muncul lagi, bisect bisa menunjuk commit yang salah.

Bikin tes yang bisa diulang dulu

Sebelum bisect, kamu butuh cara mengecek "rusak atau gak" di commit mana pun, cepat, dan hasilnya selalu sama. Untuk toko kopi tadi:

// test.js: cek pencarian menu
const { cariMenu } = require('./app.js');
const hasil = cariMenu(['Kopi Susu', 'Teh Manis', 'Es Kopi'], 'kopi');
if (hasil.length === 2) {
  console.log('OK: 2 hasil');
  process.exit(0); // 0 = good
}
console.log(`GAGAL: ${hasil.length} hasil, harusnya 2`);
process.exit(1); // 1 sampai 127, selain 125 = bad

Biar Git yang mencari: git bisect run

Perintah-perintahnya sudah kamu kenal dari pelajaran Git Bisect. Untuk regression yang punya tes otomatis, cukup tiga baris:

git bisect start HEAD v1.0     # HEAD rusak, v1.0 masih bagus
git bisect run node test.js    # Git checkout, tes, dan menandai sendiri
git bisect reset               # kembali ke branch semula

Menelusuri findFirstBad (yang dikerjakan latihan)

Di latihan, commit-nya cuma angka index, dan isBad(commit) adalah tesnya. Kamu menulis binary search yang dipakai bisect:

function findFirstBad(commits, isBad) {
  let low = 0;
  let high = commits.length - 1;
  let jawaban = -1; // belum ketemu yang rusak

  while (low <= high) {
    const mid = Math.floor((low + high) / 2);
    if (isBad(commits[mid])) {
      jawaban = mid;  // kandidat: rusak pertama ada di sini atau di kirinya
      high = mid - 1;
    } else {
      low = mid + 1;  // mid bagus, jadi semua di kirinya juga bagus
    }
  }
  return jawaban;
}

function countCalls(commits, isBad) {
  let calls = 0;
  const wrapped = (c) => { calls++; return isBad(c); };
  const firstBad = findFirstBad(commits, wrapped);
  return { firstBad, calls };
}

const commits10 = Array.from({ length: 10 }, (_, i) => i);
const r1 = countCalls(commits10, (c) => c >= 7);
console.log(r1.firstBad);     // 7
console.log(r1.calls <= 5);   // true

Ini jejaknya untuk 10 commit dengan bug mulai di index 7:

Langkah low high mid isBad(mid) Sesudahnya
1 0 9 4 false low = 5
2 5 9 7 true jawaban = 7, high = 6
3 5 6 5 false low = 6
4 6 6 6 false low = 7, loop selesai

Cuma 4 panggilan, dan hasilnya 7. Itu jawaban tebakannya.

Git sendiri butuh tes lebih sedikit dari ini, karena dia sudah tahu dua ujungnya: v1.0 bagus dan HEAD rusak.

💡 Setelah commit-nya ketemu

Lihat perubahannya dengan git show <hash>. Di commit "optimasi pencarian", ternyata toLowerCase() dibuang, jadi 'Kopi Susu'.includes('kopi') bernilai false. Perbaiki, lalu masukkan pengecekan dari test.js ke test suite proyekmu sebagai regression test, supaya CI langsung gagal kalau bug ini balik lagi.

⚠️ Jebakan yang sering ketemu

TL;DR: Regression = fitur yang dulu jalan lalu rusak. Bisect bekerja karena sifat monoton: sekali rusak, commit sesudahnya ikut rusak. Siapkan tes yang cepat dan deterministik (exit 0 = good, 1 sampai 127 selain 125 = bad, 125 = skip), lalu git bisect start HEAD v1.0, git bisect run node test.js, dan git bisect reset. Commit yang gak bisa dites dilewati dengan git bisect skip. Di latihan, findFirstBad adalah binary search: isBad(mid) benar berarti simpan jawaban = mid dan geser ke kiri, salah berarti geser ke kanan; 10 commit dengan bug di 7 cuma butuh 4 panggilan, 1.000 commit cuma 10, gak ada yang rusak hasilnya -1, semua rusak hasilnya 0. Setelah ketemu, perbaiki dan tambahkan regression test.

Yang akan kamu pelajari