Чи можна застосовувати DRY без збільшення зчеплення?


14

Припустимо, у нас є програмний модуль A, який реалізує функцію F. Інший модуль B реалізує ту ж функцію, що і F '.

Існує кілька способів позбутися від дублюючого коду:

  1. Нехай A використовує F 'від B.
  2. Нехай B використовує F від A.
  3. Помістіть F у власний модуль C і дозвольте їм використовувати і A, і B.

Усі ці параметри генерують додаткові залежності між модулями. Вони застосовують принцип DRY за рахунок збільшення зчеплення.

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

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

Правка (для уточнення): Я припускаю, що рівність F і F '- це не просто збіг. Якщо F доведеться модифікувати, F ', ймовірно, доведеться модифікувати таким же чином.


2
... Я думаю, що DRY може бути дуже корисною стратегією, але це питання ілюструє неефективність щодо DRY. Деякі (наприклад, ентузіасти OOP) можуть стверджувати, що ви повинні скопіювати / вставити F у B лише задля підтримання концептуальної автономії A і B, але я не натрапив на сценарій, де я це зробив би. Я думаю, що код копіювання / вставлення - це лише найгірший варіант, я не витримую того "короткострокової втрати пам'яті", коли я був просто таким впевненим, що вже написав метод / функцію, щоб щось зробити; виправлення помилки в одній функції та забуття оновити іншу може бути ще однією основною проблемою.
jrh

3
Існують багато принципів цього ОО, що суперечать один одному. У більшості випадків вам доведеться знайти розумний компроміс. Але принцип IMHO DRY є найціннішим. Як писав @jrh: однакова поведінка, реалізована в декількох місцях, є кошмаром технічного обслуговування, якого слід уникати будь-якою ціною. Дізнавшись про те, що ви забули оновити одну із зайвих копій у виробництві, це може знищити ваш бізнес.
Тімоті Вікторле

2
@TimothyTruckle: Ми не повинні називати їх принципами ОО, оскільки вони застосовуються і до інших парадигм програмування. І так, DRY є цінним, але й небезпечним, якщо перестаратися. Не тільки тому, що вона має тенденцію створювати залежності і, таким чином, складність. Він також часто застосовується до дублікатів, викликаних збігом обставин, які мають різні причини зміни.
Френк Пуффер

1
... також іноді, коли я зіткнувся з цим сценарієм, мені вдалося розбити F на частини, які можуть бути корисними для того, що потрібно А, а що потрібно B.
jrh

1
З'єднання не є по суті поганою справою, і часто необхідно зменшити помилки та збільшити продуктивність. Якби ви використовували функцію parseInt зі стандартної бібліотеки вашої мови у межах своєї функції, ви з'єднали б свою функцію зі стандартною бібліотекою. Я не бачив програми, яка б цього не робила протягом багатьох років. Головне - не створювати зайвих муфт. Найчастіше використовується інтерфейс для уникнення / видалення такого з’єднання. Наприклад, моя функція може сприйняти реалізацію parseInt як аргумент. Однак це не завжди потрібно, а також не завжди розумно.
Джошуа Джонс

Відповіді:


14

Усі ці параметри генерують додаткові залежності між модулями. Вони застосовують принцип DRY за рахунок збільшення зчеплення.

Чому так вони роблять. Але вони зменшують зв’язок між лініями. Що ви отримуєте, це сила змінити муфту. З'єднання буває у багатьох формах. Витяг коду збільшує непрямість і абстракцію. Збільшення може бути добрим чи поганим. Річ номер один, яка вирішує, яку ви отримаєте, це ім’я, яке ви використовуєте для цього. Якщо дивлячись на ім’я, мене дивує, коли я заглядаю всередину, то ти нікому не робив жодної прихильності.

Крім того, не слідкуйте за DRY у вакуумі. Якщо ви вб'єте дублювання, ви берете на себе відповідальність за передбачення, що ці два використання цього коду будуть змінюватися разом. Якщо вони можуть змінитися незалежно, ви спричинили плутанину та додаткову роботу для невеликої користі. Але справді гарне ім’я може зробити це більш приємним. Якщо все, що ви можете придумати, - це невірне ім'я, то будь ласка, просто зупиніться зараз.

