زمان تخمینی مطالعه: 12 دقیقه
مفهوم Refactoring چیست؟
مفهوم Refactoring یک فرآیند سیستماتیک برای بهبود کد بدون ایجاد قابلیت جدیدی است که بتواند تغییر ایجاد کرده و قابلیت این را داشته باشد که یک آشفتگی در کد را به یک نسخه تمیز و با طراحی ساده تبدیل کند.
مفهوم کد تمیز Clean code
هدف اصلی Refactoring مبارزه با بدهی فنی(Technical Dept) است. طبق تعریف ارائه شده در بالا این مفهوم آشفتگی ساختاری کد را به حالتی تمیز و طراحی سادهتر تبدیل میکند. خوب! اما به هر حال کد تمیز چیست؟ در اینجا به برخی از ویژگیهای آن اشاره میکنیم:
- کد تمیز برای برنامه نویسان دیگر واضح و خواناست: اینجا در مورد الگوریتمهای فوق العاده پیچیده صحبت نمیکنم. نامگذاری ضعیف متغیرها، کلاسها و متدهای متورم(Bloated) و غیره که همه اینها باعث میشود کد شلخته و درک آن دشوار باشد.
- کد تمیز دارای تکرار زیاد نیست: هر بار که باید تغییری در یک کد تکراری ایجاد کنید، باید به یاد داشته باشید که همان تغییر را در هر نمونههای همان کد باید ایجاد کنید. این باعث افزایش بار شناختی و همچنین کاهش سرعت پیشرفت میشود.
- کد تمیز حاوی حداقل تعداد کلاسها و سایر قطعات متحرک است: کد کمتر چیزهای بار کمتری برای نگه داشتن در ذهن شماست. کد کمتر نگهداری کمتری و همچنین باگ کمتری دارد. کد یک مسئولیت است، آن را کوتاه و ساده نگه دارید.
- کد تمیز تمام تستها را پشت سر میگذارد: شما می دانید حتی زمانی که 95 درصد از تستهای شما قبول شدهاند، کد شما هنوز کثیف است. وقتی پوشش آزمایشی شما 0 درصد باشد، میدانید که این نشانه این است که دچار مشکل شده اید.
- کد تمیز در واقع کدی است که نگهداری آن سادهتر و ارزان تر است!
بدهی فنی(Technical Dept)
استعاره «بدهی فنی» در رابطه با کد کثیف در ابتدا توسط Ward Cunningham پیشنهاد شد. همه برنامه نویسان تلاش خود را میکنند تا از ابتدا کدهای عالی بنویسند. احتمالاً هیچ برنامه نویسی وجود ندارد که عمداً کدهای کثیف را در پروژههای خود بنویسد. اما باید دید کد تمیز در چه مرحلهای به کد کثیف تبدیل میشود؟
تصور کنید اگر از بانک وام بگیرید، این کار به شما امکان میدهد که با داشتن پول سریعتر اقدام به خرید کنید. در واقع شما برای تسریع در فرآیند خرید حاضر هستید که هزینه اضافی به عنوان سود وام به بانک پرداخت کنید. در برخی شرایط ممکن است حتی مقدار سود پرداختی به وام از کل مبلغ وام فراتر رود و باعث شود که بازپرداخت کامل وام غیرممکن شود.
همین اتفاق ممکن است با کد رخ دهد. به عبارتی میتوانید در پروژه کد نویسی خود به طور موقت بدون نوشتن تست برای ویژگیهای جدید، سرعت خود را افزایش دهید، اما این کار به تدریج پیشرفت شما را هر روز کندتر میکند تا اینکه در نهایت با نوشتن تستها بدهی خود به کد را پرداخت کنید.
– علل بدهی فنی
- فشار تجاری: گاهی اوقات شرایط تجاری ممکن است شما را مجبور کند که ویژگیها را قبل از اتمام کامل ارائه دهید. در این حالت، پچها و راه حلهای ناقص و کثیف در کد ظاهر میشوند تا قسمتهای ناتمام پروژه بتوانند مخفی شوند.
- عدم درک عواقب بدهی فنی: گاهی اوقات کارفرمای شما ممکن است درک نکند که بدهی فنی تا آنجا که سرعت توسعه را با انباشته شدن بدهی کاهش میدهد،دارای “بهره” و سود مضر است. این موضوع میتواند اختصاص زمان تیم به بازسازی را بسیار دشوار کند زیرا مدیریت ارزش آن را درک نمیکند.
- ناتوانی در مبارزه با انسجام دقیق اجزا: این زمانی رخ میدهد که پروژه به جای محصولی با تک تک ماژولها، شبیه monolith(شکل یکپارچه) است. در این صورت، هر گونه تغییر در یک قسمت از پروژه، سایر قسمتها را تحت تأثیر قرار میدهد. در این حالت توسعه تیمی دشوارتر میشود زیرا جدا کردن کار تک تک اعضا دشوار خواهدبود.
- عدم وجود آزمایشات: فقدان بازخورد فوری، راهحلهای سریع اما پرخطر را ترویج میکند. در بدترین حالت ممکن، این تغییرات بدون هیچ گونه آزمایش قبلی، مستقیماً در محصول نهایی پیادهسازی و مستقر میشوند. اینگونه عملکردها میتوانند دارای عواقب فاجعه بار باشند. به عنوان مثال، یک Hotfix با ظاهری معصوم ممکن است یک ایمیل آزمایشی عجیب را به هزاران مشتری ارسال کند یا حتی بدتر از آن، کل پایگاه داده را پاک یا خراب کند.
- عدم وجود مستندات: این امر ورود افراد جدید به پروژه را کند میکند و در صورت ترک افراد اصلی پروژه، میتواند فرآیند توسعه را متوقف کند.
- عدم تعامل بین اعضای تیم: اگر پایگاه دانش موجود در سراسر مجموعه توزیع نشود، اعضاء در نهایت با درک قدیمی و گذشته از فرآیندها و اطلاعات مربوط به پروژه کار خواهند کرد. این وضعیت زمانی تشدید میشود که توسعه دهندگان جوان توسط مربیان خود آموزش نادرست داشته باشند.
- توسعه بلند مدت همزمان در چندین شاخه: این مورد میتواند منجر به انباشته شدن بدهی فنی گردد که پس از آن ادغام تغییرات افزایش خواهد یافت. هرچه تغییرات بیشتری به صورت جداگانه ایجاد شود، میزان کل بدهی فنی بیشتر میشود.
- Refactoring با تاخیر: به طور کلی الزامات پروژه دائماً در حال تغییر است و در برخی مواقع ممکن است مشخص شود که بخشهایی از کد منسوخ شده و یا دست و پا گیر شده است و باید برای برآورده کردن نیازهای جدید دوباره طراحی شود. از سوی دیگر، برنامهنویسان پروژه هر روز کد جدیدی مینویسند که با قطعات منسوخ شده کار میکند. بنابراین، هر چه مفهوم refactoring بیشتر به تأخیر بیفتد، کدهای وابسته بیشتری باید در آینده دوباره باز تولید شوند.
- عدم نظارت بر انطباق: عدم نظارت بر انطباق زمانی اتفاق رخ میدهد که هرکسی که بر روی پروژه کار میکند، آنطور که صلاح میداند، کد مینویسد.
- بی کفایتی: این مورد زمانی رخ میدهد که توسعه دهنده نمیداند چگونه کد مناسب بنویسد.
چه زمانی refactoring انجام دهیم؟
معمولا مفهوم Refactoring زمانی استفاده میشود که قانون سه حاکم باشد. این قانون دارای مفاد زیر است:
- وقتی برای اولین بار کاری را انجام میدهید، فقط آن را انجام دهید.
- وقتی برای بار دوم کاری مشابه انجام میدهید، از اینکه مجبور به تکرار آن هستید بغض کنید اما به هر حال همان کار را انجام دهید.
- وقتی برای سومین بار کاری را انجام میدهید، refactoring را شروع کنید.
– هنگام افزودن یک ویژگی
- مفهوم Refactoring به شما کمک میکند کد دیگران را درک کنید. اگر مجبور به مقابله با کد کثیف شخص دیگری هستید، ابتدا سعی کنید آن را اصلاح کنید. درک کد تمیز بسیار سادهتر است. شما آن کد را نه تنها برای خودتان، بلکه برای کسانی که بعد از شما از آن کد استفاده میکنند نیز بهبود خواهید داد.
- Refactoring اضافه کردن ویژگیهای جدید را آسانتر میکند. ایجاد تغییرات در کد تمیز بسیار سادهتر است.
– هنگام رفع اشکال
- اشکالات موجود در کد دقیقاً مانند موارد موجود در زندگی واقعی رفتار میکنند: آنها در تاریکترین و کثیفترین مکانهای کد زندگی میکنند. کد خود را تمیز کنید و خطاها عملا خود را رسوا خواهند کرد.
- مدیران از Refactoring پیشگیرانه قدردانی میکنند زیرا نیاز به انجام وظایف Refactoring مجدد را در آینده برطرف میکند. رئیسهای شاد برنامه نویسان را خوشحال میکنند!
– در طول بررسی کد
- بررسی کد ممکن است آخرین فرصت برای مرتب کردن کد قبل از اینکه در دسترس عموم قرار گیرد باشد.
- بهتر است چنین بررسیهایی را به صورت جفت با یک نویسنده انجام دهید. به این ترتیب میتوانید مشکلات ساده را به سرعت برطرف کنید و زمان رفع مشکلات سختتر را بسنجید.
نحوه انجام مفهوم Refactoring
Refactoring باید به عنوان یک سری تغییرات کوچک انجام شود، که هر کدام کد موجود را کمی بهتر میکند و در عین حال برنامه را در حالت فعالیت قرار میدهد. در ادامه چک لیست صحیح Refactoring آورده خواهد شد:
- کد باید تمیزتر شود: اگر کد پس از Refactoring به همان اندازه قبل کثیف باقی بماند باید اعلام کرد ک فقط یک ساعت از عمر خود را تلف کردهاید. باید سعی کنید تا بفهمید چرا این اتفاق افتاده است. این اغلب زمانی اتفاق میافتد که با تغییرات کوچک از Refactoring فاصله میگیرید و یک دسته کامل از بازسازیها را در یک تغییر بزرگ ترکیب میکنید. بنابراین فراموش کردن کار بسیار آسانی است، به خصوص اگر محدودیت زمانی داشته باشید. اما ممکن است هنگام کار با کد بسیار شلخته و کثیف نیز اتفاق بیفتد. در این حالت هرچه بهبود انجام دهید باز هم کد به طور کلی یک فاجعه باقی میماند. در این مورد، ارزش آن را دارد که به بازنویسی کامل بخشهایی از کد فکر کنید. اما قبل از آن باید تستهای کتبی داشته باشید و زمان خوبی را کنار بگذارید. در غیر این صورت، به نتیجههای گفته شده در بالا کار خود را به پایان خواهید رساند.
- عملکرد جدید نباید در طول Refactoring ایجاد شود: مفهوم Refactoring و توسعه مستقیم ویژگیهای جدید را با هم مخلوط نکنید. سعی کنید این فرآیندها را حداقل در محدوده تعهدات فردی جدا کنید.
- پس از Refactoring باید تمام آزمایشات موجود پاس شوند: دو مورد وجود دارد که تستها پس از Refactoring خراب میشوند: 1) شما در حین Refacoring خطا کردید. رفع این مورد راحت است و میتوانید خطا را برطرف کنید. 2) تستهای شما خیلی سطح پایین بود. به عنوان مثال، شما متدهای خصوصی کلاسها را آزمایش میکردید. در این مورد، خود آزمایشات مقصر هستند. میتوانید خود آزمونها را اصلاح کنید یا مجموعهای کاملاً جدید از آزمونهای سطح بالاتر بنویسید. یک راه عالی برای جلوگیری از چنین موقعیتی، نوشتن تستهای BDD-style است.
تکنیکهای Refactoring
تکنیکهای انجام مفهوم Refactoring در ادامه آورده شدهاند. امید است بیان این مطالب در درک شما از مفاهیم تخصصی گامی مهم باشد:
– روشهای ترکیب Composing Methods
بخش اعظمی از refactoring به ترکیب صحیح روشها اختصاص دارد. در بیشتر موارد، روشهای بیش از حد طولانی ریشه همه مشکلات هستند. ابهامات کد داخل این روشها منطق اجرا را پنهان میکند و درک روش را بسیار سخت و حتی تغییر آن را سختتر میکند. تکنیکهای refactoring در این گروه روشها را سادهسازی میکنند، کدهای تکراری را حذف میکنند و راه را برای پیشرفتهای آینده هموار میکنند. این روشها عبارتند از:
- Extract Method
- Inline Method
- Extract Variable
- Inline Temp
- Replace Temp with Query
- Split Temporary Variable
- Remove Assignments to Parameters
- Replace Method with Method Object
- Substitute Algorithm
– ویژگیهای جابجایی بین اشیا
حتی اگر عملکردها را بین کلاسهای مختلف به روشی غیر کاملتر توزیع کرده باشید، هنوز امیدی وجود دارد. این تکنیکهای ریفکتورینگ نشان میدهند که چگونه میتوان عملکردها را بین کلاسها به طور ایمن جابجا کرد، کلاسهای جدید ایجاد کرد و جزئیات پیادهسازی را از دسترسی عمومی پنهان کرد. این روشها عبارتند از:
- Move Method
- Move Field
- Extract Class
- Inline Class
- Hide Delegate
- Remove Middle Man
- Introduce Foreign Method
- Introduce Local Extension
– سازماندهی دادهها
این تکنیکها و مفهوم Refactoring به مدیریت دادهها کمک میکنند، و جایگزینهای اولیه با قابلیتهای کلاس، غنی میشوند. یکی دیگر از نتایج مهم، از هم گسیختگی کلاسهای انجمنی(Class Associations) است که کلاسها را قابل حملتر و قابل استفادهتر میکند. این روشها عبارتند از:
- Change Value to Reference
- Change Reference to Value
- Duplicate Observed Data
- Self Encapsulate Field
- Replace Data Value with Object
- Replace Array with Object
- Change Unidirectional Association to Bidirectional
- Change Bidirectional Association to Unidirectional
- Encapsulate Field
- Encapsulate Collection
- Replace Magic Number with Symbolic Constant
- Replace Type Code with Class
- Replace Type Code with Subclasses
- Replace Type Code with State/Strategy
- Replace Subclass with Fields
– سادهسازی عبارات شرطی
شرطها در طول زمان بیشتر و بیشتر در منطق خود پیچیده میشوند، و هنوز تکنیکهای بیشتری برای مبارزه با آن وجود دارد. برای این مورد روشهای زیر ارائه شده است:
- Consolidate Conditional Expression
- Consolidate Duplicate Conditional Fragments
- Decompose Conditional
- Replace Conditional with Polymorphism
- Remove Control Flag
- Replace Nested Conditional with Guard Clauses
- Introduce Null Object
- Introduce Assertion
– فراخوانی ساده متد
روشهای ارائه شده در این بخش فراخوانیهای متد را سادهتر و قابل فهمتر میکنند. و به نوبه خود، رابطها(Interface) را برای تعامل بین کلاسها ساده میکند.
- Add Parameter
- Remove Parameter
- Rename Method
- Separate Query from Modifier
- Parameterize Method
- Introduce Parameter Object
- Preserve Whole Object
- Remove Setting Method
- Replace Parameter with Explicit Methods
- Replace Parameter with Method Call
- Hide Method
- Replace Constructor with Factory Method
- Replace Error Code with Exception
- Replace Exception with Test
– تعامل با تعمیم
Abstraction گروه خاص خود را از تکنیکهای refactoring دارد که عمدتاً با حرکت عملکرد در امتداد سلسله مراتب وراثت کلاس، ایجاد کلاسها و رابطهای جدید، و جایگزینی وراثت با تفویض اختیار و بالعکس مرتبط است. در ادامه روشهای این دسته آورده شده است: