דלג לתוכן

יסודות התכנות

מדעי מחשב באוניברסיטה · נושא 1

1.1

מה הקורס הזה הוא, וכיצד להשתמש בו

זהו הקורס הראשון במדעי מחשב באוניברסיטה: יסודות התכנות , המיועד עם Python 3. הקורס אינו דורש ידע קודם בתכנות. כל תוכנית ב חומר לימודי זה היא מלאה וניתנת להרצה — הכנס אותה, הרץ אותה, שנה שורה אחת, וחזה מה ישתנה לפני ההרצה השנייה. לולאת חזה-ואז-הרץ זוהי כל המיומנות.

הקורס הוא סולם:

  • יחידות 1.1 עד 1.3 בונות את המכניקה: ערכים וטיפים, בחירה, איטרציה.
  • יחידה 1.4 אורזת עבודה לפונקציות.
  • יחידות 1.5 ו1.6 מוסיפות את האוספים הסטנדרטיים והקבצים עם טיפול בשגיאות.
  • יחידה 1.7 היא התחום המרכזי: פירוק בעיות, בדיקות, ותיקון שגיאות.

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

1.1

ערכים, סוגים והזנה/הוצאה

אפליקציית קניות מציגה "4.20" — האם זה טקסט או מספר? אם זה טקסט, חיבור שני מחירים יניב "4.203.50"; אם זה מספר, יניב 8.7. איזה מהשניים יקרה נקבע על ידי הסוג, והכלל של יחידה 1.1 הוא ללמוד לראות סוגים לפני ש רצים את כלום.

ערך 📦 הוא בעל סוג, והסוג קובע מה ניתן לעשות עמו: int (מספרים שלמים), מספרים), float (עשרוניים), str (טקסט), bool (True/False). ה-type(...) מזהה סוג של כל ערך — ניתן להשתמש בה בחופשיות בזמן הלמידה.

age = 21              # int
price = 4.20          # float
name = "Ada"          # str
open_ = True          # bool
print(type(age))      # <class 'int'>

משתנים הם שמות הקשורים לערכים; הזנת ערך age = 22 מקשרת מחדש את השם — מעולם לא עורך את הערך הישן. התמונה המנטלית, ששווה לשמירה לכל תואר:

מצב התוכנה כתיבות: כל תג שם מצביע על תיבת הערך שלו; שינוי השקעה מניע את התג, אך לעולם לא עורך את התיבה. הביטוי 6 * 3.50 קורא שתי תיבות ומייצר תיבת float חדשה 21.0
השמות הם תוויות על קופסאות; ביטויים קוראים קופסאות ויוצרים קופסאות חדשות

קריאת ביטוי היא "להתקין את הקופסאות, לחשב, לסגור קופסה חדשה": count * 3.50 עם count = 6 מתקין את הקופסה int 6 ואת הקופסה float 3.50, ומערבול Python הכלל מעודד את int — התוצאה היא float 21.0, לעולם לא int 21.

ההמרה מפורשת: int("21") הוא 21, אך "4" + 2 הוא TypeError — Python מסרב לנחש האם התכוונת ל-"42" או ל-6. השתמש ב-print(...); קלט עם input(...), שחזר תמיד מחרוזת טקסט, ולכן מספרים צריכים המרה:

born = int(input("Birth year? "))
print("About", 2026 - born, "years old")

דוגמה פותרת 1 (קופסת הקפה). count = int(input("Coffees? ")), ולאחר מכן print("Total:", count * 3.50). עבור 6 קופות קפה זה מדפיס Total: 21.0 — ה-.0 מציין מספר עשרוני, כי int * float מעלה למספר עשרוני. מעקב אחר סוג התוצאה של ביטוי הוא כל ההתנהגות של 1.1. עקוב אחריו באופן מצבי: input מחזיר את המחרוזת "6"; int(...) יוצר קופסה חדשה שמכילה את ה-int 6; הכפל יוצר קופסת float חדשה 21.0; print מציג אותה.

דוגמה פותרת 2 (התאונה הקלאסית, מתוקנת). מדפסת לוחות משלבת ספירה על הודעה:

count = int(input("Tickets? "))
print("Sold " + count + " today")

