تولید نرم افزار با الگو

وبلاگ شخصی ابراهیم خانی

۸ مطلب در خرداد ۱۳۹۵ ثبت شده است

event sourcing

به بیان ساده Event sourcing راهکاری برای ذخیره سازی حالت یک شی بر اساس رخداد هایی [1] است که از زمان ایجاد برای آن اتفاق افتاده است. در این صورت قادر خواهیم بود حالت هر شی در هر لحظه از زمان را با بازسازی این رخدادها، ایجاد کنیم. هر چند فواید بسیاری بر این روش مترتب است، نباید از جان مایه آن که توجه به رفتار [2] به جای حالت [3] است غافل شد.  سایر مزایای استفاده از این روش عبارتند از :

  • تغییر در سازوکار ذخیره سازی، تغییری در برنامه ایجاد نمی کند.
  • به خاطر سادگی ذخیره سازی رخدادها، کارایی بهبود می یابد.
  • عدم نیاز به لایه orm [4] برای ذخیره سازی.
  • نگهداری تاریخچه رخدادهایی که در سیستم اتفاق افتاده است.
  • سادگی تجمیع [5] با سایر زیر سیستم ها.
  • سیر در گذشته و پیش بینی آینده.
  • خطایابی سیستم که در حال کار است با تکرار رخداد هایی که منجر به خطا شده اند.

سناریوی اصلی پیاده سازی این الگوی معماری در قطعه کد زیر نشان داده شده است. با هر درخواست در لایه application service، شناسه aggregate root ای که برای پاسخ گویی به این درخواست وظیفه ای برعهده دارد مشخص می شود. سپس رخدادهایی که تا این لحظه برای این شی اتفاق افتاده است از منبع ذخیره سازی رخداد ها [6] دریافت می شود. با دریافت این رخدادها و اعمال آنها به شی مورد نظر، این شی به حالتی قبل از ورود درخواست می رسد. سپس بر اساس درخواست ورودی، متدی روی شی مورد نظر فراخوانی می شود. در پاسخ به این متد فراخوانی شده، دنباله ای از رخداد ها اتفاق می افتد که شی مورد نظر این دنباله را در اختیار دارد. در ادامه رخدادهای اتفاق افتاده در منبع ذخیره سازی رخدادها ذخیره می شود و پاسخ درخواست ورودی، ارسال می شود.


public Customer EsRoutine(Guid id, Action<Customer> action)
{
    var events = _eventStore.Get(id);

    var customer = new Customer(events);

    action(customer);

    _eventStore.Save(customer.Id, customer.Changes);

    return customer;
}


کد منبع مربوط به این پست از این آدرس قابل دریافت است.

هر چند این الگو مزایای زیادی به دنبال دارد، استفاده از آن چالش هایی به همراه دارد. اول اینکه با توجه به اینکه با هر درخواست ورودی می بایست aggregate root درگیر در این درخواست را با اعمال رخدادهایی که تا کنون اتفاق افتاده است به حالت فعلی رساند، در صورتی که تعداد این رخدادها زیاد باشد باعث ایجاد سربار بالایی برای سیستم خواهد شد. استفاده از snapshot و cache دو راه معمول برای حل این مشکل است. از طرف دیگر هر چند ذخیره سازی به صورت blob [7] و clob [8] باعث سادگی درج [9] اطلاعات می شود اما جستار [10] اطلاعات ذخیره شده با این روش ها مقرون به صرفه نیست. استفاده از الگوی cqrs [11] راه حلی شناخته شده برای این مشکل است که انشاا... در پست های آتی مورد بررسی قرار خواهد گرفت. 



[1] - Event

[2] - Behavior

[3] - State

[4] - Object Relational Mapper

[5] - Integration

[6] - Event store

[7] - Binary Large OBject

[8] - Character Large OBject

[9] - Insert

[10] - Query

[11] - Command Query Responsibility Segregation 

۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

code contract

در پست قبلی به design by contract اشاره شد. در .net framework 4 امکان پیاده سازی این روش با افزودن کلاس هایی که در فضای نام [1] System.Diagnostics.Contracts  قرار دارند ایجاد شد. باید توجه کرد که استفاده از این کلاس ها تنها برای تعریف قراردادها [2] است و وظیفه بررسی صحت این قرارداد ها در زمان اجرا و کامپایل به عهده ابزار جداگانه ای گذاشته شده است. به عبارت دیگر با کامپایل کدی که شامل فراخوانی متدهای مربوط به کلاس ایستای [3]  Contractاست، ابزاری به نام binary rewriter [4] فراخوانی می شود که باعث بازنویسی کد میانی تولید شده به منظور جایگزینی متدهای فراخوانی شده با متدهای کلاس _ContractRuntime (و کارهای دیگر مانند قرار دادن پیش شرط ها و پس شرط ها در محل مناسب) می شود. باید توجه کرد که تمامی متدها تعریف شده در کلاس Contract دارای ویژگی شرطی [5] CONTRACTS_FULL هستند. این بدین معنا است که بدون تعریف سمبل CONTRACTS_FULL این متدها اجرا نخواهند شد.


contract_full


در قطعه کد زیر یک پیش شرط برای تابع Messenger تعریف شده است. با کامپایل کردن این کد و مشاهده کد میانی [6] تولید شده، مشخص می شود که پیش شرط تعریف شده تاثیری در کد میانی حاصل ندارد. 


static void Messenger(string message)
     {
         Contract.Requires(message != null);

         Console.WriteLine($"message is {message}");
     }


علت این است که سمبل CONTRACTS_FULL تعریف نشده است. با تعریف این سمبل و کامپایل مجدد این قطعه کد و مشاهده کد میانی تولید شده، مشخص می گردد که فراخوانی متد روی کلاس Contract توسط ابزار binary rewriter به فراخوانی روی کلاس _ContractRuntime ترجمه شده است.


Contract_Runtime



[1] - Namespace
[2] - Contract
[3] - Static
[4] - ccrewrite.exe
[5] - Conditional attribute
[6] - IL
۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

pessimistic concurrency

همانطور که در پست قبلی اشاره شد، یکی از روش های دستکاری مسائل مربوط به همزمانی [1] الگوی optimistic lock است. اگرچه این الگو [2] قادر به حل مشکل lost update است اما کشف این وضعیت تنها هنگامی صورت می گیرد که کاربر داده هایی را وارد و اقدام به بروز رسانی [3] حالت [4] کرده است. اگر ماهیت تعامل کابران با  سیستم مورد نظر به صورتی باشد که این وضعیت به کرات صورت بگیرد، استفاده از این روش باعث اتلاف وقت کاربر و منابع سیستم است. راه کار دیگر برای حل این مشکل استفاده از الگوی pessimistic lock است. در این الگو در هر تراکنش [5] روی داده ها (در سطوح مختلف بسته به شرایط) قفل ایجاد می شود. در واقع این الگو به دنبال پیشگیری مشکلات مربوط به همزمانی است درحالی که الگوی خوش بینانه رویکرد درمانی دارد.

به طور کلی در پایگاه داده ها دو دسته از فعالیت ها وجود دارد که می بایست نسبت به یکدیگر همگام شوند. این دو دسته نویسنده ها [6] و خواننده ها [7] هستند. نویسنده ها دستورات select هستند که حالت پایگاه داده را تغییر نمی دهند و خواننده ها دستوراتdelete ,update ,insert  که باعث تغییر در حالت پایگاه داده می شوند. برای انجام عملیات خواندن، sql server یک قفل به نام قفل مشترک [8] روی سطری [9] که قرار است خوانده شود ایجاد می کند. همچنین زمان نوشتن یک قفل به نام قفل انحصاری [10] روی سطری که قرار است خوانده شود ایجاد می کند. نکته حائض اهمیت ناسازگاری این دو قفل با یکدیگر است، بدین معنا که امکان انجام خواندن و نوشتن روی یک سطر به طور همزمان وجود ندارد.

در sql server مفهوم isolation level برای توصیف مدت زمانی که خواننده ها قفل مشترک را در اختیار دارند به کار می رود.  سطح جداسازی پیش فرض read committed است. مشکل این سطح نبود پایداری در خواندن [11] است. به عبارت دیگر خواندن چند باره یک سطر در یک تراکنش، خروجی های مختلفی می تواند داشته باشد. علت این مشکل آزاد شدن قفل مشترک با خوانده شدن هر سطر داده است. در این سطح جداسازی هنگامی که نویسنده در یک تراکنش قفل اختصاصی را بدست می آورد، خواننده های دیگر نمی توانند قفل مشترک برای خواندن را بدست آورند.

برای غلبه بر این مشکل از سطح جداسازی repeatable read استفاده می شود. در این سطح جداسازی تنها پس از پایان تراکنش، قفل مشترک رها می شود. در این سطح جداسازی هنگامی که خواننده ای قفل مشترک را بدست آورد، سایر نویسندگان قادر به بدست آورد قفل اختصاصی نخواهند بود (بر عکس حالت read committed)

سطح جداسازی read uncommitted به معنای خواندن بدون به دست آوردن قفل مشترک است. در این حالت در صورتی که تراکنشی که در حال به روز رسانی داده است بازگشت کند [12] و پیش از این بازگشت در حالت جداسازی read uncommitted اقدام به خواندن آن شده باشد، حالتی را خوانده ایم که در پایگاه داده وجود ندارد. به عبارت بهتر در این حالت سازگاری [13] داده ها به خطر می افتد.

سخت گیرانه ترین سطح جداسازی serializable است که برای فراهم کردن read stability و جلوگیری از phantom read است. برای فراهم کردن این سطح جدا سازی sql server از روشی به نام key range lock استفاده می کند. در این روش به ازای هر سطر که مقدار آن برای ستون خاص در یک دامنه خاص قرار می گیرد یک قفل خاص هنگام خواندن قرار داده می شود. اگر تعداد این قفل ها بیش از 5000 شود، sql server قفل را روی کل جدول قرار می دهند. به هنگام استفاده از این قفل بهتر از است که در ستون مورد نظر یک non cluster index قرار داده شود.



[1] - Concurrency
[2] - Pattern
[3] - Update
[4] - State
[5] - Transaction
[6] - Writer
[7] - Reader
[8] - Sheared lock
[9] - Row
[10] - Exclusive lock
[11] - Read stability 
[12] - Rollback
[13] - Consistency

 

 

۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

optimistic concurrency

یکی از مشکلات مربوط به همزمانی که در پست قبلی به آن اشاره شد، lost update است. امکان به وجود آمدن این وضعیت در یک تراکنش سیستمی در sql server امکان پذیر نیست. اما هنگامی که نوبت به تراکنش های کسب و کار [1] می رسد داستان متفاوت می شود. از آنجایی که حین انجام یک تراکنش کسب و کار امکان انجام چند تراکنش سیستم وجود دارد، تظمینی برای جلوگیری از lost update وجود ندارد. یکی از راه های غلبه بر این مشکل استفاده از الگوی optimistic lock است.

ایده اصلی این الگو استفاده از row versioning است.  به عبارت دیگر برای هر سطر هنگام انجام عملیات مربوط به نوشتن یک version هم در پایگاه داده ثبت می شود. برای این version در sql server از نوع داده ی row version استفاده می شود. این نوع داده یک عدد افزایشی است که با هر عمل نوشتن در پایگاده داده افزایش می یابد.  برای بررسی lost update در هر عمل نوشتن، version سطری که در قرار است در پایگاه داده نوشته شود با version ای که پایگاه داده وجود دارد مقایسه می شود که عدم تطابق آنها نشانه تغییر در سطر مد نظر است. باید دقت کرد که optimistic lock قادر به شناسایی dirty read نیست (در پست های قبلی توضیح داده شد).

برای غلبه بر مشکل dirty read  با استفاده از روش خوشبینانه [2] می توان از سطح جداسازی [3] read committed snapshot استفاده کرد. این سطح جداسازی پیاده سازی optimistic از read committed است. در این پیاده سازی مشکل dirty read نخواهیم داشت. اساس روش های خوش بینانه عدم نیاز به قفل برای خواننده است. در این سطح جداسازی، در طول تراکنش مربوط به نوشتن، مقدار قدیمی یک ستون در version store که در tempdb ذخیره می شود نگهداری می شود. در خلال این تراکنش، هنگامی که یک خواننده قصد خواندن مقدار این ستون را دارد، مقدار اولیه این ستون را از version store می خواند.



