نکات Refactoring برای برنامه نویسان C#



در این مقاله قصد داریم به بررسی Code Smell ها و راه حل های Refactoring آنها بپردازیم.




در این مقاله از GitHub Gist استفاده شده است و لود شدن بخش مربوط به کد ها ممکن است کمی زمانبر باشد


وجود متد های بلند و بزرگ

یکی از نکاتی که هنگام Refactoring باید در نظر داشته باشید شکاندن متد های بزرگ به متد های کوچکتر می باشد. هنگام نوشتن کد معمولا متوجه وجود متد های بلند نمیشویم ولی هنگامی که میخواهیم آنها را Review و Refactor کنیم متوجه میشویم که متد شامل indentation زیاد و در نتیجه فهم آن بسیار سخت و گاهی اوقات غیر ممکن میشود. همچنین متد های بلند اصل Single Responsibility را زیر سوال میبرد. به طور مثال متد زیر را در نظر بگیرید.

https://gist.github.com/babaktaremi/57dd6989a291e654ccb967470883ed91

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

https://gist.github.com/babaktaremi/6a4ff75d0361d785dc51bb4d18ba16e3
https://gist.github.com/babaktaremi/9c32ecdd325c50728f557a473384fa67


https://gist.github.com/babaktaremi/1be060031f5411a74f68af8e9f515cb4

استفاده بیش از حد از if و Conditional Complexity

گاهی اوقات یک متد بیش از حد دارای if Statement می باشد که خوانایی کد و Track کردن اینکه کدام شرط به کدام مسیر از کد منجر می شود را دشوار می سازد. که در این حالت کد دارای Indentation بسیار زیاد میشود که آن را بد شکل می سازد. به طور مثال کد زیر را در نظر بگیرید.

https://gist.github.com/babaktaremi/a1528ddc3dee06886f9fbd21beeed798

همانطور که میبینید دنبال کردن اینکه کدام شرط به کدام مسیر منجر میشود کار بسیار سختی است. همیشه سعی کنید که تعداد If Statement های تو در تو را به کمترین حالت ممکن برسانید و در صورت نیاز متد را به متد های کوچکتر بشکانید و شرط ها را داخل آن چک کنید.

مشکل Primitive Obsession

این مشکل زمانی رخ میدهد که وروردی و یا خروجی متد های مختلف به جای اینکه یک data structure مناسب (مانند کلاس و...) باشد ، یک نوع primitive مانند int و یا string و... است که باعث ایجاد constraint ها و چک کردن نوع داده و در نتیجه duplicate code می شود. به طور مثال متد AddHoliday(2,3) را در نظر بگیرید. در این متد مشخص نیست که عدد اول ماه است یا عدد دوم. بهتر است که با ساخت Data structure مناسب صریحا نوع تاریخ را مشخص کنیم. پس متد AddHoliday را به شکل زیر Refactor میکنیم.

https://gist.github.com/babaktaremi/a541ffa4b5d762306e0df656feeb6c75


نام گذاری ضعیف

یکی دیگر از نکات مهم هنگام Refactor کردن کد، استفاده از نام های مناسب برای متغیرهاست. متغیر ها باید دارای نام هایی باشند که:

  • به اندازه کافی درباره کاربرد داده توضیح دهنده باشند
  • از استاندارد ها پیروی کنند( نام متغیر های محلی بصورت camel case و نام کلاس ها و پراپرتی های پابلیک و متد ها PascalCase باشد و...)
  • نام ها نباید خلاصه شده و مختصر باشند.
  • باید نام گذاری متغیر و متد دقیق و Self Descriptive باشد.

برای مطالعه بیشتر درباره naming guideline ها میتوانید به این لینک مراجعه کنید

کد تکرای

یکی از مشکلات رایج که هنگام Refactoring باید به آن دقت داشته باشید کد تکراری است. این مشکل اغلب به خاطر Copy/Paste کد بوجود می آید. اصل Don't Repeat Yourself و یا DRY بیان میکند که تا جای ممکن باید از کد تکراری پرهیز کرد و اگر دو متد دارای یک بخش با Logic یکسان هستند، باید آن بخش را به عنوان یک متد مجزا Extract کرد و در هر دو متد استفاده کرد.مارتین فاولر قانون معروفی به نام Rule of Three معرفی کرده است که بیان میکند که اگر کدی 3 بار یا بیشتر تکرار شود باید استخراج و در procedure خود گنجانده شود.