הרץ את הקוד ו-Python יעורר TypeError: can only concatenate str (not "int") to str — הכלל מסרב לנחש. הפתרון מייחד במפורש את ההמרה: print("Sold " + str(count) + " today"), או ה-print("Sold", count, "today"), which inserts the spaces for you. The lesson to carry: + שפירושו חיבור למספרים, איחוד למחרוזות, ולעולם לא ערבוב שקט.

שגיאות נפוצות. לשכוח שה-input() תמיד מחזיר מחרוזת טקסט, ולכן input() * 2 חוזר על טקסט במקום להכפיל מספר. כתיבת 4.20 + 6 והתקוויות תוצאת int — כל מספר עשרוני בביטוי הופך אותו למספר עשרוני. הסמכה 0.1 + 0.2 == 0.3 (זהו False; עיגול בינארי — יחידת 1.2 מציגה את התיקון).

סיכום: ארבעה סוגים בסיסיים קובעים כל פעולה; השמות הם תגיגים, והאתאה מחברת מחדש; ביטויים מערבלים סוגים באמצעות הרמה עם מספרים עשרוניים; ההמרות הן גלומות ו- input() דורש אחד.

מילון מונחים אימון
English עברית
programming fundamentals/ˈprəʊɡræmɪŋ ˌfʌndəˈmentlz/ יסודות התכנות
type/taɪp/ סוג
set/set/ קבוצה
1.2

בחירה: בחירה בין מסלולים

סבב פארק שעשועים חייב לגבות מהילדים, מבוגרים ומקשישים כמות שונה — תוכנית אחת, מספר מסלולים. בחירה היא האופן שבו תוכנית בוחרת: if, elif, else מפעיל בלוק רק כאשר תנאי מתקיים.

תרשים זרימה של כללי המחירים בקולנוע עם בדיקת גיל לפני בדיקת הסטודנט; סטודנט בן 70 שנים לוקח את הערוץ האמתי הראשון ומשלם 5
גיל 70, תלמיד: הענף האמתי הראשון קובע את המחיר

התנאי הוא כל ביטוי בוליאני; השוואות (==, !=, <, >=) ו- לוגיקה (and, or, not) בונות אותו. שתי כללים הופכים כל החלטה לקריאה:

  1. הענפים נבדקים מלמעלה למטה; הענף האמיתי הראשון זוכה ושארם מוחרצים.
  2. הרווחים אינם קישוט — הם הבלוק. רווח אחד מעלה משנה אילו שורות שייכות לענף.
temp = 37.5
if temp >= 38:
    print("Fever")
elif temp >= 36 and temp < 38:
    print("Normal")
else:
    print("Low")

דוגמה מוצגת 1 (מחיר הקולנוע). תחת 12 או 65+ משלם 5, סטודנטים משלמים 7, כל שאר האנשים 10:

age = int(input("Age? "))
student = input("Student? y/n ") == "y"
if age < 12 or age >= 65:
    price = 5
elif student:
    price = 7
else:
    price = 10
print("Pay", price)

הסדר חשוב: הענף האמיתי הראשון זוכה, ולכן בדיקת הגיל באה לפני בדיקת התלמיד — תלמיד בן 70 אמור לשלם 5, והסדר הזה אומר זאת. רץ השתמש ב-70 וב-y כדי לראות את המסלול השמאלי ביותר של התרשים.

דוגמה מוצגת 2 (רמות ציון, מואצות). אותו שלד, עכשיו עם שרשרת שכל elif זקוק רק להשוואה אחת, כי הבדיקות שמעלה כבר נכשלו:

score = int(input("Score? "))
if score >= 80:
    band = "A"
elif score >= 70:
    band = "B"
elif score >= 60:
    band = "C"
else:
    band = "F"
print("Band:", band)

ב-79 הבדיקה הראשונה נכשלת, השנייה מצליחה: Band: B. ההאצה ל- שים לב: ה-elif score >= 70 אינו זקוק גם ל-and score < 80 — הגעה לקו זה כמעט מבטיחה שהציון הוא מתחת ל-80. האמנה בהמשך התוכנית (fall-through) כזו היא מה שהופך את השרשרות לקצרות; בדיקה מחדש של הכל הוא הגרסה הארוכה והשגויה יותר של חובבן. גרסה.

