טיפול בחריגות (תכנות)
מתוך ויקיפדיה, האנציקלופדיה החופשית
טיפול בחריגות הינו מנגנון המשלב בתוכנית מחשב קוד שייעודו להתריע על שגיאות המתרחשות בזמן ריצת התוכנית ולטפל בהן. שגיאות כאלו גורמות לחריגה מזרימת התוכנית הרגילה.
תוכן עניינים |
[עריכה] שגיאות זמן ריצה
בכתיבת תוכנה ייתכנו סוגי שגיאות שונות, שחלקן עלולות לעורר בעיה בזמן ריצת התוכנית:
- שגיאות תחביר – הן טעויות תחביריות על פי חוקי שפת התכנות בה נכתב קוד מקור. שגיאות אלו מתגלות על פי רוב על ידי המהדר בשלב הקומפילציה והמהדר אינו מאפשר לקמפל את קובץ קוד המקור, עד לתיקון שגיאות אלו. עורכי קוד בסביבות פיתוח מודרניות אף מסמנים בצבע בולט את מיקומן של שגיאות תחביר, כך שהמתכנת מבחין בהן מיד עם כתיבתן. שגיאות תחביר – עקב תיקונן בזמן ההידור – אינן נחשבות לשגיאות זמן ריצה. דוגמה לשגיאת תחביר: חוסר של התו ; בסוף משפט, בשפות הדורשות זאת.
בשפות תסריט שגיאות תחביר מתגלות רק בזמן-ריצה, שכן לא מתבצע הידור כלל.
אחת התכונות האופיניות לשפות שתומכות בטיפוסיות חזקה היא בכך ששגיאות שימוש בטיפוסים מתגלות בזמן ההידור ובכך נמנעות שגיאות זמן ריצה. - שגיאות לוגיות – הינן אלגוריתמים שגויים לוגית, הגורמים לקבלת תוצאות לא רצויות בזמן ריצת התוכנית. מקורן של שגיאות אלו בטעות לוגית של המתכנת. לדוגמה, שימוש בתנאי if (x < 0) במקום שצריך להיות if (x <= 0). שגיאות לוגיות המאפשרות לתוכנית להמשיך ולהתבצע, אינן כלולות בחריגות.
- שגיאות שמקורן בקלט שגוי מהמשתמש – לדוגמה, כאשר משתמש מזין אפס כקלט לתכנת מחשבון ובהמשך המחשבון מנסה לחלק מספר כלשהו באפס שנקלט מהמשתמש. ניסיון זה יגרום לשגיאה כיוון שחלוקה באפס אינה חוקית. במקרים רבים ניתן לבצע תיקוף קלט במטרה למנוע שגיאות אלו.
- שגיאות מערכת – מתרחשות כאשר מערכת ההפעלה אינה יכולה למלא אחר בקשת התוכנית והיא מכשילה ביצוע של פקודה מסוימת. לדוגמה, במערכת ההפעלה ישנה הגבלה על מספר הקבצים שניתן לפתוח בו זמנית. תוכנית שתנסה לפתוח קובץ, כאשר כבר פתוחים מספר מקסימלי של קבצים, מערכת ההפעלה תמנע זאת.
- שגיאות חומרה – נגרמות בחומרת המחשב, למשל שינוי סיבית בעקבות קפיצה במתח החשמל.
[עריכה] חשיבות הטיפול בשגיאות
כאשר יוצרים תוכנית יש לתכנן מראש כיצד לפתור את כל הבעיות האפשריות שעלולות להתעורר. תוכנית טובה מתוכננת להתמודד עם דברים בלתי רצויים שעלולים להתרחש.
רבות משגיאות זמן הריצה ניתן למנוע כאשר מאתרים אותן עוד בשלב ניפוי השגיאות. במקרה כזה ניתן לתקן את הקוד הפגום ולהדר אותו מחדש. אך נותרות שגיאות, בדרך כלל בלתי צפויות, בהן תיתקל התוכנית בזמן הפעלתה על ידי המשתמש. ישנן אף שגיאות שניתן לצפותן מראש – כמו הכנסת שם קובץ שאינו קיים על ידי המשתמש, או שגיאות מערכת ושגיאות חומרה – ובכל זאת הטיפול בהן ייעשה בזמן ריצה של התוכנית.
המנעות מטיפול בשגיאה, עלולה לאפשר קשת של מצבים, שיגרמו לחוסר שביעות רצון ואף לכעס אצל של משתמשי התוכנה:
- התוכנה אינה מבצעת את ייעודה.
- התוכנה "נתקעת" – מצב זה עלול להגרם כתוצאה מלולאה אינסופית, בתוכנית שרצה על תרד אחד.
- התוכנה נכבת באמצע פעולתה.
- קבלת תיבת דו שיח ממערכת ההפעלה, או מסביבת זמן הריצה של התוכנית על שגיאה. תיבה זו מאפשרת למשתמש לסגור את התוכנית או לעיתים "לוותר" על ביצוע קטע הקוד הבעייתי.
רבות מהבעיות ניתנות לטיפול באמצעות כתיבת קוד שייתן להם מענה הולם בזמן הריצה. אך אף אם לא כותבים קוד שכזה, רצוי לפחות להציג בפני המשתמש הודעה שתיידע אותו בדרך אלגנטית על המתרחש, לפני סגירת התוכנית. כך תמנע ממנו תחושת המרמור והוא אף יידע בפני מה הוא עומד ואולי ידיעה זו תאפשר לו להמנע מהבעייה, כאשר יפעיל את התוכנית בפעם הבאה.
[עריכה] דרכי טיפול אפשריות בשגיאות זמן ריצה
קיימות מספר שיטות ידועות לטיפול בשגיאה המתרחשת בפונקציה, ולכולן ישנן בעיות:
- סיום התוכנית – דרך זו אינה רצוייה למשתמשי התוכנית. בדרך זו איננו מתמודדים עם השגיאה, אלא נכנעים לה. בדרך כלל נרצה להמנע מכך.
- החזרת ערך שגיאה – ניתן להחזיר מפונקציה שבה אירעה השגיאה ערך שיסמן שגיאה שקרתה בה. לדוגמה, -1. לדרך זו מספר חסרונות: ישנן פונקציות שעשויות להחזיר טווח רחב של ערכים שאינו מותיר מקום לערך שגיאה. בנוסף, מבנאי של אובייקט לא ניתן להחזיר ערך (אם כי מיקרוסופט הנהיגה ב־MFC פונקציה בשם OnInitInstance המחזירה ערך bool והמיועדת לעקוף בעייה זו, בכך שהיא מבצעת את איתחול האובייקטים במקום ה־construtor). דרך זו גם מחייבת בדיקה פרטנית של כל פונקציה הנקראת, האם היא החזירה ערך שגיאה. בדיקות אלו הופכות את הקוד למורכב ומקשות על הטיפול בו.
- משתנה דגל שבו ניתן לסמן שגיאה – שיטה זו מצריכה בדיקה קבועה של הדגל כדי שלא יידרס. והיא גם אינה יעילה בתכנות מקבילי.
בשפות מונחות עצמים ישנה בעייה נוספת, במקרה שמתרחשת חריגה באובייקט כלשהו, ייתכן שהפונקציה ההורסת שלו לא תופעל וזה עלול ליצור בעיות לא צפויות בתוכנית.
[עריכה] מנגנון הטיפול בחריגות
החסרונות המאפיינים את השיטות השונות, הביאו ליצירת שיטה חדשה. בשיטה זו פותח מנגנון המאפשר לתוכניות לאותת זו לזו על תקלות שמתרחשות בעת ריצת התוכנית ולהגן על הקוד.
שלושה אזורים בקוד מעורבים בטיפול בחריגות:
- הקטע שבו עלולה להתרחש התקלה – תפקידו לזרוק (throw) הודעת חריגה המתריעה ומדווחת לתוכנית שהפעילה אותו, על התרחשות תקלה ועל מהותה של התקלה.
- הקטע שקורא לקטע הקודם – זהו קטע מוגן, המנסה (try) להפעיל את הקוד, שבו ישנו חשש שישלחו התראות. במקרה שמתקבלת הודעת חריגה שנזרקה, ביצוע הקוד שבקטע המוגן מופסק מיידית והטיפול בחריגה מופנה לקטע הקוד הבא:
- הקטע המטפל בחריגה – בו נלכדת (catch) הודעת החריגה שנזרקה ובו מטפלים בבעיה.
בשפות מסוימות (לדוגמה ג'אווה) ניתן ליצור קטע מסיים (finally) לטיפול בחריגות. קטע זה מקומו לאחר קטעי ה-try וה-catch והוא יבוצע תמיד, בלא תלות בתהליך שקדם לו. קטע זה יטפל לדוגמה בשחרור זיכרון ובסגירת אובייקטים.
יש להגן על כל קטע קוד בו משערים שעלולה להתרחש שגיאה. את הקוד שעליו רוצים להגן מפני שגיאות זמן ריצה, יש להציב בקטע מוגן שינסה (try) לבצע אותו. אם הקוד שבקטע המוגן – או קוד שנקרא על ידי הקטע המוגן – זורק (throw) הודעת חריגה, ביצוע הקוד שבקטע המוגן ייעצר מייד והתוכנית תעבור לביצוע הקוד הרשום בקטע הלוכד (catch) את השגיאה. בקטע ה־catch ניתנת הזדמנות לביצוע קוד השולט במתרחש.
ניתן לקנן בלוקי try ו־catch. ניתן גם להגדיר משפטי catch מרובים עבור משפט try אחד. את משפטי ה-catch המרובים יש לכתוב בסדר הירארכי מהפרטני שבהם לכללי. בשפות מסוימות ניתן גם לטפל בשגיאה ולאחר מכן לזרוק אותה זריקה חוזרת, בכדי ליידע את הפונקציה הקוראת על התרחשות החריגה.
כאשר מתרחשות שגיאות שמקורן במערכת ההפעלה, מערכת ההפעלה שולחת התראות באמצעות הספריות הסטנדרטיות של המהדר.
מחסנית הקריאות (call stack) היא קטע זיכרון דינמי המאחסן נתונים עבור תוכנית מחשב. המחסנית נכנסת לפעולה כאשר אוגרי המחשב (registers) מתמלאים. במחסנית הקריאות של התוכנית, נשמרות מסגרות של פונקציות הקוראות זו לזו, אחת על גבי רעותה, החל מהפונקציה המתחילה את התוכנית (או ה־thread) וכלה בפונקציה האחרונה המתבצעת הנמצאת בראש מחסנית הקריאות.
כאשר מתעוררת חריגה, המחסנית מתחילה בחיפוש אחר פונקציה שבה קטע catch שילכוד את החריגה שהתעוררה ויטפל בה. החיפוש אחר קטע ה־catch, מתחיל בפונקציה שבראש המחסנית ובמקרה שאין בה קטע catch משוחרר הזיכרון שהוקצה לנתוני פונקציה זו והחיפוש עובר לפונקציה שתחתיה במחסנית וכך מפונקציה לפונקציה עד לזו המתחילה את התוכנית (main). אם נמצא קטע catch שילכוד את החריגה, התוכנית עוברת לביצוע הקוד שבו. אבל במקרה שלא נמצאה פונקציה שתטפל בחריגה, התוכנית מסתיימת.
מנגנון ניהול הטיפול בשגיאות כתוב באופן יעיל במיוחד מבחינת צריכת הזיכרון וזמן העיבוד. אך בסביבות שונות טיפול בחריגה שנזרקה כרוך בפגיעה בביצועי התוכנית. לכן בכל מקרה שניתן לטפל בחריגה באמצעי אחר, כגון באמצעות משפט if else, יש לשקול את הדבר ובמקרה שהשגיאה שכיחה כדאי להימנע מלהשתמש במנגנון הטיפול בחריגות, למרות שגם משפט התנאי כרוך בעלות זמן עיבוד. אך במקרה שהשגיאה אינה שכיחה עדיף להשתמש במנגנון הטיפול בחריגות.
[עריכה] התפתחות הטיפול בחריגות
מנגנון הטיפול בחריגות פותח לאחר הצגת שפת C++ והתקבל מאוחר יותר בוועידת ANSI. המהדר של בורלנד תומך בטיפול בחריגות, החל מגירסה 4.0 המשתמשת בספריית המחלקות 2OWL.3 ואילו סביבת הפיתוח Visual C++ החלה לתמוך בזה רק מאוחר יותר. אם כי גם גרסאות מוקדמות יותר שלה, נתנו מענה חלקי בעזרת פקודות מאקרו. Visual Studio .NET תומכת באופן מלא בטיפול בחריגות, בשפות להן היא מיועדת כגון - ++C, #C, VB.NET. בנוסף, גם שפת Java תומכת בטיפול בחריגות.
[עריכה] טיפול בחריגות בשפות תכנות שונות
הטיפול בחריגות נתמך בשפות תכנות שונות. אך ישנם הבדלים בנושא בין השפות השונות.
[עריכה] שפות מונחות עצמים
מנגנון הטיפול בחריגות מאפשר לזרוק התראה אף מהפונקציה הבונה, שאין לה ערך מוחזר. יש ממליצים להימנע מזריקת חריגה מהפונקציה הבונה, או משימוש בתוך הפונקציה הבונה בפונקציות שעשויות לזרק חריגה, כיוון שמתכנתים אינם מצפים לקבל חריגה מפונקציה זו ועלולים לא לטפל בה.
כאשר מגדירים אובייקטים בתוך בלוק try, במקרה של יציאה מן הבלוק עקב התרחשות חריגה, הפונקציות המפרקות של האובייקטים נקראות ומשתחרר זיכרון האובייקטים שנשמר על גבי המחסנית. דבר זה תורם ל"בטיחות" הקוד.
בשפות אלו, תכונת הפולימורפיזם מאפשרת ללכוד בבלוק ה־catch אובייקט מכל מחלקת שגיאה שנזרק, באמצעות התייחסות/מצביע למחלקת השגיאות הראשית ולהפעיל באמצעות התייחסות זו את הפונקציה הווירטואלית של המחלקה המוצבעת בפועל.
אם מובנית בשפה ספריית מחלקות לטיפול בחריגות, ניתן להגדיר מחלקה יורשת מהן, המותאמת לצרכי תוכנית כלשהי.
[עריכה] שפות NET.
שפות כדוגמת C#, שנכתב עבורן מהדר העומד בתקן CLS יכולות לנצל את יתרונות סביבת הריצה CLR של NET Framework. ובהן מחלקות מוכנות רבות לטיפול בחריגות – כגון: Exception, או ArithmeticException – הקיימות במרחב השמות הראשי System ובמרחבי השמות שתחתיו.
בכדי להשתמש באחת ממחלקות אלו, יש לפנות אליה דרך שם מרחב השמות בו היא נמצאת. אלא אם כן כתבנו בתחילת קובץ קוד המקור using על מרחב השמות המדובר, שאז ניתן לפנות אליה ישירות.
ניתן לזרוק חריגה תקנית של .Net Framework, בתוכנית הכתובה בשפת .Net אחת ולקלוט את החריגה בתוכנית הכתובה בשפת .Net אחרת.
[עריכה] C++
עקב תמיכת השפה במצביעים ובהקצאת זיכרון דינמית על הערימה, במקרה שנזרקת חריגה יש לתת את הדעת לשחרור הזכרון שהוקצה דינאמית על הערימה, כיוון שאיננו משתחרר מאליו. באופן רגיל, היה זה בלתי-אפשרי, אך הספריה הסטנדרטית של ++C מציעה דרך לבצע זאת באמצעות Smart Pointer בשם auto_ptr שעוטף את המצביעים, ודואג לשחרר את הזכרון שהוקצה אליהם במקרה של יציאה מטווח-ההכרה. מסיבה זו מומלץ על ידי ספרים רבים ועל ידי ביורן, סטרוסטרופ, אבי השפה, לעטוף את כל המצביעים ב-auto_ptr, שכן אין לדעת האם פונקציה הנקראת על ידי הפונקציה הנוכחית תיזום חריגה.
ההתראה הנשלחת יכולה להיות מטיפוסים שונים: מספר שלם, מחרוזת, מחלקה, או מצביע לאובייקט.
בעבר היה על המתכנתים ב־C++ לכתוב בעצמם את המחלקות המטפלות בחריגות. כך למשל כאשר פותחה ספריית MFC, בתחילה היא לא תמכה בטיפול בחריגות אלא באמצעות פקודות מאקרו – דומות לאלו שהיו קיימות כבר בשפת C – אך בהמשך פותחו וצורפו אליה מחלקות המטפלות בחריגות. עם צאת סטדנרט ANSI ל-C++ צורפו אליו פקודות סטנדרטיות (throw לזריקה, catch לתפיסה) לטיפול בשגיאות.
פונקציה יכולה להגדיר בכותרתה את סוג ההתראות שהיא שולחת (throw()). הגדרה שכזו מהווה התחייבות כלפי פונקציה הקוראת לפונקציה זו, שהתראות מסוג אחר לא יישלחו.
[עריכה] Java
היררכיית מחלקות החריגות בשפה זו, מתחילה במחלקה Object, ממנה יורשת Throwable המספקת פונקציות לאבחון הבעייה ומ־Throwable יורשות Error ובנותיה המיועדות לשגיאות חמורות עליהן על פי רוב לא ניתן להתגבר והתוכנית מסתיימת, וכן Exception ובנותיה המיועדות לחריגות הניתנות לטיפול ללא צורך בסיום התוכנית.
על פונקציה הזורקת חריגה, חובה להודיע על כך בכותרת הפונקציה (throws [exception name])
ב־Java לא ניתן להתעלם מקוד הזורק חריגה ופונקציה שבה קוד כזה, חייבת לטפל בו, או לזרוק את החריגה הלאה על ידי הכרזה על כך בכותרת הפונקציה.
קיימות חריגות המטופלות על ידי המכונה המדומה בזמן ריצה ושאין הכרח לטפל בהן. אך אם מעוניינים, אפשר לטפל גם בחריגות אלו.
[עריכה] Visual Basic
בשפה זו ישנו הבדל משמעותי בין גירסה 6.0 והגרסאות שקדמו לה – לבין גירסה 7.0, העומדת בתקן CLS של .NET.
- עד גירסה 6.0 (ו־6.0 בכלל) – הטיפול בחריגות נעשה באמצעים הבאים: פקודה בשם On Error נכתבת לפני קטע קוד מוגן והיא תכנס לפעולה במקרה שתתרחש שגיאה באחד המשפטים הבאים אחריה. ניתן להפנות מפקודה זו לקטע קוד המסומן בתווית, או לפונקציה המטפלת בשגיאה, על ידי כתיבת Goto [destination] אחריה. לחילופין ניתן לדלג באמצעות פקודת Resume לפקודה הבאה או לתווית שצויינה.
ניתן לקבל מידע על מספר השגיאה מהמשתנה Err. ניתן גם לקבל מחרוזת המתארת את השגיאה מהמשתנה Error. האובייקט Err הנוצר בעת התרחשות שגיאה, מכיל מאפיינים המייצגים את מספר השגיאה ואת תיאורה. - בגירסה 7.0 הנקראת Visual Basic .NET – ניתן להשתמש במחלקות החריגות של .NET Framework.
[עריכה] Perl
עד גרסה 5 לא היה טיפול בחריגות. החל מגרסה 5 התומכת חלקית בתכנות מונחה עצמים, ישנו טיפול בחריגות שמיושם על ידי die (לזריקה) ו־eval לתפיסה. המנגנון הזה משתמש בטכניקות של Perl לסיום התוכנית (שזאת פעולת ברירת המחדל). עד גרסה 5.6 ניתן היה לזרוק רק מחרוזות, אולם החל מגרסה זו קיימת גם האפשרות לזרוק אובייקטים.
[עריכה] לקריאה נוספת
- מאיר סלע, JAVA על כוס קפה – מרכז ההדרכה 2000.
- Bradley L. Jones, C# סדנת לימוד – SAMS והוד־עמי.
- יצחק גרבר, C++ כשפת אם – BUG.
[עריכה] קישורים חיצוניים
- Handling and Throwing Exceptions - In MSDN Library

