C# enthusiast. NET foundation member
نکات Refactoring برای برنامه نویسان C#
در این مقاله قصد داریم به بررسی Code Smell ها و راه حل های Refactoring آنها بپردازیم.
در این مقاله از GitHub Gist استفاده شده است و لود شدن بخش مربوط به کد ها ممکن است کمی زمانبر باشد
وجود متد های بلند و بزرگ
یکی از نکاتی که هنگام Refactoring باید در نظر داشته باشید شکاندن متد های بزرگ به متد های کوچکتر می باشد. هنگام نوشتن کد معمولا متوجه وجود متد های بلند نمیشویم ولی هنگامی که میخواهیم آنها را Review و Refactor کنیم متوجه میشویم که متد شامل indentation زیاد و در نتیجه فهم آن بسیار سخت و گاهی اوقات غیر ممکن میشود. همچنین متد های بلند اصل Single Responsibility را زیر سوال میبرد. به طور مثال متد زیر را در نظر بگیرید.
هم اکنون متد ذکر شده هم مسئول خواندن فایل و هم نوشتن محتوایات آن تحت شرایط خاص در کنسول می باشد. میتوانیم این متد را به شکل زیر به چند متد کوچک تر بشکانیم. با این کار هم متد مربوطه خوانا تر می شود و هم قابلیت استفاده مجدد از کد افزایش می یابد.
استفاده بیش از حد از if و Conditional Complexity
گاهی اوقات یک متد بیش از حد دارای if Statement می باشد که خوانایی کد و Track کردن اینکه کدام شرط به کدام مسیر از کد منجر می شود را دشوار می سازد. که در این حالت کد دارای Indentation بسیار زیاد میشود که آن را بد شکل می سازد. به طور مثال کد زیر را در نظر بگیرید.
همانطور که میبینید دنبال کردن اینکه کدام شرط به کدام مسیر منجر میشود کار بسیار سختی است. همیشه سعی کنید که تعداد If Statement های تو در تو را به کمترین حالت ممکن برسانید و در صورت نیاز متد را به متد های کوچکتر بشکانید و شرط ها را داخل آن چک کنید.
مشکل Primitive Obsession
این مشکل زمانی رخ میدهد که وروردی و یا خروجی متد های مختلف به جای اینکه یک data structure مناسب (مانند کلاس و...) باشد ، یک نوع primitive مانند int و یا string و... است که باعث ایجاد constraint ها و چک کردن نوع داده و در نتیجه duplicate code می شود. به طور مثال متد AddHoliday(2,3) را در نظر بگیرید. در این متد مشخص نیست که عدد اول ماه است یا عدد دوم. بهتر است که با ساخت Data structure مناسب صریحا نوع تاریخ را مشخص کنیم. پس متد AddHoliday را به شکل زیر Refactor میکنیم.
نام گذاری ضعیف
یکی دیگر از نکات مهم هنگام 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 می باشد که در آن اعتبار یک کلاس یا متغیر سنجیده می شود. به مثال زیر دقت کنید.
برای بررسی معتبر بودن یک متغیر، بهتر است که کد اعتبارسنجی آن را نوشته و در جاهای مختلف استفاده کنیم. متد بالا را میتوان به شکل زیر بازنویسی کرد:
مشکل Hidden Temporal Coupling
این مشکل زمانی رخ میدهد که بین دو متد هیچ رابطه ای به صورت صریحا وجود ندارد ولی ترتیب اجرای متد ها مهم است. متد ها باید تا جای ممکن Side Effect Free باشند( درباره مطالعه Side Effect میتوانید به این مقاله مراجعه کنید)
به طور مثال متدهای زیر را در نظر بگیرید.
در این متد ها هیج راهی برای فهمیدن ترتیب اجرای آنها وجود ندارد و یک Developer باید با آزمون و خطا ترتیب اجرای آنها را بفهمد. میتوانیم از با استفاده از Template Method متد های بالا را در یک کلاس Abstract آورده و به ترتیب آنها را صدا بزنیم. پس کد بالا را به شکل زیر Refactor میکنیم.
حال متد BakeProcess صریحا ترتیب لازم برای اجرای متد های ذکر شده را مشخص میکند.
یک راه حل دیگر میتواند این باشد که هر متد نتیجه متد قبلی خود را درخواست کند. پس میتوانیم مانند زیر عمل کنیم:
مشکل ادغام 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 مقاله ای تهیه کردم که برای مطالعه میتوانید به لینک های زیر مراجعه کنید.
مشکل متغیر موقت
این مشکل زمانی رخ میدهد که یک متعیر فقط در یک شرایط خاص مقدار دهی میشود و در باقی شرایط مقدار Null و یا پیش فرض خود را دارد. در قطعه کد پایین CalculateBonus تنها وقتی مقدار درست و غیر صفر دارد که بعد از متد CalculateEarnings صدا زده شود:
بهتر است که در چنین شرایطی مستقیما پارامتر را به متد پاس داد و یا متد محاسبه Earning را از کلاس Employee جدا کرده و یک instance معتبر از Employee را به آن پاس داد. پس میتوان یک کلاس به شکل زیر ایجاد کرد.
در اینجا به جای آنکه یک متغیر که گاهی اوقات مقدار دهی میشود را به متد های مختلف پاس بدهیم، یک instance از Employee که همیشه مقدار دهی شده است را به متد های مختلف پاس میدهیم. که در این حالت مشکل Null و یا مقدار پیش فرض بودن یک متغیر را نخواهیم داشت.
مشکل وابستگی پنهان
این مشکل زمانی رخ میدهد که یک کلاس یا متد وابستگی هایی داشته باشد که از طریق Constuructor و یا Parameter خود این وابستگی ها را درخواست نکرده باشد و به نوعی این وابستگی ها را پنهان کرده باشد.
به کلاس زیر دقت کنید.کلاس Employee در متد Calculate وابستگی مستقیم به کلاس Calculator دارد که این وابستگی به هیچ وجه از خارج کلاس مشخص نیست.
بهتر است که کلاس وابستگی خود به کلاس Calculator را صریحا نشان دهد و یک instance از کلاس Calculator را به جای ایجاد کردن، در Construcutor خود درخواست کند. پس کلاس Employee را به شکل زیر Refactor میکنیم.
کلاس های Anemic
یکی دیگر از مشکلاتی که هنگام Refactoring به آن برمیخوریم، کلاس های دارای تنها فیلد و پراپرتی و بدون رفتار هستند که معمولا برای انتقال Data از آنها استفاده میشود. این گونه کلاس ها معمولا وابسته به سایر کلاس ها برای اعتبارسنجی و سایر عملیات هستند. به مثال زیر دقت کنید.
این کلاس هیچگونه رفتاری ندارد و وابسته کلاس دیگر برای انجام عملیات می باشد. به طور مثال یک کلاس دیگر میتواند از این کلاس به شکل زیر استفاده کند:
میتوانیم متد CalculateInterst را مستقیما درون کلاس Account پیاده سازی کنیم و این ویژگی را وابسته به کلاس Account کنیم. پس کلاس Account را به صورت زیر Refactor میکنیم.
نتیجه گیری
در این مقاله به بررسی 10 نکته برای Refactor کد های C# پرداختیم. به طور کلی رعایت اصول SOLID و استفاده از Design Pattern ها میتواند به داشتن یک کد تمیز کمک کند. خوشحال میشوم که نظرات شما در رابطه با مقاله را مطالعه کنم. همچنین اگر دوست داشتید میتوانید من را به یک قهوه مهمان کنید.
مقالات بیشتر در دات نت زوم
مطلبی دیگر از این انتشارات
معرفی RabbitMQ: بخش اول، RabbitMQ چیست؟
مطلبی دیگر از این انتشارات
معرفی RabbitMQ: بخش سوم، پیاده سازی با سی شارپ
مطلبی دیگر از این انتشارات
پروژه gRPC ؛ نسل بعدی RESTful !