השוואות נקודה צפה דורשות זהירות: 0.1 + 0.2 == 0.3 הוא False (עיגול בינארי ), לכן השווה לסף סטייה: abs(x - y) < 1e-9.

שגיאות נפוצות. כתיבת = במקום == — הבאג הקלאסי, ו-Python דוחה אותו בתנאים (SyntaxError) בדיוק כי הוא נפוץ כל כך. בדיקת המקרה המיוחד לאחר הכללי, כך שהכללי תמיד מנצח. שימוש ב elif עם תנאי שלא יכול לעולם להיגש כי ענף קודם כבר תפס אותו.

סיכום: תנאים בוחרים מסלולים, ה-true הראשון מנצח, התחברות היא הגבלה; סדר בדיקות ספציפי-לכללי; הצפות משווים לפי סף סטייה, לעולם לא לפי ==.

1.3

איטרציה: חזרה על הפעולה

תשלום חשבוניות 5,000 ידנית הוא קריירה; עם לופ זה תוכנית. איטרציה חוזרת על עבודה: לופ for עובר על רצף ידוע; לופ while רץ בזמן ש- התנאי מתקיים. השאלה בעיצוב היא תמיד: האם ידוע מספר הנסיעות, או ש- תנאי קובע?

total = 0
for i in range(1, 101):      # 1..100 — range stops BEFORE its end
    total += i
print(total)                  # 5050

השתמש בfor כאשר מספר הנסיעות ידוע; השתמש בwhile כאשר תנאי קובע. כל while דורש שלושה דברים נראים בקוד: נקודת ההתחלה של המשתנה, התנאי, ומשהו בתוך זה שמזיז לכיוון יציאה — אחרת זו לולאת אינסופית. לולאה אינסופית**.

לולאת הפקטוריאלי לצד טבלת הסימון שלה: n ופקטוריאלי לאחר כל סיבוב, מ5 ועד 1, למעלה ל1 ולמעלה ל120, כאשר התנאי הופך ל-False
טבלת מעקב ללולאת הפקטוריאלי
n = 5
factorial = 1
while n > 1:
    factorial *= n
    n -= 1                    # the step toward exiting
print(factorial)              # 120

טבלת המעקב היא הרגל בדיקה של הקורס עבור לולאות: עקוב אחרי המשתנים סיבה אחר סיבה, על נייר תחילה. עבור לולאת הפקטוריאלי: (n=5, f=1) → (4, 5) → (3, 20) → (2, 60) → (1, 120). בנה את הטבלה ידנית, ואז הוסף print(n, factorial) בתוך הלולאה והאפשר ל-Python לדרג אותך.

2 גדולה יותר מ-break — מספר ההשלכות לא ידוע, התנאי מחליט: 7 שהוא גדול יותר מ-100 — מספר הטיולות אינו ידוע, התנאי מחליט:

n = 100
while True:
    if n % 7 == 0:
        break                 # found it — leave the loop now
    n += 1
print(n)                      # 105

המעקב קצר: 100, 101, …, 104 כולם נכשלים בבדיקה; 105 עובר ו-break מיטביים עם break. הה escalated הנוספת, הכפלת הכסף: ב-continue% בשנה, כמה זמן כלי קריאות — לולאה שחייבת לסרוק עד שתמצא משהו, ואז תיעצר, קריאה י ל-break להגיע ל-5? יציג מעבר של המאזן לקו ב-1000, ולא נחיתה מדויקת ב-2000.

balance = 1000.0
years = 0
while balance < 2000.0:       # the condition itself is the goal
    balance *= 1.05
    years += 1
print(years, round(balance, 2))   # 15 2078.93

חמש עשר שנים, והלולאה עצירה ברגע שהתנאי משתנה — טבלת המעקב ההגדרה מספקת פרמטר (2078.93, 2000); הקריאה מספקת פונקציה ללא

שגיאות נפוצות. עריכת רשימה (יחידה 1.5) או משתנה מחזור שאתם מחזירים עליו ב-for. range(1, 100) כאשר התכוונתם ל-100 — הסוף אינו כלול. while שבו חסר שלב יציאה או הוא מוגדר כהערה: התנאי לעולם לא מתהפך, והתוכנית נתקעת.

סיכום: for לספירות ידועות, while לתנאים; בכל מחזור מופיעים תחילה, תנאי ושלב; לבצע מעקב על נייר, ואז להדפיץ ברמת ציון; break ו-continue עבור יציאות מוקדמות מסודרות.

1.4

פונקציות: הגדרת משימה פעם אחת

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

def total_price(count, unit=3.50):
    """Return the total for count items at unit price."""
    return count * unit

print(total_price(6))            # 21.0
print(total_price(6, unit=4.0))  # 24.0

count מחזירה unit, והקורא שניסה לבצע עליו פעולות אריתמטיות (6). ערך ברירת מחדל (unit=3.50) הופך את הארגומנט לאופציונלי. return מחזיר ערך וחוסם את הקריאה; הדפסה אינה החזרת ערך — פונקציה פונקציה ללא return מחזירה None, והקורא שניסה ביצוע אריתמטי עליו מתקבל TypeError.

שלושה מסגרות קריאה: פונקציית הדפסה מקיימת את order_total(250, 4), שהופסקה בזמן ש-add_fee(1000) רצה; 1003 מוחזר לאורך השרשרת לקריאה כלשהי יש תחום מקומי משלה: שמות בתוכה בלתי נראים

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

דוגמה פתורה 1 (ההזמנה הכוללת עמלה).

def add_fee(amount):
    """Return amount plus a fixed 3 pence fee."""
    return amount + 3

def order_total(unit_pence, quantity):
    """Return the order total: subtotal plus the fixed fee."""
    return add_fee(unit_pence * quantity)

print(order_total(250, 4))   # 1003

החוזה בתוך המסביר (docstring) חשוב: פנסים שלמים נכנסים, פנסים שלמים יוצאים. מעקב order_total(250, 4): סכום ביניים 1000, add_fee מחזירה 1003 — והמסגרת ברשימה למעלה מראה את העצירה והמסירה.

דוגמה פתורה 2 (אלגוריתם אמיתי בתוך פונקציה קטנה, מוגבה). is_prime(n) מחזירה True כאשר שום דבר מ-2 ועד $\sqrt{n}$ אינו מחלק את n:

def is_prime(n):
    if n < 2:
        return False
    d = 2
    while d * d <= n:        # only test up to the square root
        if n % d == 0:
            return False     # found a divisor — answer known, stop
        d += 1
    return True

print([p for p in range(2, 30) if is_prime(p)])
# [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

שני כללי פונקציות נפגשים כאן: חזרה מוקדמת ב-return מסתיימת את השיחה ברגע שהתשובה ידועה (אין צורך במשתנה סימון), וגבול הלולאה d * d <= n נובע מה הסקת הנוסחה — אם $n = a \times b$ עם $a \le b$, אזי $a \le \sqrt{n}$, ולכן בדיקה מעבר ל-$\sqrt{n}$ בודק מחלקים שנראו כבר. הרץ אותו: הרשימה היא בדיוק המספרים הראשוניים מתחת ל-30.

טעויות נפוצות. הדפסה בתוך פונקציה במקום החזרת ערך, כך שהתוצאה לא ניתנת לשימוש חוזר. שכחת ה-return בנתיב אחד — מסלול זה מייצר None בשקט. גישה למשתנים מקומיים של פונקציה מחוץ לה; הם נעלמים כאשר השיחה נסתיימת.

סיכום: חזרה אחת ב-def, שיחה בכל מקום; פרמטרים נכנסים, ערך אחד יוצא באמצעות return; חזרות מוקדמות מסתיימות את השיחה; משתנים מקומיים נולדים ומתים עם השיחה; מסבירים (docstrings) מגדירים את החוזה.