[1] - Business transaction
[2] - Optimistic
[3] - Isolation level
۰ نظر موافقین ۱ مخالفین ۰
ابراهیم خانی

offline concurrency

از ویژگی های عمومی برنامه های سازمانی [1] دسترسی چندین کاربر به طور همزمان به آنها است. از آنجایی که در این دسترسی ها، حالت قبلی (که در پایگاه داده قرار دارد) مورد ارجاع قرار می گیرد، با چالش دسترسی چند process و یا thread به داده مشترک رو به رو خواهیم شد و این تعریف همزمانی [2] است. در نگاه اول به نظر می رسد استفاده از تراکنش ها [3] حلال همه مشکلات باشد. اما در بسیاری از موارد قرار دادن تراکنش کسب و کار [4] در یک تراکنش سیستم امکان پذیر نیست. در اینگونه از موارد الگوهایی برای دستکاری همزمانی وجود دارد که Fowler آنها را offline concurrency می نامد.

به طور کلی در پایگاه داده دو دسته از فعالیت ها وجود دارد که می بایست نسبت به یکدیگر همگام شوند. این دو دسته نویسنده ها[5] و خواننده ها[6] هستند. نویسنده ها دستورات select هستند که حالت پایگاه داده را تغییر نمی دهند و خواننده ها دستوراتdelete ,update ,insert  که باعث تغییر در حالت پایگاه داده می شوند.  دسترسی همزمان این فعالیت ها به یک داده در پایگاه داده مشکلاتی را به همراه خواهد داشت. این مشکلات عبارتند از :

Lost update : این مشکل زمانی به وجود می آید که دو تراکنش نویسنده به طور همزمان یک سطر داده را تغییر دهند. در این صورت تغییرات تراکنش اول توسط تراکنش دوم بازنویسی [7] خواهد شد.

Dirty read : این مشکل زمانی به وجود می آید که یک تراکنش خواننده حین اجرای یک تراکنش نویسنده، سطر داده ای که تراکنش نویسنده نوشته است را بخواند.

Non repeatable read : این مشکل زمانی به وجود می آید که در فاصله بین دو عمل خواندن در یک تراکنش خواننده، یک تراکنش نویسنده اقدام به تغییر سطر مورد نظر نماید. در نتیجه تراکنش خواننده مقادیر متفاوتی را برای سطر مورد نظر خواهد خواند.

Phantom read : این مشکل زمانی به وجود می آید که در فاصله بین دو عمل خواندن در یک تراکنش خواننده، یک تراکنش نویسنده اقدام به تغییر در جدول مورد نظر نماید. در نتیجه تعداد سطرهایی که تراکنش خواننده در دفعات مختلف خواهد خواند، متفاوت خواهد بود.

در پست های آتی الگوهای offline concurrency مورد بررسی قرار خواهد گرفت.



[1] - Enterprise
[2] - Concurrency
[3] - Transaction
[4] - Business transaction
[5] - Writer
[6] - Reader
[7] - Override 
۰ نظر موافقین ۰ مخالفین ۰
ابراهیم خانی

applying design patterns

اگرچه شناخت الگوها برای یک مهندس نرم افزار امری ضروری است (تا جایی که برخی از بزرگان این اعتقاد دارند شناخت الگوها مرز بین مهندس نرم افزار و برنامه نویس است)، آگاهی از چگونگی به کارگیری آنها در تولید نرم افزار، امری مهم تر و چالش برانگیز تر است. به طور کلی دو رویکرد برای به کارگیری الگوها در تولید نرم افزار وجود دارد :

  • رویکرد بی نظم : در این رویکرد طراح با استفاده از تجربه خود، با ترکیب الگوها طراحی موردنظرش را انجام می‌دهد؛ بنابراین فرآیند طراحی در این رویکرد قابل تکرار نیست.
  • رویکرد نظام مند : در این رویکرد انتخاب و ترکیب الگوها جهت طراحی نرم‌افزار بر اساس یک فرآیند مشخص انجام می‌شود.

