لینک کوتاه مطلب : https://hsgar.com/?p=5001

چرا DRY بیش از حد رتبه بندی شده ترین اصل برنامه نویسی است | افکاری در مورد نرم افزار

فکر کردم وبلاگ جدیدم را با بیشترین کلیکی که می توانم به آن فکر کنم راه اندازی کنم. من گمان می کنم که هر توسعه دهنده ای که این را می خواند از اصل DRY آگاه باشد زیرا بسیار فراگیر است. اگر اینطور نیست، فقط باید بدانید که مخفف عبارت Don’t Repeat Yourself است و عموماً زمانی استفاده می شود که به مردم توصیه می شود تکه های کد را در همه جا کپی و جایگذاری نکنند و در عوض منطق را در یک مکان مرکزی ادغام کنند.

DRY اولین اصل برنامه نویسی بود که با آن مواجه شدم و احتمالاً تنها چیزی بود که در سال اول که توسعه دهنده بودم از آن آگاه بودم. همچنین احتمالاً یکی از ساده ترین اصول برای درک است. اگر دو چیز را در کد خود مشاهده کردید که یکسان هستند، شاید آنها فقط یک چیز باشند. مجادله با آن سخت است. اما، من فکر می کنم که DRY درست مانند هر اصل دیگری است – جای خود را دارد، اما بهتر است در حد اعتدال مصرف شود. و من فکر می‌کنم که به دلیل همه‌گیر بودن و سادگی آن، ما تمایل داریم که DRY را خیلی دور و خیلی زیاد انجام دهیم.

بنابراین بدون مقدمه، اجازه دهید به سه انتقاد من از DRY بپردازیم.

1. از DRY برای حذف تکرار تصادفی استفاده نادرست می شود

گاهی اوقات همه چیز یکسان است، اما این فقط یک تصادف است. به عنوان مثال، برخی از کدهای پایتون را در نظر بگیرید که از یک API ساختگی پیتزا درخواست می کنند.

def make_hawaiian_pizza():
    payload = {
        crust: "thin",
        sauce: "tomato",
        cheese: "regular",
        toppings: ["ham", "pineapple"]
    }
    requests.post(PIZZA_URL, payload)

def make_pepperoni_pizza():
    payload = {
        crust: "thin",
        sauce: "tomato",
        cheese: "regular",
        toppings: ["pepperoni"]
    }
    requests.post(PIZZA_URL, payload)

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

def make_pizza(toppings):
    payload = {
        crust: "thin",
        sauce: "tomato",
        cheese: "regular",
        toppings: toppings
    }
     requests.post(PIZZA_URL, payload)

def make_pepperoni_pizza():
    make_pizza(["pepperoni"])

def make_hawaiian_pizza():
    make_pizza(["pepperoni"])

مشکل اینجاست که این دو پیتزا فقط پوسته، سس و پنیر یکسانی دارند. اگر با دو نوع پیتزا که پوسته/سس/پنیر متفاوتی دارند شروع به کار کرده بودیم، هرگز این ریفاکتور را نمی ساختیم. به جای اینکه کد ما حول مفهوم نحوه ساخت پیتزا به صورت انتزاعی طراحی شود، معماری آن به شدت با نیازهای خاص این دو پیتزا که اتفاقاً با آنها سروکار داشتیم همراه است. احتمال اینکه ما این کد را به حالت قبل برگردانیم بسیار زیاد است.

2. DRY یک فرض قابل استفاده مجدد ایجاد می کند

تصور کنید ما در شرکتی هستیم با یک پایگاه کد بزرگ که در آن چندین منطقه محصول می‌خواهند سفارش پیتزا را ادغام کنند. به جای اینکه هر محصولی خودش را بنویسد make_pizza() عملکرد، چرا آن را در یک نمی چسبانیم common کتابخانه ای که هر محصولی می تواند وارد کند و تماس بگیرد؟

بنابراین ما این مسیر را طی می کنیم و در نهایت به 5 محصول در هر تماس می رسیم make_pizza() با آرایه های مختلف استدلال برای انواع پیتزاهایی که می خواهند.

در حال حاضر تیمی از محصولات پیشرفته می آیند و آنها واقعاً می خواهند شروع به ساخت پیتزاهایی کنند که نیمی هاوایی و نیمی پپرونی هستند. توسعه دهندگان این تیم تماماً در مورد DRY هستند و می دانند که یک عملکرد مشترک پیتزا عالی وجود دارد، بنابراین از آن استفاده می کنند. تنها مشکل این است که نمی‌تواند سفارشات پیتزا را به صورت تقسیمی قبول کند. برخی اصلاحات باید انجام شود.

# cool_product/pizza.py
left_toppings = ["beef"]
right_toppings = [] 
make_pizza([left_toppings, right_toppings])  # this will be a very funny pizza 

