Любая веская причина для дублирования кода?

В настоящее время я просматриваю очень старый проект C++ и вижу там много дублирования кода.

Например, есть класс с 5 обработчиками сообщений MFC, каждый из которых содержит 10 одинаковых строк кода. Или здесь и там есть 5-строчный фрагмент для очень специфического преобразования строки. Уменьшение дублирования кода в этих случаях вообще не проблема.

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

Что может быть веской причиной для дублирования кода?


person sharptooth    schedule 01.09.2009    source источник


Ответы (20)


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

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

Год спустя, когда меня попросили сделать что-то очень похожее, я намеренно решил проигнорировать DRY. Я собрал базовый процесс и сгенерировал весь повторяющийся код. Дублирующийся код был задокументирован, и я сохранил шаблон, используемый для генерации кода. Когда клиент запросил конкретное условное изменение (например, если x == y^z + b, то 1+2 == 3,42), это было проще простого. Это было невероятно легко поддерживать и изменять.

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

person John MacIntyre    schedule 01.09.2009
comment
+1. Изменение требований часто губительно для DRY. - person Konrad Rudolph; 01.09.2009
comment
Меняющиеся требования убивают. DRY не является чем-то особенным. - person Ira Baxter; 06.09.2009

Полезно прочитать об этом крупномасштабное проектирование программного обеспечения на С++< /strong> Джона Лакоса.

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

Наиболее важным моментом является вопрос при принятии решения об удалении дублирования или повторяющегося кода:

Если этот метод изменится в будущем, хочу ли я изменить поведение дублированного метода или нужно оставить его таким, какой он есть?

В конце концов, методы содержат (бизнес) логику, и иногда вам нужно изменить логику для каждого вызывающего объекта, а иногда нет. Зависит от обстоятельств.

В конце концов, все дело в обслуживании, а не в красивом источнике.

person Sam    schedule 01.09.2009
comment
Согласованный. Люди становятся параноиками по поводу структурных изменений, особенно если это уже работает и релизы не за горами. - person Mike Lewis; 01.09.2009
comment
В конце концов, все дело в обслуживании, а не в красивом источнике. ‹-- Очень важно!! :) - person cwap; 01.09.2009
comment
Правильный ответ — поместить общую логику в отдельную функцию, common(), а затем сделать так, чтобы businesslogic1() и businesslogic2() вызывали ее. Если (только) businesslogic1() необходимо изменить в будущем, затем скопируйте и вставьте в него common() и внесите изменения. (Но только если вы не можете легко параметризовать common() для обработки обоих случаев.) - person j_random_hacker; 27.01.2011

Лень, это единственная причина, о которой я могу думать.

На более серьезной ноте. Единственная веская причина, которую я могу придумать, — это изменения в самом конце производственного цикла. Они, как правило, подвергаются гораздо более тщательной проверке, и самые маленькие изменения, как правило, имеют самый высокий уровень успеха. В этом ограниченном случае легче получить дублирующее изменение кода, чем рефакторинг меньшего изменения.

До сих пор оставляет неприятный привкус во рту.

person JaredPar    schedule 01.09.2009
comment
Это своего рода обратная лень, не так ли? Я имею в виду, что было бы гораздо ленивее сделать функцию и вызывать ее везде... - person Justicle; 01.09.2009
comment
@Justicle Верно, но когда вы просто хотите закончить эту одну функцию и попробовать свой код, проще добавить туда 5 строк кода, чем думать о передаче параметров/типов возвращаемого значения и всех других вещах, которые идут с функцией. - person DeusAduro; 01.09.2009
comment
Кроме того, метод копирования и вставки означает, что вам вообще не нужно изменять исходный код, что означает меньше шансов что-то сломать — важная проблема, если вы находитесь в период непосредственно перед выпуском. - person Jeremy Friesner; 01.09.2009

Помимо неопытности, могут появиться повторяющиеся вхождения кода:

Нет времени на надлежащий рефакторинг

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

Обобщение кода невозможно/некрасиво из-за языковых ограничений

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

Вы не уверены, что вообще будет обобщение

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

Добавлю еще, если что-то еще вспомню.


Добавлено позже...

Развертывание цикла

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

