
امروز قصد داریم در مورد بخش سوم clean code صحبت کنیم. این بخش به شخصه بخش مورد علاقه من هستش. چون سعی کردم فلسفه و روشی رو که تو این بخش توضیح داده شده رو روی کدی بشدت شلوغ و نامرتب اجرا کنم و نتیجه رو هم دیدم همه چی به مراتب تمیز تر و خوانا تر شده بود و و ضریب تکرار کد خیلی پایین اومد بود. و هر بار که فرصتی پیدا می کنم سعی می کنم اون کد و تمیز تر کنم. به طور خلاصه استفاده از دستور العمل های این بخش به من خیلی کمک کرد.در ادامه این بخش رو بررسی می کنیم.
در سال های ابتدایی معرفی شدن برنامه نویسی، سیستم ها از روتین ها و زیر روتین ها (routines and subroutines)، برنامه ها و زیر برنامه ها (programs, subprograms) و تابع ها (function) تشکیل شده بودند. در زبان های برنامه نویسی امروزی این فاکشن ها هستن که هنوز با قدرت جایگاه خود را حفظ کرده اند. و از مهم ترین ارکان یک سیستم محسوب می شوند. حتما با این مشکل مواجه شده اید که یک فاکشن به قدری بزرگ شده باشه که عملا امکان اعمال تغییرات رو سخت یا ناممکن کرده باشد. و این معمولا اولین مشکلی است که همه برنامه نویس ها با ان مواجه می شوند. فاکشن های بزرگ که خوانایی کد رو کم می کنند و در اینده امکان درک کد را خیلی مشکل می کنند. در ادامه چند نکته و روش و راه حل رو باهم بررسی می کنیم.
کتاب اینطوری شروع می شه که:
اولین قانون برای فانکشن ها این است که باید خیلی کوچک باشند و دومین قانون این که باید از این هم کوچک تر باشند. توجیهی برای این ادعای خود ندارم. و هیچ تحقیق و مرجعی به منظور ارجاع و اثبات این که فانکشن های کوچک بهترند ندارم ولی من به مدت 40 سال فاکشن با اندازه های مختلف نوشته ام. از فاکشن های با حدود 3000 خط کد تا فاکشن هایی با 20 خط کد. و این تجربه به من اموخت که تست و اشکال یابی در فانکشن های کوچک خیلی راحت و سریع تر است.
اگر بخواهیم اندازه فانکشن رو به صورت کمی بسنجیم هر فانکشن باید در ابعاد یک صفحه نمایش بگنجند اما با توجه به ویژگی های محیط های برنامه نویسی امروزی و امکان تغییر فونت و اندازه فونت این معیار، معیار دقیقی نیست. اما برای بهتر کردن این معیار هر فانکشن باید کمتر از 20 خط باشد. بر اساس نمونه کدهای Kent Beck بهترین فانکشنها فانکشنهای 3 الی 4 خطی هستن که این نشانگر دقت و مهارت بالای برنامه نویس است.
نکته-Blocks and Indenting: بر اساس توضیحات قبلی بهتر است درون عبارات کنترلی مانند (if,while) تک خطی باشد. بنابراین بهتر است از فانکشن ها استفاده کرد. و همین طور نباید سطوح تو در تو بیشتر از یک یا دو سطح باشند. فانکشن نباید به اندازه ای بزرگ باشد که تمام سطوح تو در تو را در بر بگیرد.
فانکشن ها باید یک عمل را انجام بدهند. ان ها باید ان را به خوبی انجام دهند. و فقط ان را انجام دهند.
تلاش برای کوچک بودن فانکشن از چند کاره بودن ان جلوگیری می کند و تلاش برای ساخت فانکشن با یک وظیفه به کوچک بودن ان کمک می کند. به نوعی این دو قانون برای یک دیگر نقش کاتالیزور رو دارند.
مفهوم ساده ی مطرح شده که مشخص می کند فانکشن فقط به یک وظیفه عمل می کند این است که اگر ما بتوانیم از یک فانکشن یک فانکشن دیگر با نامی جدید که مفهومی مشابه با فانکشن قبلی نداشته باشد استخراج کنیم بنابراین عملا فانکشن قبلی ما تک وظیفهای نبوده بنابراین باید فانکشن را به فانکشن های کوچک تر بشکنیم.
مفهوم دقیق تری که مطرح شده، کدها در زیر فانشکن با نام معین باید در یک سطح انتزاع (abstraction) قرار داشته باشند. برای متوجه شدن دقیق تر این مفهوم به نمونه کد زیر (Listing 3-2) توجه کنید. برخی از این کد از بالاترین سطح انتزاع (مانند getHtml)، برخی در سطح میانی (مانند String pagePathName = PathParser) و برخی در پایین ترین سطح انتزاع قرار دارند (مانند ()append.) تشکیل شده است.
Listing 3-2 public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception { boolean isTestPage = pageData.hasAttribute("Test"); if (isTestPage) { WikiPage testPage = pageData.getWikiPage(); StringBuffer newPageContent = new StringBuffer(); includeSetupPages(testPage, newPageContent, isSuite); newPageContent.append(pageData.getContent()); includeTeardownPages(testPage, newPageContent, isSuite); pageData.setContent(newPageContent.toString()); } return pageData.getHtml(); }
خواندن کدی با ترکیبی از سطوح انتزاع، گیج کننده است و باعث میشود تشخیص عمکرد اصلی برای خواننده دشوار شود.
در ادامه برای بررسی تک وظیفه ای بودن متدی ارائه شده است که به (TO paragraph) معروف است. طبق این متد از بالا ترین انتزاع شروع میکنیم تا به پایین ترین انتزاع برسیم و باید توجه کنیم در هر انتزاع باید کدهای همان انتزاع را قرار دهیم. به نمونه زیر توجه کنید که مربوط به کد های بالاست (Listing 3-2). هر انتزاع با کلمه کلید TO شروع شده هر بخش از انتزاع (هر بخش برار متن بین دو ویرگول)که نیاز به سطح بعدی داشته باشد در ادامه مانند دستور العمل امده است که هم مشخص کننده انتزاع بعدی است و هم به دلیل شروع با کلمه کلیدی TO خود به عنوان یک انتزاع و فانکشن در نظر گرفته شده است. استفاده از این متد کمکی زیادی به ما می کند تا حرفه ای تر شویم.
To include the setups and teardowns, we include setups, then we include the test page content, and then we include the teardowns.
To include the setups, we include the suite setup if this is a suite, then we include the regular setup.
To include the suite setup, we search the parent hierarchy for the “SuiteSetUp” page and add an include statement with the path of that page.
To search the parent. . .
نکته-Use Descriptive Names: همان طور که قبلا گفته شد انتخاب نام مناسب از اهمیت بالایی برخوردار است. هر قدر که توابع ما کوچک تر باشند و بر روی یک وظیفه متمرکز باشند انتخاب اسم مناسب (descriptive name) برای ما اسان تر می شود.
نکته: اسم طولانی ولی توصیف کننده عملکرد فانکشن از یک اسم کوتاه و مبهم و حتی از کامنت گذاری هم مهم تر و بهتر است.
تعداد ورودی های یک فانکشن نیز از اهمیت بالایی برخوردار است. بهترین تعداد ورودی برای هر فانکشن صفر است. اما دستیابی به این عدد همیشه امکان پذیر نیست. از انجایی که وجود ورودی برای یک فانکشن پیچیدگی ادارکی ان را افزایش می دهد بنابراین وجود بیش از 3 ورودی برای توابع قابل قبول نیست و در صورت وجود چنین توابعی باید ریفکتور صورت گیرد. وجود ورودی برای فانکشن ها نه تنها اشنایی با عملکرد فانکشن های را سخت می کند بلکه در هنگام تست هم باید ترکیبات ورودی ها مورد تست قرار گیرد. که معمولا فانکشن های بیش از 2 ورودی فرایند تست سختی دارند.
زمانی که یک فانکشن بیش از دو یا سه ورودی داشت با دو دیدگاه در صورت امکان می توان تعداد ورودی های ان را کاهش داد:
Circle makeCircle(double x, double y, double radius); Circle makeCircle(Point center, double radius);
String.format("%s worked %.2f hours.", name, hours); String.format("%s worked %.2f hours.",List or Object);
این عنوان اندکی مبهم و در نگاه اول ممکن است مقصود رو منتقل نکند.ولی چیست؟ یک فانکشن باید در یک زمان یا کاری انجام دهد یا جوابی تولید کند. انجام هر دو کار در یک زمان برای یک فانکشن درست نیست. اما منظور چیست با یک مثال ببینیم.
public boolean set(String attribute, String value);
فرض کنید فانکشنی داریم که مقداری برای نام کاربری قرار می دهد و در صورت موفقیت true و در صورت عدم موفقیت false را بازمی گرداند. و باعث استفاده های عجیبی مانند مثال زیر شود.
if (set("username", "unclebob"))...
از دیدگاه خواننده برداشت های متفاوتی از عبارت بالا انجام میشود. ایا مقدار نام کاربری را بررسی می کند که قبلا unclebob قرار داده شده بود؟ یا بررسی می کند که مقدار نام کاربری به درستی unclebob قرار داده شود؟
همانطور که مشخص است فهمیدن عملکرد امکان پذیر نیست. یک راه حل انتخاب setAndCheckIfExists
به عنوان نام فانکشن است که خوانایی شرط را کم می کند و راه حل دوم جدا سازی شرط(query) از عملیات (command) است که کد به صورت زیر تغییر پیدا می کند.
if (attributeExists("username")) { setAttribute("username", "unclebob"); ... }
که به این ترتیب هر فانکشن یک عملکرد خاص را انجام می دهد.
تکرار(duplication)، مشکلی که باعث به وجود امدن مشکلات زیادی در کد نویسی می شود به اصطلاح باعث باد کردن (bloat) کد می شود. با یک تغییر در الگوریتم و یا یک ویژگی از برنامه نیاز به اصلاح n بخش کد و نیاز به n بار کد نویسی است.
Duplication may be the root of all evil in software
تکرار، می تواند ریشه تمام مشکلات نرم افزاری باشد. به همین دلیل اصول و روش های (principles and practices) مختلفی برای کم کردن و جلوگیری از تکرار ارائه شده است. متد ها و روش هایی که باید هر برنامه نویسی بر حسب نیاز با ان ها اشنا شود و از ان ها هنگام کد نویسی استفاده کند. از جمله این متد ها:
object-oriented programming ,Structured programming,Aspect Oriented Programming, Compo-nent Oriented Programming
تا اونجایی که می شد سعی کردم این بخش رو خلاصه و مفید کنم که حداقل دید کلی به خواننده بده امیدوارم تونسته باشم این کارو بکنم.مطمئنا مفهوم مطرح شده توی بخش رو تو اکثر بخش های کداتون دیدین جایی وجود نداره که شما فانکشن نبینین و این تعدد استفاده نیاز داره تا ما بدونیم چطوری و کجا ازشون استفاده کنیم تا بتونیم خوب کد بنویسیم. البته این بخش یه اشاره هایی ام به مفهوم شی گرایی داره شاید تو نوشته من خیلی این اشاره مشخص نباشه ولی در نهایت خیلی از این مشکلات در فانکشن ها با شی گرایی قابل حل هست یکی از اصول مهمیه که باید بدونین سعی کنین یه مطالعه ای پیرامونش بکنین.