Можно ли применять DRY без увеличения сцепления?

14

Предположим, у нас есть программный модуль A, который реализует функцию F. Другой модуль B реализует ту же функцию, что и F '.

Есть несколько способов избавиться от дубликата кода:

  1. Пусть A использует F 'из B.
  2. Пусть B использует F из A.
  3. Поместите F в его собственный модуль C и позвольте A и B использовать его.

Все эти параметры создают дополнительные зависимости между модулями. Они применяют принцип СУХОЙ за счет увеличения сцепления.

Насколько я вижу, при применении СУХОГО сцепление всегда увеличивается или в аренду перемещается на более высокий уровень. Кажется, существует конфликт между двумя из самых основных принципов разработки программного обеспечения.

(На самом деле я не нахожу удивительным, что есть такие конфликты. Вероятно, это и делает создание хорошего программного обеспечения таким сложным. Я нахожу удивительным, что эти конфликты обычно не рассматриваются во вводных текстах.)

Изменить (для пояснения): я предполагаю, что равенство F и F 'не просто совпадение. Если F нужно будет изменить, F ', вероятно, придется изменить таким же образом.

Фрэнк Пуффер
источник
2
... Я думаю, что DRY может быть очень полезной стратегией, но этот вопрос иллюстрирует неэффективность DRY. Некоторые (например, энтузиасты ООП) могут утверждать, что вы должны скопировать / вставить F в B только ради сохранения концептуальной автономии A и B, но я не сталкивался со сценарием, в котором я бы это сделал. Я думаю, что копировать / вставлять код - это наихудший вариант, я не могу смириться с ощущением «кратковременной потери памяти», когда я был настолько уверен, что уже написал метод / функцию, чтобы что-то сделать; исправление ошибки в одной функции и забывание обновить другую может быть еще одной серьезной проблемой.
JRH
3
Есть множество принципов этого ОО, противоречащих друг другу. В большинстве случаев вы должны найти разумный компромисс. Но ИМХО принцип СУХОЙ является наиболее ценным. Как писал @jrh: одинаковое поведение, реализованное в нескольких местах, - это кошмар обслуживания, которого следует избегать любой ценой. Выяснив, что вы забыли обновить одну из избыточных копий в рабочей среде, вы можете потерять бизнес.
Тимоти Траклле
2
@TimothyTruckle: Мы не должны называть их принципами ОО, потому что они применимы и к другим парадигмам программирования. И да, СУХОЙ является ценным, но также опасным, если перестарался. Не только потому, что это имеет тенденцию создавать зависимости и, следовательно, сложность. Это также часто применяется к дубликатам, вызванным совпадением, которые имеют разные причины для изменения.
Фрэнк Пуффер
1
... также иногда, когда я сталкивался с этим сценарием, я был в состоянии разбить F на части, которые могут быть использованы для того, что нужно A и что нужно B.
JRH
1
Связь по своей сути не является плохой вещью и часто необходима для уменьшения ошибок и повышения производительности. Если бы вы использовали функцию parseInt из стандартной библиотеки вашего языка в вашей функции, вы бы связали свою функцию со стандартной библиотекой. Я не видел программу, которая не делает это много лет. Ключ не должен создавать ненужные связи. Чаще всего интерфейс используется, чтобы избежать / удалить такую ​​связь. Например, моя функция может принимать реализацию parseInt в качестве аргумента. Однако это не всегда необходимо и не всегда разумно.
Джошуа Джонс

Ответы:

14

Все эти параметры создают дополнительные зависимости между модулями. Они применяют принцип СУХОЙ за счет увеличения сцепления.

Почему да, они делают. Но они уменьшают связь между линиями. То, что вы получаете, это сила, чтобы изменить соединение. Муфта бывает разных форм. Извлечение кода увеличивает косвенность и абстракцию. Увеличение может быть хорошим или плохим. Номер один, который решает, что вы получите, это имя, которое вы используете для него. Если, глядя на имя, я удивляюсь, когда заглядываю внутрь, значит, ты никому не сделал одолжений.

