DCSIMG
Log Your Assertions - Liran Chen's Blog

Liran Chen's Blog

.Net Internals, Debugging, Multithreading - and More!

Log Your Assertions

שימוש ב-Assert יכול להיות פתרון נוח ושימושי כשרוצים לוודא שה-State שאנחנו נמצאים בו כרגע, באמת מתאים למה שאנחנו מצפים. אנחנו יכוללים להשתמש בו בשביל לבדוק כל מיני מקרי קצה ביזארים, כל מיני מצבים ש"אין מצב" שיקרו (על פי הלוגיקה הקיימת בקוד). כל מיני מצבים שהמשך הקוד שלנו מסתמך על זה שהם לא אפשריים, ולא יקרו בזמן הוא רץ. בדרך כלל נקרא ל-Assert דרך המחלקה הסטאטית Debug, שעושה שימוש ב-ConditionalAttribute עם הערך "DEBUG", כך שאנחנו יודעים שכל הבדיקות האלו לא יעלו לנו כלום כשנצא לגרסת ה-Release, כך שאנחנו גם לא צריכים "להתקמצן" על כל Assert קטן בקוד שיכול לעזור לנו למצוא כל מיני מצבים לא הגיוניים בתוכנית שלנו.

ברגע שה-Assert שלנו יכשל, יופיע MessageBox גדול ויפה שיציג לנו את ההודעה שרצינו להדפיס במידה וה-Assert נכשל, ובנוסף לכך את ה-StackTrace הרלוונטי. הבעיה מתחילה כשאנחנו חס וחלילה.. סוגרים את החלון הזה. ופוף! הכל נעלם. אנחנו אולי יודעים בערך איזה Assert בקוד נכשל, אבל מן הסתם אנחנו לא זוכרים את ה-StackTrace, או חשוב מכך, את הזמן שבו ה-Assert קרה (כך שנוכל אחר כך להתאים אותו ביחס לשאר הלוגים שהדפסנו במהלך הריצה, למעשה להבין פחות או יותר מה היה יכול להשפיע על הכישלון הזה).
צריל לזכור ש-Assert כושל לא יגרור זריקת Exception. כל מה שהוא יעשה זה לעבור על כל ה-TraceListener'ים שהוצמדו ל-Debug, ופשוט לקרוא לפונקצית ה-Fail שלהם. כברירת מחדל ה-Listener היחיד שמוצמד הוא ה-DefaultTraceListener. שרק מדפיס ל-Output את ההודעה, ובנוסף מציג את ההודעה בחלון ה-MessageBox המוכר (במידה והערך הנמצא בשדה AssertUiEnabled מאפשר לו לעשות זאת).
מכאן, שלא משנה בכמה Try-Catch'ים עטפנו את הקוד שלנו, הכשלון הזה לא יתפס, ואנחנו יכולים למצוא עכשיו את עצמנו מגששים באפלה אחר סיבות אפשריות לכישלון בבדיקה.
אולם, המצב לא חייב להיות כך. ואנחנו יכולים לדאוג שכל כשלון כזה יטופל כמו כל שגיאה אחרת, וישמר אוטומטית בלוג של התוכנית שלנו. נוסף על כך, אם נרצה נוכל גם להכריח את התוכנית להסגר, במידה ואנחנו לא מעוניינים שהיא תמשיך לעבוד במצב זומבי שכזה, בו אנחנו בין כה וכה לא יודעים מה קורה איתה, ומצפים שהיא תקרוס בקרוב בגלל איזו תופעת לוואי שנגרמה כתוצאה מה-State הלא צפוי אליו היא נכנסה.
הפתרון הוא פשוט למדי, כל מה שעלינו לעשות זה ליצור TraceListener חדש משלנו, ולהשתמש בו. אנחנו יכולים ליצור מחלקה חדש שתרש מהמחלקה האבסטרקטית TraceListener, ותדרוס את המימוש של הפונקציה Fail. בתוך המימוש החדש, נשאר לנו רק לשמור ללוג את ההודעה + ה-StackTrace שמעניין אותנו, וזה הכל. מעכשיו כל Assert כושל שיתרחש אצלנו, ישמר אוטומטית ללוג.
מאחר וה-Listener'ים מופעלים בצורה סינכרונית, אחד אחרי השני. אפשר וכדאי לדאוג שה-Listener שלנו יהיה הראשון שמופעל, כך שלא נבזבז זמן על זה שחלון ה-MessageBox מוצג, או שבטעות מישהו יסגור את התוכנית לפני שנספיק לשמור את השגיאה ללוג שלנו (באותה מידה אפשר גם לדאוג שה-Listener שלנו יהיה היחיד שקיים, ואז בסוף ה-Fail לזרוק למשל שגיאה ... בכל אופן, לא חסרות דרכים להתמודד עם הבעיה).

class Program

{

    static void Main()

    {

        FooListener foo = new FooListener();

        Debug.Listeners.Add(foo);

 

        Debug.Assert(false, "whoops..");

    }

}

 

class FooListener : TraceListener

{

    public override void Write(string message) { }

    public override void WriteLine(string message) { }

 

    public override void Fail(string message)

    {

        Console.ForegroundColor = ConsoleColor.Red;

 

        // use your preferred logger here..

        string stackTrace = Environment.StackTrace;

        Console.WriteLine("Assertation Failed. Message: {0}, StackTrace: {1}", message, stackTrace);

    }

}

תוכן התגובה

Shlomo כתב/ה:

לא הכרתי, תודה

# July 11, 2009 11:31 PM

Avi Pinto כתב/ה:

nice one

# July 14, 2009 7:57 AM
שלח תגובה

(שדה חובה)  

(שדה חובה)  

(אופציונלי)

(שדה חובה) 

Please add 1 and 7 and type the answer here:


Enter the numbers above: