Иллюзорное дублирование кода

56

Обычный инстинкт - удалить любое дублирование кода, которое вы видите в коде. Однако я оказался в ситуации, когда дублирование иллюзорно .

Чтобы описать ситуацию более подробно: я разрабатываю веб-приложение, и большинство представлений в основном одинаковы - они отображают список элементов, которые пользователь может прокручивать и выбирать, второй список, содержащий выбранные элементы, и «Сохранить». Кнопка, чтобы сохранить новый список.

Мне показалось, что проблема проста. Тем не менее, у каждого представления есть свои особенности - иногда вам нужно что-то пересчитать, иногда вы должны хранить некоторые дополнительные данные и т. Д. Я решил это, вставив хуки обратного вызова в основной логический код.

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

Проблемы:

  • различия настолько малы, что код выглядит почти одинаково во всех представлениях,
  • Есть так много различий, что, когда вы смотрите на детали, код не похож

Как мне справиться с этой ситуацией?
Является ли ядро ​​логики, состоящее полностью из вызовов обратного вызова, хорошим решением?
Или мне лучше дублировать код и отбросить сложность кода, основанного на обратном вызове?

Мэл
источник
38
Я обычно нахожу полезным отпустить дублирование изначально. Когда у меня есть несколько примеров, гораздо легче увидеть, что общего, а что нет, и придумать способ поделиться общими частями.
Уинстон Эверт
7
Очень хороший вопрос - нужно учитывать не только физическое дублирование кода, но и семантическое дублирование. Если изменение в одном фрагменте кода обязательно означает, что такое же изменение будет дублировано во всех остальных, то эта часть, вероятно, является кандидатом на рефакторинг или абстракцию. Иногда вы можете нормализовать ситуацию до такой степени, что вы фактически поймаете себя в ловушку, поэтому я также рассмотрел бы практические последствия для трактовки дублирования как семантически различного - они могут быть перевешены последствиями попытки дедупликации.
Муравей P
Помните, что вы можете использовать только тот код, который делает то же самое. Если ваше приложение выполняет разные действия на разных экранах, оно потребует различных обратных вызовов. Никаких «если», «и» или «но» об этом.
CorsiKa
13
Моё личное практическое правило: если я внесу изменения в код в одном месте, будет ли это ошибкой, если я не внесу то же самое изменение в другом месте? Если так, то это плохое дублирование. Если я не уверен, выбирайте то, что сейчас более читабельно. В вашем примере различия в поведении являются преднамеренными и не считаются ошибками, поэтому некоторое дублирование вполне допустимо.
Ixrec
Возможно, вам будет интересно почитать об аспектно-ориентированном программировании.
Бен Джексон

Ответы:

53

В конечном счете, вам необходимо принять решение о том, комбинировать ли подобный код для устранения дублирования.

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

Как и все в жизни, вы должны учитывать преимущества по сравнению с затратами. Сколько дублированного кода будет удалено? Сколько раз код повторяется? Сколько будет усилий, чтобы написать более общий дизайн? Сколько вы можете разработать код в будущем? И так далее.

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

Недостаточное внимание к дизайну является ловушкой, но также стоит остерегаться чрезмерного дизайна.


источник
Я думаю, что ваш комментарий о "неудачных тенденциях" и слепом следовании руководящим принципам находится на месте.
Mael
1
@Mael Вы говорите, что если вы не будете поддерживать этот код в будущем, у вас не будет веских причин получить правильный дизайн? (без обид, просто хочу знать, что вы об этом думаете)
Пятнистый
2
@Mael Конечно, мы можем считать это просто неудачным поворотом слова! : D Тем не менее, я думаю, что мы должны быть такими же строгими с собой, как и с остальными при написании кода (я считаю себя другим, когда читаю свой собственный код через 2 недели после его написания).
Пятно
2
@ user61852, тогда тебе очень не понравится The Codeless Code .
RubberDuck
1
@ user61852, ха - ха , - но что , если это действительно все зависит (от информации , не учитывая в этом вопросе)? Немногие вещи менее полезны, чем избыточная уверенность.
43

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

Часть знаний может быть фактом («максимально допустимое отклонение от предполагаемого значения составляет 0,1%») или это может быть какой-то аспект вашего процесса («эта очередь никогда не содержит более трех элементов»). По сути, это любая отдельная часть информации, закодированная в вашем исходном коде.

Поэтому, когда вы решаете, является ли что-то дублированием, которое следует удалить, спросите, является ли это дублированием знаний. Если нет, то это, вероятно, случайное дублирование, и его извлечение в какое-то обычное место вызовет проблемы, когда позже вы захотите создать аналогичный компонент, в котором эта явно дублированная часть отличается.

Бен Ааронсон
источник
12
Этот! В центре внимания СУХОГО - избежать дублирования изменений .
Матье М.
Это довольно полезно.
1
Я думал, что целью DRY было убедиться, что нет двух битов кода, которые должны вести себя одинаково, но не должны . Проблема не в удвоенной работе, потому что изменения кода должны применяться дважды, реальная проблема заключается в том, что изменение кода нужно применять дважды, но это не так.
gnasher729
3
@ gnasher729 Да, в этом все дело. Если два фрагмента кода имеют дублирование знаний , то вы ожидаете, что когда один из них потребуется изменить, то и другой потребуется изменить, что приведет к описанной вами проблеме. Если у них есть случайное дублирование , тогда, когда один должен измениться, другой, возможно, должен остаться тем же самым. В этом случае, если вы извлекли общий метод (или что-то еще), теперь у вас есть другая проблема для решения
Бен Ааронсон
1
Также существенное дублирование и случайное дублирование , см . Случайный двойник в Ruby, и я испортил мой код, и теперь с ним трудно работать. Что случилось? , Случайные дубликаты также встречаются на обеих сторонах границы контекста . Резюме: объединять дубликаты только в том случае, если для их клиентов имеет смысл одновременно изменять эти зависимости .
Эрик
27

Рассматривали ли вы использовать шаблон стратегии ? У вас будет один класс View, который содержит общий код и подпрограммы, вызываемые несколькими представлениями. Дочерние элементы класса View будут содержать код, специфичный для этих экземпляров. Все они будут использовать общий интерфейс, который вы создали для View, и, таким образом, различия будут инкапсулированы и согласованы.

LindaJeanne
источник
5
Нет, я не учел это. Спасибо за предложение. Из краткого прочтения о шаблоне «Стратегия» кажется, что я что-то ищу. Я определенно буду расследовать дальше.
Mael
3
есть шаблон метода шаблона . Вы также можете рассмотреть это
Шакил
5

Каков потенциал перемен? Например, наше приложение имеет 8 различных бизнес-областей с потенциалом 4 или более типов пользователей для каждой области. Представления настраиваются в зависимости от типа пользователя и области.

Первоначально это было сделано с использованием одного и того же представления с несколькими проверками здесь и там, чтобы определить, должны ли показываться разные вещи. Со временем некоторые сферы бизнеса решили делать совершенно разные вещи. В конце концов, мы в основном перешли на одно представление (частичное представление в случае ASP.NET MVC) на единицу функциональности на бизнес-область. Не все бизнес-сферы имеют одинаковую функциональность, но если один хочет функциональность, которая есть у другого, эта область получает свое собственное представление. Это намного менее громоздко для понимания кода, а также для тестируемости. Например, внесение изменений в одну область не вызовет нежелательных изменений в другой области.

Как упомянуто @ dan1111, это может привести к решению суда. Со временем вы можете узнать, работает ли он или нет.

ps2goat
источник
2

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

A(a,b,c) //a,b,c are your callbacks or other dependencies

Вместо нескольких уровней в зависимости от того, сколько контроля требуется:

//high level
A(a,b,c)
//lower
A myA(a,b)
B(myA,c)
//even lower
A myA(a)
B myB(myA,b)
C myC(myB,c)
//all the way down to you just having to write the code yourself

Насколько я понял, вы только выставляете интерфейс высокого уровня (A), скрывая детали реализации (другие вещи там).

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

Итак, у вас есть два варианта. Либо вы используете только низкоуровневый интерфейс, либо низкоуровневый интерфейс, потому что высокоуровневый интерфейс был слишком трудоемким для обслуживания, либо открываете интерфейсы как высокого, так и низкого уровня. Единственный разумный вариант - предложить интерфейсы как высокого, так и низкого уровня (и все, что между ними), при условии, что вы хотите избежать избыточного кода.

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

Используйте один объект, где вам нужно мало контроля.

Используйте функциональность самого низкого уровня, когда должно произойти что-то странное.

Это также не очень черно-белый. Возможно, ваш большой класс высокого уровня МОЖЕТ разумно охватить все возможные варианты использования. Возможно, варианты использования настолько различны, что ничего, кроме примитивной функциональности самого низкого уровня, не достаточно. До вас, чтобы найти баланс.

Waterlimon
источник
1

Уже есть другие полезные ответы. Я добавлю мой.

Дублирование плохо, потому что

  1. это загромождает код
  2. это мешает нашему пониманию кода, но самое главное
  3. потому что если вы измените что-то здесь, и вам также придется что-то изменить там , вы можете забыть / ввести ошибки / .... и трудно никогда не забыть.

Итак, суть в том, что вы не устраняете дублирование ради него или потому, что кто-то сказал, что это важно. Вы делаете это, потому что хотите уменьшить количество ошибок / проблем. В вашем случае, похоже, что если вы измените что-то в представлении, вам, вероятно, не нужно будет менять одну и ту же строку во всех других представлениях. Таким образом, у вас есть очевидное дублирование , а не фактическое дублирование.

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

Francesco
источник