Кроме того, не следуйте за СУХОЙ в вакууме. Если вы убьете дублирование, вы берете на себя ответственность за предсказание того, что эти два использования этого кода изменятся вместе. Если они могут измениться независимо, вы вызвали путаницу и дополнительную работу ради небольшой выгоды. Но действительно хорошее имя может сделать это более приемлемым. Если все, что вы можете думать, это плохое имя, тогда, пожалуйста, просто прекратите сейчас.

Связь всегда будет существовать, если ваша система не будет настолько изолирована, что никто никогда не узнает, работает ли она. Так что рефакторинг связи - это игра выбора вашего яда. Следование DRY может окупиться, сводя к минимуму создаваемую связь, повторяя одно и то же дизайнерское решение во многих местах, пока его очень трудно изменить. Но СУХОЙ может сделать невозможным понимание вашего кода. Лучший способ спасти эту ситуацию - найти действительно хорошее имя. Если вы не можете придумать хорошее имя, я надеюсь, что вы умеете избегать бессмысленных имен

candied_orange
источник
Просто чтобы убедиться, что я правильно понимаю вашу точку зрения, позвольте мне сказать это немного по-другому: если вы назначаете хорошее имя для извлеченного кода, извлеченный код больше не важен для понимания программного обеспечения, потому что имя говорит обо всем (в идеале). Связь все еще существует на техническом уровне, но не на когнитивном уровне. Поэтому это относительно безвредно, верно?
Фрэнк Пуффер
1
Nevermind, мой плохой: meta.stackexchange.com/a/263672/143358
Basilevs
@FrankPuffer лучше?
candied_orange
3

Есть способы нарушить явные зависимости. Популярным является внедрение зависимостей во время выполнения. Таким образом, вы получите СУХОЙ, снимите сцепление за счет статической безопасности. Это настолько популярно в наши дни, что люди даже не понимают, что это компромисс. Например, контейнеры приложений обычно обеспечивают управление зависимостями, чрезвычайно усложняя программное обеспечение, скрывая сложность. Даже простой ввод в конструктор не гарантирует некоторые контракты из-за отсутствия системы типов.

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

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

Таким образом, единственный тип зависимостей, который у вас будет, - это D, зависящий от каждого другого модуля.

Или зарегистрируйте C в контейнере приложения со встроенным внедрением зависимостей и наслаждайтесь удачным автоматическим подключением медленно растущих циклов загрузки классов и тупиковых ситуаций.

Basilevs
источник
1
+1, но с явным предупреждением, что это обычно плохой компромисс. Эта статическая безопасность необходима для вашей защиты, и обойти ее с помощью жутких действий на расстоянии - это просто просить трудно выявляемых ошибок дальше по линии, когда сложность вашего проекта немного возрастает ...
Мейсон Уилер
1
Можно ли сказать, что DI нарушает зависимость? Даже формально, вам нужен интерфейс с подписью F для его реализации. Но, тем не менее, если модуль A постоянно использует F из C, он зависит от него, независимо от того, был ли C вставлен во время выполнения или напрямую связан. Я не нарушаю зависимость, ошибка только откладывает сбой, если зависимость не предоставлена
max630
@ max630 заменяет зависимость реализации на договорную, которая слабее.
Васильев
@ max630 Вы правы. Нельзя сказать, что я нарушаю зависимость. Фактически DI - это метод введения зависимостей, и он действительно ортогональн к вопросу, заданному в отношении связи. Изменение в F (или в интерфейсе, его инкапсулирующем) все равно потребует изменения как в A, так и в B.
king-side-slide
1

Я не уверен, что ответ без дальнейшего контекста имеет смысл.

Есть ли Aуже зависит Bили наоборот? - в этом случае у нас может быть очевидный выбор дома для F.

