۱۳۹۰/۰۷/۱۳

آشنايي با Refactoring - قسمت 2


قسمت دوم آشنايي با Refactoring به معرفي روش «استخراج متدها» اختصاص دارد. اين نوع Refactoring بسيار ساده بوده و مزاياي بسياري را به همراه دارد؛ منجمله:
- بالا بردن خوانايي كد؛ از اين جهت كه منطق طولاني يك متد به متدهاي كوچكتري با نام‌هاي مفهوم شكسته مي‌شود.
- به اين ترتيب نياز به مستند سازي كدها نيز بسيار كاهش خواهد يافت. بنابراين در يك متد، هر جايي كه نياز به نوشتن كامنت وجود داشت، يعني بايد همينجا آن‌ قسمت را جدا كرده و در متد ديگري كه نام آن، همان خلاصه كامنت مورد نظر است، قرار داد.
- اين نوع جدا سازي منطق‌هاي پياده سازي قسمت‌هاي مختلف يك متد، در آينده نگهداري كد نهايي را نيز ساده‌تر كرده و انجام تغييرات بر روي آن را نيز تسهيل مي‌بخشد؛ زيرا اينبار بجاي هراس از دستكاري يك متد طولاني، با چند متد كوچك و مشخص سروكار داريم.

براي نمونه به مثال زير دقت كنيد:
using System.Collections.Generic;

namespace Refactoring.Day2.ExtractMethod.Before
{
    public class Receipt
    {
        private IList<decimal> Discounts { get; set; }
        private IList<decimal> ItemTotals { get; set; }

        public decimal CalculateGrandTotal()
        {
            // Calculate SubTotal
            decimal subTotal = 0m;
            foreach (decimal itemTotal in ItemTotals)
                subTotal += itemTotal;

            // Calculate Discounts
            if (Discounts.Count > 0)
            {
                foreach (decimal discount in Discounts)
                    subTotal -= discount;
            }

            // Calculate Tax
            decimal tax = subTotal * 0.065m;
            subTotal += tax;

            return subTotal;
        }
    }
}

همانطور كه از كامنت‌هاي داخل متد CalculateGrandTotal مشخص است، اين متد سه كار مختلف را انجام مي‌دهد؛ جمع اعداد، اعمال تخفيف، اعمال ماليات و نهايتا يك نتيجه را باز مي‌گرداند. بنابراين بهتر است هر عمل را به يك متد جداگانه و مشخص منتقل كرده و كامنت‌هاي ذكر شده را نيز حذف كنيم. نام يك متد بايد به اندازه‌ي كافي مشخص و مفهوم باشد و آنچنان نيازي به مستندات خاصي نداشته باشد:

using System.Collections.Generic;

namespace Refactoring.Day2.ExtractMethod.After
{
    public class Receipt
    {
        private IList<decimal> Discounts { get; set; }
        private IList<decimal> ItemTotals { get; set; }

        public decimal CalculateGrandTotal()
        {
            decimal subTotal = CalculateSubTotal();
            subTotal = CalculateDiscounts(subTotal);
            subTotal = CalculateTax(subTotal);
            return subTotal;
        }

        private decimal CalculateTax(decimal subTotal)
        {
            decimal tax = subTotal * 0.065m;
            subTotal += tax;
            return subTotal;
        }

        private decimal CalculateDiscounts(decimal subTotal)
        {
            if (Discounts.Count > 0)
            {
                foreach (decimal discount in Discounts)
                    subTotal -= discount;
            }
            return subTotal;
        }

        private decimal CalculateSubTotal()
        {
            decimal subTotal = 0m;
            foreach (decimal itemTotal in ItemTotals)
                subTotal += itemTotal;
            return subTotal;
        }
    }
}