מילון מונחים אימון
English עברית
string/strɪŋ/ מחרוזת
selection/sɪˈlekʃn/ בחירה
iteration/ˌɪtəˈreɪʃn/ איטרציה
infinite loop/ˈɪnfɪnət luːp/ לולאה אינסופית
trace table/treɪs ˈteɪbl/ טבלת מעקב
function/ˈfʌŋkʃn/ פונקציה
return value/rɪˈtɜːn ˈvæljuː/ ערך חזרה
parameter/pəˈræmɪtə/ פרמטר
argument/ˈɑːɡjuːmənt/ פרמטר אקטואלי
local scope/ˈləʊkl skəʊp/ תחום פעולה מקומי
list/lɪst/ רשימה
dictionary/ˈdɪkʃənəri/ מילון
tuple/ˈtuːpl/ טופל
1.5

אוספים: רשימות, מילונים, טופלים, קבוצות

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

רשימה היא רצף מסודר וניתן לשינוי: marks = [72, 85, 61], עם אינדקס מתחיל ב-0, וחוצה עם marks[1:3]. מילון מקשר מפתחות לערכים: price = {"flat white": 3.5, "tea": 2.8}, נקרא ונכתב לפי מפתח. טופל הוא רצף קבוע (53.1, -1.5) — מתאים לקואורדינטות שלא צריכות להשתנות. סט מחזיק פריטים ייחודיים ללא סדר: {"a", "b"}.

marks = [72, 85, 61]
marks.append(90)               # [72, 85, 61, 90]
best = max(marks)              # 90
average = sum(marks) / len(marks)   # 77.0
שני שמות a ו-b מצביעים על אותו אובייקט רשימה אחת, שהכילה 1 ו-2 לאחר b.append(2)
הפניה כפולה: שני שמות, רשימה אחת

הערכה לעולם לא עותק: a = [1]; b = a; b.append(2) גורמת ל-a להיות [1, 2] — הרשימה משותפת, השמות אינם. הפניה הכפולה הזו היא קופסאות היחידה 1.1 שוב, עכשיו עם תווית כפולה על קופסה אחת, והיא מסבירה גם את הכלל הבא: שינוי ברשימה בזמן סיור בה דורש דלגת אלמנטים — סרוק על עותק (for m in marks[:]) או בנה רשימה חדשה.

דוגמה פותרת 1 (ספירה לפי קטגוריה).

orders = ["tea", "flat white", "tea", "tea", "mocha"]
tally = {}
for item in orders:
    tally[item] = tally.get(item, 0) + 1
print(tally)   # {'tea': 3, 'flat white': 1, 'mocha': 1}

המפתח ה-.get(key, 0) במילון מספק את ה-0 לצפייה ראשונה — הביטוי המקובל הופך ספירה לשורה אחת. בדוק ידנית: "tea" מופיע שלוש פעמים ב-orders, ומדפסת הספירה מאשרת זאת.

דוגמה פותרת 2 (המוצר הנמכ ביותר ביום, מורחב). על בסיס הספירה, דווח על הפריט המוביל ואת מספר הופעותיו:

best = max(tally, key=tally.get)
print(best, tally[best])       # tea 3

הארגומנט key=tally.get מבקש מ-max להשוות פריטים לפי ספרייתם ולא לפי אלפביתית — אותה טריק key= מדגדר כל אוסף לפי כל מדד. ה- סדר החשוב: תוכניות אמיתיות מקשרות כלים אלו (מנייה, ואז דירוג) ולא סורקים מחדש את orders פעמיים.

בחירת האוסף היא החלטת העיצוב: סדר + כפילות → רשימה; חיפוש לפי שם → מילון; קבוצות קבועות → טופל; ייחודיות → ערך.

טעויות נפוצות. גישה ל-tally[item] += 1 במפתח שאינו קיים עוד במילון (KeyError) — ה-.get הוא הפתרון. ציפייה מכך שערך או מילון יזכרו סדר. הנחה כי b = a מעתיקה רשימה; היא יוצרת קישור אליה.

סיכום: ארבעה מיכלים לארבעה משימות; השמה משתפה, לעולם לא מעתיקה; .get מונעת בעיות במנייה; key= הופכת את הדירוג לקו אחד.

מילון מונחים אימון
English עברית
aliasing/ˈeɪlɪəsɪŋ/ כינוי
1.6

קבצים ושגיאות

הקפה נסגר, החשמל ננתק — והתוצאה של הבוקר תשרד רק אם התוכנית כתבה אותה לדיסק. קובץ חייך מהתוכנית. with open(path) as f: opens, closes on exit, and reads text; open(path, "w") כותבת (וריקה תחילה — התאונה הקלאסית).

