خلاصه کتاب Pragmatic Programmer. درس26

درس 26: How to Balance Resourcesما همه مون وقتی کد میزنیم، منابعو مدیریت میکنیم، از حافظه رم بگیر، ترنزکشنها، تردها، کانکشن های شبکه، فایل سیستمها، تایمرها و هر چیزی از این دست که محدود هستن. در اکثر مواقع هم استفاده از منابع یک پترن قابل پیش بینی رو دنبال میکنه، شما اونو الوکیت(allocate) میکنید، استفاده میکنید از اون ریسورس و در اخر هم اونو دی‌الوکیت(deallocate) میکنید.با این حال خیلی از برنامه نویسا پلن مدونی برای استفاده و سروکله زدن با منابع ندارن، در اینجا ی سری نکته که ممکنه به درد بخوره میاریم:· تمومش کن چیزی که شروعش کردیواین نکته در اکثر شرایط خیلی ساده قابل استفاده است. این نکته به زبون ساده میگه، فانکشن یا ابجکتی که منبعی رو الوکیت کرده، خودش موظف و مسئوله برای دی‌الوکیت کردنش. در اینجا یک مثال به زبون روبی رو بررسی میکنیم، این کد یک فایلو باز میکنه، اطلاعات مشتریا رو ازش میخونه، یک فیلدو اپدیت میکنه و فایلو مینویسه. برای اینکه مثال شفاف تر باشه ما از مدیریت خطاها چشم پوشی کردیم تو این مثال:def read_customer @customer_file = File.open(@name + ".rec", "r+") @balance = BigDecimal(@customer_file.gets) enddef write_customer @customer_file.rewind @customer_file.puts @balance.to_s @customer_file.close enddef update_customer(transaction_amount) read_customer @balance = @balance.add(transaction_amount,2) write_customer endتو نگاه اول شاید قابل توجیه باشه ی تابع اپدیت کاستومر که به ترتیب میخونه یه رکوردو، اپدیت میکنه بالانسو، و بعدم مینویسدش. اما خیلی فاجعه اس، دقت کنید تابع رید و رایت کاستومر کاملا بهم وابسته شدن، اون یک اینستنس شیر شده رو استفاده میکنن متغیر customer_file، وقتی اول رید کاستومر اجرا میشه اینستنس کاستومر فایل اینیشیال میشه و بعد هم همون اینستنس توی رایت کاستومر استفاده میشه. چرا بده این کد؟ فرض کنید لاجیک برنامه ی تغییر کوچیکی میخاد بکنه جهت اینکه بالانس مشتری فقط در صورتی که مثبت بود اپدیت بشه، اونوقت تابع اپدیت کاستومر به این شکل میشه:def update_customer(transaction_amount) read_customer if (transaction_amount >= 0.00) @balance = @balance.add(transaction_amount,2) write_customer endendشاید تستهارو هم رد بکنه، اما وقتی میره تو پروداکشن، هرچند ساعت یک بار میره تو دیوار، با این خطا too many file open، چون کد به شکلیه که ممکنه بعضی از مشتریا اصن واسشون کاستومر رایت کال نشه و فایل بسته نشه و باز بمونه...یک راه حل خیلی بد واسه هندل این شرایط میتونه تغییر اپدیت کاستومر به این شکل باشه:def update_customer(transaction_amount) read_customer if (transaction_amount >= 0.00) @balance += BigDecimal(transaction_amount, 2) write_customerelse @customer_file.close # Bad idea! endendاین کار کثیف، خطا رو هندل کرد، الان در هر صورت فایل بسته میشه، ولی الان سه تا فانکشن بهم وابسته شدن هر سه تاشون دارن با اینستنس customer_file کار میکنن. و با این کار میفتیم تو تله. این مدیریت منابع نیست...** هر تابعی که ریسورسی رو میگیره خودش باید ازادش کنه، کدو به شکل زیر ریفاکتور میکنیم:def read_customer(file) @balance=BigDecimal(file.gets) enddef write_customer(file) file.rewind file.puts @balance.to_s enddef update_customer(transaction_amount) file=File.open(@name + ".rec", "r+") # >-- read_customer(file) # | @balance = @balance.add(transaction_amount,2) # |write_customer(file)file.close endحالا تنها فانکشن اپدیت کاستومره که مسئولیت گرفتن و ازاد کردن اینستنس فایلو داره، همچنین در یکسری از زبونای برنامه نویسی با فورس کردن اسکوپ میتونیم طول عمر یک ابجکتو محدود کنیم. (مثه بلاک using() توی سی شارپ)· تخصیص لانه ایما برای حالتایی که به بیش از یک نسخه از منبعی نیازداریم، باید پترن نگهداری منابعمون رو گسترش بدیم چون پیچیدگی بیشتری توی این حالت وجود داره، دوتا ایده در این حالت وجود داره:- منابع رو به ترتیب عکس الوکیت کردنشون، دی الوکیت کنید. با این روش شما خیالتون راحته که ابجکت های یتیم نخاهید داشت.(ابجکتی که به ابجکت دی الوکیت شده رفرنس داره)- وقتی از یک منبع تعدادی رو در جاهای مختلف کدتون لازم دارید، همیشه در یک جهت الوکیشن رو انجام بدید، با اینکار از ددلاک جلوگیری میکنید فرض کنید پراسس 1 منبع یک رو داره و میخاد از 2 هم استفاده کنه و پراسس 2 بالعکس که توی این حالت هر دو پروسه ویت میشنمهم نیست چه جنس از منابع رو الوکیت میکنید از کانکشن شبکه بگیر تا فایل و حافظه و ... پایه ای ترین پترن مدیریت منابع اینه که هر کسی که منبعی رو الوکیت میکنه خودش مسئول و موظف دی الوکیت کردنشه.- استفاده از destructor در زبونهای ابجکت ارینتد این امکانو بهتون میده که اگر منابعی رو توسط ابجکتی الوکیت میکنید اون رو ازاد کنید.- در شرایطی که اکسپشن رخ بده، شما باید گارانتی کنید که منبعی که گرفتید ازاد بشه، فرض کنید در ابتدای تابع منبعو گرفتید و وسطش خطا رخ میده و کد به انتهای تابع برای ازادسازی منبع نرسه، دوتا انتخاب دارید برای هندل این شرایط:1- استفاده از متغیرهای اسکوپ(مثل متغیرهایی که در استک نگهداری میشن در c++ , rust)مثلا کد زیر در زبون راستو در نظر بگیرید:{let mut accounts = File::open("mydata.txt");// use 'accounts' //}// 'accounts' is now out of scope, and the file is// automatically closed2- استفاده از بلاک فاینالی finally که در ترای کش استفاده میشه و در نهایت این کد اجرا میشه و میتونید توش ازادسازی رو انجام بدید.try // some dodgy stuff catch // exception was raised finally // clean up in either case- حالا یک آنتی پترن توی حالت اکسپشن بررسی کنیم، معمولا میبینیم که تازه کارا اینجوری کد میزنن:beginthing = allocate_resource()process(thing)finallydeallocate(thing)endخوب حالا فرض کن که خطا روی خوده الوکیشن منبع رخ بده ینی اصن منبعی الوکیت نشه، حالا توی فاینالی میخاد چیو دی الوکیت کنه؟!!!!پترن درستش اینجوری میشه:thing = allocate_resource()beginprocess(thing)finallydeallocate(thing)end· چه موقع نمیتونید بالانس کنید مدیریت منابعومعمولا در مواقعی که برنامه از دیتا استراکچرهای داینامیک استفاده میکنه، فرض کنید یک تابع مقداری از حافظه رو الوکیت کرده و اونو ارتباط داده به استراکچر بزرگتری از کد توی این حالت ها، پترن پایه برای مدیریت منابع کارساز نیست.شما نیاز دارید تصمیم بگیرید که کدومشون مسئول ازاد سازی منابع هستن. چه اتفاقی میفته اگر استراکچر بالا سری رو دی الوکیت کنید؟ چند تا گزینه وجود داره:- استراکچر بالاسری مسئول ازادسازی همه منابع خودش و زیر دستی ها هست پس باید به صورت ریکرسیو همه شونو آزاد کنه- استراکچر بالاسری که دی الوکیت شد، ابجکت هایی که بهش رفرنس داشتن به صورت یتیم باقی بمونن- استراکچر بالاسری امتناع کنه از دی الوکیت شدن مادامی که تمام زیر دستی ها دی الوکیت و ازادسازی شون انجام بشهبرای این حالت ها ممکنه نیاز داشته باشید در بعضی از زبونای برنامه نویسی کد بنویسید و یا حتی برای هر مدل از استراکچرها ماژولی کتابخونه ای و اماده بنویسید و ازش استفاده کنید.· مدیریت منابعو چک کنیداز اونجایی که برنامه نویسای عملگرا به هیچ کس اعتماد ندارن، حتی خودشون، ایده خوبی به نظر میرسه که کدهایی برای چک کردن وضعیت ریسورس ها و منابع بزنید. مثلا wrapper هایی برای انواع ریسورس ها بنویسید و در کدتون بررسی کنید وضعیت منابع رو.همچنین از پروفایلرها هم میتونید استفاده کنید و مموری لیک ها (memory leak) رو دیتکت کنید.دروس مرتبط: 24, 30, 33منبع کانال تلگرامی: https://t.me/pragmaticprogrammer_fa