person Daniel Mošmondor    schedule 01.09.2009
comment
+1 за ваш последний пункт - это тот же принцип, что и преждевременная оптимизация. - person Andrzej Doyle; 02.11.2009

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

Do_A_Policy()
{
  printf("%d",1);
  printf("%d",2);
}

Do_B_Policy()
{
  printf("%d",1);
  printf("%d",2);
}

Теперь вы можете предотвратить «дублирование кода» с помощью такой функции:

first_policy()
{
printf("%d",1);
printf("%d",2);
}

Do_A_Policy()
{
first_policy()
}

Do_B_Policy()
{
first_policy()
}

Однако существует риск того, что какой-то другой программист захочет изменить Do_A_Policy() и сделает это, изменив first_policy(), что вызовет побочный эффект изменения Do_B_Policy(), побочный эффект, о котором программист может не знать. так что такого рода «дублирование кода» может служить защитным механизмом от подобных изменений в программе в будущем.

person Liran Orevi    schedule 01.09.2009
comment
Что ж, мне кажется, что first_policy нужно будет принимать своего рода параметр. - person GManNickG; 01.09.2009
comment
Этот пример кричит о модульном тесте. - person John MacIntyre; 01.09.2009
comment
Я понимаю, к чему вы клоните, но я думаю, что гораздо удобнее учитывать логику в first_policy(), как вы это сделали. Если позже вам потребуется найти все варианты использования этой логики, гораздо проще найти все вызовы first_policy(), чем все пары операторов printf(), которые выглядят так. Кодировщика, который изменяет семантику функции, не проверив все места вызова, нужно... убедить не делать этого. :) - person j_random_hacker; 27.01.2011

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

person cwap    schedule 01.09.2009
comment
можете привести реальный пример такой ситуации? - person flybywire; 01.09.2009
comment
@flybywire их много, и не выполнение того, что предлагает cwap, является довольно распространенной (и часто упускаемой из виду) ошибкой проектирования. Это часто приводит к большому количеству ветвлений/переключений состояния, когда вам нужно выполнить логику, и именно потому, что рефакторинг обычно выполняется локально, а не через границы. - person Rune FS; 10.04.2012

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

Пример (псевдокод):

procedure setPropertyStart(adress, mode, value)
begin
  d:=getObject(adress)
  case mode do
  begin
    single: 
       d.setStart(SingleMode, value);
    delta:
       //do some calculations
       d.setStart(DeltaSingle, calculatedValue);
   ...
end;

procedure setPropertyStop(adress, mode, value)
begin
  d:=getObject(adress)
  case mode do
  begin
    single: 
       d.setStop(SingleMode, value);
    delta:
       //do some calculations
       d.setStop(DeltaSingle, calculatedValue);
   ...
end;

Вы можете каким-то образом реорганизовать вызов метода (setXXX), но в зависимости от языка это может быть сложно (особенно с наследованием). Это дублирование кода, так как большая часть тела одинакова для каждого свойства, но бывает сложно реорганизовать общие части.

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

person Tobias Langner    schedule 01.09.2009
comment
+1 - важная часть здесь зависит от языка, но я согласен, что это может произойти даже в таких простых случаях, как этот. - person orip; 01.09.2009

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

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

Тем не менее, я не могу придумать никакого разумного способа оправдать дублирование кода. Посмотрите, почему это плохо.

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

person GManNickG    schedule 01.09.2009

Для такого дублирования кода (множество строк дублируется много раз) я бы сказал:

  • либо лень (вы просто вставляете какой-то код здесь и там, не беспокоясь о том, какое влияние он может оказать на другие части приложения — при написании новой функции и использовании ее в двух местах, я полагаю, некоторое влияние)
  • или незнание какой-либо передовой практики (повторное использование кода, разделение разных задач в разных функциях/методах)

Вероятно, первое решение из того, что я обычно видел :-(

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

Разделение кода на несколько функций, правильное повторное использование кода и все это часто приходит с опытом — или вы наняли не тех людей ;-)

