Teknik debugging yang digunakan di browser
(alan.norbauer.com)- Jika Anda bisa menggunakan developer tools browser pada tingkat menengah ke atas, conditional breakpoint dapat diperluas dari sekadar kondisi berhenti menjadi alat untuk logging, tracing, mengubah state, dan mengukur waktu
- Jika Console API seperti
console.log,console.count,console.trace, danconsole.timedimasukkan ke breakpoint atau Watch Pane, Anda dapat merekam alur dan state tanpa menghentikan eksekusi - Dengan menggabungkan jumlah argumen fungsi, waktu, nilai CSS terhitung, jumlah eksekusi, sampling acak, dan boolean global, Anda bisa mengontrol momen berhenti dengan jauh lebih presisi
monitor,debug,copy,$0,getEventListeners, danmonitorEventsdi Chrome membantu menangani pemanggilan fungsi, DOM, event, dan pekerjaan clipboard dengan cepat dari konsol- Pada layar yang DOM-nya terus berubah karena JS atau event pengguna, pause JS,
setTimeout(debugger), snapshot DOM, dan pemantauan fokus sangat berguna khususnya untuk debugging HTML/CSS
Memperluas conditional breakpoint menjadi alat debugging
-
Conditional breakpoint dapat berisi bukan hanya kondisi true/false, tetapi juga ekspresi JavaScript yang memiliki efek samping
-
Dengan memanfaatkannya, breakpoint biasa bisa dipakai seperti alat logging, tracing, perubahan state, dan pengukuran waktu
-
Logpoint dan tracepoint
- Jika Anda memanggil
console.logdi conditional breakpoint, itu akan bertindak seperti logpoint yang meninggalkan nilai di konsol tanpa menghentikan eksekusi - Dengan
console.count, Anda juga bisa menghitung berapa kali baris tersebut dieksekusi - Per pembaruan Mei 2020, browser utama mendukung logpoint/tracepoint secara langsung
- Jika Anda memanggil
-
Menggunakan Console API di Watch Pane
- Anda juga bisa memasukkan ekspresi seperti
console.logke Watch Pane - Setiap kali aplikasi berhenti di debugger, Anda bisa mengambil snapshot localStorage dengan
console.table(localStorage) - Jika breakpoint perubahan DOM dipakai bersama ekspresi Watch, Anda bisa menyimpan snapshot setelah perubahan subtree DOM ke dalam array
(window.doms = window.doms || []).push(document.documentElement.outerHTML)- Breakpoint perubahan DOM tidak bisa dibuat agar tidak menghentikan eksekusi
- Anda juga bisa memasukkan ekspresi seperti
-
Melacak call stack
- Saat ada fungsi untuk menampilkan loading spinner dan fungsi untuk menyembunyikannya, dan Anda mencari masalah ketika hanya pemanggilan tampil yang tersisa, Anda bisa menaruh
console.tracedi conditional breakpoint - Dengan mengeklik pemanggil pada stack trace terakhir dari fungsi tampil, Anda bisa pindah ke lokasi pemanggilan tersebut
- Saat ada fungsi untuk menampilkan loading spinner dan fungsi untuk menyembunyikannya, dan Anda mencari masalah ketika hanya pemanggilan tampil yang tersisa, Anda bisa menaruh
-
Mengubah perilaku program saat berjalan
- Dengan menimpa argumen fungsi di ekspresi conditional breakpoint, Anda bisa langsung mengubah perilaku program di dalam browser
- Misalnya, Anda bisa mengubah nilai
idpada fungsigetPerson - Ekspresi seperti
id=1akan dievaluasi sebagai true sehingga debugger berhenti; jika tidak ingin berhenti, Anda bisa menambahkan, false
-
Pengukuran performa cepat
- Karena waktu evaluasi conditional breakpoint ikut tercampur, ini tidak cocok untuk profiling performa yang presisi
- Saat Anda butuh pengukuran cepat, Anda bisa menaruh
console.time('label')pada kondisi di titik awal danconsole.timeEnd('label')pada kondisi di titik akhir untuk mencatat waktu eksekusi ke konsol
Membuat kondisi breakpoint lebih presisi
-
Memanfaatkan jumlah argumen fungsi
- Jika Anda ingin berhenti hanya saat fungsi saat ini dipanggil dengan 3 argumen, Anda bisa memakai kondisi berikut
arguments.callee.length === 3- Ini berguna pada fungsi overload yang memiliki argumen opsional
- Jika Anda ingin berhenti hanya ketika jumlah argumen yang didefinisikan berbeda dari jumlah argumen yang benar-benar dikirim, gunakan kondisi berikut
(arguments.callee.length) != arguments.length- Ini membantu menemukan bug di lokasi pemanggilan fungsi
-
Kondisi berbasis waktu
- Jika Anda tidak ingin berhenti sebelum 5 detik berlalu setelah halaman dimuat, gunakan kondisi berikut
performance.now() > 5000- Ini cocok jika Anda hanya tertarik pada breakpoint setelah initial page load
- Jika Anda ingin breakpoint tidak berhenti selama 5 detik berikutnya setelah diatur, lalu mulai berhenti sesudahnya, Anda bisa menyimpan waktu acuan di variabel global
window.baseline = window.baseline || Date.now(), (Date.now() - window.baseline) > 5000- Anda bisa memulai ulang penghitung dari konsol dengan
window.baseline = Date.now()
-
Kondisi berbasis status CSS
- Anda bisa berhenti berdasarkan nilai CSS terhitung
- Misalnya, jika Anda ingin berhenti hanya saat warna latar
document.bodymerah, gunakan kondisi berikut
window.getComputedStyle(document.body).backgroundColor === "rgb(255,0,0)" -
Jumlah eksekusi dan sampling
- Dengan menaikkan penghitung setiap kali baris dieksekusi, Anda bisa berhenti sekali setiap dua kali
window.counter = (window.counter || 0) + 1, window.counter % 2 === 0- Jika Anda ingin berhenti hanya 1 dari 10 kali dengan sampling acak, gunakan kondisi berikut
Math.random() < 0.1 -
Jangan pernah berhenti di lokasi tertentu
- Di Chrome, klik kanan gutter lalu pilih “Never Pause Here” untuk membuat conditional breakpoint yang selalu bernilai
false, sehingga tidak berhenti di baris itu - Ini bisa dipakai untuk mengecualikan baris tertentu dari breakpoint XHR atau mengabaikan exception tertentu
- Di Chrome, klik kanan gutter lalu pilih “Never Pause Here” untuk membuat conditional breakpoint yang selalu bernilai
-
Memberi ID instance secara otomatis
- Anda bisa menaruh conditional breakpoint di constructor kelas untuk menyimpan setiap instance ke dalam array
(window.instances = window.instances || []).push(this)- ID unik dari instance tertentu bisa dicek lewat indeks array
window.instances.indexOf(instance) -
Toggle global untuk breakpoint
- Satu atau lebih conditional breakpoint bisa dikendalikan dengan boolean global
- Anda bisa mengubahnya langsung dari konsol, mengubahnya dari breakpoint lain, atau mengubahnya lewat timer
window.enableBreakpoints = true setTimeout(() => (window.enableBreakpoints = true), 5000)
Melacak pemanggilan fungsi dan kelas
-
Memantau pemanggilan method kelas
- Dengan metode command line
monitordi Chrome, Anda bisa melacak pemanggilan method kelas dengan mudah - Jika Anda ingin melihat pemanggilan method untuk semua instance dari kelas
Dog, Anda bisa menerapkanmonitorke semua properti milik prototype
var p = Dog.prototype; Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));- Konsol akan menampilkan informasi pemanggilan seperti
function bark called with arguments: 2 - Jika Anda ingin berhenti saat dipanggil alih-alih hanya meninggalkan log, gunakan
debugsebagai gantimonitor
- Dengan metode command line
-
Melacak kelas dari instance tertentu
- Jika Anda tidak tahu nama kelasnya tetapi punya instance, Anda bisa memantaunya dengan cara yang sama lewat prototype dari constructor
var p = instance.constructor.prototype; Object.getOwnPropertyNames(p).forEach((k) => monitor(p[k]));- Ini berguna saat membuat fungsi yang bekerja untuk instance sembarang tanpa terikat ke kelas tertentu
-
Debugging tepat sebelum fungsi dipanggil dari konsol
- Sebelum memanggil fungsi yang ingin Anda debug dari konsol, jalankan
debugger, lalu lakukan Step into pada pemanggilan fungsi berikutnya untuk masuk ke implementasi fungsi
debugger; fn(1);- Ini berguna jika Anda malas mencari lokasi definisi fungsi atau ketika
fndiikat secara dinamis sehingga Anda tidak tahu lokasi sumbernya - Di Chrome, Anda juga bisa memanggil
debug(fn)agar eksekusi berhenti di dalamfnsetiap kali ia dipanggil
- Sebelum memanggil fungsi yang ingin Anda debug dari konsol, jalankan
Debugging perubahan URL dan pembacaan properti
-
Berhenti sebelum routing SPA
- Jika Anda ingin berhenti tepat sebelum single-page application mengubah URL, Anda bisa menghubungkan fungsi yang berisi
debuggerkehistory.pushState,history.replaceState,window.onhashchange, danwindow.onpopstate
const dbg = () => { debugger; }; history.pushState = dbg; history.replaceState = dbg; window.onhashchange = dbg; window.onpopstate = dbg;- Versi
dbgini tidak mencakup implementasi yang menghentikan sambil tetap menjaga navigasi agar tidak rusak - Jika
window.location.replaceatauwindow.location.assigndipanggil langsung, halaman akan di-unload segera setelah penugasan, jadi cara ini tidak bisa menanganinya - Di Chrome, Anda bisa memasang
debugpada method terkait untuk melihat sumber redirect dan state saat itu
debug(window.location.replace); debug(window.location.assign); - Jika Anda ingin berhenti tepat sebelum single-page application mengubah URL, Anda bisa menghubungkan fungsi yang berisi
-
Menemukan titik pembacaan properti
- Jika Anda ingin tahu persis saat properti tertentu pada objek dibaca, Anda bisa menaruh
debuggerdi dalam getter
{ get configOption() { debugger; return true; } }- Ini berguna untuk memeriksa kode mana yang menggunakan opsi konfigurasi dan bagaimana cara memakainya
- Anda bisa menerapkannya dengan mengubah source asli atau lewat conditional breakpoint
- Jika Anda ingin tahu persis saat properti tertentu pada objek dibaca, Anda bisa menaruh
Menyalin data dari konsol
- Dengan
copy()Console API, Anda bisa menyalin informasi dari browser ke clipboard tanpa masalah pemotongan string - Ini bisa dipakai untuk menyalin snapshot DOM saat ini, metadata resource seperti gambar, JSON blob besar, atau dump localStorage
copy(document.documentElement.outerHTML)
copy(performance.getEntriesByType("resource"))
copy(JSON.parse(blob))
copy(localStorage)
Debugging HTML/CSS
-
Memeriksa DOM saat eksekusi JS dihentikan
- Di DOM inspector, pada Chrome/Windows Anda bisa menekan
ctrl+\untuk menghentikan eksekusi JS kapan saja - Bahkan ketika JS mengubah DOM atau event seperti
mouseovermengubah DOM, Anda tetap bisa memeriksa snapshot DOM saat ini dengan stabil
- Di DOM inspector, pada Chrome/Windows Anda bisa menekan
-
Memeriksa elemen yang menghilang
- Elemen DOM bersyarat yang menghilang saat mouse dipindahkan bisa diperiksa dengan memasang timer 5 detik di konsol untuk menjalankan debugger
setTimeout(function() { debugger; }, 5000);- Setelah UI ditampilkan selama 5 detik dan eksekusi JS dihentikan, elemen tidak akan menghilang, sehingga Anda bisa berpindah ke developer tools untuk memeriksanya
- Selama eksekusi JS dihentikan, Anda bisa memeriksa elemen, mengedit CSS, dan menjalankan perintah konsol JS
- Ini efektif saat memeriksa DOM yang bergantung pada posisi kursor atau fokus
-
Merekam snapshot DOM
- Jika Anda ingin menyalin DOM saat ini, gunakan perintah berikut
copy(document.documentElement.outerHTML);- Jika Anda ingin menyimpan string DOM ke array setiap 1 detik, Anda bisa memakai
setIntervalseperti berikut
doms = []; setInterval(() => { const domStr = document.documentElement.outerHTML; doms.push(domStr); }, 1000);- Anda juga bisa terus mencetaknya ke konsol tanpa menyimpannya ke array
-
Memantau elemen yang sedang fokus
- Dengan membandingkan
document.activeElementsecara berkala, Anda bisa menampilkan perubahan fokus ke konsol
(function () { let last = document.activeElement; setInterval(() => { if (document.activeElement !== last) { last = document.activeElement; console.log("Focus changed to: ", last); } }, 100); })(); - Dengan membandingkan
-
Menemukan elemen yang ditampilkan tebal
- Anda bisa mencari elemen yang
fontWeightterhitungnya bernilai"bold"atau"700"
const isBold = (e) => { let w = window.getComputedStyle(e).fontWeight; return w === "bold" || w === "700"; }; Array.from(document.querySelectorAll("*")).filter(isBold);- Jika Anda hanya ingin mencari di antara turunan elemen yang saat ini dipilih di inspector, Anda bisa mencari berdasarkan
$0
Array.from($0.querySelectorAll("*")).filter(isBold); - Anda bisa mencari elemen yang
-
Menangani elemen yang dipilih dan event
$0di konsol adalah referensi otomatis ke elemen yang saat ini dipilih di Element Inspector- Di Chrome dan Edge, elemen yang sebelumnya diperiksa bisa diakses sebagai
$1, dan elemen sebelumnya lagi sebagai$2 - Di Chrome, Anda bisa memeriksa event listener dari elemen yang sedang dipilih
getEventListeners($0)- Untuk men-debug semua event pada elemen yang dipilih, Anda bisa memakai
monitorEvents($0) - Jika Anda hanya ingin melihat grup event tertentu, Anda bisa memberikan array
monitorEvents($0, ["control", "key"])
1 komentar
Opini Hacker News
Selama beberapa dekade terakhir, alat debugging bawaan di browser benar-benar sudah berkembang pesat
Sebagai orang yang sudah lama memakai JS, saya sangat berterima kasih kepada orang-orang yang membuat debugging kode di dalam browser menjadi seintuitif ini
Saat beralih ke area pengembangan lain seperti backend atau bahasa lain, saya jadi merindukan ekosistem alat debugging yang disediakan browser modern secara bawaan
Saya sudah banyak melakukan debugging selama bertahun-tahun di Python, Java, dan C++, tetapi tidak pernah merasa kekurangan alat
Tentu saja saya cukup sering melihat orang yang tidak tahu opsi-opsi apa saja yang tersedia di bidang ini
Dibanding neraka itu, saya lebih memilih console
setTimeout(function() { debugger; }, 5000);itu cerdasPada akhirnya, satu-satunya cara bagi debugger Chrome untuk mengalahkan tumpukan kura-kura rekursif saat men-debug dirinya sendiri adalah statement
debuggersam.pldari worm Myspace Sammy yang terkenal buruk juga memakai jebakan debugging agar pengunjung tidak bisa membongkar homepage HTML yang diobfuscateMereka menjalankan main loop, menyisipkan statement
debugger;yang menjengkelkan di sana-sini, lalu mengeksekusinya pada 30FPS / interval 32ms sehingga DevTools menjadi tidak bergunaKarena tidak ada cara untuk “mengabaikan” statement
debuggercmd-\Halaman akan berhenti di baris JS yang sedang berjalan, dan Anda bisa menginspeksi elemen bermasalah seperti biasa
debuggerSebagian besar kasus penggunaan statement
debuggeryang sah bisa diganti dengan breakpoint biasa jika Anda sedang mengerjakan kode sendiriTerlihat jelas bahwa
queryObjectsbelum disebutIni API yang cukup gila, yang mengembalikan daftar semua objek yang dibuat oleh constructor tertentu
Misalnya, dengan
queryObjects(Function)Anda bisa mendapatkan daftar semua function yang ada di heapBahkan function “private” di dalam suatu modul pun ikut dikembalikan
Ini benar-benar fitur gila, dan sepertinya khusus Chromium
Saya sama sekali tidak bisa memakai watch variable dengan benar
Scope dan aturan pembaruannya terlalu tidak jelas, rasanya hanya variabel global yang bisa dipantau, tetapi itu pun tidak berjalan sesuai harapan, jadi saat testing akhirnya saya membanjiri log dengan nilai
Selama bertahun-tahun saya berpikir seharusnya ada UI bergaya Data.gui [1] di console agar variabel dan nilai konfigurasi bisa dilihat dan diuji
Anda bisa melihatnya bekerja di CodePen ini [2]
Secara teori, browser punya banyak fitur debugging yang bagus, tetapi dalam praktiknya tampaknya tidak bekerja dengan andal
Bahkan breakpoint pun tidak bisa saya buat selalu terpasang dengan stabil
Saat kodenya masih terbuka apa adanya, semuanya baik-baik saja, tetapi begitu dibundel, meskipun belum diminify, banyak fitur debugger tampaknya rusak
Karena saya bukan engineer frontend, mungkin saja ada sesuatu yang saya lakukan dengan keliru
Kemungkinan besar itulah sumber frustrasinya, dan saya masih tidak paham mengapa fitur itu aktif secara default padahal pengalaman debugging aktif saat fitur itu menyala bisa seburuk ini
Akan sangat bagus kalau ada cara untuk mengakses variabel lokal IIFE tanpa menghentikan eksekusi kode di dalam scope IIFE
Apakah ada cara untuk meyakinkan debugger agar melakukan itu?
https://en.m.wikipedia.org/wiki/Immediately_invoked_function...
Kalau sudah sepenuhnya berada di luar IIFE, menurut saya itu tidak mungkin
Variabel yang dicari mungkin tidak ada di memori sebelum atau sesudah eksekusi
JavaScript tidak punya variabel statis seperti bahasa lain, jadi setiap kali IIFE dipanggil, variabel internalnya dibuang
JavaScript punya model single-thread yang cukup kuat kecuali memakai sesuatu seperti web worker, jadi kecuali Anda sengaja membuat race condition dengan pemanggilan
async/await, konsep bahwa variabel seperti itu tetap berada di memori di luar scope IIFE sendiri sulit berlakuKalau menyalahgunakan
varuntuk menginisialisasi variabel di luar scope IIFE, Anda bisa melihat nilai yang dibuat oleh pemanggilan IIFE terakhirPerlu sedikit modifikasi, tetapi hasilnya besar dibanding usaha yang dikeluarkan
https://github.com/kristopolous/_inject
Anda bisa berkeliling hampir ke konteks fungsi mana pun pada titik waktu arbitrer dan melihat apa yang terjadi
Caranya dengan menyalahgunakan reference counter agar konteks tidak dihancurkan
Saat saya banyak mengerjakan JS sisi klien, ini benar-benar berguna
Versi killer app yang dibuat dengan benar untuk ini mungkin berupa membuka REPL di konteks mana pun
Dalam kondisi sekarang pun, untuk memakainya dengan baik dibutuhkan keahlian yang cukup tinggi
https://firefox-source-docs.mozilla.org/devtools-user/debugg...
Edit: baru sadar bahwa itu benar-benar hal pertama yang dibahas di artikel yang ditautkan
[[Scopes]]Sepertinya tidak ada cara di Firefox
Demi kelengkapan, saya bisa merekomendasikan Werkzeug
Saya memakainya untuk pengembangan backend Django dan ini sangat berguna
Ini memungkinkan Anda memakai shell “PDB” di dalam browser kapan pun dan di mana pun exception terjadi
1/0di tempat Anda ingin memunculkan Werkzeug di browserSangat sederhana dan intuitif
django-extension/runserver_plus?Satu trik yang selalu saya pakai adalah men-debug dengan mencari skrip yang dimuat berdasarkan string UI
Buka panel Network, mulai perekaman network request, buka sidebar kiri, lalu cari kode atau string UI yang ingin ditemukan
Biasanya ditemukan di dalam file chunk JS bundle yang aneh, dan ketika hasilnya diklik, network request untuk file tersebut akan terbuka
Klik kanan di mana saja di dalam file itu lalu pilih item semacam “Open in Sources” untuk berpindah ke debugger
Sekarang tinggal letakkan pernyataan
debugger, dan dalam proses ini source map mungkin juga ikut dimuatSaya sudah lama bekerja sebagai programmer Python/Elixir dan banyak memakai
pdb.set_trace()sertaIEx.pry()Baru-baru ini saya mewarisi backend NodeJS yang sangat berantakan, dan tanpa tool debugging yang layak saya sampai ingin menjambak sisa rambut saya
Saya kembali ke debugging dengan
console.log, tapi rasanya seperti menjadi manusia purbaSulit dipercaya ekosistem sepopuler ini tidak punya debugging REPL yang layak; bisa beri tahu saya harus ke mana?
Pakai
node --inspect-brk, lalu Anda bisa tersambung dengan VS Code atau Chrome DevToolsBagian yang tricky adalah apakah build tool seperti transformasi TypeScript berada sebelum perintah
nodejsatau tidakKalau Anda hanya menjalankan
node, itu cukup mudahDebugging REPL-nya adalah konsol JavaScript itu sendiri
Kalau ditambahkan sebagai opsi debugging, Anda bisa memasang breakpoint dan step into ke dalam fungsi
Karena sebagian besar dienkapsulasi di dalam objek, JavaScript cukup enak untuk di-debug
--inspectatau--inspect-brkDi VSCode Anda juga bisa memakai perintah “open JavaScript debug terminal”
Perintah ini membuka terminal tempat perintah
nodeapa pun otomatis dimulai dengan debugger terpasanghttps://pypi.org/project/snoop/
Ini dekorator untuk tracing mendalam
Saya belum melihat fitur seperti ini di ekosistem Node JS
Di bagian “Debugging Property Reads”, saya penasaran bagaimana breakpoint bersyarat digunakan untuk mengubah
{configOption: true}menjadi{get configOption() { debugger; return true; }}Anda cukup menimpa nilainya dengan getter di konsol, dan kalau punya izin tulis, bisa juga mengubahnya langsung di source code
Ini sepertinya sesuatu yang wajib dipelajari, tapi penasaran apakah ada buku khusus atau materi belajar untuk itu
Atau memang satu-satunya cara adalah terus menggali pengembangan web dan frontend tanpa henti?
Saat sedang sibuk bekerja, mungkin tidak terasa menarik untuk mengeklik tautan demi membaca catatan rilis, tetapi meluangkan 5 menit untuk meninjau sekilas setiap kali versi baru keluar sudah sangat berharga
Ada juga video beberapa menit yang mudah dicerna, sehingga bisa mendapat gambaran singkat
Tujuannya memang memperkenalkan fitur baru, tetapi dari pengalaman, ini juga sering membantu memahami batasan alat saat ini