Тлумачення принципу DRY


10

Зараз я боюся з цією концепцією DRY (не повторюй себе) у своєму кодуванні. Я створюю цю функцію, в якій боюся, що вона стане занадто складною, але я намагаюся слідувати принципу DRY.

createTrajectoryFromPoint(A a,B b,C c,boolean doesSomething,boolean doesSomething2)

Ця функція, яку я кажу, приймає 3 вхідні параметри, і тоді функція зробить щось дещо інше, враховуючи булеві комбінації doesSomethingта doesSomething2. Однак проблема, яка у мене виникає, полягає в тому, що ця функція сильно зростає з кожним доданим булевим параметром.

Отже, моє запитання: чи краще мати купу різних функцій, які поділяють багато однієї й тієї ж логіки (тому порушує принцип DRY) або одну функцію, яка поводиться дещо інакше з огляду на ряд параметрів, але робить її набагато складнішою (але збереження сухих)?


3
Чи можна загальну / загальну логіку враховувати в приватні функції, які всі загальні createTrajectory...функції викликають?
FrustratedWithFormsDesigner

Це може бути, але тим приватним функціям все одно знадобиться отримати ці
булі

2
Я думаю, що це буде / набагато простіше відповісти, даючи якийсь конкретний приклад. Моя негайна реакція полягає в тому, що дихотомія, яку ви зображуєте, не зовсім реальна - тобто це не єдині два варіанти. Як сторону, я б розглядав будь-яке використання booleanпараметра як параметри, дещо підозрілого в кращому випадку.
Джеррі Труну


Ага, чому ти не розбиваєш умовні речі на власні функції?
Риг

Відповіді:


19

булеві аргументи для запуску різних кодових шляхів в одній функції / методу - жахливий запах коду .

Те, що ви робите, порушує принципи вільної зв'язаності та високої згуртованості та єдиної відповідальності , які є набагато важливішими за перевагу, ніж DRY.

