• مقالات
  • /
  • Clean Code
  • /
  • اصول کد تمیز توابع

اصول کد تمیز توابع


در نوشتن یک کد تمیز و اصولی، توابع در خط مقدم قرار دارند و درست و تمیز نوشتن آنها موضوع بحث این مقاله می باشد.

برای درک بهتر مطلب نگاهی به قطعه کد زیر بندازین و سعی کنین اون رو توی 3 دقیقه بررسی کنید و بفهمید که داره چی کار میکنه.

 

public static String testableHtml(  PageData pageData,boolean includeSuiteSetup) throws Exception {
        WikiPage wikiPage = pageData . getWikiPage();
        StringBuffer buffer = new StringBuffer();
        if (pageData . hasAttribute("Test")) {
            if (includeSuiteSetup) {
                WikiPage suiteSetup = PageCrawlerImpl . getInheritedPage( SuiteResponder . SUITE_SETUP_NAME, wikiPage);
                if (suiteSetup != null) {
                    WikiPagePath pagePath = suiteSetup . getPageCrawler() . getFullPath(suiteSetup);
                    String pagePathName = PathParser . render(pagePath);
                    buffer . append("!include -setup ."). append(pagePathName). append("\n");
                }
            }
            WikiPage setup = PageCrawlerImpl . getInheritedPage("SetUp", wikiPage);
            if (setup != null) {
                WikiPagePath setupPath = wikiPage . getPageCrawler() . getFullPath(setup);
                String setupPathName = PathParser . render(setupPath);
                buffer . append("!include -setup ."). append(setupPathName).append("\n");
            }
        }
        buffer . append(pageData . getContent());
        if (pageData . hasAttribute("Test")) {
            WikiPage teardown = PageCrawlerImpl . getInheritedPage("TearDown", wikiPage);
            if (teardown != null) {
                WikiPagePath tearDownPath = wikiPage . getPageCrawler() . getFullPath(teardown);
                String tearDownPathName = PathParser . render(tearDownPath);
                buffer . append("\n"). append("!include -teardown ."). append(tearDownPathName). append("\n");
            }
            if (includeSuiteSetup) {
                WikiPage suiteTeardown = PageCrawlerImpl . getInheritedPage(SuiteResponder . SUITE_TEARDOWN_NAME, wikiPage );
                if (suiteTeardown != null) {
                    WikiPagePath pagePath = suiteTeardown . getPageCrawler() . getFullPath(suiteTeardown);
                    String pagePathName = PathParser . render(pagePath);
                    buffer . append("!include -teardown ."). append(pagePathName).append("\n");
                }
            }
        }
        pageData . setContent(buffer . toString());
        return pageData . getHtml();
    }

کد بالا رو مطالعه کردید؟ آیا متوجه شدین که داره چی کار میکنه؟ احتمالا متوجه نشدین! میدونین چرا؟

این کد خیلی طولانیه و یکی از دلایلی وقتی میخونیمش گیج میشیم هم همینه. شاید اگه کوتاه تر بود راحت تر میشد فهمید داره چی کار میکنه. ضمنا یه سری شرط های تو در تو و استفاده از سطوح انتزاع مختلف توی کد باعث میشه درکش سخت تر باشه!

حالا بذارین سعی کنیم کمی تغییر توی این کد ایجاد کنیم. یه سری متغییر ها رو تغییر نام بدیم و چند تا تابع از دل این تابع بزرگ بکشیم بیرون.

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();
    }

حالا کمی بهتر شد! دوباره به کد بالا نگاه کنید و سعی کنید سر در بیارین داره چیکار میکنه! این بار کد خیلی کوتاه تره و اسامی بهتری هم برای متغییرها استفاده شده. پس احتمالا نسبت به کد اول خیلی قابل درک تر و تمیز تره!

البته هنوزم کاملا شفاف نیست. احتمالا اگه یکی از اعضای تیم Fitness که این کد رو نوشتن نیستید، کامل متوجه نمیشین داره چیکار میکنه و شای فقط حدس میزنین که یه صفحه HTML رو داره رندر میکنه و اگه یه TestPage بهش بدیم، قبل از رندر کردن یه کارایی روش میکنه!!