نباید اجازه داده شود که بار منفی موجود در واژه بی نظم بر روش اول سایه بیاندازد. منظور از بی نظمی در این جا، نبود یک فرایند برای به کارگیری الگوها در تولید نرم افزار است. برای پیاده سازی رویکرد نظام مند دو راهکار کلی وجود دارد. راهکار اول استفاده از زبان الگو [1] است. زبان الگو به دنبال معرفی یک مجموعه از الگوهای مرتبط باهم و ارتباطات بین آن‌ها، جهت حل مشکلات موجود در یک دامنه خاص است. این راهکار مصداق مثل "چیزی به عنوان غذای مجانی وجود ندارد" [2] است. چرا که زبان الگو، فرایند ضمنی موجود در خود را مرهون محدود کردن دامنه الگوهای آن به یک حوزه خاص است. برای شناخت دقیق زبان الگو مطالعه کتاب [3] توصیه می شود. راهکار دوم استفاده از یک فرایند به جهت استفاده نظام مند از الگوها در تولید نرم افزار است. متدولوژی های مطرح شده در این حوزه با دو چالش اساسی روبه رو هستند. چالش اول اینکه نمایشی برای الگوها که مورد پذیرش جامعه مهندسین نرم افزار باشد وجود ندارد. چالش دوم که نشات گرفته از چالش اول است، نیاز به روشی جهت ترکیب الگوهاست.

برای ترکیب الگوها دو رویکرد اصلی وجود دارد :

  • رویکرد ساختاری : در این رویکرد طراحی نرم‌ افزار از اتصال نمودار کلاس [4] الگوها ایجاد می ‌شود.
  • رویکرد رفتاری : در این رویکرد از مدل نقش [5] معرفی شده در متدولوژی OORAM [6] جهت ترکیب الگوها استفاده می شود.

رویکرد ساختاری علاقه مند به ترکیب الگوها در سطح پایین تری از انتزاع [7] است. دو نمونه بارز این رویکرد، روش POT [8] و روش POAD [9] است. در روش POT که در سال 1997 معرفی شد، ابتدا مدل شی رسم می شود و سپس بر اساس تعاملات بین اشیاء، مدل رسم شده به گروه هایی تقسیم می شود و در نهایت در این گروه ها الگوهای طراحی شناسایی می شوند. روش POAD که در سال 2003 در معرفی شد شامل سه مرحله است. در مرحله تحلیل الگوهای قابل کاربرد بر اساس دامنه مساله شناسایی می شوند. سپس در مرحله طراحی این الگوها با استفاده از واسط ترکیب می شوند و در مرحله دقیق سازی طراحی الگوهای ترکیب شده نمونه سازی می شوند. روش POT دید پایین به بالا دارد و در طرف مقابل روش POAD از دید بالا به پایین استفاده می کند. برای جزییات بیشتر در مورد این دو روش مطالعه [10] و [11] توصیه می شود.

رویکرد رفتاری علاقه مند به ترکیب الگوها در سطح بالاتری از انتزاع است. دو نمونه بازر این رویکرد، روش مطرح شده در متدولوژی OORAM و روش مطرح شده توسطDirk Rielhe  است. در متدولوژی OORAM از مدل نقش به عنوان سطح بالاتری از انتزاع نسبت به مدل شی استفاده می شود.  درحالی‌که یک کلاس به‌عنوان یک قالب برای ایجاد اشیا نمود پیدا می‌کند، نقش به مسئولیت این اشیا تأکید دارد. این متدولوژی در دو مرحله انجام می شود. ابتدا مدل های نقش ایجاد و در مرحله بعد این مدل ها با یکدیگر ترکیب می شوند. برای جزییات بیشتر مطالعه کتاب [12] پیشنهاد می شود. Dirk Rielhe با الهام از متدولوژی OORAM، روشی مبتنی بر مدل نقش برای ترکیب الگوها ارایه می دهد. در این روش ابتدا الگوهای موجود بر اساس مدل نقش، مدل می شوند. سپس یک مدل شیء از دامنه مساله ترسیم می شود (Rielhe این مدل را مدل نمونه سازی اولیه می نامد). در مرحله بعد با تحلیل مدل نمونه‌سازی اولیه،الگوهای قابل کاربرد شناسایی و نقش‌های موجود در این الگوها به اشیا موجود در مدل نمونه‌سازی اولیه اختصاص داده می‌شود. درنهایت با بررسی روابط بین نقش ها، ترکیب بین الگوها شکل می گیرد. برای جزییات بیشتر این روش، مطالعه مقاله [13] پیشنهاد می گردد. دو ایراد اساسی به مدل نقش وارد است. اول اینکه زبان UML از این مدل پشتیبانی نمی کند. دوم اینکه این مدل قابلیت نمایش ساختار سلسله مراتبی (که عصاره بسیاری از الگوهاست) را ندارد.



