טיפול בחריגות

במחשוב ותכנות מחשבים, טיפול בחריגות הוא מנגנון המזהה, מתריע ומגיב לאירועים חריגים המתרחשים בזמן ריצת התכנית, משבשים את פעולתה התקינה ודורשים טיפול מיוחד המקודד מראש לטיפול במקרים כאלו. מנגנון הטיפול בחריגים עשוי להיות משולב בשפת התכנות או במערכת ההפעלה או בחומרת המחשב. מנגנון הטיפול בחריגות מאפשר הפרדה בין קוד העוסק בביצוע היישום שלצורך ביצועו נכתבה התוכנית, לבין קוד שתפקידו לטפל במקרים חריגים ושגיאות מערכת. בעת כתיבת תוכנה ייתכנו סוגי שגיאות שונים. שגיאות עלולות לגרום בעיה בזמן ההידור (כגון שימוש לא תקני בתחביר השפה) או בזמן הריצה. מנגנון הטיפול בחריגות לא נועד לטפל בשגיאות הידור אלא בשגיאות ריצה בלבד. שגיאות לוגיות – אלגוריתמים שגויים לוגית, הגורמים לקבלת תוצאות לא רצויות בזמן ריצת התוכנית. מקורן של שגיאות אלו בטעות לוגית של המתכנת. שגיאות שמקורן בקלט שגוי – לדוגמה, כאשר הקלט עבור המחלק (בפעולת חילוק בין שלמים) הוא אפס, תיגרם שגיאה כיוון שחלוקה באפס אינה חוקית. שגיאות מערכת – מתרחשות כאשר מערכת ההפעלה אינה יכולה למלא אחר בקשת התוכנית והיא מכשילה את ביצועה של פקודה. לדוגמה, ניסיון להקצות זיכרון כאשר לא נותר מקום פנוי בזיכרון המחשב יגרום לשגיאה. כאשר מתרחשות שגיאות שמקורן במערכת ההפעלה, מערכת ההפעלה שולחת התראות באמצעות הספריות הסטנדרטיות של המהדר. שגיאות חומרה – נגרמות בחומרת המחשב, למשל שינוי סיבית בעקבות קפיצה במתח החשמל. רבות משגיאות זמן הריצה ניתן למנוע בשלב ניפוי השגיאות (Debugging). במקרה כזה ניתן לתקן את הקוד הפגום ולהדרו מחדש. סוג נוסף של שגיאות הוא כזה שניתן לצפותו מראש אך לא ניתן למנוע אותו, והטיפול בו ייעשה בזמן ריצה של התוכנית. טיפול בחריגות מאפשר לטפל בשגיאות הנוצרות בזמן ריצה. שלושה אזורים בקוד מעורבים בטיפול בחריגות: הקטע בו עלולה להתרחש התקלה – תפקידו לזרוק (Throw) הודעת חריגה המתריעה ומדווחת לתוכנית שהפעילה אותו, על התרחשות תקלה ועל מהותה של התקלה. הקטע הקורא – זהו קטע מוגן, המנסה (Try) להפעיל את הקוד, שבו ישנו חשש שיזרקו חריגות, והיודע כיצד לטפל בהן (או בחלקן). במקרה שמתקבלת הודעת חריגה שנזרקה, ביצוע הקוד שבקטע המוגן מופסק מיידית והטיפול בחריגה מופנה לקטע הקוד המטפל בחריגה. הקטע המטפל בחריגה – בו נלכדת (Catch) הודעת החריגה שנזרקה ומופעל קטע הקוד לטיפול בחריגה. בשפות מסוימות, ניתן ליצור קטע מסיים לטיפול בחריגות. קטע זה ממוקם לאחר קטעי ה-Try וה-Catch והוא יבוצע תמיד, בלא תלות בתהליך שקדם לו. קטע זה יטפל לדוגמה בשחרור זיכרון ובסגירת התחברויות. בחלק משפות התכנות ניתן לקנן בלוקי try ו־catch, ניתן גם להגדיר משפטי catch מרובים עבור משפט try אחד כדי שייטפלו בסוגי חריגות שונים. בשפות מסוימות ניתן לבצע טיפול חלקי בשגיאה, ולאחר מכן לזרוק אותה שוב, להמשך טיפול. כאשר מתעוררת חריגה מתבצע חיפוש במחסנית הקריאות אחר פונקציה שבה קטע Catch שילכוד את החריגה שהתעוררה ויטפל בה. החיפוש אחר קטע ה־Catch מתחיל בפונקציה שבראש המחסנית ומחפש כלפי מטה. אם נמצא קטע Catch שילכוד את החריגה, התוכנית עוברת לביצוע הקוד שבו. אם לא נמצאה פונקציה שתטפל בחריגה, התוכנית מסתיימת. חסרונו של מנגנון ניהול הטיפול בחריגות הוא בפגיעה בביצועי התוכנית, לפיכך נהוג להשתמש בו רק במקרים שבהם יעילות התוכנית לא תיפגע באופן משמעותי כתוצאה מכך. אף על פי שמנגנון זה מהווה בעצם שיטת בקרת זרימה אלטרנטיבית, וניתן להשתמש בו למטרות אחרות (כגון יציאה מלולאה אינסופית) לא מומלץ להשתמש בו שלא לצורך טיפול בשגיאות למעט במקרים חריגים. מנגנון הטיפול בחריגות פותח לאחר הצגת שפת C++ והתקבל מאוחר יותר על ידי ה-ANSI. המהדר של בורלנד תומך בטיפול בחריגות, החל מגרסה 4.0 המשתמשת בספריית המחלקות 2OWL.3. גרסאות מוקדמות של סביבת הפיתוח Visual C++ אפשרו טיפול חלקי בחריגות באמצעות פקודות מאקרו. לאחר מכן אפשרה סביבת הפיתוח Visual Studio.NET טיפול מלא בחריגות. הטיפול בחריגות נתמך בשפות תכנות שונות. מנגנון הטיפול בחריגות מאפשר לזרוק חריגה אף מפונקציה בונה שאין לה ערך מוחזר. קיימת בעיה בזריקת חריגה מהפונקציה הבונה, או בשימוש בתוך הפונקציה הבונה בפונקציות שעשויות לזרוק חריגה, כיוון שישנם הקשרים בהם לא ניתן לטפל בחריגה כזאת - לדוגמה, בעת אתחול משתנים גלובליים. במקרה של יציאה מבלוק Try עקב התרחשות חריגה, הפונקציות המפרקות של האובייקטים נקראות וזיכרון האובייקטים משתחרר. באופן זה נוצר קוד בטוח יותר. בשפות אלו, תכונת הפולימורפיזם מאפשרת ללכוד בבלוק ה־Catch אובייקט מכל מחלקת שגיאה שנזרק, באמצעות התייחסות/מצביע למחלקת השגיאות הראשית ולהפעיל באמצעות התייחסות זו את הפונקציה הווירטואלית של המחלקה המוצבעת בפועל. אם בשפה קיימת ספריית מחלקות לטיפול בחריגות, ניתן להגדיר מחלקה יורשת מהן, המותאמת לצורכי תוכנית כלשהי. ב++C ניתן לזרוק כל אובייקט שהוא, כולל מספרים, מצביעים או טיפוסים שהוגדרו על ידי המשתמש. השפה מאפשרת - אך לא מחייבת - הגדרה בחתימת הפונקציה של רשימת האובייקטים אותם הפונקציה עשויה לזרוק. פונקציה המשתמשת באפשרות הזאת איננה יכולה לזרוק חריגות אחרות. בעבר נהגו מתכנתי C++ לכתוב בעצמם את המחלקות המטפלות בחריגות, אך עם השנים פותחו עבור השפה מחלקות המטפלות בחריגות. בתקן ה-ANSI ל++C צורפו אליו פקודות סטנדרטיות לטיפול בחריגות. כאשר נזרקת חריגה ב++C, הבקרה לא מופנית ישירות אל הנקודה שבה יתבצע הטיפול בחריגה, אלא עולה בהיררכיית הקריאות תוך קריאה ל־destructor של כל אובייקט שהוקצה על המחסנית ויצא מטווח הקריאה (scope). כך נמנעת אפשרות שלא יתבצע destructor עבור משתנים מקומיים לפונקציה בזמן יציאה ממנה, דבר שעלול לגרום דליפות זיכרון ובמקרים מסוימים להתנהגות לא מוגדרת של התוכנית. טכניקת תכנות נפוצה מנצלת תכונה זו על מנת לדאוג לכך שכל משאב מערכת מוגבל שנתפס - זיכרון שהוקצה דינמית, קובץ שנפתח, מנעול וכדומה - ישוחרר בכל מקרה, על ידי יצירת אובייקט שכל תפקידו הוא לבצע את השחרור של המשאב בזמן ההריסה (לדוגמה, מצביעים חכמים לטיפול בהקצאות זיכרון דינמיות). אם נזרקת חריגה בהקשר האסור על ידי השפה, למשל זריקת חריגה נוספת תוך כדי טיפול בחריגה נוכחית (ע"פ רוב באמצעות destructor), או זריקת חריגה שלא נכללת ברשימה שהוגדרה בחתימת הפונקציה, מתבצעת קריאה מיידית לפונקציה terminate שמטרתה להביא לסיום התוכנית. בשפת Java קיימות מספר מחלקות היררכיות לטיפול בחריגות. ניתן לזרוק אך ורק מחלקות היורשות מהמחלקה האבסטרקטית Throwable בה קיימות פונקציות המחזירות מידע על החריגה. מ־Throwable יורשות Error ובנותיה המיועדות לשגיאות חמורות, וכן Exception ובנותיה המיועדות לחריגות הניתנות לטיפול ללא צורך בסיום התוכנית. ב-Java יש הפרדה בין שני סוגים של חריגות. חריגת זמן ריצה (Runtime Exception) וחריגה רגילה. חריגת זמן ריצה היא חריגה היורשת מהמחלקה RuntimeException, והיא יכולה להיזרק גם במהלך פעולה תקינה של המערכת. (לדוגמה, NullPointerException, הנזרקת במקרה של ניסיון קריאה לפונקציה באובייקט לא מאותחל). פונקציה המאפשרת זריקת חריגה שאינה חריגת זמן ריצה, מחויבת להצהיר על כך בכותרת הפונקציה. אין צורך להצהיר על קוד העלול לזרוק חריגות זמן ריצה. ב־Java לא ניתן להתעלם מקוד הזורק חריגה שאינה חריגת זמן ריצה, ופונקציה שבה קוד כזה, חייבת לטפל בחריגה או לזרוק אותה הלאה על ידי הצהרה על כך בכותרת הפונקציה. ב-Java ניתן להשתמש במילה השמורה finally לאחר בלוק try על מנת לוודא ביצוע פעולות הכרחיות בכל מקרה, כגון שחרור משאבים חיוניים (כיוון שאין בשפה destructor-ים, לא ניתן להשתמש באובייקטים לצורך כך, כמו שתואר לגבי ++C לעיל). שפות כדוגמת C#, שנכתב עבורן מהדר העומד בתקן CLS יכולות לנצל את יתרונות סביבת הריצה CLR של NET Framework. ובהן מחלקות מוכנות רבות לטיפול בחריגות – כגון: Exception, או ArithmeticException – הקיימות במרחב השמות הראשי System ובמרחבי השמות שתחתיו. ניתן לזרוק חריגה תקנית של.Net Framework, בתוכנית הכתובה בשפת.Net אחת ולקלוט את החריגה בתוכנית הכתובה בשפת.Net אחרת. עד גרסה 6.0 (כולל) – הטיפול בחריגות נעשה באמצעות פקודות השייכות לשפה אשר מפעילות שגרת טיפול. העברת נתונים לגבי החריגה התבצעה באמצעות האובייקט Err אשר נוצר בעת התרחשות שגיאה. החל מגרסה 7.0 הנקראת Visual Basic.NET – ניתן להשתמש במחלקות החריגות של.NET Framework שפת Perl החלה לתמוך בטיפול בחריגות החל מגרסה 5. עד גרסה 5.6 ניתן היה לזרוק רק מחרוזות, אולם החל מגרסה זו קיימת גם האפשרות לזרוק אובייקטים. החל מגרסה 5 שפת התכנות PHP תומכת בחריגות. ניתן לזרוק מופעים של המחלקה Exception או מחלקות היורשות ממנה (קיימת מחלקה מובנית אחת שכזו - ErrorException). בPHP ניתן לתפוס חריגות הן בעזרת מבנה try catch, והן על ידי קביעת פונקציה לטיפול בחריגות בעזרת הפונקציה set_exception_handler. קוד PL/SQL בנוי מבלוקים. לטיפול בחריגות ניתן להוסיף בלוק EXCEPTION. מאיר סלע, JAVA על כוס קפה – מרכז ההדרכה 2000. Bradley L. Jones, C# סדנת לימוד – SAMS והוד־עמי. Stroustrup, Bjarne (2000). The C++ Programming Language, Special Edition, Addison-Wesley. ISBN 0-201-70073-5. Robert C. Martin (2008), Clean Code: A Handbook of Agile Software Craftsmanship, Pearson, ISBN 9780136083238 Handling and Throwing Exceptions - In MSDN Library Java Exception Handling - Jakob Jenkov Article "C++ Exception Handling" by Christophe de Dinechin PHP Exceptions - php.net