خوب، بریم سراغ اصل مطلب:

چه فاکتورهایی باید تو نوشتن توابع رعایت بشه تا تر تمیز و اصولی باشن و هر کس که دیدشون بتونه بفهمتشون؟

توابع باید کوتاه باشند:

توی نوشتن توابع باید سه تا اصل و قانون طلایی رو حتم حتما رعایت کنیم:

اولین و مهمترین قانونی که باید تو نوشتن توابع رعایت کنین، اینه که تا حد ممکن باید تابع شما کوتاه باش!

 دومین قانون که باید حتما رعایت کنین اینه که تابعی که نوشتین حتی از اینی که هست هم باید کوتاهتر باشه!

و سومین اصلی که باید رعایت کنین اینه که باز هم سعی کنید کد تابع رو کوتاه تر کنید.

آنکل باب که یکی از پیشروها در زمینه کد تمیز هست و حتی یکی از بنیان گذاران اجایل هست میگه:

"در سال هایی که من تجربه کد نویسی دارم، کدها و توابع زیادی با طول های مختلف نوشتم. برخی توابه من به 3000 خط هم میرسید و برخی در حد 200 یا 300 خط بود و بعضیا حدود 10 خط! ولی چیزی که به صورت عملی توی این سال ها بدست اوردم اینه که توابع باید در حد امکان کوتاه و کوتاه تر باشن!"

توی دهه 80 اینجوری مرسوم بوده که توایه باید جوری نوشته بشن که به صورت کامل در صفحه مانیتور قابل دیدن باشن! با توجه به تکنولوژی در اون زمان، تقریبا توابع باید در نهایت 24 خط و 80 کاراکتر در هر خط میداشتن!

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

کدهای توابع نباید 200 خط و یا حتی 100 خط باشن ! طول اونا باید به سختی به 20 خط برسه! و اگه درستشو بخواین توابعی که طولی حدود 3 یا 4 خط داشته باشن ایده آل هستن!!

به کد زیر دقت کنین:

public static String renderPageWithSetupsAndTeardowns( PageData pageData, boolean isSuite) throws Exception {
        if (isTestPage(pageData))
            includeSetupAndTeardownPages(pageData, isSuite);
        return pageData.getHtml();
    }

 

شاید حدس زده باشین که این کد، در واقع همون کد اول مقاله هست!! با این تفاوت که حسابی تمیز شده و الان دیگه فقط شامل یک سطح انتزاع هستش و البته قابل درک برای هر کسی که این کد رو نگاه کنه هست. طول توابعی که مینویسن نباید بیشتر از این باشه!

بلوک ها و فرو رفتگی ها

تا حالا فکر کردین که چندتا شرط تو در تو میتونین در کد خود استفاده کنین؟ خوب عملا از لحاظ فنی شما میتونین ده ها if و while و for رو داخل هم استفاده کنین! ولی هر چی بیشتر این کار رو بکنین، کدتون کثیف تر میشه و بیشتر خوانایی کدتون از بین میره!  

توی کتاب کد تمیز، عمو باب میگه که اگه میخواین کدی خوانا داشته باشین نباید بیشتر از یک سطح از if و for و while استفاده کنین!! تازه اونم به این شرط که بدنه این بلوک ها هم خودشون یک تابع باشن!!

اینجوری عملا امکان اینکه چند تا فر رفتگی داخل هم داشته باشین اصلا به وجود نمیاد! و این عالیه.

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

 

توابع باید فقط یک کار انجام دهند

 

 

خوب، نکته بعدی تو نوشتن توابع اینه که تابعی که مینویسین باید فقط یک کار انجام بده و یک هدف داشته باشه، نه اینکه چندین کار رو با هم انجام بده. یا به عبارتی:

تابع باید تنها یک کار را انجام دهید و آن یک کار را به بهترین شکل انجام دهد.

مسلما این جمله که "تابع باید تنها یک کار انجام بدهد" خیلی خوبه! ولی مشکل اینجاست که "یک کار" یعنی چی؟ و ما چجوری باید بفهمیم که الان تابعی که نوشتیم داره یک کار میکنه یا چند تا کار؟؟!!

