kodingup.camp

Beranda/JavaScript Mastery: Prototype, Class, Waktu, Error & Async

Teknik Debugging Lanjutan

Modul 11 dari 154 menit bacaAdvanced Debugging Techniques

Isi modul

    Kenapa ini penting

    Programmer profesional menghabiskan banyak waktunya untuk mencari tahu kenapa kode tidak bekerja seperti yang diharapkan. Perbedaan pemula dan profesional bukan pada jumlah bug yang mereka buat (semua orang membuat bug), melainkan pada seberapa cepat mereka menemukannya. Pemula menebar console.log("sini") di mana-mana dan menebak-nebak. Profesional membaca pesan error dengan teliti, menghentikan program di titik yang tepat, lalu memeriksa nilai variabel satu per satu.

    Analoginya seperti dokter. Dokter tidak menebak penyakit dari satu gejala, ia memakai alat: stetoskop, termometer, hasil lab. Alat debugging-mu adalah DevTools browser, pernyataan debugger, beragam method console selain log, dan kemampuan membaca stack trace. Modul ini mengajarkan alat-alat itu dan, yang lebih penting, cara berpikir sistematis saat berburu bug.

    Konsep inti

    Membaca pesan error dan stack trace

    Contoh stack trace:

    TypeError: Cannot read properties of undefined (reading 'harga')
        at hitungTotal (keranjang.js:12:28)
        at checkout (keranjang.js:25:17)
        at main (keranjang.js:31:1)
    

    Cara membaca:

    1. Baris pertama: jenis error (TypeError) dan penjelasannya. "Cannot read properties of undefined (reading 'harga')" artinya kamu menulis sesuatu.harga dan sesuatu bernilai undefined.
    2. Baris at paling atas: lokasi persis error terjadi, hitungTotal di file keranjang.js baris 12 kolom 28.
    3. Baris di bawahnya: siapa yang memanggil siapa (dari terdalam ke terluar). main memanggil checkout, checkout memanggil hitungTotal.

    Sering kali bug sebenarnya bukan di baris yang error, melainkan di pemanggil yang mengirim data salah. Telusuri stack ke bawah.

    Method console selain log

    Method Kegunaan
    console.table(arr) Tampilkan array object sebagai tabel
    console.group(label) / groupEnd() Kelompokkan log yang berkaitan (bisa bertingkat)
    console.time(label) / timeEnd(label) Ukur durasi kode
    console.count(label) Hitung berapa kali baris tertentu dijalankan
    console.warn / console.error Log berwarna kuning/merah, mudah difilter
    console.assert(kondisi, pesan) Cetak pesan hanya jika kondisi salah
    console.dir(obj, { depth: null }) Tampilkan object bersarang secara lengkap
    console.trace() Cetak stack trace saat ini tanpa error

    Trik kecil: console.log({ total, diskon }) menampilkan nama variabel bersama nilainya.

    Breakpoint di DevTools

    Breakpoint menghentikan eksekusi di baris tertentu sehingga kamu bisa melihat semua variabel saat itu. Di Chrome/Edge: buka DevTools (F12 atau Ctrl+Shift+I / Cmd+Option+I), tab Sources (Firefox: Debugger), buka file, klik nomor baris.

    Jenis breakpoint yang berguna:

    Saat program berhenti, gunakan tombol kendali:

    Tombol Fungsi
    Resume (F8) Lanjut sampai breakpoint berikutnya
    Step over (F10) Jalankan baris ini, tidak masuk ke function
    Step into (F11) Masuk ke function yang dipanggil
    Step out (Shift+F11) Selesaikan function saat ini, kembali ke pemanggil

    Panel Scope menampilkan semua variabel lokal dan closure, Watch memantau ekspresi pilihanmu, Call Stack menunjukkan rantai pemanggilan.

    Pernyataan debugger

    Menulis debugger; di kode sama dengan memasang breakpoint di baris itu, asal DevTools sedang terbuka. Jika tidak, pernyataan ini diabaikan. Di Node.js, jalankan node --inspect-brk file.js lalu buka chrome://inspect untuk men-debug dengan DevTools yang sama.

    Cara berpikir sistematis

    1. Reproduksi: pastikan bug bisa dimunculkan dengan langkah yang jelas.
    2. Persempit: cari bagian terkecil yang masih menghasilkan bug (bagi dua, matikan sebagian kode).
    3. Hipotesis: "Saya kira total jadi string karena input dari form."
    4. Buktikan: pakai breakpoint/log untuk mengecek hipotesis, bukan menebak sambil mengubah kode.
    5. Perbaiki dan uji ulang, termasuk kasus yang sebelumnya berhasil.

    Contoh kode

    // Data keranjang dengan bug tersembunyi
    const keranjang = [
      { nama: "Kopi Aceh", harga: 85000, qty: 2 },
      { nama: "Teh Hijau", harga: "45000", qty: 1 }, // harga berupa string!
      { nama: "Gula Aren", harga: 30000, qty: 3 },
    ];
    
    // 1. console.table: lihat data sekilas
    console.table(keranjang);
    // → (tabel dengan kolom (index), nama, harga, qty)
    
    // 2. console.assert: periksa asumsi
    keranjang.forEach((item) => {
      console.assert(typeof item.harga === "number", `Harga ${item.nama} bukan angka:`, item.harga);
    });
    // → Assertion failed: Harga Teh Hijau bukan angka: 45000
    
    // 3. console.group untuk log bertingkat
    function hitungTotal(items) {
      console.group("hitungTotal");
      let total = 0;
      for (const item of items) {
        const subtotal = item.harga * item.qty; // "45000" * 1 = 45000 (perkalian mengubah ke angka)
        total += subtotal;
        console.log({ nama: item.nama, subtotal, total });
      }
      console.groupEnd();
      return total;
    }
    console.log("Total:", hitungTotal(keranjang)); // → Total: 305000
    
    // 4. Bug muncul saat memakai + (penjumlahan string!)
    function hitungTotalBug(items) {
      return items.reduce((acc, item) => acc + item.harga, 0);
    }
    console.log(hitungTotalBug(keranjang)); // → 850004500030000 (string, bukan 160000!)
    // Perbaikan: Number(item.harga)
    
    // 5. console.time: ukur performa
    console.time("urutkan 100rb angka");
    const angka = Array.from({ length: 100000 }, () => Math.random());
    angka.sort((a, b) => a - b);
    console.timeEnd("urutkan 100rb angka"); // → urutkan 100rb angka: 25.3ms (bervariasi)
    
    // 6. console.count: berapa kali function dipanggil
    function render() { console.count("render"); }
    render(); render(); render();
    // → render: 1
    // → render: 2
    // → render: 3
    
    // 7. console.dir untuk object bersarang dalam
    const pesanan = { id: 1, pelanggan: { alamat: { kota: { nama: "Medan", kode: "MDN" } } } };
    console.dir(pesanan, { depth: null });
    // → { id: 1, pelanggan: { alamat: { kota: { nama: 'Medan', kode: 'MDN' } } } }
    
    // 8. Membaca stack trace secara program
    function a() { b(); }
    function b() { c(); }
    function c() { null.properti; }
    try {
      a();
    } catch (err) {
      console.log(err.message); // → Cannot read properties of null (reading 'properti')
      console.log(err.stack.split("\n").slice(0, 4).join("\n"));
      // → TypeError: Cannot read properties of null (reading 'properti')
      // →     at c (/path/file.js:63:16)
      // →     at b (/path/file.js:62:16)
      // →     at a (/path/file.js:61:16)
    }
    
    // 9. debugger: berhenti di sini jika DevTools terbuka (diabaikan jika tidak)
    function diskon(harga, persen) {
      debugger; // periksa nilai harga & persen di panel Scope
      return harga - harga * (persen / 100);
    }
    console.log(diskon(200000, 15)); // → 170000
    

    Coba praktik di browser: simpan kode di atas sebagai debug.js, panggil dari file HTML (<script src="debug.js"></script>), buka DevTools tab Sources, lalu pasang conditional breakpoint di baris const subtotal = ... dengan kondisi typeof item.harga !== "number". Program hanya berhenti di item "Teh Hijau". Untuk Node.js: node --inspect-brk debug.js, buka chrome://inspect, klik inspect.

    Kesalahan umum

    1. Hanya membaca baris pertama error. Stack trace di bawahnya menunjukkan jalur ke sumber masalah. Baca dari atas, telusuri pemanggil.
    2. Mengubah kode sambil menebak tanpa membuktikan hipotesis. Hentikan program dengan breakpoint dan lihat nilai sebenarnya.
    3. console.log(obj) lalu objeknya berubah. Di browser, object yang di-log ditampilkan "hidup": saat kamu klik untuk melebarkan, isinya sudah nilai terbaru. Log salinan: console.log(structuredClone(obj)) atau JSON.stringify(obj).
    4. Lupa menghapus debugger dan log berlebihan sebelum kode dirilis. Gunakan linter (misalnya aturan ESLint no-debugger) untuk mendeteksinya.
    5. Mengabaikan peringatan kuning (warning). Banyak bug besar diawali peringatan kecil.

    Latihan

    1. Mudah, Diberikan array 5 siswa { nama, nilai }, tampilkan dengan console.table, lalu hitung rata-rata dan bungkus log-nya dengan console.group("Statistik").
    2. Sedang, Kode const total = ["10", "20", "30"].reduce((a, b) => a + b, 0) menghasilkan "0102030". Jelaskan sebabnya memakai console.log di dalam callback, lalu perbaiki.
    3. Menantang, Tulis function ukur(label, fn) yang menjalankan fn, mengukur durasinya dengan console.time, mencetak error ke console.error jika fn gagal (tanpa menghentikan program), dan mengembalikan hasilnya atau null.
    Lihat solusi
    // 1
    const siswa = [
      { nama: "Ana", nilai: 88 }, { nama: "Bayu", nilai: 75 }, { nama: "Citra", nilai: 92 },
      { nama: "Dodi", nilai: 64 }, { nama: "Eka", nilai: 81 },
    ];
    console.table(siswa);
    console.group("Statistik");
    const rata = siswa.reduce((s, x) => s + x.nilai, 0) / siswa.length;
    console.log("Rata-rata:", rata); // → Rata-rata: 80
    console.log("Tertinggi:", Math.max(...siswa.map((x) => x.nilai))); // → Tertinggi: 92
    console.groupEnd();
    
    // 2
    ["10", "20", "30"].reduce((a, b) => {
      console.log({ a, b, tipeA: typeof a }); // langkah ke-2 memperlihatkan a = '010' (string)
      return a + b;
    }, 0);
    // Penyebab: 0 + "10" = "010" (penggabungan string), dan seterusnya.
    const total = ["10", "20", "30"].reduce((a, b) => a + Number(b), 0);
    console.log(total); // → 60
    
    // 3
    function ukur(label, fn) {
      console.time(label);
      try {
        return fn();
      } catch (err) {
        console.error(`[${label}] gagal:`, err.message);
        return null;
      } finally {
        console.timeEnd(label);
      }
    }
    console.log(ukur("parse ok", () => JSON.parse('{"a":1}').a)); // → parse ok: 0.05ms  lalu  1
    console.log(ukur("parse rusak", () => JSON.parse("{")));        // → [parse rusak] gagal: ...  lalu  null
    

    Ringkasan

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.