Smart-World Surf

יחידה 8: טיפול בחריגות

בניית קוד חזק ועמיד בפני שגיאות זמן ריצה.
מנגנון try-catch-throwהיררכיית חריגותחריגות מוגדרות משתמשRAII (Resource Acquisition Is Initialization)

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

יסודות הטיפול בחריגות: מנגנון try-catch-throw

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

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

מנגנון try-catch-throw:

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

כאשר חריגה נזרקת, המערכת "מתגלגלת לאחור" (unwinds the stack) עד שהיא מוצאת בלוק catch שיכול לטפל בסוג החריגה שנזרקה. אם לא נמצא בלוק catch מתאים, התוכנית תסתיים בדרך כלל באופן לא נורמלי.

טיפול בשגיאות עם try-catch

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

טיפול בשגיאות עם קודי חזרה

משלב את לוגיקת הטיפול בשגיאות בתוך לוגיקת התוכנית. מתאים למצבים צפויים או שגרתיים. דורש בדיקה ידנית של קוד החזרה בכל קריאה לפונקציה.

היררכיית חריגות וחריגות מוגדרות משתמש

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

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

חריגות מוגדרות משתמש:

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

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

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

RAII – עקרון מפתח לניהול משאבים בטוח

אחד האתגרים הגדולים בטיפול בחריגות הוא הבטחת שחרור נכון של משאבים (כגון קבצים פתוחים, זיכרון שהוקצה, נעילות) גם כאשר חריגה נזרקת. כאן נכנס לתמונה עקרון RAII (Resource Acquisition Is Initialization).

RAII (Resource Acquisition Is Initialization): עקרון תכנותי שבו ניהול משאבים (הקצאה ושחרור) קשור למחזור החיים של אובייקטים. משאבים נרכשים בבנאי של אובייקט ומשוחררים אוטומטית במפרק שלו.

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

דוגמאות נפוצות ליישום RAII כוללות מצביעים חכמים (smart pointers) לניהול זיכרון, מחלקות עטיפה (wrappers) לקבצים, נעילות (mutexes) ועוד.

בטיחות חריגות (Exception Safety): מדוע חשוב להבטיח שהקוד שלנו יתנהג בצורה צפויה ונכונה גם כאשר חריגות נזרקות. הבנה של רמות בטיחות חריגות (בסיסית, חזקה, ללא כשל) היא קריטית לתכנון מערכות עמידות.
  • בטיחות בסיסית (Basic Guarantee): אם חריגה נזרקת, התוכנית נשארת במצב תקין (לא מושחת), אך המצב המדויק של המערכת אינו מוגדר. לא תהיה דליפת משאבים.
  • בטיחות חזקה (Strong Guarantee): אם חריגה נזרקת, מצב התוכנית נשאר ללא שינוי, כאילו הפעולה מעולם לא התרחשה (commit or rollback).
  • ללא כשל (No-Fail Guarantee / Nothrow Guarantee): הפעולה מובטחת להצליח ולא לזרוק חריגה כלל.
בחינות נוטות לבחון את היכולת לזהות ולתכנן קוד העומד ברמות בטיחות חריגות שונות, במיוחד בהקשר של ניהול משאבים ו-RAII.

שאלות לדיון

  • תאר תרחיש שבו שימוש בחריגות עדיף באופן מובהק על פני החזרת קודי שגיאה, והסבר מדוע.
  • הסבר כיצד עקרון RAII תורם לבטיחות חריגות. תן דוגמה קונקרטית לאובייקט המיישם RAII.
  • מהם החסרונות הפוטנציאליים של שימוש נרחב בחריגות בתוכנית, ומתי יש לשקול חלופות?
  • תכנן מחלקת חריגה מוגדרת משתמש עבור מצב שגיאה ספציפי ביישום לניהול חשבונות בנק, לדוגמה, "InsufficientFundsException". אילו שדות מידע היית כולל בה?

נקודות לתשובת מודל

  • תרחיש חריגות מול קודי שגיאה: חריגות מתאימות לאירועים בלתי צפויים, חמורים, או כאלה שאינם חלק מזרימת הבקרה הרגילה. הן מפרידות את לוגיקת הטיפול בשגיאות, משפרות קריאות קוד ומונעות "בלגן" של בדיקות קוד שגיאה בכל שלב. דוגמה: קריסת מערכת קבצים, שגיאת רשת בלתי צפויה.
  • RAII ובטיחות חריגות: RAII מבטיח שחרור אוטומטי ודטרמיניסטי של משאבים באמצעות מפרקים של אובייקטים. כאשר חריגה נזרקת, המחסנית מתגלגלת לאחור, והמפרקים של אובייקטים מקומיים נקראים אוטומטית, ובכך מונעים דליפות משאבים ומבטיחים בטיחות בסיסית לפחות. דוגמה: std::unique_ptr או std::lock_guard.
  • חסרונות השימוש בחריגות:
    • ביצועים: זריקת חריגה וגלגול המחסנית כרוכים בעלות ביצועים משמעותית.
    • מורכבות זרימת בקרה: יכולות להקשות על הבנת זרימת הבקרה של התוכנית, במיוחד בקודים גדולים ומורכבים.
    • שימוש לרעה: שימוש בחריגות עבור זרימת בקרה רגילה (במקום if-else) הוא אנטי-תבנית.
  • תכנון חריגה מוגדרת משתמש (InsufficientFundsException):
    • תירש מ-std::exception (או מחלקת בסיס אחרת של חריגות).
    • שדות מידע: סכום ניסיון משיכה (double requestedAmount), יתרה נוכחית (double currentBalance), מספר חשבון (std::string accountNumber).
    • בנאי המקבל את השדות הללו ומאתחל אותם, ואולי גם הודעת שגיאה מפורטת.
    • מתודת what() (וירטואלית) שתחזיר הודעת שגיאה מפורטת.
מצאתם טעות או שחסר משהו?
→ הקודמת
תבניות (Templates/Generics)
הבאה ←
ניהול זיכרון מתקדם