یکی از موارد کد تکراری Validation Logic می باشد که در آن اعتبار یک کلاس یا متغیر سنجیده می شود. به مثال زیر دقت کنید.

https://gist.github.com/babaktaremi/1ae89bdb5d79d69fb6928ff488c8808e


برای بررسی معتبر بودن یک متغیر، بهتر است که کد اعتبارسنجی آن را نوشته و در جاهای مختلف استفاده کنیم. متد بالا را میتوان به شکل زیر بازنویسی کرد:

https://gist.github.com/babaktaremi/0702487a19f1cfd5e9225466c3093804


مشکل Hidden Temporal Coupling

این مشکل زمانی رخ میدهد که بین دو متد هیچ رابطه ای به صورت صریحا وجود ندارد ولی ترتیب اجرای متد ها مهم است. متد ها باید تا جای ممکن Side Effect Free باشند( درباره مطالعه Side Effect میتوانید به این مقاله مراجعه کنید)

به طور مثال متدهای زیر را در نظر بگیرید.

https://gist.github.com/babaktaremi/93bc35182afc69d323bdc8426bc9e517

در این متد ها هیج راهی برای فهمیدن ترتیب اجرای آنها وجود ندارد و یک Developer باید با آزمون و خطا ترتیب اجرای آنها را بفهمد. میتوانیم از با استفاده از Template Method متد های بالا را در یک کلاس Abstract آورده و به ترتیب آنها را صدا بزنیم. پس کد بالا را به شکل زیر Refactor میکنیم.

https://gist.github.com/babaktaremi/55646bd93c9ecb765dd1f08c65e540a1


حال متد BakeProcess صریحا ترتیب لازم برای اجرای متد های ذکر شده را مشخص میکند.

یک راه حل دیگر میتواند این باشد که هر متد نتیجه متد قبلی خود را درخواست کند. پس میتوانیم مانند زیر عمل کنیم:

https://gist.github.com/babaktaremi/36dbdec2b30ffce85bd1b5a2ef61dd4e



مشکل ادغام Query و Command با یکدیگر

یکی از نکات مهم هنگام Refactoring، جداسازی Query و Modifier آز یکدیگر است. باید سعی شود که Query ها که یک مقدار را برمیگردانند بدون Side Effect باشند و از طرفی Modifier ( یا Command) قابل تشخیص و جداسازی از Query ها باشند چرا که دارای Side Effect هستند. در این رابطه اصل CQRS یا Command Query Responsibility Segregation بیان میکند که هر درخواست باید Command یا Query باشد، مدل های درخواست نیز باید یا Command و یا Query باشند. این به آن معنی است که مدل های پروژه نیز کاملا از هم جدا هستند و هر یک نماینده یک Command و یا Query می باشند. قبلا در رابطه با CQRS مقاله ای تهیه کردم که برای مطالعه میتوانید به لینک های زیر مراجعه کنید.

https://virgool.io/dotnetzoom/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%B9%D9%85%D9%84%DB%8C-cqrs-%D8%A8%D8%AE%D8%B4-%D8%A7%D9%88%D9%84-%D9%85%D9%82%D8%AF%D9%85%D9%87-%D8%A7%DB%8C-%D8%A8%D8%B1-cqrs-pqcvdqzbcend
https://virgool.io/dotnetzoom/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%B9%D9%85%D9%84%DB%8C-cqrs-%D8%A8%D8%AE%D8%B4-%D8%AF%D9%88%D9%85-%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%A7%D9%84%DA%AF%D9%88%DB%8C-mediator-%D8%A8%D8%A7-%D8%A7%D8%B3%D8%AA%D9%81%D8%A7%D8%AF%D9%87-%D8%A7%D8%B2-%DA%A9%D8%AA%D8%A7%D8%A8%D8%AE%D8%A7%D9%86%D9%87-mediatr-aavpwkagy2pu
https://virgool.io/dotnetzoom/%D8%A8%D8%B1%D8%B1%D8%B3%DB%8C-%D8%B9%D9%85%D9%84%DB%8C-cqrs-%D8%A8%D8%AE%D8%B4-%D8%B3%D9%88%D9%85-%D9%BE%D8%B1%D9%88%DA%98%D9%87-%D8%B9%D9%85%D9%84%DB%8C-ccam8lktowgk


