Как я могу предотвратить неизвестное дублирование кода?

33

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

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

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

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

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

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

Earlz
источник

Ответы:

30

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

Шаг 1. Начните писать тесты на унаследованном коде (желательно с использованием инфраструктуры тестирования)

Шаг 2. Перепишите / реорганизуйте код, который дублируется, используя то, что вы узнали из тестов

Вы можете использовать инструменты статического анализа для обнаружения дублированного кода, а для C # существует множество инструментов, которые могут сделать это за вас:

Подобные инструменты помогут вам найти точки в коде, который делает подобные вещи. Продолжайте писать тесты, чтобы определить, что они действительно делают; используйте те же тесты, чтобы сделать дубликат кода более простым в использовании. Этот «рефакторинг» может быть выполнен несколькими способами, и вы можете использовать этот список, чтобы определить правильный:

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

  1. Определить точки изменения
  2. Найти контрольные точки
  3. Разрушить зависимости
  4. Написать тесты
  5. Внести изменения и рефакторинг

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

В этом случае

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

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

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

Поскольку OP является новым для кода, перед тем, как что-либо предпринимать, нужно сделать еще кое-что:

  • Потратьте время, чтобы извлечь уроки из кодовой базы, то есть сломать «все», проверить «все», вернуться.
  • Попросите кого-нибудь из команды проверить ваш код перед тем, как его совершить ;-)

Удачи!

Spoike
источник
На самом деле у нас довольно много модульного и интеграционного тестирования. Не 100% охват, но некоторые вещи, которые мы делаем, почти невозможно модульно протестировать без радикальных изменений в нашей кодовой базе. Я никогда не думал об использовании статического анализа, чтобы найти дублирование. Я должен попробовать это дальше.
Эрлз
@Earlz: Статический анализ кода потрясающий! ;-) Кроме того, всякий раз, когда вам нужно внести изменения, подумайте о решениях, которые бы упростили внесение изменений (для этого обратитесь к каталогу рефакторинга шаблонов)
Spoike,
+1 Я бы понял, если бы кто-то положил награду за этот вопрос, чтобы наградить его как «очень полезного». Каталог Refactor to Patterns - это золото, подобные вещи в стиле GuidanceExplorer.codeplex.com - отличные средства программирования.
Джереми Томпсон
2

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

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

Автор этой книги, Йоханнес Саметингер, описывает ряд барьеров для повторного использования кода, некоторые концептуальные, некоторые технические. Например:

Концептуальные и технические

  • Трудность в поиске программного обеспечения многократного использования : программное обеспечение не может быть повторно использовано, если его не найти. Повторное использование вряд ли произойдет, если в хранилище недостаточно информации о компонентах или когда компоненты плохо классифицированы.
  • Неповторимость найденного программного обеспечения : легкий доступ к существующему программному обеспечению не обязательно увеличивает повторное использование программного обеспечения. Непреднамеренно программное обеспечение редко пишется таким образом, чтобы другие могли его использовать. Модификация и адаптация чужого программного обеспечения может стать даже более дорогой, чем программирование необходимой функциональности с нуля.
  • Устаревшие компоненты не пригодны для повторного использования : повторное использование компонентов является трудным или невозможным, если они не были спроектированы и разработаны для повторного использования. Простого сбора существующих компонентов из различных унаследованных программных систем и попытки использовать их для новых разработок недостаточно для систематического повторного использования. Реинжиниринг может помочь в извлечении повторно используемых компонентов, однако усилия могут быть значительными.
  • Объектно-ориентированные технологии . Широко распространено мнение, что объектно-ориентированные технологии оказывают положительное влияние на повторное использование программного обеспечения. К сожалению и неправильно, многие также считают, что повторное использование зависит от этой технологии или от того, что для повторного использования программного обеспечения достаточно принятия объектно-ориентированной технологии.
  • Модификация : компоненты не всегда будут именно такими, какими мы их хотим. Если изменения необходимы, мы должны быть в состоянии определить их влияние на компонент и его предыдущие результаты проверки.
  • Повторное использование мусора : сертификация повторно используемых компонентов с определенным уровнем качества помогает минимизировать возможные дефекты. Плохой контроль качества является одним из основных препятствий для повторного использования. Нам нужны некоторые средства для оценки соответствия требуемых функций функциям, предоставляемым компонентом.

Другие основные технические трудности включают

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

По мнению автора, разные уровни повторного использования происходят в зависимости от зрелости организации.

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

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

edalorzo
источник
1

Есть 2 возможных решения:

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

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

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

Euphoric
источник
0

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

Если язык C / C ++, дублирование будет легче из-за гибкости компоновки (можно вызывать любые externфункции без предварительной информации). Для Java или .NET вам может потребоваться разработать вспомогательные классы и / или служебные компоненты.

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

9dan
источник
0

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

Для этого есть решение, и оно называется Generics, которое было представлено в Java 6. Это эквивалент C ++, называемый Template. Код, точный класс которого еще не известен в общем классе. Пожалуйста, проверьте Java Generics, и вы найдете тонны и тонны документации для него.

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

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

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

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

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

У меня была похожая ситуация, когда в обобщенном классе в конце концов заменили что-то вроде 6 или 7 других почти идентичных классов, которые были почти полностью идентичны, но были скопированы и вставлены различными программистами в течение определенного периода времени.

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

Надеюсь, что это поможет и удачи.

Андре ван Кувен
источник
0

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

Если, скажем, вы даете мне возможность использовать:

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

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

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

Но есть также связь и стабильность, о которых стоит подумать, и некоторое скромное дублирование здесь и там может служить механизмом развязки, который также увеличивает стабильность (неизменный характер) пакета.

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


источник
-2

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

На это может быть три причины:

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

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

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

И этот третий случай случается очень часто. Если вы дублируете «по незнанию», в основном именно по этой причине - это не настоящее дублирование!

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

Гангнус
источник
2
code duplication is not always harmfulэто один плохой совет.
Тулаинс Кордова
1
Должен ли я преклониться перед твоей властью? Я изложил свои причины здесь. Если я ошибаюсь, покажи где ошибка. Теперь это выглядит как ваша слабая способность продолжать обсуждение.
Гангнус
3
Дублирование кода является одной из основных проблем при разработке программного обеспечения, и многие ученые и теоретики вычислительной техники разработали парадигмы и методологии просто для того, чтобы избежать дублирования кода в качестве основного источника проблем с поддержкой в ​​разработке программного обеспечения. Это все равно что сказать «писать плохой код не всегда плохо», таким образом, все может быть риторически оправдано. Возможно, вы правы, но избегать дублирования кода - слишком хороший принцип, чтобы жить по нему, чтобы поощрять обратное ..
Tulains Córdova
Я бы поставил здесь аргументы. У вас нет Ссылка на власти не будет работать с 16-го века. Вы не можете гарантировать, что правильно их поняли и что они для меня тоже авторитеты.
Гангнус
Вы правы, дублирование кода не является одной из основных проблем при разработке программного обеспечения, и для его предотвращения не было разработано никаких парадигм и методологий.
Тулаинс Кордова