در بسیاری موارد (مانند سیستمهای Multi Tenant) لازم هست تا مانع از این شویم که دادههای کاربران با هم تداخل پیدا کند و یا آنها بتوانند به دادههای هم دسترسی داشته باشند. مثلا میخواهیم کاربران هر شعبه از سازمان، تنها به اطلاعات شعبه خودشان دسترسی داشته باشند. یک کار ساده، پردردسر و بسیار بد آن است که از برنامه نویسها بخواهیم در هر کوئری عبارتی را اضافه کنند که سطح دسترسی را چک کند. اما اگر برنامه نویس جایی فراموش کرد چی؟ اگر سیاست دسترسی پیچیدهتر بود و مبنی بر پارامترهای مختلف محاسبه میشد چه خواهد شد؟ این راهکار در حجم بزرگ غیر مطمئن و غیرقابل نگهداری است.
در EF6 قابلیتی به نام Interception وجود دارد که با استفاده از آن میتوان سیاست دسترسی به داده را در لایههای پایینی طراحی کرد. در این روش برنامه نویس لایه هایی بالا، بدون آنکه درگیر مفاهیمی مانند Tenant و سیاستها بشود، میتواند به راحتی کوئری هایش را تولید کند. سپس EF به طور خودکار تغییری در کوئریها خواهد داد تا دسترسیهای لازم رعایت کرده باشد. برای اینکار میتوانید از کتابخانه EntityFramework.DynamicFilters استفاده کنید.
این روش هم علی رغم همه مزایا معایبی هم دارد. اگر بخواهیم از همین پایگاه داده استفاده کنیم ولی در محیط دات نت نباشیم و یا از EF6 استفاده نکنیم، دوباره مشکلات اغاز میشوند. سیاستها را باید در همه جا کپی کنیم و در صورت لزوم هم، مجددا همه را تغییر دهیم.
در SQL Server 2016 قابلیتی به نام Row Level Security وجود دارد، که به ما اجازه میدهد سیاستهای دسترسی با داده را در لایه پایگاه داده متمرکز کنیم. در این صورت اپلیکشنها هیچگونه آگاهی ایی نسبت به سیاستها نخواهند داشت و درگیر این مفاهیم در سطح کد نخواهیم بود. همچنین در صورت لزوم به تغییر سیاست ها، فقط لازم است تغییراتی را در پایگاه داده بدهیم. با این روش، به هر طریقی و از هر ابزاری که به پایگاه داده کوئری هایمان را ارسال کنیم، سیاستهای دسترسی به داده اعمال خواهند شد و امنیت بالا و البته ریزدانه ای (granular) را خواهیم داشت.
در مثال زیر خواهیم دید که چگونه میتوان با استفاده از EF6 از ویژگی RLS بهره برد. این مثال یکی دیگر از کاربردهای Interception را نیز توضیح میدهد.
SignalR
Contact me
بله. من از این طریق امرار معاش میکنم.
خیلی از شرکتها به دنبال تهیه برنامه حاضر و آماده از جایی نیستند و ترجیح میدهند یک یا چند نفر برنامه نویس مستقر داشته باشند تا اینها بتوانند مطابق شرایط و نیازهای روز شرکت، برنامهها را توسعه و تغییر دهند و یا سیستمها را نگهداری کنند.
علت هم این است که برنامه بدون پشتیبانی یعنی برنامه بیمصرف. نیازهای شرکتها گاهی به صوت هفتگی تغییر میکند و این نیازها، وجود یک برنامه نویس مستقر را ضروری میکند.
رفع مشکلات:
در قسمت قبل با ذکر یک مثال و بیان مشکلات آن از دیدگاه اصول Defensive Code قصد داشتیم که مساله را روشنتر کنیم. مواردی که در
قسمت قبل ذکر شدند، به سادهترین شکل ممکن بیان
شدند و شما به راحتی با بررسی این موارد و تفکر در کدهای خود، میتوانید
این موارد را در کدی که خودتان مینویسید رعایت کنید. حل پیچیدگیهای موجود در کد قبل، با در نظر گرفتن اصول مذکور و اصولهای طراحی مختلف میتواند به روشهای مختلفی انجام گیرد. برای مثال میتوان برای هر یک از کارهایی که کد مثال قبل انجام میدهد، یک کلاس مجزا ایجاد نمود و اصول مذکور را در آن رعایت کرد. درنهایت این
کلاسها را در قالب یک Class Library دسته بندی کرد.
Predictability:
در ادامه قصد داریم در مورد قابلیت پیش بینی و فواید رعایت اصول آن در کد، بحث کنیم. به صورت کلی یک متد، یکسری پارامترها را به عنوان ورودی دریافت میکند؛ یک عملیات خاص را بر روی پارامترهای ورودی انجام میدهد و در نهایت، در صورت لزوم یک مقدار را بر میگرداند. پارامترهای ورودی میتوانند از سمت کاربر و از یک سورس خارجی وارد شوند و یا میتوانند از یک متد دیگر به این متد ارسال شوند. اولین مرحله برای ایجاد قابلیت Predictability این است که یکسری گاردها را به کد خود اضافه کنید تا به وسیلهی آنها پارامترهای ورودی را بررسی کنید و فقط اجازهی ورود، ورودیهای معتبر را بدهیم. برای مثال کد ذیل را درنظر بگیرد.
پارامترهای ورودی این کد با مستطیل قرمز رنگ مشخص شده اند. حال ما سعی داریم با قرار دادن یکسری گارد برای پارامترهای ورودی، از ورود مقادیر ناخواسته جلوگیری کنیم.
بر اساس اصول (GIGO (Garbage in-Garbage out در برنامه نویسی متدی که ورودیهای نامعتبر به آن پاس داده شوند، خروجیهای نامعتبری هم پس خواهد داد. بنابراین برای جلوگیری از این مسئله باید از ورود ورودیهای نامعتبر به متدها جلوگیری کرد. گاردها از ورود مقادیر نامعتبر به متدها جلوگیری خواهند کرد و در نتیجه خروجی مناسب و قابل پیش بینی از متد گرفته خواهد شد. برای جلوگیری از ورود دادههای نامعتبر، باید با استفاده از این دستورات که در ابتدای متد قرار داده میشوند، از ورود دادههای نامعتبر جلوگیری کرد. به این دستورات Guard Clauses گفته میشود. غیر از این مساله، کاهش دادن تعداد پارامترها و قراردادن قانونی برای تعیین اولویت پارامترهای متدها (برای مثال با توجه به اهمیت) میتواند به افزایش Predictability متدها بسیار کمک کند. با پیروی کردن از این اصول ساده شما میتوانید میزان خطاهایی که از پارامترهای ورودی منشاء میگیرند را کاهش دهید.
اجازه دهید با یک مثال؛ مسالهی بالا را تشریح کنیم. برای مثال یک برنامهی کوچک نوشتهایم؛ برای شمردن گام ها. در این برنامه تعداد قدمهای هدف و تعداد قدمهای برداشته شدهی امروز تعیین میشوند و سپس هدف، بر حسب درصد بیان خواهد شد.
با استفاده از این Application میخواهیم مفاهیمی را که بیان کردیم، به صورت کاربردی
نمایش دهیم. کدی این محاسبه را برای ما انجام میدهد، در ذیل نمایش داده شده و
در قالب یک متد تعیین شده است.
private decimal CalculatePercentOfGoalSteps (string goalSteps, string actualSteps) { return (Convert.ToDecimal(actualSteps) / Convert.ToDecimal(goalSteps)) * 100; }
این متد دارای دو پارامتر از نوع string می باشد و نتیجه هم در قالب یک مقدار decimal بازگشت داده خواهد شد. این جمله کلیتی از متد را بیان خواهد کرد. نحوهی فراخوانی این متد هم در کد ذیل آورده شد است.
private void Calculate_Click(object sender, EventArgs e) { var result =CalculatePercentOfGoalSteps (stepGoalForTodayTxt.Text, numberOfStepsForToday.Text); lblResult.Text = "شما به" + result + "% از هدف تان رسیده اید"; }
حال Application را اجرا کرده و نتیجه کار را مشاهده میکنیم. برای مثال شکل ذیل:
در این مثال با توجه به مقادیر وارد شده، به 40 درصد از هدف مورد نظر رسیدهایم. اما هدف از بیان این مثال، این نیست که مشخص گردد که ما چقدر به هدفمان نزدیک شدهایم. بلکه هدف مسایل دیگری است. در نظر بگیرید که بجای 5000، صفر را وارد کنید. در این حالت با یک Exception روبرو میشویم:
همانطور که در شکل بالا مشاهده میکنید، خطای Divide by zero رخ داده است. برای رفع این خطا و جلوگیری از رخداد این خطا، میتوان کد
ذیل را پیشنهاد داد.
private decimal CalculatePercentOfGoalSteps(string goalSteps, string actualSteps) { decimal result =0; var goalStepsCount = Convert.ToDecimal(goalSteps); if (goalStepsCount>0) { result = (Convert.ToDecimal(actualSteps) / goalStepsCount) * 100; } return result; }
با تغییر کد به این صورت مشکل Exception بالا حل میشود، اما باز هم مشکل دیگری وجود دارد. فرض کنید همانند شکل ذیل textbox اول را خالی کنیم و بعد از آن سعی در محاسبه داشته باشیم،
باز هم یک Exception دیگر
علت بوجود آمدن این مشکل این است که ما در کد امکان خالی بودن پارامترهای متد را در نظر نگرفتهایم و پیش بینیهای لازم صورت نگرفته است بنابراین دستور Convert .با مشکل مواجه شد. برای حل این مشکل میتوان به جای Convert از decimal.Tryparse استفاده کرد.
private decimal CalculatePercentOfGoalSteps(string goalSteps, string actualSteps) { decimal result = 0; decimal goalStepsCount = 0; decimal.TryParse(goalSteps, out goalStepsCount); decimal actualStepsCount = 0; decimal.TryParse(actualSteps, out actualStepsCount); if (goalStepsCount>0) { result = (actualStepsCount / goalStepsCount) * 100; } return result; }
با انجام دادن این کارها از بروز خطاهایی که ناشی از ورودیهای نامعتبر در کد هستند، جلوگیری کردیم. اما آیا این پایان کار است؟ خیر با استفاده کردن از این روش ما توانستهایم که از بروز خطا در برنامه جلوگیری کنیم. اما مشکلی که این روش دارد این است که کاربر متوجه نمیشود که چه زمانی برنامه دچار مشکل شده است. کاری که ما انجام میدهیم این است که برای تمامی حالات خطا، مقدار صفر را بر میگردانیم.
برای اینکه بتوانیم این کد به راحتی debug کنیم باید از مفهوم Fail Fast استفاده کنیم . این مفهوم قابلیتی را در کد ایجاد میکند که در صورتی که کد، دادههای نامعتبری را دریافت کرد، سریعا اجرای آن متوقف میشود و همزمان نیز اطلاعاتی در مورد خطا در اختیار کاربر قرار میدهد. برای این منظور با قرار دادن یکسری Guard Clauses، کد بالا را همانند شکل ذیل تغییر خواهیم داد.
private decimal CalculatePercentOfGoalSteps(string goalSteps, string actualSteps) { decimal goalStepsCount = 0; decimal actualStepsCount = 0; /// اطمینان حاصل میکنند که پارامترهای ورودی دارای مقدار هستند if (string.IsNullOrWhiteSpace(goalSteps)) throw new ArgumentException("مقدار هدف باید وارد شود", "goalSteps"); if (string.IsNullOrWhiteSpace(actualSteps)) throw new ArgumentException("مقدار واقعی باید وارد شود", "goalSteps"); ///اطمینان حاصل میکنند که مقادیر وارد شده حتما عددی هستند if (!decimal.TryParse(goalSteps, out goalStepsCount)) throw new ArgumentException("مقدار هدف باید عددی باشد", goalSteps); if(!decimal.TryParse(actualSteps, out actualStepsCount)) throw new ArgumentException("مقدار واقعی باید عددی باشد", actualSteps); ///اطمینان حاصل میکند که مقدار متغیر نباید صفر باشد if (goalStepsCount <= 0) throw new ArgumentException("مقدار هدف نباید صفر و یا کمتر از صفر باشد", "goalStepsCount"); return (actualStepsCount / goalStepsCount) * 100; }
ایجاد کردن این تغییرات در متد باعث افزایش خوانایی
کد میشود و هدف متد را روشنتر بیان خواهد کرد. اضافه کردن این کدها به دلیل اینکه
تمامی شرایط تست را تعیین خواهیم کرد Test-ability کد را بالا میبرد. اضافه کردن کدهای بالا به برنامه کمک خواهد کرد که
شرایط خطا در برنامه به درستی هندل شود و به طبع آن تصمیمات مناسبی گرفته شود و در
نهایت Predictability متدها و کل برنامه را افزایش میهد.
اهمیت code review
اگر کدی آزمایش شده، مرور شده و "کار میکنه"، دیگه نیازی به تغییر اون وجود نداره. برنامه نویس ها عموما در دوران حرفه ای خودشون، حداقل یک بار با "وسوسه بازنویسی همه چیز از نو" روبرو میشن، وسوسه ای که در ابتدا، افق های روشنی رو برامون ترسیم میکنه، اما در انتها، منجر به داشتن کدی به مراتب بدتر از اون چیزی که در ابتدا داشتیم، میشه.
وقتی کدی قدیمی (که بدون مشکل کار میکنه) رو دور میندازیم، در حقیقت داریم زمانی رو که صرف رفع ایرادهای موجود در اون کرده بودیم (که میتونه روزها، هفته ها یا ماه ها باشه) رو هدر میدیم. گذشته از این، چون احتمالا به تمام بخش های کد و عملکرد اون اشراف نداریم، چیزهایی ممکنه در کد ببینیم که به نظرمون احمقانه بیاد و حذف اونها، باز موجب از کار افتادن بخش هایی از سیستم بشه که Debug کردن اون، مستلزم صرف زمانی هستش که تیم قبلی اون زمان رو یکبار صرف این کار کرده بوده. بنابراین، نمی تونیم به عنوان یه اصل کلی عبارت "کدی که کار میکنه رو نباید تغییر داد" رو رد کنیم! این مساله، باید بازای Case های مختلف، بدقت بررسی بشه و بعد در مورد اون Case خاص، نظر داده بشه.
طبیعتا، با دیدن کد آورده شده در این پست میشه به این مساله پی برد که نویسنده اون کد، در وهله اول، با اصول و مفاهیم اولیه نوشتن یک کد تمیز، بیگانه بوده. چنین افرادی، ابتدا باید آموزش ببینن و مرور یا عدم مرور کد اونها، در طولانی مدت، هیچ سودی در پی نخواهد داشت.
موفق باشید.
نسخهی دات نت زبان Lua
بررسی وضعیت فعلی پروژه Roslyn
مقایسهای بین امکانات Rider و Visual Studio
<script src="/path/to/jquery.cookie.js"></script>
$.cookie('the_cookie', 'the_value');
$.cookie('the_cookie'); // => "the_value"
<script type="text/javascript"> $(function () { $('#write').click(function () { $.cookie('data', '{"iri":"Iran","usa":"United States"}', { expires: 365, json: true }); alert('Writed'); }); $('#show').click(function () { var obj = jQuery.parseJSON($.cookie('data')); alert(obj.iri); }); $('#remove').click(function () { $.removeCookie('data'); }); }) </script>
<body> <a href="#" id="write">Write</a> <br /> <a href="#" id="show">Show</a> <br /> <a href="#" id="remove">Remove</a> </body>
$('#write').click(function () { $.cookie('data', '{"iri":"Iran","usa":"United States"}', { expires: 365, json: true }); alert('Writed'); });
با کلیک بر روی لینک Write کوکی data با مقدار مشخص پر میشود.
$('#show').click(function () { var obj = jQuery.parseJSON($.cookie('data')); alert(obj.iri);
$('#remove').click(function () { $.removeCookie('data'); });
$.cookie('data', JSON.stringify(jsonobject), { expires: 365, json: true });
- رابط کاربری جدید
- اتصال به دیتابیس
- قابل نصب بر روی نسخههای Delphi 7, 2007, 2009, 2010, XE, XE2
- رفع اشکال توابع محاسباتی