مشکل متغیر موقت

این مشکل زمانی رخ میدهد که یک متعیر فقط در یک شرایط خاص مقدار دهی میشود و در باقی شرایط مقدار Null و یا پیش فرض خود را دارد. در قطعه کد پایین CalculateBonus تنها وقتی مقدار درست و غیر صفر دارد که بعد از متد CalculateEarnings صدا زده شود:

https://gist.github.com/babaktaremi/aea916789c34b009844457bf096fd32d

بهتر است که در چنین شرایطی مستقیما پارامتر را به متد پاس داد و یا متد محاسبه Earning را از کلاس Employee جدا کرده و یک instance معتبر از Employee را به آن پاس داد. پس میتوان یک کلاس به شکل زیر ایجاد کرد.

https://gist.github.com/babaktaremi/0346cfef593b93e461779bfe630ea6ef

در اینجا به جای آنکه یک متغیر که گاهی اوقات مقدار دهی میشود را به متد های مختلف پاس بدهیم، یک instance از Employee که همیشه مقدار دهی شده است را به متد های مختلف پاس میدهیم. که در این حالت مشکل Null و یا مقدار پیش فرض بودن یک متغیر را نخواهیم داشت.


مشکل وابستگی پنهان

این مشکل زمانی رخ میدهد که یک کلاس یا متد وابستگی هایی داشته باشد که از طریق Constuructor و یا Parameter خود این وابستگی ها را درخواست نکرده باشد و به نوعی این وابستگی ها را پنهان کرده باشد.

به کلاس زیر دقت کنید.کلاس Employee در متد Calculate وابستگی مستقیم به کلاس Calculator دارد که این وابستگی به هیچ وجه از خارج کلاس مشخص نیست.

https://gist.github.com/babaktaremi/4f5af0f612f43d4a2d3c9cdd324298f8

بهتر است که کلاس وابستگی خود به کلاس Calculator را صریحا نشان دهد و یک instance از کلاس Calculator را به جای ایجاد کردن، در Construcutor خود درخواست کند. پس کلاس Employee را به شکل زیر Refactor میکنیم.

https://gist.github.com/babaktaremi/0ff42c17b93092ff1c2ef601d6b59f3d


کلاس های Anemic

یکی دیگر از مشکلاتی که هنگام Refactoring به آن برمیخوریم، کلاس های دارای تنها فیلد و پراپرتی و بدون رفتار هستند که معمولا برای انتقال Data از آنها استفاده میشود. این گونه کلاس ها معمولا وابسته به سایر کلاس ها برای اعتبارسنجی و سایر عملیات هستند. به مثال زیر دقت کنید.

https://gist.github.com/babaktaremi/67745053a492f69981332cee77848682

این کلاس هیچگونه رفتاری ندارد و وابسته کلاس دیگر برای انجام عملیات می باشد. به طور مثال یک کلاس دیگر میتواند از این کلاس به شکل زیر استفاده کند:

https://gist.github.com/babaktaremi/cacd748929c944112c4052ba9b692bb5

میتوانیم متد CalculateInterst را مستقیما درون کلاس Account پیاده سازی کنیم و این ویژگی را وابسته به کلاس Account کنیم. پس کلاس Account را به صورت زیر Refactor میکنیم.

https://gist.github.com/babaktaremi/cf5d21ac47d7d6febafec2dac8f244b9


نتیجه گیری

در این مقاله به بررسی 10 نکته برای Refactor کد های C# پرداختیم. به طور کلی رعایت اصول SOLID و استفاده از Design Pattern ها میتواند به داشتن یک کد تمیز کمک کند. خوشحال میشوم که نظرات شما در رابطه با مقاله را مطالعه کنم. همچنین اگر دوست داشتید میتوانید من را به یک قهوه مهمان کنید.

https://coffeebede.ir/buycoffee/bobby


مقالات بیشتر در دات نت زوم

https://t.me/DotNetZoom