این پست از سری برداشت های من از کتاب Clean Code نوشته Robert C.Martin و ادامه بخش اول توابع هست.
هر تابعی قراره که یک کار مشخص رو انجام بده. بعضی وقت ها پیاده سازی تابع ها به شکلی میشه که علاوه بر کار مورد نظر تاثیرات جانبی هم ایجاد میشه. این تاثیرات میتونن تغییر روی متغیر های global، تغییر روی متغیر های کلاس مورد استفاده و هر چیزی که جدای کار اصلی تابع هست باشه. اثرات جانبی باعث ایجاد تداخل های ناخواسته و مشکلات غیر قابل انتظار میشن.
مثال کتاب تو این مورد رو ببینیم:
اونطور که از اسم تابع checkPassword به نظر میاد، قراره که نام کاربری و رمز رو باهم تطابق بده و بگه که نتیجه مثبت بود یا منفی. چیزی که فقط با خوندن کل تابع متوجه میشیم ()Session.initialize هست. این نمونه واضحی از Side Effect ه که نه اسم تابع نه کاربرد اون چنین چیزی رو نشون نمیده و ممکنه کسی که از این تابع با خیال راحت جایی از کد استفاده کنه اصلا متوجه نشه که با این کار داره Session رو مجدد میسازه و اطلاعات احتمالی که روی اون هست رو پاک میکنه. اگر نیاز به چنین حالتی دارید حداقل کاری که میتونید انجام بدید اینه که اسم تابع رو طوری تغییر بدید که عملکردش مشخص باشه و کسی رو به اشتباه نندازه. توی این مثال checkPasswordAndInitializeSession میتونه این کار رو انجام بده.
توی پیاده سازی یک سری توابع متغیری که به عنوان نتیجه قرار هست گرفته بشه رو در قالب ورودی دریافت میکنیم. این کار باعث سردرگمی میشه و این نیاز رو بوجود میاره که ورودی های تابع رو دائم بررسی کنیم که آیا واقعا ورودی هستن یا در اصل خروجی هایین که به شکل ورودی تعریف شدن.
بیشترین جایی که چنین الگویی دیده میشه وقتیه که متغیری دریافت میشه و روش تغییراتی انجام میشه و خودش داخل خودش به روز میشه. توی این موارد بهتره که (حداقل توی زبان های شی گرا) از Object استفاده کنیم.
این مثال رو ببینین:
کاری که این تابع انجام میده اینه که قوانینی رو بررسی میکنه (که این جا خلاصه شده و با ...// مشخص شده) و در صورتی که نتیجه مثلت باشه مقدار isValid رو روی order ست میکنه.
حالا حالت دوم رو ببینین:
توی حالت دوم با استفاده از شی گرایی ساختار واضح شده و نیازی به آپدیت متغیر ورودی به شکل نامشخص نداریم. استفاده از خروجی ها در قالب ورودی تابع به طور کلی بهتره که اتفاق نیافته و از راه های شفاف استفاده کنیم.
یکی از هدف های اصلی قاعده های کد تمیز اینه که برای فهمیدن کد لازم نباشه همه ی پیاده سازیش رو بخونیم.
توابع یا باید مقداری رو بخونن و برگردونن یا کار دیگه ای انجام بدن. یعنی خوندن یک مقدار یا دیدن این که یک مقدار وجود داره یا نه خودش «کار» به حساب میاد و یادمون هست که تابع باید یک کار انجام بده. پس این که توی یک تابع هم مقداری رو تغییر بدیم و هم اون رو بخونیم اشتباهه.
پس برای مثال اگر میخواید ببینید که یک فیلد روی یک Object وجود داره و اگر وجود داشت اون رو تغییر بدید این رو درقالب دو تابع جدا پیاده سازی کنین.
خلاصه: یادتون باشه که خوندن یا چک کردن وضعیت یک متغیر خودش یک کار هست و خب تابع هم قرار بود فقط یک کار انجام بده.
یکی از تکراری ترین الگو هایی که توی کدبیس یه نرم افزار میبینید هدایت کردن حالت های خطا به روند منطقیه که بهش Exception Handling میگیم. زمانی که میخواین حالت خطایی رو اعلام کنید از Exception استفاده کنید و سراغ روش هایی مثل return کردن یک کد خطا و امثال اون نرید. استفاده از روش های غیر Exception باعث میشه که نتیجه تابع رو مجبور باشید با چندین دستور if بررسی کنید و برای هر حالت روند درستی رو پیاده کنید. با استفاده از Exception میتونیم این کار رو (Exception handling) با ساختار صحیح و با استفاده از دستور های try/catch پیاده سازی کنیم.
تابعی که قراره حالت های خطا رو برطرف کنه باید با دستور try شروع شه و بعد از دستور های catch یا finally هم نباید کد دیگه ای وجود داشته باشه به این دلیل که برطرف کردن خطا خودش یک «کار» هست و نباید بیشتر از یک کار داشته باشیم داخل هر تابع. به طور ایدهآل باید داخل خود بخش های try و catch (یا finally) هم فقط یک تابع رو صدا بزنیم.
بیاید مثال کتاب رو در این موارد با هم ببنیم:
توی این مثال تابعی به اسم deletePage داریم که به جای Exception داره کد خطا رو برمیگردونه.
حالا اون رو به شکلی تغییر میدیم که از Exception استفاده کنه:
همونطور که گفتم برای گرفتن نتیجه تمیز تر میتونیم (و چه بهتر که) داخل بخش های try و catch رو هم به تابع های جداگانه منتقل کنیم:
به مثال ها جدای محتوای اصلی هم نگاه کنین و ببینین که چقدر نسخه های اصلاح شده با قواعد کد تمیز، راحت تر خونده میشن و متوجه میشیم که چه کاری رو انجام میدن.
نوشتن کد مثل هر نوع نوشتن دیگه یک فرایند چند مرحله ایه. شما وقتی یک مقاله عادی هم بخواید بنویسید، اول افکارتون رو مکتوب میکنید و بعد توی چند مرحله اون ها رو ساختار مند و مرتب میکنید و مطمئن میشید که از نظر نگارشی درست هستن. قرار نیست کد از اول به شکل درست و تمیز نوشته بشه، بهتره کدی که کار میکنه رو بنویسید بعد توی چند مرحله اسم ها رو اصلاح کنید، از دل کد، تابع ها (و حتی کلاس ها)ی جدید بیرون بکشید و توی حالت ایده آل چون برای نسخه اصلی کد تست نوشتید با پاس شدن تست ها مطمئن میمونید که کد اصلاح شده همچنان همان کار اولیه رو به درستی انجام میده. (کلا یکی از مهم ترین کاربرد های تست نوشتن اینه که refactor کردن و تغییر دادن کد رو تبدیل به کار شیرین و بدون استرس میکنه)
این بخش من رو یاد جمله ی معروف Kent Beck میندازه که نوشتن کد رو توی سه مرحله پیشنهاد میده:
Make it work, make it right, make it fast.
با رعایت کردن نکته هایی که گفته شد، توابعی که از این به بعد مینویسین کوتاه، منظم و دارای اسم های به جا خواهد بود. اگر کل یک کد بیس رو زبانی بدونیم که داستان یک سیستم رو توضیح میدن، توابع در واقع فعل های اون زبان به خصوص هستن و هدف اصلی از کل محتوای کتاب اینه که کد هایی بنویسیم که داستان سیستم رو به درستی و وضوح بیان کنن.
امیدوارم با خوندن این سری مقاله ها ضمن اینکه بخشی از محتوای کتاب Clean Code رو متوجه میشید، علاقمند به خوندن این کتاب هم بشید و سراغش برید.
بخش های قبلی از این سری که قبلا منتشر شدن: