Debugging ב': תהליך הדיבאג וה-Debugger של VS Code
- ליישם את חמשת שלבי הדיבאג — Reproduce, Isolate, Root Cause, Fix, Verify
- להציב Breakpoint ולהריץ קוד במצב Debug ב-VS Code
- להשתמש ב-Step Over, Step Into ו-Continue, ולעקוב אחרי משתנים בחלון Variables
- למצוא ולתקן Logical Error בקוד אמיתי בעזרת ה-Debugger
מה נלמד?
בשיעור הקודם למדנו לקרוא שגיאות. עכשיו נלמד לצוד באגים, גם כאלה שאין להם הודעת שגיאה:
- תהליך — חמישה שלבים קבועים לכל באג.
- כלי — ה-Debugger של VS Code: עצירת הקוד באמצע ובדיקה של מה קורה בתוכו.
- תרגול אמיתי — נפתור יחד באג שלב אחרי שלב.
למה צריך את זה?
הדרך של מתחילים היא “לנחש ולשנות”: משנים משהו, מריצים, ואם לא עבד, משנים משהו אחר. זה איטי, מתסכל, ולעיתים קרובות יוצר באג חדש. מקצוענים עובדים בשיטה, וה-Debugger מאפשר לראות את הקוד “בהילוך איטי”, במקום לנחש מה קורה בו.
הסבר מהבסיס
חמשת שלבי הדיבאג
| שלב | שם | השאלה שהוא עונה עליה |
|---|---|---|
| 1 | Reproduce (שחזור) | איך גורמים לבאג לקרות בכל פעם? איזה קלט? |
| 2 | Isolate (בידוד) | איפה בקוד הבעיה? מצמצמים עד שורה או פונקציה. |
| 3 | Root Cause (שורש הבעיה) | למה זה קורה? לא הסימפטום, אלא הסיבה. |
| 4 | Fix (תיקון) | מתקנים את הסיבה, ולא מסתירים את הסימפטום. |
| 5 | Verify (בדיקה) | הבאג נעלם? ושום דבר אחר לא נשבר? |
רופא טוב לא נותן משכך כאבים לכל מטופל. הוא משחזר (“מתי זה כואב?”), מבודד (“איפה בדיוק?”), מוצא את הסיבה (בדיקות), מטפל בסיבה, ובודק בביקורת שהמטופל החלים. משכך כאבים בלבד הוא “תיקון סימפטום”. הכאב חוזר, והבעיה ממשיכה.
מהו Debugger?
Debugger הוא כלי שמריץ את התוכנית בשליטה שלכם: הוא עוצר אותה בשורה שבחרתם, ומאפשר להתקדם שורה אחר שורה ולראות את הערך של כל משתנה בכל רגע.
המושגים:
| מושג | מה זה |
|---|---|
| Breakpoint | “נקודת עצירה”: סימון על שורה. כשהריצה מגיעה אליה, התוכנית נעצרת לפני ביצוע השורה. |
Continue (F5) | המשך ריצה רגילה, עד ה-Breakpoint הבא (או עד הסוף). |
Step Over (F10) | בצע את השורה הנוכחית כולה ועצור בשורה הבאה. אם יש בה קריאה לפונקציה, הפונקציה רצה “בבת אחת”. |
Step Into (F11) | אם בשורה יש קריאה לפונקציה, היכנס לתוכה ועצור בשורה הראשונה שלה. |
Step Out (Shift+F11) | סיים את הפונקציה הנוכחית וחזור למי שקרא לה. |
| Variables | חלון שמציג את כל המשתנים והערכים שלהם ברגע זה. |
| Watch | ביטויים שבחרתם לעקוב אחריהם, למשל total / count. |
| Call Stack | שרשרת הקריאות: איזו פונקציה קראה לאיזו. זה ה-Traceback, רק “חי”. |
Stop (Shift+F5) | עצירת הדיבאג. |
אתם קוראים מתכון, ויש בו שורה “הכינו בצק (ראו עמוד 40)”. Step Over = מכינים בצק בלי לפתוח את עמוד 40, ועוברים לשורה הבאה במתכון. Step Into = פותחים את עמוד 40 וקוראים את מתכון הבצק שורה אחר שורה.
תחביר
איך מפעילים את ה-Debugger ב-VS Code:
- ודאו שההרחבה Python של Microsoft מותקנת. היא כוללת את ה-Debugger.
- פתחו קובץ
.py. - הצבת Breakpoint: לחצו בשוליים השמאליים, משמאל למספר השורה. תופיע נקודה אדומה 🔴 (או
F9על השורה). - הפעלה:
F5, או החץ הקטן ליד כפתור ▷ ← Python Debugger: Debug Python File. אם נשאלים איזו תצורה, בוחרים Python File. - התוכנית נעצרת על ה-Breakpoint. השורה הצהובה היא השורה הבאה לביצוע.
- בצד שמאל מופיעים Variables, Watch ו-Call Stack. למעלה מופיע סרגל הכפתורים: Continue, Step Over, Step Into, Step Out, Restart, Stop.
טריק: אפשר גם לרחף עם העכבר מעל משתנה בקוד בזמן עצירה, ולראות את הערך שלו.
דוגמת קוד ראשונה
נתחיל בתוכנית תקינה, רק כדי להכיר את הכלי. week-2/debug_intro.py:
def apply_tax(amount):
tax = amount * 0.17
return amount + tax
prices = [100, 250, 40]
total = 0
for price in prices:
total = total + apply_tax(price)
print("Total with tax:", total)התרגיל:
- שימו Breakpoint על השורה
total = total + apply_tax(price). F5. התוכנית נעצרת. בחלון Variables:price = 100,total = 0.F10(Step Over). עכשיוtotal = 117.0, והשורה הצהובה חזרה ל-for.F5(Continue). נעצרים שוב באותה שורה:price = 250.- הפעם
F11(Step Into). אנחנו בתוךapply_tax, ו-amount = 250. ב-Call Stack רואים:apply_tax←<module>. F10פעמיים: רואיםtax = 42.5, ואזreturn.F5עד הסוף.
פלט:
Total with tax: 456.3פירוק הקוד שורה אחר שורה
מה רואים בכל עצירה, וכך מבינים את הזרימה:
| עצירה | שורה צהובה | Variables | הסבר |
|---|---|---|---|
| 1 | total = total + apply_tax(price) | price=100, total=0 | העצירה היא לפני ביצוע השורה, ולכן total עדיין 0. |
אחרי F10 | for price in prices: | total=117.0 | השורה בוצעה כולה, כולל הפונקציה. |
| 2 | total = total + apply_tax(price) | price=250, total=117.0 | סיבוב שני. |
אחרי F11 | tax = amount * 0.17 | amount=250 | נכנסנו לפונקציה. רואים רק את המשתנים שלה (Local). |
אחרי F10 | return amount + tax | amount=250, tax=42.5 |
דוגמה נוספת
באג אמיתי — מההתחלה ועד הסוף. תוכנית שמחשבת ממוצע ציונים בלי הציון הנמוך ביותר (כמו שמקובל בקורסים: “הציון הנמוך לא נחשב”). week-2/drop_lowest.py:
def find_lowest(grades):
lowest = 0
for grade in grades:
if grade < lowest:
lowest = grade
return lowest
def average_without_lowest(grades):
lowest = find_lowest(grades)
total = sum(grades) - lowest
return total / (len(grades) - 1)
my_grades = [80, 90, 70, 100]
print("Average without lowest:", average_without_lowest(my_grades))ציפייה: הנמוך הוא 70. נשארים 80, 90, 100, והממוצע 90.0.
פלט בפועל:
Average without lowest: 113.33333333333333ממוצע מעל 100! זה Logical Error: אין הודעת שגיאה, רק תוצאה בלתי אפשרית.
שלב 1 — Reproduce: הקלט [80, 90, 70, 100] מייצר את הבאג בכל הרצה. מצוין, יש לנו מקרה יציב.
שלב 2 — Isolate: יש שתי פונקציות. איזו אשמה? שמים Breakpoint על השורה total = sum(grades) - lowest ומריצים עם F5. בחלון Variables: lowest = 0. אבל הנמוך הוא 70! הבעיה כבר לפני השורה הזו, בתוך find_lowest. בודדנו.
שלב 3 — Root Cause: עוצרים, שמים Breakpoint על lowest = find_lowest(grades), F5, ואז F11 (Step Into) לתוך find_lowest, ו-F10 שורה אחר שורה:
| סיבוב | grade | grade < lowest? | lowest |
|---|---|---|---|
| התחלה | — | — | 0 |
| 1 | 80 | 80 < 0 → False | 0 |
| 2 | 90 | 90 < 0 → False | 0 |
| 3 | 70 | 70 < 0 → False | 0 |
| 4 | 100 | 100 < 0 → False | 0 |
שורש הבעיה: lowest מתחיל ב-0. אף ציון חיובי לא קטן מ-0, ולכן הוא אף פעם לא מתעדכן. ההנחה “0 הוא ערך התחלתי טוב” שגויה.
שלב 4 — Fix: מתחילים מהציון הראשון ברשימה. הוא תמיד מועמד אמיתי:
def find_lowest(grades):
lowest = grades[0]
for grade in grades:
if grade < lowest:
lowest = grade
return lowest(ל-Python יש גם min(grades) מובנית, ובקוד אמיתי עדיף להשתמש בה. כאן כתבנו ידנית כדי לתרגל.)
שלב 5 — Verify:
Average without lowest: 90.0ובודקים מקרים נוספים, לא רק את המקרה המקורי:
[50, 50, 50]→50.0✓[100, 0]→100.0✓ (הנמוך 0 מוסר)[70]← קורס!ZeroDivisionError(מחלקים ב-len - 1 = 0). מצאנו באג נוסף בזכות שלב הבדיקה. מה לעשות ברשימה של ציון אחד? זו החלטה עסקית. למשל: להחזיר את הציון עצמו.
טעות נפוצה
הטעות 1: לדלג על Reproduce. “המשתמש אמר שזה לא עובד לפעמים”, אז משנים קוד בלי מקרה שמשחזר את הבאג. בלי שחזור, אין דרך לדעת שתיקנתם.
הטעות 2: Debugging ב-print בלבד, בלי לנקות. הדפסות זמניות (print("HERE", x)) הן כלי לגיטימי ומהיר, אבל הן נשכחות בקוד ומלכלכות את הפלט. ה-Debugger מראה את כל המשתנים בלי לשנות את הקוד. (ובנושא Python נושא Exceptions ו-Logging נלמד Logging, הדרך המקצועית להשאיר מידע בקוד.)
הטעות 3: Step Into לכל דבר. F11 על print(...) או sum(...) מכניס אתכם לקוד הפנימי של Python, ושם קל ללכת לאיבוד. על פונקציות שלכם משתמשים ב-Step Into, ועל פונקציות מובנות ב-Step Over. (אם נכנסתם בטעות, Shift + F11 מוציא.)
הטעות 4: לתקן ולא לבדוק מקרים נוספים. כמו שראינו, שלב Verify חשף באג שני. תמיד בודקים גם מקרי קצה: רשימה ריקה, איבר אחד, ערכים שליליים, אפס.
תרגול
התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.
תרגיל 1: חמשת השלבים
סדרו את שלבי הדיבאג לפי הסדר, והסבירו במשפט מה עושים בכל שלב:
Fix · Reproduce · Verify · Understand · Isolate
הצג פתרון
- Reproduce — לגרום לבאג לקרות שוב, בכוונה, עם קלט ידוע.
- Isolate — לצמצם: באיזו פונקציה, באיזו שורה, עם איזה ערך הבעיה מתחילה.
- Understand — להבין למה זה קורה, לפני שמשנים קוד.
- Fix — לתקן את שורש הבעיה, לא את הסימפטום.
- Verify — להריץ שוב את המקרה שנכשל, וגם מקרים שעבדו קודם.
תרגיל 2: המקסימום שיצא 0
def find_max(numbers):
largest = 0
for number in numbers:
if number > largest:
largest = number
return largest
print(find_max([4, 9, 2]))
print(find_max([-7, -3, -10]))הקריאה השנייה מחזירה 0, למרות ש-0 בכלל לא ברשימה.
- איפה הייתם שמים Breakpoint, ומה הייתם רואים בחלון Variables?
- תקנו את הבאג.
פלט צפוי:
9
-3הצג פתרון
- Breakpoint על השורה
if number > largest:. בחלון Variables רואים ש-largestנשאר 0 לאורך כל הלולאה — אף מספר שלילי לא גדול מ-0. - מתחילים מהאיבר הראשון של הרשימה, ולא מ-0:
def find_max(numbers):
largest = numbers[0]
for number in numbers:
if number > largest:
largest = number
return largest
print(find_max([4, 9, 2]))
print(find_max([-7, -3, -10]))9
-3תרגיל 3: return בתוך הלולאה
def count_vowels(word):
count = 0
for letter in word:
if letter in "aeiou":
count = count + 1
return count
print(count_vowels("banana"))הפונקציה מחזירה 0 במקום 3. השתמשו ב-Step Over כדי לעקוב אחרי הלולאה — מה קורה אחרי האות הראשונה? תקנו.
פלט צפוי:
3הצג פתרון
עם Step Over רואים שאחרי האות הראשונה (b, לא תנועה) ההרצה מגיעה ישר ל-return count — בתוך הלולאה — והפונקציה מסתיימת עם 0. ה-return מוזח רמה אחת יותר מדי.
def count_vowels(word):
count = 0
for letter in word:
if letter in "aeiou":
count = count + 1
return count
print(count_vowels("banana"))3תרגיל 4: כלי ה-Debugger
הסבירו במשפט אחד כל מונח, ומתי משתמשים בו:
- Breakpoint
- Step Over
- Step Into
- חלון Variables
הצג פתרון
- Breakpoint — נקודה שבה ההרצה עוצרת, כדי לבדוק את המצב באותו רגע. שמים אותו בשורה שלפני המקום החשוד.
- Step Over — מריץ את השורה הנוכחית ועובר לבאה, בלי להיכנס לתוך פונקציות שנקראות בה.
- Step Into — נכנס לתוך הפונקציה שנקראת בשורה הנוכחית, כדי לבדוק מה קורה בפנים.
- חלון Variables — מראה את הערכים של כל המשתנים ברגע הנוכחי. כאן רואים איפה הערך שונה ממה שציפיתם.
תרגיל 5: Off-by-one
def sum_to(n):
total = 0
for i in range(1, n):
total = total + i
return total
print(sum_to(5))הסכום 1+2+3+4+5 צריך להיות 15, אבל יוצא 10. מצאו את הבאג ותקנו.
פלט צפוי:
15הצג פתרון
range(1, n) לא כולל את n — הלולאה רצה על 1, 2, 3, 4 בלבד. זה באג “Off-by-one” קלאסי.
def sum_to(n):
total = 0
for i in range(1, n + 1):
total = total + i
return total
print(sum_to(5))15סיכום
- חמשת השלבים: Reproduce → Isolate → Root Cause → Fix → Verify. לא מנחשים.
- Breakpoint עוצר לפני ביצוע השורה. F5 = Continue, F10 = Step Over, F11 = Step Into, Shift+F11 = Step Out.
- חלון Variables מראה את מצב התוכנית ברגע העצירה. Call Stack מראה מי קרא למי.
- Logical Errors מוצאים בהשוואה של ערכי המשתנים בפועל לערכים הצפויים, שורה אחר שורה.
- אחרי תיקון בודקים גם מקרי קצה: ריק, איבר אחד, אפס, שלילי.
דוקומנטציה רשמית
- VS Code — Python debugging — הרצת Debugger על קובץ Python
- VS Code — Debugging: Breakpoints — Breakpoints, Step Over ו-Step Into