# Debugging א': מהו Bug, סוגי שגיאות וקריאת Stack Trace<no value>

## מה נלמד?

- מהו **Bug**.
- שלושת סוגי השגיאות: **Syntax Error**, **Runtime Error**, **Logical Error**.
- איך קוראים **Stack Trace** (Traceback), הודעת השגיאה של Python.
- השגיאות הנפוצות ביותר, ומה כל אחת אומרת.

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

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

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

### מהו Bug?

**Bug** (באג) הוא **פער בין מה שהתוכנה אמורה לעשות לבין מה שהיא עושה בפועל.** לפעמים התוכנה קורסת, ולפעמים היא "עובדת" אבל מחזירה תוצאה שגויה.

{{< box "tip" "למה קוראים לזה Bug?" >}}
ב-1947 מצאו מהנדסים של מחשב Mark II עש (bug) תקוע בתוך אחד הממסרים, והדביקו אותו ביומן העבודה עם הכיתוב "first actual case of bug being found". המילה הייתה בשימוש עוד קודם לתקלות טכניות, אבל הסיפור הזה הפך אותה למפורסמת.
{{< /box >}}

### שלושת סוגי השגיאות

| סוג | מתי מתגלה | האם Python מודיעה? | דוגמה |
|---|---|---|---|
| **Syntax Error** | **לפני** הריצה (בבדיקת התחביר) | כן, ושום שורה לא רצה | חסר `:` או סוגר |
| **Runtime Error** (Exception) | **בזמן** הריצה, בשורה הבעייתית | כן, והשורות שלפניה כבר רצו | חלוקה ב-0, משתנה לא קיים |
| **Logical Error** | **אף פעם** אוטומטית | **לא!** הקוד רץ "בהצלחה" | `+` במקום `-`, תנאי הפוך |

זוכרים את נושא מחשב, מערכת הפעלה ורשת? Syntax Error נתפסת כש-Python מתרגמת ל-Bytecode, לפני הביצוע. Runtime Error קורית כשה-PVM מבצע שורה ספציפית ונכשל.

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

### Stack Trace — מפת השגיאה

כש-Runtime Error קורית, Python מדפיסה **Traceback** (נקרא גם Stack Trace): המסלול שהקוד עשה עד לשגיאה.

```text
Traceback (most recent call last):
  File "shop.py", line 9, in <module>
    total = checkout(cart)
  File "shop.py", line 5, in checkout
    return subtotal / count
ZeroDivisionError: division by zero
```

**קוראים מלמטה למעלה:**

1. **השורה האחרונה** — **סוג** השגיאה (`ZeroDivisionError`) ו**הודעה** (`division by zero`). מתחילים תמיד מכאן.
2. **השורה שמעליה** — **איפה** בדיוק: קובץ `shop.py`, שורה 5, בתוך הפונקציה `checkout`, והקוד עצמו.
3. **מעליה** — **מי קרא** לפונקציה הזו: שורה 9, ברמה הראשית של הקובץ (`<module>`).

למה "Stack"? כי כל קריאה לפונקציה נערמת על ה**מחסנית** (Call Stack) של הקריאות. ה-Traceback מציג את הערימה ברגע הקריסה, מהקריאה הראשונה (למעלה) ועד האחרונה (למטה).

### השגיאות הנפוצות ב-Python

| שגיאה | משמעות | סיבה נפוצה |
|---|---|---|
| `SyntaxError` | הקוד לא תקין תחבירית | חסר `:`, סוגריים, מירכאות |
| `IndentationError` | הזחה שגויה | ערבוב רווחים, הזחה לא עקבית |
| `NameError` | שם לא מוגדר | שגיאת כתיב, שימוש לפני הגדרה |
| `TypeError` | פעולה על טיפוס לא מתאים | `"5" + 3`, קריאה לפונקציה עם מספר ארגומנטים לא נכון |
| `ValueError` | טיפוס נכון, ערך לא תקין | `int("hello")` |
| `ZeroDivisionError` | חלוקה באפס | מכנה שלא נבדק |
| `IndexError` | אינדקס מחוץ לרשימה | `items[5]` כשיש 3 איברים |
| `KeyError` | מפתח לא קיים ב-dict | (נלמד בנושא מבני נתונים ופונקציות) |
| `AttributeError` | אין לאובייקט תכונה/מתודה כזו | `"abc".push()`, שגיאת כתיב במתודה |
| `FileNotFoundError` | קובץ לא קיים | נתיב שגוי, Working Directory |

## תחביר

אין תחביר חדש. הכלי הוא **קריאה**. התבנית של כל Traceback:

```text
Traceback (most recent call last):
  File "<file>", line <N>, in <function or <module>>
    <the code on that line>
  ...                                  ← more frames, each a function call
<ErrorType>: <message>                 ← START READING HERE
```

ושל Syntax Error (בלי Traceback, כי שום דבר לא רץ):