[1] - Pattern language
[2] - There is no such thing as a free lunch
[3] - Pattern Oriented Software Architecture Volume 5 : On Patterns And Pattern Languages
[4] - Class diagram
[5] - Role model
[6] - Object oriented role analysis method
[7] - Abstraction
[8] - Pattern Oriented Technique
[9] - Pattern Oriented Analysis And Design
[10] - A pattern oriented technique for software design
[11] - Pattern-Oriented Analysis and Design: Composing Patterns to Design Software Systems
[12] - Working With Objects:The Ooram Software Engineering Method
[13] - Composite design patterns
۰ نظر موافقین ۰ مخالفین ۰
ابراهیم خانی

error handling

در صورتی که در آرمان شهر [1] زندگی نمی کنید، با مواردی که قواعد شما را نقض می کنند رو به رو خواهید شد. تولید نرم افزار نیز از این قاعده مستثنی نیست. از این رو هر سیستمی برای ادامه حیات، نیازمند راهکاری نظام مند به منظور مقابله با اینگونه موارد است.

به طور کلی دو الگو برای کشف خطا در نرم افزار وجود دارد؛ کنش گرا [2] و واکنش گرا [3]. در الگوی کنش گرا ابتدا امکان انجام یک عملیات بررسی می شود و در صورت امکان اجرای آن عمل (بر اساس مقادیر ورودی و حالت) آن عملیات انجام می شود.  هر چند شعار "پیشگیری بهتر از درمان" در سایر حوزه ها مقبولیت عام دارد، مشکل اصلی این الگو (که با نام Tester-Doer در ادبیات .net شناخته می شود) نبود قابلیت Thread-Safety است. در قطعه کد زیر، چگونگی استفاده از این الگو دیده می شود.

if (!dictionary.ContainsKey("Tester"))
{
    dictionary.Add("Tester","Doer");
}

در الگوی واکنش گرا، عملیات بدون بررسی امکان فرخوانی، انجام می شود و در صورت بروز خطا پاسخ مناسب برای آن در نظر گرفته می شود. در دستکاری [4] خطای رخ داده دو حالت وجود دارد؛ یا به این خطا توسط متدی که در آن خطا رخ داده دستکاری می شود و یا به فراخواننده متد، در مورد خطا اطلاع رسانی می شود. برای اطلاع رسانی نیز دو راهکار وجود دارد؛ از طریق استثنا [5] و یا از طریق بازگردادن کد خطا. استفاده از استثنا ها باعث تغییر جریان اجرا می شود که در نهایت منجر به ایجاد Spaghetti code خواهد شد. Meyer در کتاب گرانقدر خود [6] به استفاده نادرست از استثنا در توابع اشاره می کند که به نوعی شبیه سازی Goto و هر چند بدتر از آن است. چرا که استثنا ها به مکانی که جریان اجرا را منتقل می کنند اشاره ای نمی کنند. پیاده سازی این الگو با روش بازگردادن کد خطا با نام Try-Parse در ادبیات .Net وجود دارد. در قطعه کد زیر چگونگی استفاده از این الگو دیده می شود.  در صورتی که متد TryGetValue به صورت Thread-Safe پیاده سازی شود، این الگو مشکل اشاره شده در الگوی Tester-Doer را ندارد.