person Pascal MARTIN    schedule 01.09.2009
comment
По моему опыту, младшие разработчики, которые начали поддерживать уродливый код (и так и не увидели ни одного хорошего кода), только приобрели менталитет исправления, пока он не заработает. - person Wim Coenen; 01.09.2009
comment
@wcoenen Похоже, вам нужно нанять лучших младших разработчиков. Небольшое прикосновение к ОКР может быть полезным. - person Mike Lewis; 01.09.2009

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

Однако в данном случае я не думаю, что они это делали, хех.

person jmq    schedule 01.09.2009

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

Кроме того, иногда — даже когда логика дублируется — стоимость сокращения дублирования слишком высока. Это может произойти, особенно когда это не просто дублирование кода: например, если у вас есть запись данных с определенными полями, которая повторяется в разных местах (определение таблицы БД, класс C++, текстовый ввод), обычный способ уменьшить это дублирование с генерацией кода. Это усложняет ваше решение. Почти всегда эта сложность окупается, но иногда нет — это ваш компромисс.

person orip    schedule 01.09.2009

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

person Paddy    schedule 01.09.2009

Похоже, первоначальный автор либо был неопытен, либо ему не хватало времени. Большинство опытных программистов объединяют вещи, которые используются повторно, потому что позже будет меньше обслуживания — форма лени.

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

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

person AndersK    schedule 01.09.2009

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

Сводится к лени или плохой практике обзора.

РЕДАКТИРОВАТЬ:

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

Вы смотрели историю изменений в файле?

person Everyone    schedule 01.09.2009

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

person Ignacio Soler Garcia    schedule 01.09.2009
comment
Современные языки позволяют вам писать функции, а затем громко намекать, что их следует встроить. Это дает вам лучшее из обоих миров: встраивание и избежание избыточного кода. - person Ira Baxter; 03.09.2009

У меня нет проблем с дублированным кодом, когда он создается генератором исходного кода.

person Stephen C    schedule 01.09.2009

Что-то, что мы обнаружили, что заставило нас дублировать код, было нашим кодом манипулирования пикселями. Мы работаем с ОЧЕНЬ большими изображениями, и накладные расходы на вызовы функций съедают порядка 30% нашего времени обработки каждого пикселя.

Дублирование кода манипулирования пикселями дало нам на 20 % более быстрый обход изображения за счет сложности кода.

Это, очевидно, очень редкий случай, и в конце концов он значительно раздул наш исходный код (300-строчная функция теперь 1200 строк).

person Ron Warholic    schedule 09.09.2009
comment
Вы пытались использовать какую-либо директиву компилятора, чтобы принудительно встроить вызовы функций? - person sharptooth; 09.09.2009
comment
Мы попробовали ключевое слово inline, но оно не обеспечивало надежного встраивания функции во всех необходимых нам случаях. - person Ron Warholic; 09.09.2009

Нет веской причины для дублирования кода.

См. шаблон проектирования Беспощадный рефакторинг.

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

person Asaph    schedule 01.09.2009
comment
-1, хотя случай этого спрашивающего не является уважительной причиной, как и почти любой другой случай дублирования, конечно, есть веские причины (см. ответы). - person orip; 01.09.2009

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

или, давайте обратимся к цитате Ларри Уолла:

«Мы будем поощрять вас развивать три великих достоинства программиста: лень, нетерпение и высокомерие».

совершенно очевидно, что дублирование кода не имеет ничего общего с «ленью». хаха ;)

person varnie    schedule 02.11.2009
comment
Действительно? Благодаря волшебству копирования/вставки спамить ХАХАХАХАХАХАХАХАХАХАХАХА в поле для комментариев гораздо проще, чем писать что-то вдумчивое. Дублирующийся код легко написать. Часто это ленивое решение. - person jalf; 08.12.2009
comment
Ленивым решением было бы пойти выпить кофе и подумать о том, как высушить вместо c&p (что было бы глупо). Есть разница между ленивыми умными людьми и ленивыми людьми. Программисты (которые считаются умными) сокращают работу, в то время как другие люди сидят на диване и толстеют. - person atamanroman; 19.07.2010

Поскольку существует «Шаблон стратегии», нет веской причины для дублирования кода. Ни одна строчка кода не должна дублироваться, все остальное — эпический фейл.

person Turing Complete    schedule 06.07.2010