Це означає, що речі повинні залежати від інших речей лише тоді, коли вони повинні ( З'єднання ) і що вони повинні робити одне і тільки одне (дуже добре) ( Згуртованість ).

З вашого власного упущення, це занадто щільно поєднане (усі булеві прапори є типом залежності держави, яка є однією з найгірших!) І має занадто багато індивідуальних обов'язків, що переплутані (надмірно складні).

Те, що ви робите, все одно не в дусі СУХОГО. DRY більше про повторення ( Rозначає REPEAT). Уникання копіювання та вклеювання - це його найпростіша форма. Те, що ви робите, не пов'язане з повторенням.

Ваша проблема в тому, що ваш розклад коду не на правильному рівні. Якщо ви думаєте, що у вас буде дублікат коду, то це повинна бути його власна функція / метод, параметризована відповідним чином, а не копіювання та вставлення, а інші повинні бути названі описово та делеговані основній функції / методу.


Гаразд, здається, що я пишу не дуже добре (мої початкові підозри). Я не дуже впевнений, що це за «
Сприт сухих»


4

Те, що ви переходите в булі, щоб змусити функцію робити різні дії, є порушенням Принципу єдиної відповідальності. Функція повинна виконувати одне. Він повинен робити лише одне, і він повинен робити це добре.

Це пахне тим, що вам потрібно розділити його на кілька менших функцій із описовими іменами, відокремлюючи кодові шляхи, відповідні значенням цих булей.

Після цього вам слід шукати загальний код у отриманих функціях та розподіляти його на власні функції. Залежно від того, наскільки ця річ складна, ви навіть зможете виділити клас чи два.

Звичайно, це передбачає, що ви використовуєте систему контролю версій, і що у вас є хороший тестовий набір, щоб ви могли рефакторировать, не боячись щось зламати.


3

Чому ви не створите іншу функцію, що містить всю логіку у вашій функції, перш ніж ви вирішите зробити щось або щось2, а потім мати три функції, такі як:

createTrajectoryFromPoint(A a,B b,C c){...}

dosomething(A a, B b, C c){...}

dosomething2(A a, B b, C c){...}

А тепер, передавши три однакові типи параметрів трьом різним функціям, ви знову будете повторювати себе, тому вам слід визначити структуру або клас, що містить A, B, C.

Крім того, ви можете створити клас, що містить параметри A, B, C і список операцій, які потрібно виконати. Додайте, які операції (щось, щось2) ви хочете статися з цими параметрами (A, B, C), зареєструвавши операції з об'єктом. Тоді є метод викликати всі зареєстровані операції на вашому об’єкті.

public class MyComplexType
{
    public A a{get;set;}
    public B b{get;set;}
    public C c{get;set;}

    public delegate void Operation(A a, B b, C c);
    public List<Operation> Operations{get;set;}

    public MyComplexType(A a, B b, C c)
    {
        this.a = a;
        this.b = b;
        this.c = c   
        Operations = new List<Operation>();
    }

    public CallMyOperations()
    {
        foreach(var operation in Operations)
        {
            operation(a,b,c);
        }
    }
}

Для врахування можливих комбінацій значень для булевих дозометів і дозомета2, вам знадобиться 4 функції, а не 2, крім основної функції createTrajectoryFromPoint. Такий підхід не масштабується, оскільки кількість варіантів збільшується, і навіть іменування функцій стає стомлюючим.
JGWeissman

2

DRY можна зайняти занадто далеко, найкраще використовувати принцип єдиної відповідальності (SRP) спільно з DRY. Додавання прапорів bool до функції, щоб змусити її робити трохи інші версії одного і того ж коду, може бути ознакою того, що ви занадто багато використовуєте одну функцію. У цьому випадку я б запропонував створити окрему функцію для кожного випадку, який представляють ваші прапори, тоді, коли у вас буде виписана кожна функція, це має бути досить очевидним, якщо є загальний розділ, який можна перемістити до приватної функції, не передаючи всі прапори , якщо немає очевидного розділу коду, то ви насправді не повторюєте себе, у вас є кілька різних, але схожих випадків.


1

Я зазвичай проходжу кілька кроків з цією проблемою, зупиняючись, коли не можу зрозуміти, як пройти далі.

Спочатку зробіть те, що зробили. Ідіть важко з DRY. Якщо ви не закінчите з великим волохатим безладом, ви закінчили. Якщо, як і у вашому випадку, у вас немає дублікату коду, але у кожного булевого значення перевірено його значення у 20 різних місцях, перейдіть до наступного кроку.

По-друге, розділіть код на блоки. Кожне булеве посилання посилається лише один раз (ну, може, іноді і двічі), щоб направити виконання на правий блок. З двома булевими, ви закінчуєте чотири блоки. Кожен блок майже однаковий. СУХИЙ пішов. Не робіть кожен блок окремим методом. Це було б більш елегантно, але введення всього коду одним методом полегшує або навіть можливо, щоб кожен, хто займається технічним обслуговуванням, бачив, що вони повинні робити кожну зміну в чотирьох місцях. З добре організованим кодом та високим монітором відмінності та помилки будуть майже очевидні. Тепер у вас є реконструйований код, і він запуститься швидше, ніж оригінальний заплутаний безлад.

По-третє, спробуйте захопити повторювані рядки коду з кожного з ваших блоків і перетворити їх у приємні, прості методи. Іноді ти нічого не можеш зробити. Іноді ти не можеш багато зробити. Але кожен твій дріб, який ти робиш, повертає тебе назад у бік DRY і робить код трохи легшим у дотриманні та безпечнішим у обслуговуванні. В ідеалі ваш оригінальний метод може не мати дублікату коду. У цей момент ви можете розбити його на кілька методів без булевих параметрів, а може і не зробити. Зручність коду для виклику зараз головне питання.

Я додав свою відповідь до великої кількості вже тут через другий крок. Я ненавиджу дублюючий код, але якщо це єдиний розумний спосіб вирішити проблему, зробіть це таким чином, щоб хтось з першого погляду дізнався, що ви робите. Використовуйте кілька блоків і лише один метод. Зробіть блоки максимально однаковими за іменами, проміжками, вирівнюваннями, ... усім. Потім відмінності повинні вискочити у читача. Це може зробити очевидним, як переписати його в DRY спосіб, а якщо ні, то підтримка буде досить зрозумілою.


0

Альтернативний підхід полягає в заміні булевих параметрів на параметри інтерфейсу, з кодом для обробки різних булевих значень, перетворених на реалізацію інтерфейсу. Так ви мали б

createTrajectoryFromPoint(A a,B b,C c,IX x,IY y)

де у вас є реалізації IX та IY, які представляють різні значення булевих значень. У тілі функції, де б ви не були

if (doesSomething)
{
     ...
}
else
{
     ...
}

ви замінюєте його викликом методу, визначеного в IX, з реалізаціями, що містять опущені блоки коду.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.