Есть Aи Bуже есть какие-то общие зависимости, которые могут быть хорошим домом для F?

Насколько большой / сложный F? От чего еще Fзависит?

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

Будет ли Aи в Bконечном итоге обмена некоторые общие в любом случае зависимость?

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

С другой стороны, если вы говорите о Java или C # DLL, которые довольно легко объединяются в одной среде выполнения, это другой вопрос.


Функция является абстракцией и поддерживает DRY.

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

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

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

Эрик Эйдт
источник
2
Не опасно ли объединять абстракции и граф зависимостей?
Basilevs
Does A already depend on B or vice versa? — in which case we might have an obvious choice of home for F.Это предполагает, что A всегда будет полагаться на B (или наоборот), что является очень опасным допущением. Тот факт, что OP видит F как неотъемлемую часть A (или B), предполагает, что F существует, не будучи присущим ни одной из библиотек. Если F принадлежит одной библиотеке (например, метод расширения DbContext (F) и библиотека-оболочка Entity Framework (A или B)), то вопрос OP будет спорным.
Флэтер
0

Здесь ответы, которые сосредоточены на всех способах, которыми вы можете «минимизировать» эту проблему, оказывают вам медвежью услугу. А «решения», просто предлагающие разные способы создания связи, на самом деле вовсе не являются решениями.

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

Спросите себя, почему модули A и B разделены, если они оба зависят от одной и той же функции F? Конечно, у вас будут проблемы с управлением зависимостями / абстракцией / связыванием / you-name-it, если вы сделаете плохой дизайн.

Правильное моделирование приложения выполняется в соответствии с поведением. Таким образом, части A и B, которые зависят от F, должны быть извлечены в их собственный, независимый модуль. Если это невозможно, то A и B необходимо объединить. В любом случае A и B больше не являются полезными для системы и должны прекратить свое существование.

СУХОЙ - принцип, который может использоваться, чтобы выставить плохой дизайн, а не вызвать его. Если вы не можете достичь СУХОЙ ( когда это действительно применимо - отметив ваше редактирование) из-за структуры вашего приложения, это явный признак того, что структура стала пассивом. Вот почему «постоянный рефакторинг» также является основным принципом.

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

король-бок слайд
источник
Вы предлагаете иметь ровно один модуль в каждом приложении?
Васильев
@Basilevs Абсолютно нет (если это не гарантировано). Я предлагаю иметь как можно больше полностью отделенных модулей. В конце концов, это и есть цель модуля.
Кинг-сайд слайд
Итак, вопрос подразумевает, что модули A и B содержат несвязанные функции и, соответственно, должны быть извлечены соответствующим образом, почему и как они должны перестать существовать? Каково ваше решение проблемы, как указано?
Базилевс
@Basilevs Вопрос подразумевает, что A и B не были смоделированы должным образом. Именно этот врожденный недостаток является причиной проблемы в первую очередь. Конечно, простой факт , что они делают существуют, не доказательство того, что они должны существовать. Это то, о чем я говорю выше. Очевидно, что альтернативный дизайн необходим, чтобы не сломать СУХОЙ. Важно понимать, что самой целью всех этих «принципов проектирования» является облегчение изменения приложения.
Кинг-сайд слайд
Была ли куча других методов с совершенно разными зависимостями смоделированы плохо и должны быть переделаны, просто из-за этого единственного неслучайного? Или вы предполагаете, что модули A и B содержат по одному методу каждый?
Базилевс
0

Все эти параметры создают дополнительные зависимости между модулями. Они применяют принцип СУХОЙ за счет увеличения сцепления.

У меня другое мнение, по крайней мере, для третьего варианта:

Из вашего описания:

  • А нуждается в F
  • B нуждается в F
  • Ни А, ни В не нуждаются друг в друге.

Помещение F в модуль C не увеличивает сцепление, так как A и B уже нуждаются в функции C.

mouviciel
источник