بهتر شد! عملكرد كد نهايي، تغييري نكرده اما كيفيت كد ما بهبود يافته است (همان مفهوم و معناي Refactoring). خوانايي كد افزايش يافته است. نياز به كامنت نويسي به شدت كاهش پيدا كرده و از همه مهم‌تر، اعمال مختلف، در متدهاي خاص آن‌ها قرار گرفته‌اند.
به همين جهت اگر حين كد نويسي، به يك متد طولاني برخورديد (اين مورد بسيار شايع است)، در ابتدا حداقل كاري را كه جهت بهبود كيفيت آن مي‌توانيد انجام دهيد، «استخراج متدها» است.

ابزارهاي كمكي جهت پياده سازي روش «استخراج متدها»:

- ابزار Refactoring توكار ويژوال استوديو پس از انتخاب يك قطعه كد و سپس كليك راست و انتخاب گزينه‌ي Refactor->Extract method، اين عمليات را به خوبي مي‌تواند مديريت كند و در وقت شما صرفه جويي خواهد كرد.
- افزونه‌هاي ReSharper و همچنين CodeRush نيز چنين قابليتي را ارائه مي‌دهند؛ البته توانمندي‌هاي آن‌ها از ابزار توكار ياد شده بيشتر است. براي مثال اگر در ميانه كد شما جايي return وجود داشته باشد، گزينه‌ي Extract method ويژوال استوديو كار نخواهد كرد. اما ساير ابزارهاي ياده شده به خوبي از پس اين موارد و ساير موارد پيشرفته‌تر بر مي‌آيند.

نتيجه گيري:
نوشتن كامنت، داخل بدنه‌ي يك متد مزموم است؛ حداقل به دو دليل:
- ابزارهاي خودكار مستند سازي از روي كامنت‌هاي نوشته شده، از اين نوع كامنت‌ها صرفنظر خواهند كرد و در كتابخانه‌ي شما مدفون خواهند شد (يك كار بي‌حاصل).
- وجود كامنت در داخل بدنه‌ي يك متد، نمود آشكار ضعف شما در كپسوله سازي منطق مرتبط با آن قسمت است.


و ... «لطفا» اين نوع پياده سازي‌ها را خارج از فايل code behind هر نوع برنامه‌ي winform/wpf/asp.net و غيره قرار دهيد. تا حد امكان سعي كنيد اين مكان‌ها، استفاده كننده‌ي «نهايي» منطق‌هاي پياده سازي شده توسط كلاس‌هاي ديگر باشند؛ نه اينكه خودشان محل اصلي قرارگيري و ابتداي تعريف منطق‌هاي مورد نياز قسمت‌هاي مختلف همان فرم مورد نظر باشند. «لطفا» يك فرم درست نكنيد با 3000 سطر كد كه در قسمت code behind آن قرار گرفته‌اند. code behind را محل «نهايي» ارائه كار قرار دهيد؛ نه نقطه‌ي آغاز تعريف منطق‌هاي پياده سازي كار. اين برنامه نويسي چندلايه كه از آن صحبت مي‌شود، فقط مرتبط با كار با بانك‌هاي اطلاعاتي نيست. در همين مثال، كدهاي فرم برنامه، بايد نقطه‌ي نهايي نمايش عمليات محاسبه ماليات باشند؛ نه اينكه همانجا دوستانه يك قسمت ماليات حساب شود، يك قسمت تخفيف، يك قسمت جمع بزند، همانجا هم نمايش بدهد! بعد از يك هفته مي‌بينيد كه code behind فرم در حال انفجار است! شده 3000 سطر! بعد هم سؤال مي‌پرسيد كه چرا اينقدر ميل به «بازنويسي» سيستم اين اطراف زياد است! برنامه نويس حاضر است كل كار را از صفر بنويسد، بجاي اينكه با اين شاهكار بخواهد سرو كله بزند! هر چند يكي از روش‌هاي برخورد با اين نوع كدها جهت كاهش هراس نگهداري آن‌ها، شروع به Refactoring است.