string value;
var keyExist = dictionary.TryGetValue("TryParse", out value);
if (keyExist)
{
    Console.WriteLine(value);
}

یک پیاده سازی بهتر برای این الگو ایجاد یک کلاس برای خروجی تابع است. در این صورت می توان در این کلاس علاوه بر مقدار خروجی و موفقیت و عدم موفقیت اجرای تابع، علت شکست احتمالی آن را نیز قید کرد.



[1] - Utopia 
[2] - Proactive
[3] - Reactive
[4] - Handling 
[5] - Exception
[6] - Object oriented software construction
۰ نظر موافقین ۰ مخالفین ۰
ابراهیم خانی

design by contract

برنامه های کامپیوتری به خودی خود، درست یا غلط نیستند، بلکه باید نسبت به توصیف آنها سنجیده شوند. به عبارت بهتر، به جای صحبت از درست یا نادرست بودن برنامه می بایست از سازگاری اش نسبت به توصیف آن صحبت کرد. برای بررسی صحت توصیف نیاز به فرمولی برای تعریف صحت توصیف داریم. Meyer در کتاب ارزشمند خود [2] برای این توصیف از سه تایی Hoare استفاده می کند.  این سه تایی مفهوم اصلی در منطق Hoare است که در سال 1969 توسط Tony Hoare برای ارزیابی درستی برنامه های کامپیوتری ابداع شد. نگارش این سه تایی به این صورت است : 

{p} c {q}

که در آن p , q گزاره هستند و c برنامه (command) است و تفسیر آن چنین است : در صورتی که p درست باشد، با اجرای  c، q درست خواهد بود. قدرت این مفهوم به ظاهر ساده به خاطر قابلیت اعمال آن در سطوح متخلف تجرید است. به عبارت ساده تر هم ابزاری برای توصیف است و هم ابزاری برای بررسی انطباق برنامه با توصیف آن.

یک راه کار معقول برای بررسی درستی یک برنامه کامپیوتری، تقسیم آن به قسمت های کوچک تر و بررسی درستی هر یک از این قسمت ها است. اگر این واحد کوچکتر، تابع در نظر گرفته شود؛ اعمال سه تایی Hoare روی آن به این صورت خواهد بود : 

{pre} body {post}

که با توجه به تعریف مطرح شده در بخش قبل، معنی آن واضح به نظر می رسد؛ در صورتی که فراخواننده تابع شرایطی که در پیش شرط (precondition) مطرح شده است را فراهم کند (از طریق آرگومان های ارسالی به تابع)، با اجرای بدنه تابع (body) و خاتمه یافتن آن، شرایطی که در پس شرط (postcondition) قید شده است حاصل می گردد. اما در مدل شی گرا، توابع در کلاس ها قرار دارند و حالت این کلاس ها نیز در عملکرد توابع تاثیر گذار است. با در نظر گرفتن این موضوع، Meyer مدل مطرح شده را اینگونه گسترش می دهد : 

{pre ^ inv} body {post ^ inv}

که در آن Inv معرف حالت کلاس است که بایستی همواره برقرار باشد.  نکته ای که باید به آن توجه کرد اشتباه کردن طراحی بر اساس قرارداد با اعتبارسنجی داده ها (input validation)  است. تفاوت آشکاری بین این دو مفهوم است. در اعتبارسنجی ورودی ها، انتظار ورود دادهای غلط را داریم و به دنبال فیلتر کردن داده های ورودی هستیم اما در طراحی بر اساس قرارداد، Assertion ها نقاطی هستند برای وارسی کد نوشته شده. در واقع زمانی که ورودی غلط است با نمایش پیغام خطا و ... از کاربر می خواهیم آن را اصلاح کند اما زمانی که یک Assertion نقض می شود، نشان دهنده ایراد در کد نوشته شده است (که و اینکه می بایستی کد تغییر کند)



[1] - design by contract
[2] - object oriented software construction 
۰ نظر موافقین ۰ مخالفین ۰
ابراهیم خانی