З'єднання завжди буде, якщо ваша система не буде настільки ізольованою, що ніхто ніколи не дізнається, чи працює вона. Отже, з'єднання рефакторингу - це гра вибору вашої отрути. Слідом за DRY можна окупитися, зводячи до мінімуму з'єднання, створене шляхом висловлення одного і того ж дизайнерського рішення в багатьох місцях, поки це дуже важко змінити. Але DRY може унеможливити розуміння вашого коду. Найкращий спосіб врятувати цю ситуацію - знайти дійсно гарне ім’я. Якщо ви не можете придумати доброго імені, я сподіваюся, що ви вміли уникати безглуздих імен


Для того, щоб переконатися, що я правильно розумію вашу думку, дозвольте сказати це трохи інакше: якщо ви присвоїте добре ім'я вилученому коду, вилучений код більше не має значення для розуміння програмного забезпечення, оскільки ім'я говорить все це (в ідеалі). Зв'язок все ще існує на технічному рівні, але не на пізнавальному рівні. Тому це відносно нешкідливо, правда?
Френк Пуффер

1
Не забудьте, моє погано: meta.stackexchange.com/a/263672/143358
Basilevs

@FrankPuffer краще?
candied_orange

3

Є способи подолати явні залежності. Популярним є введення залежностей під час виконання. Таким чином, ви отримуєте СУХУ, виймаєте муфту ціною статичної безпеки. Сьогодні настільки популярно, що люди навіть не розуміють, що це компроміс. Наприклад, контейнери додатків зазвичай забезпечують управління залежностями, що надзвичайно ускладнює програмне забезпечення, приховуючи складність. Навіть звичайний старий впорскування конструктора не може гарантувати деяких контрактів через відсутність системи типу.

Відповісти на заголовок - так, можна, але будьте готові до наслідків відправлення.

  • Визначте інтерфейс F A в A, що забезпечує функціональність F
  • Визначте інтерфейс F B у B
  • Поставте F в С
  • Створіть модуль D для управління всіма залежностями (це залежить від A, B і C)
  • Адаптувати F до F A і F B в D
  • Нанесіть (пропустіть) обгортки на А і В

Таким чином, єдиним типом залежностей, які ви мали б, є D залежно від кожного іншого модуля.

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


1
+1, але з чітким застереженням, що це зазвичай поганий компроміс. Ця статична безпека є для вашого захисту, і обійти її купою моторошних дій на відстані - це просто прохання важких для відстеження помилок далі вниз, коли складність вашого проекту трохи зростає ...
Мейсон Уілер

1
Чи можна справді сказати, що DI порушує залежність? Навіть формально для його реалізації потрібен інтерфейс з підписом F. Але все ж, якщо модуль А rwally з допомогою F з З його depwnds на нього, незалежно від C впорскується під час виконання або безпосередньо linked.DI чи не порушує залежність, помилка тільки Defer провал , якщо dependenc не передбачено
max630

@ max630 вона замінює залежність від реалізації контрактною, яка слабша.
Василевс

@ max630 Ви маєте рацію. Не можна сказати, що ДІ руйнує залежність. Насправді, DI є методом введення залежностей і є дійсно ортогональним щодо питання, що задається у зв'язку зі зв'язком. Зміна F (або інтерфейс, що інкапсулює його) все-таки вимагатиме зміни як в A, так і в B.
ковзання в сторону-бік

1

Я не впевнений, що відповідь без подальшого контексту має сенс.

Чи Aвже залежить від цього Bчи навпаки? - в такому випадку ми можемо мати очевидний вибір будинку F.

Чи ділитесь Aі Bвже поділитесь якимись загальними залежностями, які могли б бути хорошим домом F?

Наскільки великий / складний F? Від чого ще Fзалежить?

Чи використовуються модулі Aта Bвикористовуються в одному проекті?

Буде чи Aі в Bкінцевому підсумку обміну деякі загальні в будь-якому випадку залежність?

Яка мова / модульна система використовується: Наскільки болісний новий модуль, болі програміста, ефективність роботи? Наприклад, якщо ви пишете на C / C ++ із системою модулів COM, що викликає біль у вихідному коді, вимагає альтернативного інструментального інструменту, має наслідки налагодження та має значення для продуктивності (для викликів між модулями), я можу зробіть якусь серйозну паузу.

З іншого боку, якщо ви говорите про Java або C # DLL, які досить легко поєднуються в одному середовищі виконання, це вже інша справа.


Функція - це абстракція і підтримує DRY.

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

Отже, я б заперечував, щоб створити кращу абстракцію Aі Bвід неї залежати, ніж просто перенести одну єдину функцію в новий модуль C

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


2
Чи не небезпечно пара абстракцій та графік залежності?
Василевс

Does A already depend on B or vice versa? — in which case we might have an obvious choice of home for F.Це передбачає, що A завжди буде покладатися на B (або навпаки), що є дуже небезпечним припущенням. Той факт, що ОП бачить F як невід'ємну частину A (або B), говорить про те, що F існує без притаманної жодній бібліотеці. Якщо F належить до однієї бібліотеки (наприклад, методу розширення DbContext (F) та бібліотеки обгортки Entity Framework (A або B)), то питання ОП буде суперечливим.
Flater

0

Тут відповіді, які зосереджуються на всіх способах, як можна «мінімізувати» цю проблему, роблять вам загрозу. І "рішення", просто пропонуючи різні способи створення з'єднання, зовсім не є справді рішеннями.

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

Запитайте себе, чому модулі A і B є окремими, якщо вони обидва залежать від однієї і тієї ж функції F? Звичайно, у вас виникнуть проблеми з управлінням залежностями / абстракцією / зчепленням / його-ім'ям, якщо ви дотримуєтесь поганого дизайну.

Правильне моделювання додатків проводиться відповідно до поведінки. Таким чином, фрагменти A і B, які залежать від F, потрібно витягти у власний, незалежний модуль. Якщо це неможливо, то A і B потрібно поєднувати. В будь-якому випадку A і B більше не корисні системі і повинні припинити своє існування.

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

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


Ви пропонуєте мати в кожному додатку рівно один модуль?
Василевс

@Basilevs Абсолютно ні (якщо це не гарантовано). Я пропоную мати якомога більше модулів, що повністю розв'язуються. Зрештою, у цьому вся мета модуля.
король-бік-слайд

Що ж, питання передбачає, що модулі A і B містять не пов'язані між собою функції, і їх вже слід витягнути, чому і як вони повинні припинити своє існування? Яке ваше рішення проблеми, як заявлено?
Василевс

@Basilevs Питання означає, що A і B не були правильно змодельовані. Саме цей властивий дефіцит викликає проблему в першу чергу. Звичайно, простий факт , що вони роблять існують, не доказ того, що вони повинні існувати. Це я зважаю на вище. Зрозуміло, що альтернативний дизайн необхідний, щоб не зламати ДУХУ. Важливо розуміти, що сама мета всіх цих «принципів дизайну» - зробити додаток легшим для зміни.
король-бік-слайд

Чи було чимало інших методів із абсолютно різними залежностями спроектовано погано і їх доводилося переробляти саме через цю єдину не випадкову? Або ви припускаєте, що модулі A і B містять по одному методу кожен?
Василевс

0

Усі ці параметри генерують додаткові залежності між модулями. Вони застосовують принцип DRY за рахунок збільшення зчеплення.

Я маю іншу думку, принаймні щодо третього варіанту:

З вашого опису:

  • А потребує F
  • Б потребує F
  • Ні А, ні В не потрібні один одному.

Введення F в модуль C не збільшує зв'язок, оскільки і A, і B вже потребують функції C.

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