```text
  File "<file>", line <N>
    <the code>
        ^                              ← where Python noticed the problem
SyntaxError: <message>
```

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

`week-2/bug_report.py`:

```python
def average(numbers):
    total = sum(numbers)
    return total / len(numbers)


scores = []
print("Average score:", average(scores))
```

**פלט:**

```text
Traceback (most recent call last):
  File "C:\dev\kodkod6\week-2\bug_report.py", line 7, in <module>
    print("Average score:", average(scores))
                            ^^^^^^^^^^^^^^^
  File "C:\dev\kodkod6\week-2\bug_report.py", line 3, in average
    return total / len(numbers)
           ~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zero
```

## פירוק הקוד שורה אחר שורה

**קריאת ה-Traceback מלמטה למעלה:**

| שורה בפלט | מה היא אומרת לנו |
|---|---|
| `ZeroDivisionError: division by zero` | **מה** קרה: חלוקה באפס. |
| `line 3, in average` + `return total / len(numbers)` | **איפה**: שורה 3, בתוך `average`. ה-`~~~^~~~` מסמן בדיוק את פעולת החילוק. |
| `line 7, in <module>` + `average(scores)` | **מי קרא**: שורה 7, ברמה הראשית. `<module>` = "לא בתוך פונקציה". |

**הסקת המסקנה:** `len(numbers)` הוא 0, כלומר הרשימה ריקה. ומי העביר רשימה ריקה? שורה 7 העבירה את `scores`, שהוא `[]`. **הבאג האמיתי** הוא שלא טיפלנו במקרה של רשימה ריקה. השורה שקרסה (3) היא **הסימפטום**, וההחלטה מה לעשות ברשימה ריקה היא **התיקון**:

```python
def average(numbers):
    if len(numbers) == 0:
        return 0
    return sum(numbers) / len(numbers)
```

## דוגמה נוספת

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

**Syntax Error:**

```python
def price_after_discount(price, percent)
    return price - price * percent / 100
```

```text
  File "discount.py", line 1
    def price_after_discount(price, percent)
                                            ^
SyntaxError: expected ':'
```

חסרות נקודתיים. Python לא הריצה **אף שורה**.

**Runtime Error:**

```python
def price_after_discount(price, percent):
    return price - price * percent / 100

print(price_after_discount("100", 10))
```

```text
Traceback (most recent call last):
  File "discount.py", line 4, in <module>
    print(price_after_discount("100", 10))
  File "discount.py", line 2, in price_after_discount
    return price - price * percent / 100
                   ~~~~~~~~~~~~~~~~^~~~~
TypeError: unsupported operand type(s) for /: 'str' and 'int'
```

התחביר תקין, אבל בזמן ריצה `price` הוא טקסט. `"100" * 10` "עובד" (חוזר על הטקסט 10 פעמים!), אבל אז החילוק ב-100 נכשל. ה-`^` מצביע על ה-`/`.

**Logical Error:**

```python
def price_after_discount(price, percent):
    return price - percent / 100

print(price_after_discount(100, 10))
```

```text
99.9
```

**אין שום שגיאה!** אבל הנחה של 10% על 100 צריכה להחזיר `90.0`. שכחנו להכפיל ב-`price`. Python לא יכולה לדעת מה **התכוונו**. רק בדיקה מול תוצאה צפויה תגלה את זה.

## טעות נפוצה

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

**הטעות 2: לתקן את השורה שקרסה, במקום את הסיבה.** בדוגמה הראשונה, "פתרון" כמו `return total / (len(numbers) + 1)` מעלים את השגיאה, אבל מחזיר תוצאה **שגויה** בכל מקרה! זה הפך Runtime Error ל-Logical Error. יותר גרוע.

**הטעות 3: לחפש את ה-Syntax Error בשורה שמופיעה בהודעה בלבד.** לפעמים הבעיה בשורה **הקודמת**:

```python
prices = [10, 20, 30
total = sum(prices)
```

```text
  File "test.py", line 1
    prices = [10, 20, 30
             ^
SyntaxError: '[' was never closed
```

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

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

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

### תרגיל 1: איזה סוג שגיאה?

לכל קטע — האם זו SyntaxError, Runtime Error או Logical Error? ומה הבעיה?

```text
# A
if age > 18
    print("adult")

# B
print(total_price)        # total_price was never defined

# C
def area(width, height):
    return width + height   # should be the area of a rectangle
```

{{< solution >}}
- **A — SyntaxError**: חסרות נקודתיים `:` בסוף שורת ה-`if`. שום שורה לא רצה.
- **B — Runtime Error** (`NameError`): התחביר תקין, אבל בזמן ריצה Python לא מוצאת משתנה בשם `total_price`.
- **C — Logical Error**: אין שום שגיאה, אבל התוצאה לא נכונה — שטח מחשבים בכפל, `width * height`. את השגיאה הזו מוצאים רק אם בודקים את התוצאה.
{{< /solution >}}

