# Debugging ב': תהליך הדיבאג וה-Debugger של VS Code<no value>

## מה נלמד?

בשיעור הקודם למדנו **לקרוא** שגיאות. עכשיו נלמד **לצוד** באגים, גם כאלה שאין להם הודעת שגיאה:

1. **תהליך** — חמישה שלבים קבועים לכל באג.
2. **כלי** — ה-**Debugger** של VS Code: עצירת הקוד באמצע ובדיקה של מה קורה בתוכו.
3. **תרגול אמיתי** — נפתור יחד באג שלב אחרי שלב.

## למה צריך את זה?

הדרך של מתחילים היא "לנחש ולשנות": משנים משהו, מריצים, ואם לא עבד, משנים משהו אחר. זה איטי, מתסכל, ולעיתים קרובות יוצר באג חדש. **מקצוענים עובדים בשיטה**, וה-Debugger מאפשר לראות את הקוד "בהילוך איטי", במקום לנחש מה קורה בו.

## הסבר מהבסיס

### חמשת שלבי הדיבאג

| שלב | שם | השאלה שהוא עונה עליה |
|---|---|---|
| 1 | **Reproduce** (שחזור) | איך גורמים לבאג לקרות **בכל פעם**? איזה קלט? |
| 2 | **Isolate** (בידוד) | **איפה** בקוד הבעיה? מצמצמים עד שורה או פונקציה. |
| 3 | **Root Cause** (שורש הבעיה) | **למה** זה קורה? לא הסימפטום, אלא הסיבה. |
| 4 | **Fix** (תיקון) | מתקנים את **הסיבה**, ולא מסתירים את הסימפטום. |
| 5 | **Verify** (בדיקה) | הבאג נעלם? **ושום דבר אחר לא נשבר?** |

{{< box "analogy" "אנלוגיה: רופא" >}}
רופא טוב לא נותן משכך כאבים לכל מטופל. הוא **משחזר** ("מתי זה כואב?"), **מבודד** ("איפה בדיוק?"), מוצא את **הסיבה** (בדיקות), **מטפל** בסיבה, ו**בודק** בביקורת שהמטופל החלים. משכך כאבים בלבד הוא "תיקון סימפטום". הכאב חוזר, והבעיה ממשיכה.
{{< /box >}}

### מהו 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`) | עצירת הדיבאג. |

{{< box "analogy" "Step Over מול Step Into" >}}
אתם קוראים מתכון, ויש בו שורה "הכינו בצק (ראו עמוד 40)". **Step Over** = מכינים בצק בלי לפתוח את עמוד 40, ועוברים לשורה הבאה במתכון. **Step Into** = פותחים את עמוד 40 וקוראים את מתכון הבצק שורה אחר שורה.
{{< /box >}}

## תחביר

**איך מפעילים את ה-Debugger ב-VS Code:**

1. ודאו שההרחבה **Python** של Microsoft מותקנת. היא כוללת את ה-Debugger.
2. פתחו קובץ `.py`.
3. **הצבת Breakpoint:** לחצו בשוליים השמאליים, משמאל למספר השורה. תופיע **נקודה אדומה** 🔴 (או `F9` על השורה).
4. **הפעלה:** `F5`, או החץ הקטן ליד כפתור ▷ ‏←‏ **Python Debugger: Debug Python File**. אם נשאלים איזו תצורה, בוחרים **Python File**.
5. התוכנית נעצרת על ה-Breakpoint. השורה הצהובה היא השורה **הבאה** לביצוע.
6. בצד שמאל מופיעים **Variables**, **Watch** ו-**Call Stack**. למעלה מופיע סרגל הכפתורים: Continue, Step Over, Step Into, Step Out, Restart, Stop.

**טריק:** אפשר גם לרחף עם העכבר מעל משתנה בקוד בזמן עצירה, ולראות את הערך שלו.

## דוגמת קוד ראשונה

נתחיל בתוכנית תקינה, רק כדי להכיר את הכלי. `week-2/debug_intro.py`:

```python
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)
```

**התרגיל:**

1. שימו Breakpoint על השורה `total = total + apply_tax(price)`.
2. `F5`. התוכנית נעצרת. בחלון Variables: `price = 100`, `total = 0`.
3. `F10` (Step Over). עכשיו `total = 117.0`, והשורה הצהובה חזרה ל-`for`.
4. `F5` (Continue). נעצרים שוב באותה שורה: `price = 250`.
5. הפעם `F11` (Step Into). אנחנו **בתוך** `apply_tax`, ו-`amount = 250`. ב-Call Stack רואים: `apply_tax` ‏←‏ `<module>`.
6. `F10` פעמיים: רואים `tax = 42.5`, ואז `return`.
7. `F5` עד הסוף.

**פלט:**

```text
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`:

```python
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**.

**פלט בפועל:**

```text
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:** מתחילים מהציון **הראשון** ברשימה. הוא תמיד מועמד אמיתי:

```python
def find_lowest(grades):
    lowest = grades[0]
    for grade in grades:
        if grade < lowest:
            lowest = grade
    return lowest
```

