۱۳۸۸/۰۵/۳۱

آشنايي با Defensive programming


تصادف براي يك راننده حتي در صورت داشتن بيمه نامه‌اي معتبر، گران تمام خواهد شد (از لحاظ جاني/مادي/...). بنابراين صرف نظر از اينكه شركت بيمه كننده چه ميزان از خسارت راننده را جبران خواهد كرد، بايد تا حد ممكن از تصادفات بر حذر بود (defensive driving).

در برنامه نويسي، استثناء‌ها (Exceptions) مانند تصادفات هستند و مديريت استثناءها (exception handling)، همانند بيمه خودرو مي‌باشند. هر چند مديريت استثناء‌ها جهت بازگردان برنامه شما به ادامه مسير مهم‌ هستند، اما جايگزين خوبي براي Defensive programming به شمار نمي‌روند. استثناء‌ها و مديريت آن‌ها براي برنامه گران تمام مي‌شوند (خصوصا از لحاظ ميزان مصرف منابع سيستمي و سربارهاي مربوطه). بنابراين در برنامه بايد توجه خاصي را به اين موضوع معطوف داشت كه چه زماني، چگونه و در كجا ممكن است استثنائي رخ دهد و علاج واقعه را پيش از وقوع آن نمود.

اصل اول Defensive programming : هميشه ورودي دريافتي را تعيين اعتبار كنيد
به مثال زير دقت بفرمائيد:

public void LogEntry(string msg)
{
string path = GetPathToLog();
using (StreamWriter writer = File.AppendText(path))
{
writer.WriteLine(DateTime.Now.ToString(CultureInfo.InstalledUICulture));
writer.WriteLine("Entry: {0}", msg);
writer.WriteLine("--------------------");
}
}

قرار هست رخ‌دادهاي برنامه را توسط اين متد، لاگ كنيم. اكنون لحظه‌اي دقت نمائيد كه اين تابع در چه مواقعي ممكن است دچار مشكل شود:
path مي‌تواند يك رشته خالي باشد.
path مي‌تواند نال باشد.
path مي‌تواند حاوي كاراكترهاي غيرمجازي باشد.
path مي‌تواند فرمت نادرستي داشته باشد.
path مي‌تواند به محلي ناصحيح اشاره نمايد.
path مي‌تواند اصلا وجود نداشته باشد.
فايل مورد نظر ممكن است readonly باشد.
برنامه ممكن است دسترسي لازم را براي نوشتن در مسير ذكر شده، نداشته باشد.
فايل مورد نظر ممكن است توسط پروسه‌اي ديگر قفل شده باشد.
ممكن است در لحظه نوشتن يا خواندن بر روي فايل، هارد ديسك دچار مشكل گردد.
و ...

رخ دادن هر كدام از موارد ذكر شد منجر به بروز يك استثناء خواهد شد.

چگونه اين وضعيت را بهبود بخشيم؟
فرض كنيد متد GetPathToLog قرار است مسير ذخيره سازي لاگ‌ها را از كاربر در يك برنامه ASP.Net دريافت كند. براي اين منظور بايد حداقل دو مورد را منظور كرد.

<asp:TextBox ID="txtPath" runat="server" MaxLength="248" />

<asp:RequiredFieldValidator ID="reqval_txtPath" runat="server" ControlToValidate="txtPath" ErrorMessage="Path is required." />

<asp:RegularExpressionValidator ID="regex_txtPath" runat="server" ControlToValidate="txtPath" ErrorMessage="Path is invalid." ValidationExpression='^([a-zA-Z]\:)(\\{1}|((\\{1})[^\\]([^/:*?<>”|]*(?<![ ])))+)$' />

براي تكست باكس ارائه شده، ابتدا يك RequiredFieldValidator در نظر گرفته شده تا مطمئن شويم كه كاربر حتما مقداري را وارد خواهد كرد. اما اين كافي نيست. سپس با استفاده از عبارات باقاعده و RegularExpressionValidator بررسي خواهيم كرد كه آيا فرمت ورودي صحيح است يا خير.

تا اينجا 4 مورد اول مشكلاتي كه ممكن است رخ دهند (موارد ذكر شده فوق)، كنترل مي‌شوند بدون اينكه احتمال رخ دادن اين استثناءها در برنامه وجود داشته باشد. Defensive programming به اين معنا است كه طراحي برنامه بايد به گونه‌اي باشد كه در اثر استفاده‌ي غير قابل پيش بيني از آن، در عملكرد برنامه اختلالي رخ ندهد.