کد تکراری: آیا باید همیشه سعی کنیم از آن اجتناب کنیم؟

Summarize this content to 400 words in Persian Lang
همانطور که مشخص است، کدهای تکراری و کپی پیست تقریباً همیشه اولین چیزهایی هستند که در کد مورد انتقاد قرار می گیرند. می توان گفت که آنها اغلب به عنوان یک علامت چراغ راهنمایی قرمز دیده می شوند که باعث نگرانی و تمایل به حذف آن می شوند و اغلب این را بر سایر وظایف ترجیح می دهند. اما آیا کد تکراری همیشه دلیلی برای هشدار است؟
من به صراحت می گویم: نه!
کد تکراری صرفاً یک سیگنال برای توجه به آن قطعه کد است، معماری و وظایفی را که کد در هر دو حالت (یا بیشتر از هر دو) انجام می دهد به دقت تجزیه و تحلیل کنید و تنها پس از آن، در صورت لزوم، آن را دوباره فاکتور کنید!
بیایید یک کار بی اهمیت را در نظر بگیریم: ما یک پایگاه داده رابطه ای داریم که از آن داده ها را با استفاده از سرویسی که آن را به یک شی DTO تبدیل می کند انتخاب می کنیم و اجازه می دهیم به شکل زیر باشد:
public class ClientDto
{
public string ClientName { get; set; }
public string ClientDepartment { get; set; }
public string CompanyName { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Website { get; set; }
public string? MailingAddress { get; set; }
public string? CityStateZip { get; set; }
public decimal Balance { get; set; }
public int DistanceMeters { get; set; }
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
بیایید تصور کنیم که این متد را از یک کنترلر برنامه ASP.NET فراخوانی می کنیم و این متد کنترلر یک مدل View ایجاد می کند و این مدل را به خود View ارسال می کند. در بیشتر موارد، این کلاس مدل View به طور قابل توجهی شبیه کلاس DTO ما یا حتی مشابه آن خواهد بود، برای مثال:
public class ClientVm
{
public string ClientName { get; set; }
public string ClientDepartment { get; set; }
public string CompanyName { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Website { get; set; }
public string? MailingAddress { get; set; }
public string? CityStateZip { get; set; }
public decimal Balance { get; set; }
public int DistanceMeters { get; set; }
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
در اینجا همه نشانه های تکرار کد را می بینیم: اگر یک فیلد جدید ظاهر شود، باید آن را به هر دو کلاس اضافه کنیم. اگر یک فیلد تغییر نام داد، باید به یاد داشته باشیم که نام آن را برای هر دو کلاس تغییر دهیم (در غیر این صورت، نتایج غیر منتظره ای خواهیم دید، به خصوص اگر از ابزارهایی مانند AutoMapper جایی که ما به طور صریح به هر فیلد اختصاص نمی دهیم) و غیره.
اما برای به صدا درآوردن زنگ خطر عجله نکنید، خود را به خاطر تقریبن اشتباه یک جونیور سرزنش کنید و فوراً یک کلاس انتزاعی جدید ایجاد کنید (بیایید آن را بنامیم ClientAbstract) که هر دو کلاس از آن ارث می برند. زیرا در این صورت، تکرار کد در واقع درست است! بله، درست شنیدی. بین این دو شر – تکرار و اختلاط مسئولیت – کوچکتر را انتخاب کنید که تکرار است. زیرا تکرار در اینجا در سطح قابل مشاهده است و به پیامدهای گسترده ای در معماری منجر نمی شود.
من میتوانم استدلال کنم که ایجاد یک کلاس انتزاعی برای این دو لایههای معماری مختلف را با هم مخلوط میکند، اما شما میتوانید مخالفت کنید که ما میتوانیم این کلاس انتزاعی را در یک اسمبلی جداگانه بدون هیچ گونه وابستگی قرار دهیم و تا حدی حق با شماست (فقط تا حدی به دلیل بازگشت سرویس ClientDtoقوانین تجاری سطح بالا را نشان می دهد که عمدتاً توسط خود کسب و کار دیکته می شود و در زمینه نرم افزار تغییر ناپذیر است و نحوه رفتار برنامه کاربردی را به جای برعکس، تعریف می کند. ClientVmیک پیادهسازی وب سطح پایین است، صرفاً یک پیشفرض که میتواند در هر لحظه تغییر کند و به این تجارت مرتبط نیست).
اصل مسئولیت واحد (SRP) به ما می گوید که یک کلاس باید تنها یک دلیل برای تغییر داشته باشد. برای یک سرویس منطق کسب و کار، این دلیل تغییر در قوانین کسب و کار است. برای لایه وب، نحوه ارائه داده ها توسط این برنامه خاص است. احتمالاً لایه وب بسیار بیشتر و غیرقابل پیش بینی تر از منطق تجاری تغییر می کند. هر تغییر در لایه وب احتمالاً مستلزم تغییر کلاس انتزاعی اساسی است. ClientAbstract، که باید به اندازه خود منطق تجاری پایدار باشد. اصل وابستگیهای پایدار (SDP) به ما میگوید که مؤلفههای پایدار کمتری که مستعد تغییر هستند، باید به مؤلفههای پایدارتر بستگی داشته باشند. در مورد ما، لایه وب باید به منطق تجاری بستگی داشته باشد، نه برعکس.تصور کنید که مشتریان فردا تصمیم بگیرند که این متغیر Balance، که در حال حاضر توسط شما به عنوان یک عدد اعشاری نمایش داده می شود، برای صفحات وب مناسب به نظر نمی رسد (و من کاملاً با آنها در این مورد موافقم!) و باید با علامت '$'، با جداکننده ها و حداکثر تا دو رقم اعشار نمایش داده شود. . علاوه بر این، DistanceMetersباید بر حسب متر یا کیلومتر نمایش داده شود. بنابراین، کلاس مدل view شکل زیر را به خود می گیرد (با انجام این تبدیل ها در سطح کنترل کننده):
public class ClientVm
{
public string ClientName { get; set; }
……
public string BalanceDollars { get; set; }
public string Distance { get; set; }
}
وارد حالت تمام صفحه شوید
از حالت تمام صفحه خارج شوید
و باید این 2 فیلد را از چکیده حذف کنید ClientAbstract، آنها را مستقیماً به آن اضافه کنید ClientDto (همانطور که در اعلام شد ClientAbstractو سپس همه چیز را آزمایش کنید تا مطمئن شوید که هیچ چیز خراب نیست (اما برنامه نویسی به این معنا ریاضی نیست، در برنامه نویسی نمی توانید به سادگی ثابت کنید که همه چیز مانند یک قضیه در ریاضیات خوب است؛ ما می توانیم احتمال خطا را به حداقل برسانیم، اما ما نمی تواند آن را از بین ببرد.این مثال ساده نشان می دهد که گاهی اوقات نیازی به تلاش آسیب شناسی برای حذف کدهای تکراری نیست. گاهی اوقات عادی است دوباره، وقتی کدهای تکراری را می بینید، ابتدا از خود بپرسید: “آیا واقعاً در هر دو مورد به یک دلیل تغییر می کند؟” اگر بله، پس احتمالاً باید برای حذف تکراری مجدداً اصلاح شود. اگر نه، شاید آن دو الگوریتم مشابه که وظایف کاملاً متفاوتی را انجام می دهند باید مشابه باقی بمانند؟ شاید اگر فردا لازم باشد تغییراتی را به یکی از آنها اضافه کنیم، باید بتوانیم بدون تأثیرگذاری بر دیگری این کار را انجام دهیم؟
PS در جانورشناسی، مفهومی به نام “تکامل همگرا” وجود دارد – زمانی که گونه های کاملاً متفاوت حیوانات (گیاهان/قارچ ها) شباهت های خارجی یا سایر ویژگی های مشابه داشته باشند. اما نمیتوانیم ادعا کنیم که مثلاً مارها از نزدیکان کرمها هستند یا نهنگها از بستگان نزدیک ماهی هستند، درست است؟ مارها بسیار به لاک پشت ها نزدیک تر هستند، حتی اگر شکل بدن آنها بیشتر شبیه کرم باشد. اما این شباهت فقط به دلیل حالت حرکتی مشابه است و نه بیشتر; در جنبه های دیگر، آنها کاملا متفاوت هستند. همین امر در مورد ارث بری کلاس نیز صدق می کند. وقتی میدانها یا رفتارهای مشترکی را بین چندین طبقه مشاهده میکنید، از خود بپرسید: “آیا این شباهت فقط سطحی و فقط در این لحظه است؟ چگونه ممکن است هر دو طبقه تحت شرایط خارجی متفاوت تکامل یابند و آیا زمینههای مشترکی برای تکامل آنها وجود دارد، یعنی تغییرات در یکی به طور خودکار نیاز به تغییرات مشابه در دیگری دارد؟
همانطور که مشخص است، کدهای تکراری و کپی پیست تقریباً همیشه اولین چیزهایی هستند که در کد مورد انتقاد قرار می گیرند. می توان گفت که آنها اغلب به عنوان یک علامت چراغ راهنمایی قرمز دیده می شوند که باعث نگرانی و تمایل به حذف آن می شوند و اغلب این را بر سایر وظایف ترجیح می دهند. اما آیا کد تکراری همیشه دلیلی برای هشدار است؟
من به صراحت می گویم: نه!
کد تکراری صرفاً یک سیگنال برای توجه به آن قطعه کد است، معماری و وظایفی را که کد در هر دو حالت (یا بیشتر از هر دو) انجام می دهد به دقت تجزیه و تحلیل کنید و تنها پس از آن، در صورت لزوم، آن را دوباره فاکتور کنید!
بیایید یک کار بی اهمیت را در نظر بگیریم: ما یک پایگاه داده رابطه ای داریم که از آن داده ها را با استفاده از سرویسی که آن را به یک شی DTO تبدیل می کند انتخاب می کنیم و اجازه می دهیم به شکل زیر باشد:
public class ClientDto
{
public string ClientName { get; set; }
public string ClientDepartment { get; set; }
public string CompanyName { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Website { get; set; }
public string? MailingAddress { get; set; }
public string? CityStateZip { get; set; }
public decimal Balance { get; set; }
public int DistanceMeters { get; set; }
}
بیایید تصور کنیم که این متد را از یک کنترلر برنامه ASP.NET فراخوانی می کنیم و این متد کنترلر یک مدل View ایجاد می کند و این مدل را به خود View ارسال می کند. در بیشتر موارد، این کلاس مدل View به طور قابل توجهی شبیه کلاس DTO ما یا حتی مشابه آن خواهد بود، برای مثال:
public class ClientVm
{
public string ClientName { get; set; }
public string ClientDepartment { get; set; }
public string CompanyName { get; set; }
public string? Phone { get; set; }
public string? Email { get; set; }
public string? Website { get; set; }
public string? MailingAddress { get; set; }
public string? CityStateZip { get; set; }
public decimal Balance { get; set; }
public int DistanceMeters { get; set; }
}
در اینجا همه نشانه های تکرار کد را می بینیم: اگر یک فیلد جدید ظاهر شود، باید آن را به هر دو کلاس اضافه کنیم. اگر یک فیلد تغییر نام داد، باید به یاد داشته باشیم که نام آن را برای هر دو کلاس تغییر دهیم (در غیر این صورت، نتایج غیر منتظره ای خواهیم دید، به خصوص اگر از ابزارهایی مانند AutoMapper
جایی که ما به طور صریح به هر فیلد اختصاص نمی دهیم) و غیره.
اما برای به صدا درآوردن زنگ خطر عجله نکنید، خود را به خاطر تقریبن اشتباه یک جونیور سرزنش کنید و فوراً یک کلاس انتزاعی جدید ایجاد کنید (بیایید آن را بنامیم ClientAbstract
) که هر دو کلاس از آن ارث می برند. زیرا در این صورت، تکرار کد در واقع درست است! بله، درست شنیدی. بین این دو شر – تکرار و اختلاط مسئولیت – کوچکتر را انتخاب کنید که تکرار است. زیرا تکرار در اینجا در سطح قابل مشاهده است و به پیامدهای گسترده ای در معماری منجر نمی شود.
من میتوانم استدلال کنم که ایجاد یک کلاس انتزاعی برای این دو لایههای معماری مختلف را با هم مخلوط میکند، اما شما میتوانید مخالفت کنید که ما میتوانیم این کلاس انتزاعی را در یک اسمبلی جداگانه بدون هیچ گونه وابستگی قرار دهیم و تا حدی حق با شماست (فقط تا حدی به دلیل بازگشت سرویس ClientDto
قوانین تجاری سطح بالا را نشان می دهد که عمدتاً توسط خود کسب و کار دیکته می شود و در زمینه نرم افزار تغییر ناپذیر است و نحوه رفتار برنامه کاربردی را به جای برعکس، تعریف می کند. ClientVm
یک پیادهسازی وب سطح پایین است، صرفاً یک پیشفرض که میتواند در هر لحظه تغییر کند و به این تجارت مرتبط نیست).
اصل مسئولیت واحد (SRP) به ما می گوید که یک کلاس باید تنها یک دلیل برای تغییر داشته باشد. برای یک سرویس منطق کسب و کار، این دلیل تغییر در قوانین کسب و کار است. برای لایه وب، نحوه ارائه داده ها توسط این برنامه خاص است. احتمالاً لایه وب بسیار بیشتر و غیرقابل پیش بینی تر از منطق تجاری تغییر می کند. هر تغییر در لایه وب احتمالاً مستلزم تغییر کلاس انتزاعی اساسی است. ClientAbstract
، که باید به اندازه خود منطق تجاری پایدار باشد. اصل وابستگیهای پایدار (SDP) به ما میگوید که مؤلفههای پایدار کمتری که مستعد تغییر هستند، باید به مؤلفههای پایدارتر بستگی داشته باشند. در مورد ما، لایه وب باید به منطق تجاری بستگی داشته باشد، نه برعکس.
تصور کنید که مشتریان فردا تصمیم بگیرند که این متغیر Balance
، که در حال حاضر توسط شما به عنوان یک عدد اعشاری نمایش داده می شود، برای صفحات وب مناسب به نظر نمی رسد (و من کاملاً با آنها در این مورد موافقم!) و باید با علامت '$'، با جداکننده ها و حداکثر تا دو رقم اعشار نمایش داده شود. . علاوه بر این، DistanceMeters
باید بر حسب متر یا کیلومتر نمایش داده شود. بنابراین، کلاس مدل view شکل زیر را به خود می گیرد (با انجام این تبدیل ها در سطح کنترل کننده):
public class ClientVm
{
public string ClientName { get; set; }
......
public string BalanceDollars { get; set; }
public string Distance { get; set; }
}
و باید این 2 فیلد را از چکیده حذف کنید ClientAbstract
، آنها را مستقیماً به آن اضافه کنید ClientDto
(همانطور که در اعلام شد ClientAbstract
و سپس همه چیز را آزمایش کنید تا مطمئن شوید که هیچ چیز خراب نیست (اما برنامه نویسی به این معنا ریاضی نیست، در برنامه نویسی نمی توانید به سادگی ثابت کنید که همه چیز مانند یک قضیه در ریاضیات خوب است؛ ما می توانیم احتمال خطا را به حداقل برسانیم، اما ما نمی تواند آن را از بین ببرد.
این مثال ساده نشان می دهد که گاهی اوقات نیازی به تلاش آسیب شناسی برای حذف کدهای تکراری نیست. گاهی اوقات عادی است دوباره، وقتی کدهای تکراری را می بینید، ابتدا از خود بپرسید: “آیا واقعاً در هر دو مورد به یک دلیل تغییر می کند؟” اگر بله، پس احتمالاً باید برای حذف تکراری مجدداً اصلاح شود. اگر نه، شاید آن دو الگوریتم مشابه که وظایف کاملاً متفاوتی را انجام می دهند باید مشابه باقی بمانند؟ شاید اگر فردا لازم باشد تغییراتی را به یکی از آنها اضافه کنیم، باید بتوانیم بدون تأثیرگذاری بر دیگری این کار را انجام دهیم؟
PS در جانورشناسی، مفهومی به نام “تکامل همگرا” وجود دارد – زمانی که گونه های کاملاً متفاوت حیوانات (گیاهان/قارچ ها) شباهت های خارجی یا سایر ویژگی های مشابه داشته باشند. اما نمیتوانیم ادعا کنیم که مثلاً مارها از نزدیکان کرمها هستند یا نهنگها از بستگان نزدیک ماهی هستند، درست است؟ مارها بسیار به لاک پشت ها نزدیک تر هستند، حتی اگر شکل بدن آنها بیشتر شبیه کرم باشد. اما این شباهت فقط به دلیل حالت حرکتی مشابه است و نه بیشتر; در جنبه های دیگر، آنها کاملا متفاوت هستند. همین امر در مورد ارث بری کلاس نیز صدق می کند. وقتی میدانها یا رفتارهای مشترکی را بین چندین طبقه مشاهده میکنید، از خود بپرسید: “آیا این شباهت فقط سطحی و فقط در این لحظه است؟ چگونه ممکن است هر دو طبقه تحت شرایط خارجی متفاوت تکامل یابند و آیا زمینههای مشترکی برای تکامل آنها وجود دارد، یعنی تغییرات در یکی به طور خودکار نیاز به تغییرات مشابه در دیگری دارد؟