with open("log.txt", "w") as f:
    f.write("opened\n")

with open("log.txt") as f:
    for line in f:
        print(line.rstrip())     # rstrip removes the trailing newline

בלוק ה-with הוא מעקה הבטיחות: בכל אירוע בתוכו, הקובץ ייסגר. קריאה מחזירה שורות עם סימן הברית, ולכן .rstrip() מתאים לכל לולאה.

שגיאות הן ערכים שמטביע Python כ-Exception: FileNotFoundError, ValueError, KeyError, TypeError. try/except תופס סוג ממונה — לעולם לא except: חשוף שמסתיר באגים אמיתיים.

דוגמה מודגמת 1 (קובץ ההגדרות החסר). קריאת גודל טקסט מקובץ הגדרות עשוי להיות חסר או עשוי שלא להכיל שלם:

try:
    with open("settings.txt") as f:
        size = int(f.read().strip())
except FileNotFoundError:
    size = 12                    # no file yet: a sensible default
except ValueError:
    size = 12                    # the file holds "big", not a number
print("Text size", size)

הפעל אותו שלוש פעמים — עם 16 בקובץ, ללא קובץ, ועם big ב קובץ — והוא מדפיס גודל שימושי בכל פעם, משלושה מסלולים שונים. זוהי צורת הקוד העמיד: אחד הגיוני עקבית אחת, דרכים רבות המובילות אליה. התמודד עם השגיאה הצפויה, כשהיא מאפשרת תגובה; היתר יקרוש ברוע גבוה.

קורא הגדרות: בלוק ה-try נפתח, קורא וממיר את הערך; הוא או מצליח, או שבענף FileNotFoundError או ValueError נקבע הגודל הרצוי 12; כל שלושת המסלולים מתחברים והתוכנית ממשיכה

דוגמה מפורטת 2 (לולאת קלט רובוסטית, בדרגה גבוהה). בקש עד שהמשתמש יקליד מספר שלם:

def read_count():
    while True:
        try:
            return int(input("How many? "))
        except ValueError:
            print("Whole numbers only.")

print(read_count())

הזן לה x ולאחר מכן 4: ההפעלה הראשונה מעלה שגיאה, נלכדת, מדפיסה הודעה, וה-while True מבקש שוב; ההפעלה השנייה מצליחה ב-int(...) וה-return יוצאת מהלולאה וגם מהפונקציה במהלך אחד. שלושה יחידות מתחברות כאן — הלולאה (1.3), חוזקת הפונקציה (1.4) והשגיאה (1.6) — ועל כך המהות של ההצלפה.

שגיאות נפוצות. ⟨open(path, "w")⟩ במסלול הלא נכון: הקובץ נוצר או מתפנה לפני שתשים לב. לכידת ⟨Exception⟩ בצורה רחבה והמשכה עם מצב פגום. שוכחים ל.rstrip(), ולכן התוצאה מציגה שורות בעלות רווח כפול.

סיכום: ⟨with open(...)⟩ לקריאה וכתיבה; ⟨"w"⟩ מפנה קודם כל; שגיאות הן ערכים — לכיד את הסוג הממונה הצפוי, היכנע באופן הגיוני, והיתר השאר נכשל.

מילון מונחים אימון
English עברית
value/ˈvæljuː/ ערך
variables/ˈveərɪəblz/ משתנים
file/faɪl/ קובץ
exceptions שגיאות
decomposition/ˌdiːkɒmpəˈzɪʃn/ פירוק
testing/ˈtestɪŋ/ בדיקות
debugging/ˈdiːbʌɡɪŋ/ ניקוד שגיאות
1.7

פירוק, בדיקה ותיקון שגיאות

שתי צוותות כותבות את אותו אפליקציה; אחת מושלמת אותה בשבוע, והשנייה טובעת במשך חודש. ההבדל אינו מהירות הקלדה — אלא כך שהקוד של הצוות המהירה בנוי מחלקים קטנים שהם יכולים להוכיח כל אחד מהם כנכון בעצמו, בעוד שהצוות האיטית מתקנת שגיאות באפליקציה שלמה בו-זמנית. יחידת 1.7 היא המשמעת הזו.

