6.5 Debugger eszközök alapjai – breakpoint, watch, step
Áttekintő
A debugger az egyik legerősebb eszköz egy fejlesztő kezében – mégis rengeteg junior soha nem tanulja meg rendesen használni. Ehelyett maradnak a print/console.log/var_dump módszernél, ami bizonyos esetekben teljesen rendben van, más esetekben viszont egy helyes debugger-használattal perc alatt megtalálnánk a hibát, amire így órákat töltünk.
Ebben az alfejezetben nem fogunk konkrét IDE-t tanítani – a debugger-gondolkodásmód ugyanaz VS Code-ban, PyCharm-ban, Chrome DevTools-ban és mindenhol máshol is. Ha egyszer megérted a koncepciókat, bármely eszközben otthon leszel.
Amit megtanulsz:
- Mi a debugger valójában és miben más, mint a print-alapú hibakeresés
- Breakpoint elhelyezése és mire jó
- Step over, step into, step out – mikor melyiket használd
- Watch változók és call stack figyelése
- Mikor érdemes debuggert, mikor loggot használni
Miért számít ez az állásinterjún?
Ha az interjúztatód megkérdezi, hogyan közelítenél egy ismeretlen hibához, és azt mondod „print-elném az értékeket" – ez belefér. Ha viszont azt mondod „breakpointot raknék az érintett területre és megnézném a call stack-et" – ez azt jelzi, hogy profi eszközöket használsz.
Miért számít production környezetben?
Production-ban nem tudsz print-elni. Loggolhatsz, igen – de egy fejlesztői gépen, ahol reprodukálni tudod a hibát, a debugger sokszor 10x gyorsabb a megoldáshoz.
Részletes leírás
Mi a debugger és miért más, mint a print?
A print-alapú hibakeresés így néz ki:
def szamol(a, b):
print(f"a = {a}") # ezt tettem be
eredmeny = a * b
print(f"eredmeny = {eredmeny}") # ezt is
return eredmeny
Ez működik. De van néhány probléma:
- Módosítod a kódot – majd el kell takarítani
- Csak azt látod, amit előre elhelyeztél – ha rossz helyre tetted, kezdheted elölről
- Nem látod a programállapot összképét – csak a kiírt értékeket
- Nem léphetsz „visszafelé" – nem tudod megnézni, mi volt 3 utasítással korábban
A debugger ezzel szemben:
- Megállítja a program futását egy adott ponton
- Megmutatja az összes változó értékét abban a pillanatban
- Lehetővé teszi a lépésenkénti végrehajtást – utasításról utasításra haladva
- Megmutatja a call stack-et – ki hívta a jelenlegi függvényt, az ki hívta, és így tovább
- Nem módosítja a kódot – nincs mit utána eltakarítani
Breakpoint – az „állj meg itt" jelzés
A breakpoint egy jelölés, amit a kódban egy adott sorra helyezel. Amikor a program futása eléri ezt a sort, megáll – és te átveszed az irányítást.
Képzeld el, mintha egy könyvet olvasnál és beragasztanál egy lapjelzőt: „ha ide érsz, állj meg és nézd meg alaposan mi van körülötted."
Hogyan kell elhelyezni? Az összes modern IDE-ben a sor számára kattintva (általában a szám melletti margóra) megjelenik egy piros pont – ez a breakpoint. Más eszközökben egy debugger; kulcsszót (JavaScript) vagy breakpoint() függvényhívást (Python 3.7+) illeszthetsz a kódba:
function szamolAr(termek, mennyiseg) {
const alapAr = termek.ar * mennyiseg;
debugger; // itt megáll a böngésző DevTools-ban
const kedvezmeny = alapAr > 10000 ? alapAr * 0.1 : 0;
return alapAr - kedvezmeny;
}
def szamol_ar(termek, mennyiseg):
alap_ar = termek["ar"] * mennyiseg
breakpoint() # itt megáll a Python debugger
kedvezmeny = alap_ar * 0.1 if alap_ar > 10000 else 0
return alap_ar - kedvezmeny
Feltételes breakpoint: A legtöbb debugger lehetővé teszi, hogy a breakpoint csak akkor aktiválódjon, ha egy feltétel teljesül. Például: „állj meg ennél a sornál, de csak ha mennyiseg > 100." Ez rendkívül hasznos, ha egy ciklus 10.000-szer fut le, de csak a 9.873. iterációban törik el valami.
Step over, step into, step out – a három alapmozdulat
Amikor a debugger megáll egy breakpointnál, te irányítod, mi történjen ezután. Három alapvető lépési mód létezik:
Step over (lépj át)
Az aktuális sort végrehajtja, és a következő sorra lép – de ha az aktuális sor függvényhívást tartalmaz, nem megy bele a függvénybe. Kívülről nézi az egészet.
→ eredmeny = szamol(a, b) ← [STEP OVER] → végrehajta szamol()-t, de nem megy bele
return eredmeny
Mikor használd: Ha egy függvényt már ismersz és nem akarod végigkövetni, csak az utána következő állapotot akarod látni.
Step into (lépj bele)
Ha az aktuális sor függvényhívást tartalmaz, bemegy a függvénybe és ott folytatja a lépésenkénti végrehajtást.
→ eredmeny = szamol(a, b) ← [STEP INTO] → bemegy a szamol() függvénybe
def szamol(a, b):
→ return a * b ← itt folytatja
Mikor használd: Ha gyanítod, hogy a hiba a hívott függvényen belül van.
Step out (lépj ki)
Ha egy függvényen belül vagy, befejezi az egész függvényt és visszatér a hívó kódhoz. Hasznos, ha véletlenül beléptél egy függvénybe, amit nem akartál.
def szamol(a, b): ← már itt vagy, de rájöttél nem ide kellett
→ return a * b
[STEP OUT] → visszaugrik a szamol() hívójához
→ eredmeny = szamol(a, b) ← itt folytatja
Összefoglalás egy mondatban: Step over = ugyanazon a szinten haladok, step into = lemegyek mélyebbre, step out = feljövök egy szinttel.
Watch – változók figyelése
A watch (vagy watch expression) lehetővé teszi, hogy megfigyeld egy változó vagy kifejezés értékét a debugger futása közben. Amikor az érték megváltozik, azonnal látod.
Nem csak változókat figyelhetsz – tetszőleges kifejezéseket is:
user.email– egy objektum adott mezőjétlista[3]– egy lista adott elemétlen(lista) > 5– egy feltételt (igaz-e éppen?)a + b– egy számítás eredményét
Ez különösen hasznos ciklusokon belül, ahol ugyanaz a változó iterációnként más értéket vesz fel.
Call stack – ki hívott kit?
A call stack (hívási verem) megmutatja a függvényhívások sorát az aktuális pontig. Alulról felfelé olvasva látod, hogyan jutottál el a program belépési pontjától az aktuális pillanatig.
Például ha a debugger megáll egy hibánál:
→ szamol_kedvezmeny() ← jelenleg itt vagy
szamol_vegosszeget() ← ezt hívta
feldolgoz_rendelest() ← ezt hívta
main() ← és ez volt az induló pont
Ez azért értékes, mert megmutatja a kontextust: nem csak azt, hogy hol van a hiba, hanem azt is, hogyan kerültél oda. Ha egy szamol_kedvezmeny() függvény rossz értékeket kap, a call stack-ből kiderül, melyik caller adta át a rossz adatot.
Debugger vs. logging – mikor melyik?
| Helyzet | Debugger | Logging |
|---|---|---|
| Fejlesztői gépen, reprodukálható hiba | ✅ Ideális | Lassabb |
| Production hiba, amit nem tudsz reprodukálni | ❌ Nem elérhető | ✅ Egyetlen lehetőség |
| Komplex adatstruktúra vizsgálata | ✅ Mindent látsz | Nehézkes |
| Ciklus futás közbeni állapotok | ✅ Feltételes breakpoint | Rengeteg sor kell |
| Aszinkron / párhuzamos kód | ⚠️ Trükkös | ✅ Jobban nyomon követhető |
| Csapattal megosztandó megfigyelés | ❌ Csak neked látható | ✅ Mindenki olvassa |
A valóságban a kettő kiegészíti egymást: debugger fejlesztés közben, logging production-ban.
Életszerű példák
1. példa: A kedvezmény nem stimmel
Egy e-kereskedelmi rendszerben a rendelések végösszege néha rosszul jön ki. A hiba csak bizonyos feltételek mellett jelenik meg.
Print-alapú megközelítés:
def szamol_vegosszeg(tetelek, felhasznalo):
alap = sum(t["ar"] * t["mennyiseg"] for t in tetelek)
print(f"alap: {alap}")
kedvezmeny = szamol_kedvezmeny(felhasznalo, alap)
print(f"kedvezmeny: {kedvezmeny}")
ado = szamol_ado(alap - kedvezmeny)
print(f"ado: {ado}")
return alap - kedvezmeny + ado
Futtatod, kiírja az értékeket, de még mindig nem érted, miért rossz a szamol_kedvezmeny() kimenete.
Debugger-alapú megközelítés:
- Breakpointot raksz a
kedvezmeny = szamol_kedvezmeny(...)sorra - Lefuttatod a hibás forgatókönyvet
- A debugger megáll – látod az összes változót:
alap = 15000,felhasznalo = {"prémium": True, "régi_ügyfél": False} - Step into → bemész a
szamol_kedvezmeny()-be - Ott rájössz, hogy a feltétel
felhasznalo["premium"](kisbetűs) helyettfelhasznalo["prémium"]-ot kellene keresni – a kulcs ékezettel van
A bug kulcselérési hiba volt. Print-el is megtaláltad volna, de a debuggerrel 3 lépés helyett egyből a problémánál vagy.
2. példa: A ciklus valahol elromlik
function feldolgozMegrendelesek(megrendelesek) {
let osszeg = 0;
for (let i = 0; i < megrendelesek.length; i++) {
osszeg += szamolTetel(megrendelesek[i]);
}
return osszeg;
}
Az összeg nem stimmel, de csak akkor, ha vannak visszáruzott tételek.
Debugger megközelítés:
- Feltételes breakpointot raksz a
szamolTetel()hívásra: csak akkor álljon meg, hamegrendelesek[i].tipus === "visszaru" - Így a debugger kihagyja a normál tételeket és egyből a problémás esetekre ugrik
- A watch-ba teszed:
megrendelesek[i],osszeg - Látod, hogy a visszáruzott tételeknél a
szamolTetel()pozitív értéket ad vissza ahelyett, hogy levonná az összeget
3. példa: Melyik függvény adja át a rossz adatot?
A PHP-s rendszerben egy save_user() hívás néha null-t kap email mezőként, bár az adatok látszólag helyesek.
Call stack használat:
- Breakpoint kerül a
save_user()-be, ahol ellenőrzöd azemailértékét - Amikor megáll, a call stack megmutatja:
``` save_user() ← itt vagy register_user() handle_form_submit() ```
- Step out → felmész
register_user()-be, megnézed mit ad tovább - Ott látod:
$email = $data['eamil']– elírás a kulcsban (eamilhelyettemail)
A hiba nem a save_user()-ben volt – a call stack nélkül valószínűleg percekig a rossz függvényt vizsgáltad volna.
Tesztfeladatok
1. Melyik debugger-lépés való mire?
Rendeld hozzá a helyes lépést minden szituációhoz:
| Szituáció | Helyes lépés |
|---|---|
| Benne vagy egy függvényben, amit véletlenül léptél be | ___ |
| Egy függvényhívás előtt állsz és gyanítod, hogy a hiba abban van | ___ |
| Egy függvényhívás előtt állsz, de az a függvény tuti jó | ___ |
Lehetséges válaszok: Step over, Step into, Step out
2. Mi az a breakpoint?
Válaszd ki a legpontosabb definíciót:
A) Egy speciális szintaxishiba, amit szándékosan beleírunk a kódba
B) Egy jelölés a kódban, amelynél a debugger megállítja a program futását
C) Egy log üzenet, ami a konzolra ír amikor eléri
D) Egy feltétel, ami kivételt dob futás közben
3. Mikor van előnye a debuggernek a print-tel szemben?
Jelöld meg az összes helyes állítást:
A) Ha egyszerre több tucat változó értékét akarod egyszerre látni
B) Ha production-ban kell nyomon követni a hibát
C) Ha komplex objektum belső struktúráját akarod vizsgálni
D) Ha a hiba csak akkor jelenik meg, ha a ciklus 5000-szer fut le
E) Ha a csapatoddal meg akarod osztani a megfigyeléseidet
4. Call stack értelmezése
A debugger megáll, és a call stack így néz ki (felülről lefelé):
validate_email() ← jelenleg itt
create_account()
register_new_user()
api_handler()
Melyik állítás igaz?
A) A validate_email() hívta a create_account()-ot
B) Az api_handler() hívta a register_new_user()-t
C) A create_account() fog következni a validate_email() után
D) A stack arra utal, hogy az api_handler() az alkalmazás belépési pontja
5. Watch kifejezés
Egy ciklusban figyeled a lista változót. Melyik watch kifejezés jelzi előre, ha a lista kiürül a következő iterációban?
A) lista
B) len(lista) == 0
C) lista[0]
D) len(lista) == 1
(Tipp: azt akarod látni, mielőtt kiürül – tehát mikor van már csak 1 elem)
6. Igaz vagy hamis?
Döntsd el minden állításról:
| Állítás | Igaz / Hamis |
|---|---|
| A debugger csak akkor hasznos, ha az IDE-d támogatja | ___ |
| A feltételes breakpoint lehetővé teszi, hogy csak bizonyos esetekben álljon meg a program | ___ |
| A call stack mindig csak egy függvényt mutat | ___ |
| Watch kifejezésként csak változóneveket adhatsz meg, kifejezéseket nem | ___ |
| Production-ban a debugger jobb eszköz, mint a logging | ___ |
7. Szimulált debugging feladat
Az alábbi pszeudokód egy rendelés feldolgozó rendszer részlete. Olvasd el és válaszolj a kérdésekre:
FUNCTION feldolgoz_rendeles(rendeles_id):
rendeles = betolt_rendeles(rendeles_id) // sor 2
felhasznalo = betolt_felhasznalo(rendeles.user_id) // sor 3
ha felhasznalo.egyenleg < rendeles.vegosszeg: // sor 4
dobj_hibat("Nincs elég egyenleg") // sor 5
vonjuk_le(felhasznalo, rendeles.vegosszeg) // sor 6
ments_rendeles(rendeles) // sor 7
FUNCTION vonjuk_le(felhasznalo, osszeg):
felhasznalo.egyenleg = felhasznalo.egyenleg - osszeg // sor 10
ments_felhasznalo(felhasznalo) // sor 11
Kérdések:
a) Hova raknál breakpointot, ha azt akarod megvizsgálni, hogy a rendeles.vegosszeg helyes értéket tartalmaz-e?
b) Ha a debugger megáll a 6. sornál és step into-t nyomsz, hova ugrik?
c) Ha a 10. sornál vagy és step out-ot nyomsz, hova kerülsz?
d) Mit érdemes watch-ba tenni, ha azt akarod figyelni, hogy az egyenleg levonás után negatív lesz-e?
8. Mikor melyik eszköz?
Szituációhoz rendeld hozzá a helyes eszközt: Debugger / Logging / Mindkettő jó / Egyik sem ideális
| Szituáció | Eszköz |
|---|---|
| Fejlesztői gépen reprodukálható bug vizsgálata | ___ |
| Production éjszaka kiesett, reggel kell kivizsgálni | ___ |
| Egy komplex adatstruktúra tartalmát akarod pillanatfelvételként látni | ___ |
| Nyomon akarod követni, hány kérés érkezett egy API endpointra | ___ |
| Pair programming közben a kollégáddal együtt keresed a hibát | ___ |
Megoldások:
1. Step out | Step into | Step over
2. B
3. A, C, D (B helytelen – production-ban debugger általában nem elérhető; E helytelen – a debugger nem osztható meg, csak logging)
4. B és D (A fordított – validate_email()-t hívta create_account(); C sem igaz – step out esetén create_account()-ba megy vissza)
5. D – amikor len(lista) == 1, a következő iterációban üres lesz
6. Hamis | Igaz | Hamis | Hamis | Hamis
7.
a) 2. vagy 4. sor – a 4. soron már láthatod a rendeles.vegosszeg értékét
b) A vonjuk_le() függvény belsejébe, a 10. sorra
c) Vissza a feldolgoz_rendeles() 7. sorára (a vonjuk_le() hívója utáni következő sorra)
d) felhasznalo.egyenleg - osszeg vagy felhasznalo.egyenleg
8. Debugger | Logging | Debugger | Logging | Mindkettő jó