(ל-Python יש גם `min(grades)` מובנית, ובקוד אמיתי עדיף להשתמש בה. כאן כתבנו ידנית כדי לתרגל.)

**שלב 5 — Verify:**

```text
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 חשף באג שני. תמיד בודקים גם **מקרי קצה**: רשימה ריקה, איבר אחד, ערכים שליליים, אפס.

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## תרגול

התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.

### תרגיל 1: חמשת השלבים

סדרו את שלבי הדיבאג לפי הסדר, והסבירו במשפט מה עושים בכל שלב:

Fix · Reproduce · Verify · Understand · Isolate

{{< solution >}}
1. **Reproduce** — לגרום לבאג לקרות שוב, בכוונה, עם קלט ידוע.
2. **Isolate** — לצמצם: באיזו פונקציה, באיזו שורה, עם איזה ערך הבעיה מתחילה.
3. **Understand** — להבין **למה** זה קורה, לפני שמשנים קוד.
4. **Fix** — לתקן את שורש הבעיה, לא את הסימפטום.
5. **Verify** — להריץ שוב את המקרה שנכשל, וגם מקרים שעבדו קודם.
{{< /solution >}}

### תרגיל 2: המקסימום שיצא 0

```python
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 בכלל לא ברשימה.

1. איפה הייתם שמים Breakpoint, ומה הייתם רואים בחלון Variables?
2. תקנו את הבאג.

**פלט צפוי:**

```text
9
-3
```

{{< solution >}}
1. Breakpoint על השורה `if number > largest:`. בחלון Variables רואים ש-`largest` נשאר **0** לאורך כל הלולאה — אף מספר שלילי לא גדול מ-0.
2. מתחילים מהאיבר הראשון של הרשימה, ולא מ-0:

```python
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]))
```

```text
9
-3
```
{{< /solution >}}

### תרגיל 3: return בתוך הלולאה

```python
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 כדי לעקוב אחרי הלולאה — מה קורה אחרי האות הראשונה? תקנו.

**פלט צפוי:**

```text
3
```

{{< solution >}}
עם Step Over רואים שאחרי האות הראשונה (`b`, לא תנועה) ההרצה מגיעה ישר ל-`return count` — **בתוך** הלולאה — והפונקציה מסתיימת עם 0. ה-`return` מוזח רמה אחת יותר מדי.

```python
def count_vowels(word):
    count = 0
    for letter in word:
        if letter in "aeiou":
            count = count + 1
    return count


print(count_vowels("banana"))
```

```text
3
```
{{< /solution >}}

### תרגיל 4: כלי ה-Debugger

הסבירו במשפט אחד כל מונח, ומתי משתמשים בו:

1. Breakpoint
2. Step Over
3. Step Into
4. חלון Variables

{{< solution >}}
1. **Breakpoint** — נקודה שבה ההרצה **עוצרת**, כדי לבדוק את המצב באותו רגע. שמים אותו בשורה שלפני המקום החשוד.
2. **Step Over** — מריץ את השורה הנוכחית ועובר לבאה, **בלי** להיכנס לתוך פונקציות שנקראות בה.
3. **Step Into** — **נכנס לתוך** הפונקציה שנקראת בשורה הנוכחית, כדי לבדוק מה קורה בפנים.
4. **חלון Variables** — מראה את הערכים של כל המשתנים **ברגע הנוכחי**. כאן רואים איפה הערך שונה ממה שציפיתם.
{{< /solution >}}

### תרגיל 5: Off-by-one

```python
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. מצאו את הבאג ותקנו.

**פלט צפוי:**

```text
15
```

{{< solution >}}
`range(1, n)` **לא כולל** את `n` — הלולאה רצה על 1, 2, 3, 4 בלבד. זה באג "Off-by-one" קלאסי.

```python
def sum_to(n):
    total = 0
    for i in range(1, n + 1):
        total = total + i
    return total


print(sum_to(5))
```

```text
15
```
{{< /solution >}}

<!-- exercises:end -->

## סיכום

- **חמשת השלבים:** Reproduce → Isolate → Root Cause → Fix → Verify. לא מנחשים.
- **Breakpoint** עוצר **לפני** ביצוע השורה. **F5** = Continue, **F10** = Step Over, **F11** = Step Into, **Shift+F11** = Step Out.
- חלון **Variables** מראה את מצב התוכנית ברגע העצירה. **Call Stack** מראה מי קרא למי.
- Logical Errors מוצאים בהשוואה של ערכי המשתנים **בפועל** לערכים **הצפויים**, שורה אחר שורה.
- אחרי תיקון בודקים גם **מקרי קצה**: ריק, איבר אחד, אפס, שלילי.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## דוקומנטציה רשמית

- [VS Code — Python debugging](https://code.visualstudio.com/docs/python/debugging) — הרצת Debugger על קובץ Python
- [VS Code — Debugging: Breakpoints](https://code.visualstudio.com/docs/debugtest/debugging#_breakpoints) — Breakpoints, Step Over ו-Step Into

<!-- docs:end -->