خوب میخوایم چند راه عملی برای فهمیدن این موضوع پیشنهاد بدیم:

یکی از ساده ترین کارهایی که میتونیم بکنیم اینه که سعی کنیم کارکرد تابع خودمون رو در یک پاراگراف شرح بدیم! قاعدتا اگه تابع شما فقط یک کار انجام بده، شرح اون نباید بیشتر از یک جمله دو خطی بشه! پس اگه موقع توضیح اینکه "تابع شما داره چیکار میکنه" دیدین باید بیشتر از 2 خط بنویسین، باید شک کنین که آیا این تابع واقعا داره یک کار میکنه؟؟

نکته دوم اینکه یک تابع عملا باید یک سطح پایین تر از سطح انتزاع نامی که براش انتخاب کردین عمل بکنه و نه بیشتر! پس اگر دیدین که تابع شما داره در چند سطح انتزاع مختلف کار میکنه باید شک کنید کنید به اینکه قانون فوق رو رعایت کردین!

"منظور از سطح انتزاع اینه که تمام اعمالی که تابع داره انجام میده در یک سطح باشند. مثلا اینطور نباشه که تابع شما هم کارهای کلی و هم جزئی رو با هم انجام بده. مثلا هم محاسبات قیمتی مربوط به فروش یک کالا رو انجام بده و هم عملیات لاگ گرفتن رو!! "

نکته سوم اینکه بدنه تابع شما نباید امکان دسته بندی شدن داشته باشه! منظور اینه که مثلا بخش اولش عملیات جمع اوری اطلاعات از پایگاه داده رو انجام بده و بخش بعدی عملیات محاسباتی روی اون اطلاعات رو!! این عملا داره دو تا کار میکنه اونم در چند سطح انتزاع مختلف!

برای اینکه بهتر متوجه بشین منظور از سطح انتزاع چیه بذارین یه مثال بزنم:

فکر کنید شما یه روزنامه خریدین و میخواین مطالعه کنین، اولین کاری که میکنین چیه؟ قاعدتا میرین سراغ سرتیتر خبر ها و عناوین اخبار رو نگاه میکنین. اگر از یکی از سرتیترها خوشتون بیاد و علاقه مند بشین که جزییات خبر رو بخونین، تازه میرین سراغ متن خبر! حالا فکر کنین که دارین اخبار ورزشی رو دنبال میکنین و به خبر "پیروزی تیم ملی والیبال ایران در برابر برزیل" بر میخورین. این تیتر برای شما خیلی جذابه، پس میرین سراغ متن خبر. در پاراگراف اول میبینین که به صورت بسیار کلی و خلاصه خبر پیروزی 3 بر 1 تیم والیبال ایران در برابر برزیل رو شرح داده، خوب اگه بازم علاقه به جزییات بیشتر داشته باشین، میرین سراغ پاراگراف دوم و میبینین که مثلا شرح داده که هر ست رو چند بر چند پیروز شدیم و اطلاعات جزیی تری از بازی داده! بازم اگه علاقه به جزییات دقیقتری داشته باشین به مطالعه ادامه میدین و میرین پاراگراف بعدی. خوب میبینین که شرح دقیق اینکه در هر ست چه بازیکنایی چندتا امتیاز برای تیمشون کسب کردن و .... رو به دقت شرح داده!

خوب به این میگن رعایت کردن ادب! و انتزاع! 

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

 

خوب دوستان این بحث همچنان ادامه داره و ادامه اون رو حتما در قسمت های بعدی با شما به اشتراک میذارم. اگه دوستاشتین میتونین مقاله اصول نامگذاری متغییر ها و توابع رو مطالعه کنید که برای نامگذاری متغییرها و همچنین توابع بسیار به درد میخوره. و یا اگه علاقه مند به مطالعه کتاب کد تمیز عمو باب هستین میتونین اونو از لینک زیر دانلود کنین.

دانلود کتاب کد تمیز عمو باب

خیلی خوشحال میشم که نظراتتون رو با ما به اشتراک بگذارین.

ارتباط با ما

کد تمیز مشتاق شنیدن نظرات و پیشنهادات سازنده شما عزیزان میباشد.

تماس با ما

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

در حوزه گیت

; ;