# common/make_pizza.py
def make_pizza(*args):
    payload = {
        crust: "original",
        sauce: "tomato",
        cheese: "regular",
    }
    if len(args) == 2:
        payload["toppings_left"] = args[0]
        payload["toppings_right"] = args[1]
    else:
        payload["toppings_left"] = args[0]
        payload["toppings_right"] = args[0]

    return requests.post(PIZZA_URL, payload)

این کار می کند، و نیازی به تغییر تمام استفاده های موجود از API ندارد. با این حال، امیدواریم که بتوانید موافق باشید که خوب نیست™. داشتن معنای تغییر آرگومان اول به این دلیل که شما آرگومان دوم اختیاری را پاس کرده اید بسیار عجیب است. راه‌های زیادی برای انجام این Refactor وجود دارد، اما من تاکید می‌کنم که هر تغییری که تماس‌های موجود را تغییر نمی‌دهد make_pizza یا ایجاد یک عملکرد کاملاً مجزا برای پیتزاهای اسپلیت (نه خشک) تا حدی بد خواهد بود.

ممکن است فکر کنید که توسعه‌دهندگان منطقی هستند، اما در واقع چنین کاری را انجام نمی‌دهند و در عوض به فراخوان‌های موجود برمی‌گردند و آنها را تغییر می‌دهند تا یک راه‌حل خوب به دست آورند، اما من دیده‌ام که این اتفاق در همه جا افتاده است. استفاده بیش از حد از DRY ما را در ذهنیتی قرار می دهد که در آن همیشه به دنبال استفاده مجدد از کد هستیم، حتی زمانی که به وضوح ما را در مسیر بدی قرار می دهد. ما در نهایت با یک فرض استفاده مجدد مواجه می شویم در حالی که واقعاً باید یک فرض تکرار داشته باشیم.

3. DRY یک داروی دروازه ای برای پیچیدگی غیر ضروری است

اگر شما یک توسعه دهنده 10x هستید، احتمالاً در این مرحله فهرست بلندبالایی از راه حل های بالقوه برای مشکلاتی که برجسته کرده ام دارید. ممکن است بگویید که من مثال‌هایم را عمداً مبهم می‌کنم تا امتیازم را به دست بیاورم و واقعاً راه‌هایی وجود دارد که بتوانم این مشکلات را برطرف کنم.

برای حل مشکل سس خود، شاید بتوانم از یک سبک OOP استفاده کنم و یک کلاس PizzaOrderer داشته باشم که می تواند برای هر نوع پیتزا طبقه بندی شود و به هر نوع اجازه می دهد تا پیش فرض های معقول سس/کراست را نادیده بگیرد. یا شاید بتوانم از یک کلاس برای نشان دادن یک پیتزا استفاده کنم و روش هایی مانند این را داشته باشم add_toppping()/add_topping_left()/add_topping_right() بنابراین مصرف‌کنندگان می‌توانند در صورت درست کردن یک پیتزا کامل، به سرعت مواد اضافه کنند، اما برای پیتزاهای تقسیم‌شده نیز دانه‌بندی را انتخاب کنند. ترفندهای زیادی وجود دارد که می توانید پیشنهاد دهید.

همه این ایده ها عالی هستند. اما به یاد داشته باشید که هدف اساسی در اینجا، ارسال یک درخواست POST با یک شی JSON است. این یک کار بسیار بسیار ساده است. اکنون ما در مورد انواع برنامه‌نویسی فانتزی صحبت می‌کنیم تا سعی کنیم مشکلاتی را حل کنیم که فقط وجود دارند زیرا نمی‌خواهیم همان قطعه ۶ خطی را در چند مکان مختلف تکرار کنیم زیرا DRY به ما می‌گوید این بد است.

اتفاقی که در حال رخ دادن است، این است که پایبندی ما به DRY ما را در مسیر باغ به سمت ساختن یک برنامه کاربردی پیچیده غیرضروری سوق می دهد که می تواند بسیار ساده نوشته شود. من فکر می کنم این نیز بسیار زیاد اتفاق می افتد. کپی و چسباندن چند خط کد تقریباً بدون فکر و زمان نیاز دارد. پیدا کردن و جایگزین کردن در پیدا کردن چیزهای تکراری بعدا اگر شروع به مراقبت کنیم بسیار خوب است. به محض اینکه فرآیند فکری را شروع می کنیم که چگونه از کپی پیست و refactor اجتناب کنیم، نبرد پیچیدگی را از دست می دهیم.

پس چی؟

خب، بدیهی است که من نمی گویم که DRY را به طور کامل از پنجره بیرون بیاندازیم. من مطمئن نیستم که واقعا امکان نوشتن کدی وجود داشته باشد که “هرگز خود را تکرار نکند”. اما من فکر می کنم که باید واکنش های تند و سریع زانو را نسبت به PR هایی که حاوی چندین تکرار از یک بلوک کد هستند، کم کنیم. حداقل چند مورد وجود دارد که ممکن است کار درستی باشد.

امیدوارم از پست لذت برده باشید و لطفا به من سر بزنید توییتر بحث کردن



لینک منبع

ارسال یک پاسخ

آدرس ایمیل شما منتشر نخواهد شد.