מדריך ניהול שגיאות בסי שארפ


ניהול שגיאות

הבסיס

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

העלאת שגיאה

נתקל בשגיאות עקב כשלון בביצוע הקוד שרשמנו, למשל לקיחת ערך ממקום הלא קיים במערך,

string[] Names = new string[]{"Yuval Shem Tov","Trevor Phillips","Calvin Harris"};
Console.WriteLine(Names[5])

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

throw new Exception("THERE IS AN ERROR OMG DONT PANIC DONT PANIUCCCCCCCCCCC AHHHHH!!!!!!");

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

שגיאה!

האובייקט

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

  • Message - הודעה המתארת את השגיאה שנוצרה.
  • Data - מילון המכיל מידע נוסף אודות השגיאה הנרצה להעביר. נשתמש בו כך,
    Exception ex = new Exception("yea, this is an exception.");
    ex.Data["ExceptionTime"] = DateTime.Now;
    ex.Data["ExceptionFavoriteNumber"] = 4;
    throw ex;

    נוכל לרשום במילון אובייקטים מכל סוג, שנשתמש בהם לאחר מכן בהתמודדתנו עם השגיאה. לפעולה הבונה של השגיאה, כמו שעשינו כאן, נוכל להזין ישירות את מאפיין הMessage
  • InnerException - מכיל הפניה לשגיאה הגרמה לשגיאה הנוכחית. נוכל להשתמש באובייקט זה כאשר השגיאה שנגרמה, נובעת משגיאה ההתרחשה מפעולה אליה קראנו, נוכל להזין את שהגיאה הגורמת כמאפיין זה. בהמשך נסקור מצב זה.
  • StackTrace - טקסט הנוצר באופן אוטומטי המתאר את המקום בקוד בו התרחשה השגיאה. לדוגמא,
    at Fetch(String Property, Int32 UserID)
    at FetchUserInfo(Int32 UserID)

    כאן מתואר שהשגיאה הועלתה בתוך הפעולה Fetch, שנקראה מFetchUserInfo.

היורשים

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

  • ArgumentException - הוזן ערך לא תקין.
  • InvalidOperationException - ניסיון לבצע פעולה לא תקינה.
  • FormatException הוזן ערך בפורמט לא תקין.
  • TimeoutException - עבר זמן רב מדי בניסיון לביצוע פעולה.

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

תפוס ת'שגיאה

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

  • try{} - בבלוק הניסיון נרשום קוד הנרצה כי יבוצע.
  • catch{} - בבלוק התפיסה נזין קוד היבוצע במידה ותצוץ בעיה כלשהי.
  • finally{} - בבלוק הסיום, שהינו אופציונאלי, נוכל להזין קוד היבוצע בכל מקרה בסוף הבלוק.

כעת נבחן דוגמא לשימוש ביכולת זו, בקוד לשליפת נתונים אודות משתמש מבסיס נתונים,

try{Console.WriteLine(FetchUserInfo(4));}
catch(Exception ex){Console.WriteLine(ex.Message);}
____
string Fetch(string Property,int UserID)
{
____throw new ArgumentException($"The Property {Property} for UserID {UserID} does not exist.");
}
User FetchUserInfo(int UserID)
{
____User CurUser = new User();
____DBConnection.Open();
____try
____{
________CurUser.Name = Fetch("Name",UserID);
________CurUser.Gender = Fetch("Gender",UserID);
________CurUser.FavoriteNumber = Fetch("FavoriteNumber",UserID);
____}
____catch(ArgumentException ex)
____{
________throw new ArgumentException($"UserID {UserID} does not exist.",ex);
____}
____catch(Exception)
____{
________throw;
____}
____finally
____{
________DBConnection.Close();
____}
____return CurUser;
}

בקוד הגדרתי,

    • בלוק try-catch הידפיס את פרטי המשתמש בעזרת הפעולה FetchUserInfo. במידה והועלתה שגיאה בביצוע הקוד שבבלוק הtry, תודפס ההודעה מאובייקט השגיאה.
    • נגש לאובייקט השגיאה כשנתפוס אותה על ידי כתיבת השגיאה כמו בכתיבת פרמטר לפעולה, אחרי מילת הcatch. במידה וסוג השגיאה הנשלח הינו הסוג שנרשם בפעולת הcatch, או שהוא יורש ממנו, הקוד שנרשם בcatch יבוצע, ומידע השגיאה יטען באובייקט ex.
  1. הגדרתי את הפעולה Fetch, המקבלת מזהה משתמש, שם מאפיין, ותחזיר את ערך המאפיין עבור המשתמש. יישמתי בפעולה העברת הודעת שגיאה בלבד, העבור כל ערך תחזיר שגיאת ערך לא תקין, האומרת שהמאפיין עבור היוזר לא קיים.
  2. הגדרתי את הפעולה FetchUserInfo המקבלת מזהה משתמש, שולפת את מאפייניו, ומזינה אותם באובייקט User המוחזר. מימשתי אותה כך,
    • יצרתי אובייקט יוזר ריק, ופתחתי את החיבור לבסיס הנתונים. לאחר מכן יצרתי בלוק try-catch.
    • בתוך בלוק הtry נשתמש מספר פעמים בפעולת הFetch לקבלת מאפייני היוזר.
    • נשתמש בcatch על מנת לתפוס ArgumentException במידה והועלה על ידי פעולות הFetch. נשים לב ששגיאה רגילה מהסוג Exception לא תתפס בבלוק זה, אלא רק ArgumentException ושגיאות היורשות ממנה. במידה וכזו נתפסה, תזרק בעיה חדשה שהודעתה תהיה שהיוזר עצמו לא קיים, והבעיה הנתפסת תוגדר כשגיאה הגורמת שלה.
    • לאחר מכן נגדיר עוד catch היתפוס בעיות מהסוג Exception. כלומר, כל בעיה תתפס שכן כולן יורשות מException. כשנגדיר שני catchים, הבעיה שנשלחה תעבור בין האופציות לפי סדר כתיבתם, והראשון שמכיל את סוגה או כזה שהיא יורשת ממנו יבוצע, ורק הוא. לכן נרשום catchים כך שהסוגים הכי ספציפיים למעלה.
    • כשבעיה תתפס בבלוק זה, הגדרנו כי היא פשוט תועבר הלאה על ידי שימוש בthrow ריק. כשלא נדרש לגשת למאפייני הבעיה הנתפסת, לא נדרש לשייך אותה למשתנה ex ורק דרוש לציין את סוג הבעיה שנתפוס.

סיכומון

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




אין תגובות:

הוסף רשומת תגובה