Debugging א': מהו Bug, סוגי שגיאות וקריאת Stack Trace
- להגדיר מהו Bug ולהבחין בין Syntax Error, Runtime Error ו-Logical Error
- לקרוא Stack Trace — לזהות סוג שגיאה, הודעה, קובץ ושורה
- להכיר את השגיאות הנפוצות ב-Python ואת הסיבה לכל אחת
- להבין למה Logical Error היא הקשה ביותר למציאה
מה נלמד?
- מהו Bug.
- שלושת סוגי השגיאות: Syntax Error, Runtime Error, Logical Error.
- איך קוראים Stack Trace (Traceback), הודעת השגיאה של Python.
- השגיאות הנפוצות ביותר, ומה כל אחת אומרת.
למה צריך את זה?
מפתחים מבלים חלק ענק מהזמן במציאת באגים ותיקונם. ההבדל בין מתחיל למקצוען הוא לא שלמקצוען אין באגים, אלא שהוא קורא את הודעת השגיאה ויודע תוך שניות איפה לחפש. מתחילים רואים הרבה טקסט אדום ונבהלים. בסוף השיעור, הטקסט האדום יהפוך למפה.
הסבר מהבסיס
מהו Bug?
Bug (באג) הוא פער בין מה שהתוכנה אמורה לעשות לבין מה שהיא עושה בפועל. לפעמים התוכנה קורסת, ולפעמים היא “עובדת” אבל מחזירה תוצאה שגויה.
ב-1947 מצאו מהנדסים של מחשב Mark II עש (bug) תקוע בתוך אחד הממסרים, והדביקו אותו ביומן העבודה עם הכיתוב “first actual case of bug being found”. המילה הייתה בשימוש עוד קודם לתקלות טכניות, אבל הסיפור הזה הפך אותה למפורסמת.
שלושת סוגי השגיאות
| סוג | מתי מתגלה | האם 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): המסלול שהקוד עשה עד לשגיאה.
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קוראים מלמטה למעלה:
- השורה האחרונה — סוג השגיאה (
ZeroDivisionError) והודעה (division by zero). מתחילים תמיד מכאן. - השורה שמעליה — איפה בדיוק: קובץ
shop.py, שורה 5, בתוך הפונקציהcheckout, והקוד עצמו. - מעליה — מי קרא לפונקציה הזו: שורה 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:
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, כי שום דבר לא רץ):
File "<file>", line <N>
<the code>
^ ← where Python noticed the problem
SyntaxError: <message>דוגמת קוד ראשונה
week-2/bug_report.py:
def average(numbers):
total = sum(numbers)
return total / len(numbers)
scores = []
print("Average score:", average(scores))פלט:
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) היא הסימפטום, וההחלטה מה לעשות ברשימה ריקה היא התיקון:
def average(numbers):
if len(numbers) == 0:
return 0
return sum(numbers) / len(numbers)דוגמה נוספת
שלושת סוגי השגיאות על אותה פונקציה. הפונקציה אמורה לחשב מחיר אחרי הנחה באחוזים.
Syntax Error:
def price_after_discount(price, percent)
return price - price * percent / 100 File "discount.py", line 1
def price_after_discount(price, percent)
^
SyntaxError: expected ':'חסרות נקודתיים. Python לא הריצה אף שורה.
Runtime Error:
def price_after_discount(price, percent):
return price - price * percent / 100
print(price_after_discount("100", 10))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:
def price_after_discount(price, percent):
return price - percent / 100
print(price_after_discount(100, 10))99.9אין שום שגיאה! אבל הנחה של 10% על 100 צריכה להחזיר 90.0. שכחנו להכפיל ב-price. Python לא יכולה לדעת מה התכוונו. רק בדיקה מול תוצאה צפויה תגלה את זה.
טעות נפוצה
הטעות 1: לקרוא את ה-Traceback מלמעלה, ולהיבהל. תמיד מתחילים מהשורה האחרונה: סוג ההודעה. אחר כך השורה שמעליה. רוב הזמן אלה כל מה שצריך.
הטעות 2: לתקן את השורה שקרסה, במקום את הסיבה. בדוגמה הראשונה, “פתרון” כמו return total / (len(numbers) + 1) מעלים את השגיאה, אבל מחזיר תוצאה שגויה בכל מקרה! זה הפך Runtime Error ל-Logical Error. יותר גרוע.
הטעות 3: לחפש את ה-Syntax Error בשורה שמופיעה בהודעה בלבד. לפעמים הבעיה בשורה הקודמת:
prices = [10, 20, 30
total = sum(prices) File "test.py", line 1
prices = [10, 20, 30
^
SyntaxError: '[' was never closedPython הגיעה לשורה 2, וגילתה שהסוגר של שורה 1 לא נסגר. בגרסאות חדשות ההודעה מדויקת, אבל באופן כללי: אם השורה המסומנת נראית תקינה, בדקו את זו שלפניה.
תרגול
התרגילים מבוססים רק על מה שנלמד בשיעור הזה ובשיעורים שלפניו. נסו לבד, ורק אז פתחו את הפתרון.
תרגיל 1: איזה סוג שגיאה?
לכל קטע — האם זו SyntaxError, Runtime Error או Logical Error? ומה הבעיה?
# 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הצג פתרון
- A — SyntaxError: חסרות נקודתיים
:בסוף שורת ה-if. שום שורה לא רצה. - B — Runtime Error (
NameError): התחביר תקין, אבל בזמן ריצה Python לא מוצאת משתנה בשםtotal_price. - C — Logical Error: אין שום שגיאה, אבל התוצאה לא נכונה — שטח מחשבים בכפל,
width * height. את השגיאה הזו מוצאים רק אם בודקים את התוצאה.
תרגיל 2: קריאת Stack Trace
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- מה סוג השגיאה ומה ההודעה?
- באיזה קובץ, באיזו שורה ובאיזו פונקציה היא קרתה?
- מאיפה הגיע הערך הבעייתי?
הצג פתרון
ZeroDivisionError: division by zero— חלוקה באפס. קוראים את השורה האחרונה קודם.- בקובץ
shop.py, שורה 3, בפונקציהaverage_price— זו השורה הכי נמוכה ברשימה. - קוראים למעלה:
average_priceנקראה משורה 7 ב-checkout, ו-checkoutנקראה משורה 12 עםcount = 0. כלומר הבעיה התחילה בקריאה בשורה 12, שהעבירה 0.
תרגיל 3: באג לוגי בהמרת טמפרטורה
def to_celsius(fahrenheit):
return fahrenheit - 32 * 5 / 9
print(to_celsius(212))
print(to_celsius(32))התוצאות צריכות להיות 100 ו-0, אבל יוצא משהו אחר. מצאו את הבאג ותקנו.
פלט צפוי:
100.0
0.0הצג פתרון
def to_celsius(fahrenheit):
return (fahrenheit - 32) * 5 / 9
print(to_celsius(212))
print(to_celsius(32))100.0
0.0סדר פעולות: כפל וחילוק קודמים לחיסור, ולכן הקוד המקורי חישב fahrenheit - (32 * 5 / 9). הסוגריים מכריחים את החיסור לקרות קודם. זו Logical Error — אין הודעת שגיאה, רק תוצאה לא נכונה.
תרגיל 4: TypeError
age = 25
print("Age: " + age)- איזו שגיאה תתקבל, ולמה?
- תקנו בשתי דרכים שונות.
פלט צפוי:
Age: 25הצג פתרון
TypeError: can only concatenate str (not "int") to str— אי אפשר לחבר טקסט ומספר עם+.- שתי דרכים:
age = 25
print("Age: " + str(age))Age: 25age = 25
print("Age:", age)Age: 25הראשונה ממירה את המספר לטקסט. השנייה מעבירה ל-print שני ערכים, והוא מוסיף רווח ביניהם.
תרגיל 5: ממוצע של רשימה ריקה
def average(numbers):
return sum(numbers) / len(numbers)
print(average([80, 90]))
print(average([]))- מה יקרה בקריאה השנייה, ואיזו שגיאה תתקבל?
- תקנו את הפונקציה כך שעבור רשימה ריקה היא תחזיר 0.
פלט צפוי:
85.0
0הצג פתרון
len([])הוא 0, ולכן מתקבלZeroDivisionError: division by zero— שגיאת זמן ריצה.- בודקים את המקרה הזה לפני החלוקה:
def average(numbers):
if len(numbers) == 0:
return 0
return sum(numbers) / len(numbers)
print(average([80, 90]))
print(average([]))85.0
0סיכום
- Bug = פער בין ההתנהגות הרצויה לבין ההתנהגות בפועל.
- Syntax Error — לפני הריצה, שום שורה לא רצה. Runtime Error — באמצע הריצה, עם Traceback. Logical Error — אין הודעה, רק תוצאה שגויה.
- Traceback קוראים מלמטה למעלה: סוג + הודעה ← שורה ופונקציה ← מי קרא.
- השורה שקרסה היא לפעמים רק הסימפטום. מחפשים את הסיבה.
- Logical Errors מתגלים רק כשמשווים תוצאה לערך צפוי.
דוקומנטציה רשמית
- Python Tutorial — Syntax Errors — שגיאות תחביר
- Python Tutorial — Exceptions — שגיאות בזמן ריצה וה-Traceback
- Built-in Exceptions — מה אומר כל סוג שגיאה