Write More Debuggable Code
רובנו כבר יודעים לשנן את האימרה שאומרת שכשאנחנו כותבים קוד, אנו כותבים
אותו עבור בני אדם, ולאו דווקא למחשב, ולכן יותר חשוב שהקוד שאנו כותבים
יהיה יותר קריא מיעיל.
למרות זאת, פעמים רבות אנו שוכחים פרטים קטנים
שלאו דווקא פוגעים בקריאות הקוד, כאלו שלא נראים לנו חשובים בעת כתיבת
הקוד. למעשה, אני מדבר על כתיבת קוד שיהיה נוח לדבג.
כי אם החוק אומר
שנשקיע יותר זמן בקריאת קוד מאשר בכתיבת קוד, אז הכרחי שהזמן שנעביר בלדבג
את הקוד יעלה כנראה על הזמן שנדרש לכתוב או לקרוא את הקוד גם יחדיו. לכן,
כדי לעשות את ה"אקסטרה" מאמץ ולכתוב קוד שיקל, אפילו במעט, על תהליך
הדיבאג.
למה הכוונה? הנה דוגמה קלאסית:
static void Main()
{
if (calculate(2, 4) > 4)
Console.WriteLine("Greater");
}
static double calculate(double x, double y)
{
return Math.Pow(y, x);
}
במבט מהיר אפשר להבין כבר מה הקוד הזה עושה, אין שום בעיה עם הקריאות שלו.
אבל,
מה יקרה כשנרצה לדבג אותו? נניח ואנו רוצים לדעת מהו ערך ההחזר של
calculate? כדי לדעת בדיוק, נהיה חייבים לקרוא לה שנית דרך פאנל
ה-Immediate, אבל מן הסתם זה לא פתרון נוח כל כך, ולא פרקטי אם מדובר
בפונקציה מורכבת יותר.
הצורה הכי פשוטה ונכונה למנוע מצב כזה מגיעה משני כיוונים:
- לא לקרוא לפונקציות בתוך משפטים מורכבים (קריאות לפונקציה, משפטי if, הכרזות על for ... למעשה כל מה שכולל בתוכו סוגריים).
- לא לבצע חישוב מתמטי/קריאה לפונקציה בתוך קריאה ל-return.
בשני המקרים, כל קריאה לפונקציה/חישוב צריכה להיעשת לפני השימוש בערך ההחזר שלה. צריך ליצור משתנה "מאוד זמני" שיחזיק אותו ובו
אפשר להשתמש בתוך הקריאה לפונקציה/return. בצורה הזאת, כשנדבג את הקוד, נוכל לבדוק את ערך ההחזר על ידי מבט מהיר ב-Watch.
מבחינת ביצועים? ההבדל זניח, אם בכלל קיים. הביטוי היחיד שלו יהיה בתוספת של מספר הוראות IL שתפקידן להעלות ולהוריד ערכים מה-Evaluation Stack. בפועל, לתוספת הקוד הזאת אין משמעות. גם אם תריצו את אותה הפונקציה, פעם בגרסה עם משתנים זמנים ופעם בגרסה בלי, גם אחרי עשרות מיליוני קריאות, לא תראו הבדל כלשהו בביצועים. זה יכול לנבוע מ-2 סיבות. האחת: ההבדל כל כך (אבל כל כך!) זניח עד שכדי שבאמת נראה הבדל "כלשהו" נצטרך לבצע כמה מילארדי קריאות לפונקציות, ורק אז, אולי, נוכל לקבל סט נתונים שיצביע על איזשהו הבדל בין 2 הגרסאות של הפונקציה. הסיבה השניה היא שלמרות שה-IL שחולל כאן שונה, יתכן וה-JIT מבצע אופטימיזציות נוספת על אותו הקוד ולמעשה מבטל את התוספת שקיימת בקוד ה-IL. כך שלמעשה, בפועל, אנחנו מריצים את אותו הקוד בשני המקרים.