טעויות של מפתחים - Conditional Attribute vs. #IF
היום במסגרת ייעוץ שנתתי לאחד הצוותים אצלנו, גיליתי טעות שהייתה יכולה לעלות לנו הרבה מאוד. מצאתי לנכון לשתף גם אתכם בנסיון הזה - היות ושמתי לב שאף אחד מצוות הפיתוח לא ממש היה מודע למצב.
אלו מאיתנו שבאו מעידן הC, זוכרים בוודאי את הנוחות שבשימוש בDirectives.. כך לדוגמא, יכולנו לייצר מספר גרסאות ע"י שינוי הדגלים איתם קימפלנו את האפליקציה. התכונה הזו נשמרה גם בעידן ה.NET. חלק מהDirectives שהכרנו הוסרו/שונו, ואחרים התווספו.
החסרון הגדול בשימוש ב#IFים בתוך האפליקציה, היה הבלאגן שזה יצר. הקוד פתאום כלל מעבר ללוגיקה רגילה - גם לוגיקה למהדר.. והקוד הפך להיות לא קריא. מיקרוסופט באו לפתור את הדבר הזה וייצרו Attribute בשם Conditional שניתן להגדיר ברמת המטודה, ואשר יבטל את הקריאה למטודה במידה ודגל הקומפילציה הרלוונטי לא מוגדר.
אלא שיש הבדל מאוד גדול בין #IF ובין ConditionalAttribute, וחשוב שמי שבאים להשתמש בשני יכירו אותו; ConditionalAttribute לא מעלים את קטע הקוד מתוך הAssembly. הוא מעלים אך ורק את הקריאה לקוד הזה.
בתור דוגמא, נגדיר את המחלקה הבאה:
using System.Windows.Forms;
using System.Diagnostics;
namespace MyClass
{ public class ConditionalClass
{ [Conditional("DEBUG")] public static void foo()
{ MessageBox.Show("This is some debugging foo.."); }
}
}
ונגדיר את הטופס הבא שישתמש בה:
using System;
using System.Windows.Forms;
namespace ConditionalAttributeDemo
{ public partial class MyForm : Form
{ public MyForm()
{ InitializeComponent();
}
private void btnClickMe_Click(object sender, EventArgs e)
{ // Calling our special logic..
MyClass.ConditionalClass.foo();
}
}
}
אחרי שנעביר את שניהם קומפילציה עם דגל Debug, נראה את הIL הבא עבור MyClass.foo:
ועבור btnClickMe_Click נראה:
אם נקמפל עכשיו כRelease ונעבור על התוצרים, נראה שהMyClass.foo לא השתנה כמעט, למעט תהליך אופטימיזציה שהמהדר העביר, שהוריד את הnop בראשית המטודה:
ומאידך - הbtnClickMe_Click נראה עכשיו כך:
ניתן לראות שהקריאה לפונקציה הוסרה, אבל הלוגיקה כולה זמינה. כך לדוגמא, אם כללנו תהליך עסקי מסווג לשימוש פנימי (בדיקות לדוגמא) או דברים אחרים שחשבנו שהשימוש בConditionalAttribute יעלים - זה לא המצב.
מאידך, הנה אותו קוד עם הגדרת #IF במקומות המתאימים. חשוב לשים לב שIF במקרה כזה צריכים להגדיר גם על הקריאה לפונקציה וגם על הפונקציה עצמה.
using System.Windows.Forms;
using System.Diagnostics;
namespace MyClass
{ public class ConditionalClass
{#if DEBUG
public static void foo()
{ MessageBox.Show("This is some debugging foo.."); }
#endif
}
}
והטופס:
using System;
using System.Windows.Forms;
namespace ConditionalAttributeDemo
{ public partial class MyForm : Form
{ public MyForm()
{ InitializeComponent();
}
private void btnClickMe_Click(object sender, EventArgs e)
{#if DEBUG
// Calling our special logic..
MyClass.ConditionalClass.foo();
#endif
}
}
}
ואם נסתכל על הIL (בRelease) נראה שאכן, אין אזכור לפונקציה foo:
ובטופס אנחנו נקבל בדיוק את מה שקיבלנו עם הConditional Attribute.