פירוק מפצל בעיה לפונקציות עם משימה אחת בכל אחת — כל אחת קטנה לספיק כדי לבדוק אותה בנפרד. בדיקה משווה תוצאת ביצוע בפועל של פונקציה ל המצופה, בגבולות: אפס, אחד, ריק, שלילי, ענק.

לולאת מבוססת בדיקות: כתיבת בדיקה שנכשלת שמגדירה את החוזה; הרצה שלה וראיית הכישלון מהסיבה הנכונה; כתיבת הגוף הפשוט ביותר שעובר; הרצה חוזרת עד ירוק; עריפת קוד עם הבדיקות שמחזיקות את הצורה
לולאת מבוססת בדיקות: אדום, ירוק, עריפת קוד
def add_fee(amount):
    return amount + 3

def order_total(unit_pence, quantity):
    return add_fee(unit_pence * quantity)

assert order_total(250, 4) == 1003
assert order_total(250, 0) == 3      # the zero-quantity boundary
assert order_total(0, 4) == 3

יחידת assert שלא פעילה מעולם היא בדיקה עוברת. כתבו את הבדיקות לפני שתכתבו את הגוף כשהאפשרות קיימת — הן החוזה שהפך לביצועי, ולולאה למעלה (אדום → ירוק → עריפת קוד) היא ההרגל הזה פועל ברציפות.

דוגמה מפורשת 1 (הרחבת הבדיקות לצד הפונקציה). הרחיבו את order_total עם הנחה של 10% מעל £10 — הבדיקה נכתבת תחילה, בגבול החדש:

def order_total(unit_pence, quantity):
    subtotal = add_fee(unit_pence * quantity)
    if subtotal >= 1000:             # pence: over £10
        subtotal = int(subtotal * 0.9)
    return subtotal

assert order_total(200, 4) == 803    # under £10 — unchanged
assert order_total(250, 4) == 902    # 1003 pence → 10% off → 902.7 → 902
assert order_total(250, 5) == 1127   # 1253 pence → 1127.7 → 1127

בדוקו את הבדיקה האמצעית ידנית: סך חלקי 1003, הנחה של עשר אחוזים נותנת 902.7, ו int(...) מעוגל כלפי מטה ל-902. שים לב גם לאסרט החשוב הראשון: ב-200 פניס וארבע כוסות, הסך הכול נשאר 803 — מבחני הגבול מוכיחים גם שההנחה מופעלת גם שאינה מופעלת מוקדם מדי.

דוגמה מודגמת 2 (חלוקה לשיניים של שגיאה עם הדפסות, חריגה). הספירה מ-1.5 הדפסות {'tea': 3} אך קו המכר ביותר מדפיס את המשקה הלא נכון. השיטה: (1) לשחזר עם הקלט הכושל הקטן ביותר; (2) להדפיס את הערכים בגבול בין החלק המאומת לחלק המוטרד — כאן, הספירה לפני ה-max; (3) לתקן דבר אחד; (4) להריץ מחדש את הבדיקות. הזזת ההדפסה באמצע הקוד בכל ריצה מחצית את החיפוש: ארבע הדפסות מוצאות שגיאה בתוכנה בעלת שש עשרה שורות, אך ככל שהיא מסתרת.

תיקון שגיאות הוא מציאת ההפרש בין מה שמשתמרים התוכנית עושה ובין מה she עושה בפועל — ההדפסה בגבול האמון/החשד היא הכלי.

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

סיכום: פירוק עד כל חלק בודד נבדק לבדו; ביצוע אסרט על הגבולים ברגע שקיימת פונקציה; כתיבת הבדיקה לפני הקוד כאשר אפשר; תיקון שגיאות באמצעות חלוקה לשיניים עם הדפסות בגבולי האמון — הרגל שעליה נבנה כל תואר לימודים.

שיעורים אינטראקטיביים בנושא זה

לעבור על הדברים צעד אחר צעד, עם תרגילים לבדיקה מיידית.

נושאים נוספים במדעי מחשב באוניברסיטה

היכנס או צור חשבון

IGCSE, A-Level & AP