### תרגיל 2: קריאת Stack Trace

```text
Traceback (most recent call last):
  File "shop.py", line 12, in <module>
    print(checkout([20, 30], 0))
  File "shop.py", line 7, in checkout
    return average_price(prices, count)
  File "shop.py", line 3, in average_price
    return sum(prices) / count
ZeroDivisionError: division by zero
```

1. מה סוג השגיאה ומה ההודעה?
2. באיזה קובץ, באיזו שורה ובאיזו פונקציה היא קרתה?
3. מאיפה הגיע הערך הבעייתי?

{{< solution >}}
1. **`ZeroDivisionError: division by zero`** — חלוקה באפס. קוראים את השורה **האחרונה** קודם.
2. בקובץ `shop.py`, **שורה 3**, בפונקציה `average_price` — זו השורה הכי נמוכה ברשימה.
3. קוראים למעלה: `average_price` נקראה משורה 7 ב-`checkout`, ו-`checkout` נקראה משורה 12 עם `count = 0`. כלומר הבעיה התחילה **בקריאה בשורה 12**, שהעבירה 0.
{{< /solution >}}

### תרגיל 3: באג לוגי בהמרת טמפרטורה

```python
def to_celsius(fahrenheit):
    return fahrenheit - 32 * 5 / 9

print(to_celsius(212))
print(to_celsius(32))
```

התוצאות צריכות להיות 100 ו-0, אבל יוצא משהו אחר. מצאו את הבאג ותקנו.

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

```text
100.0
0.0
```

{{< solution >}}
```python
def to_celsius(fahrenheit):
    return (fahrenheit - 32) * 5 / 9


print(to_celsius(212))
print(to_celsius(32))
```

```text
100.0
0.0
```

סדר פעולות: כפל וחילוק קודמים לחיסור, ולכן הקוד המקורי חישב `fahrenheit - (32 * 5 / 9)`. הסוגריים מכריחים את החיסור לקרות קודם. זו **Logical Error** — אין הודעת שגיאה, רק תוצאה לא נכונה.
{{< /solution >}}

### תרגיל 4: TypeError

```python
age = 25
print("Age: " + age)
```

1. איזו שגיאה תתקבל, ולמה?
2. תקנו בשתי דרכים שונות.

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

```text
Age: 25
```

{{< solution >}}
1. **`TypeError: can only concatenate str (not "int") to str`** — אי אפשר לחבר טקסט ומספר עם `+`.
2. שתי דרכים:

```python
age = 25
print("Age: " + str(age))
```

```text
Age: 25
```

```python
age = 25
print("Age:", age)
```

```text
Age: 25
```

הראשונה ממירה את המספר לטקסט. השנייה מעבירה ל-`print` שני ערכים, והוא מוסיף רווח ביניהם.
{{< /solution >}}

### תרגיל 5: ממוצע של רשימה ריקה

```python
def average(numbers):
    return sum(numbers) / len(numbers)

print(average([80, 90]))
print(average([]))
```

1. מה יקרה בקריאה השנייה, ואיזו שגיאה תתקבל?
2. תקנו את הפונקציה כך שעבור רשימה ריקה היא תחזיר 0.

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

```text
85.0
0
```

{{< solution >}}
1. `len([])` הוא 0, ולכן מתקבל **`ZeroDivisionError: division by zero`** — שגיאת זמן ריצה.
2. בודקים את המקרה הזה **לפני** החלוקה:

```python
def average(numbers):
    if len(numbers) == 0:
        return 0
    return sum(numbers) / len(numbers)


print(average([80, 90]))
print(average([]))
```

```text
85.0
0
```
{{< /solution >}}

<!-- exercises:end -->

## סיכום

- **Bug** = פער בין ההתנהגות הרצויה לבין ההתנהגות בפועל.
- **Syntax Error** — לפני הריצה, שום שורה לא רצה. **Runtime Error** — באמצע הריצה, עם Traceback. **Logical Error** — אין הודעה, רק תוצאה שגויה.
- **Traceback קוראים מלמטה למעלה:** סוג + הודעה ‏←‏ שורה ופונקציה ‏←‏ מי קרא.
- השורה שקרסה היא לפעמים רק **הסימפטום**. מחפשים את **הסיבה**.
- Logical Errors מתגלים רק כשמשווים תוצאה ל**ערך צפוי**.

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

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

- [Python Tutorial — Syntax Errors](https://docs.python.org/3/tutorial/errors.html#syntax-errors) — שגיאות תחביר
- [Python Tutorial — Exceptions](https://docs.python.org/3/tutorial/errors.html#exceptions) — שגיאות בזמן ריצה וה-Traceback
- [Built-in Exceptions](https://docs.python.org/3/library/exceptions.html#concrete-exceptions) — מה אומר כל סוג שגיאה

<!-- docs:end -->
