Несмотря на то, что проблемы, связанные с дублированием кода, упоминаются довольно часто, актуальность этих проблем из года в год остается почти неизменной. Во многих популярных проектах количество клонов измеряется сотнями или даже тысячами.  В рамках данной статьи мне бы хотелось напомнить, что такое программные клоны, какие они влекут за собой проблемы и как с ними можно бороться. В статье приводятся примеры рефакторинга реальных клонов из популярного фреймворка Spring. В качестве инструментов используются Java 8, IDE IntelliJ IDEA 2017.1 и плагин Duplicate Detector 1.1.  По сути, клоны — это просто схожие фрагменты исходного кода. В основном они появляются при копировании, даже не смотря на то, что копирование является общеизвестно плохой практикой. Конечно, это не единственная возможная причина появления клонов, существуют и другие, более объективные. Например, сам язык программирования может быть недостаточно выразительным, или у разработчика может не быть возможностей для соответствующего изменения исходного кода. Можно выделить следующие основные причины возникновения клонов: С одной стороны, дублированный код обладает рядом очевидных недостатков. Такой код труднее изменять и развивать, из-за дубликатов увеличивается размер проекта и усложняется его понимание. Кроме того, при копировании также возникают риски распространения ошибок из исходных фрагментов. С другой стороны, удаление дубликатов также может привести к ошибкам, особенно, если для этого необходимо вносить существенные изменения в текст программы. Однако главным аргументом против удаления клонов является то, что такое удаление часто приводит к увеличению числа зависимостей. Довольно интересно про это написано в статье "Redundancy vs dependencies: which is worse?".  С моей точки зрения, клоны являются признаком не очень качественного исходного кода и, соответственно, влекут за собой те же проблемы. К сожалению, их не всегда можно эффективно удалить, да и не всегда именно они являются настоящей проблемой. В некоторых случаях они могут указывать на неудачный выбор архитектуры или на чрезмерную захламленность функции. В конечном счете, удалять клоны или нет — зависит от конкретной ситуации. Однако, в любом случае, дублированный код — это всегда повод задуматься. Существует довольно много инструментов для поиска клонов: PMD, CCFinder, Deckard, CloneDR, Duplicate finder (maven plugin), и многие другие. К сожалению, в основном эти инструменты не интегрированы со средой разработки. Отсутствие интеграции значительно затрудняет навигацию и рефакторинг клонов. При этом, инструментов, встроенных в среду разработки, оказывается не так много. Например, в случае IntelliJ IDEA выбор стоит только между стандартными инспекциями и двумя плагинами (PMD и Duplicate Detector). Данная статья преследует две цели. С одной стороны, с ее помощью мне бы хотелось внести свой скромный вклад в борьбу с дублированием исходного кода. С другой стороны, я бы хотел познакомить читателя с плагином Duplicate Detector, разработчиком которого я и являюсь. На данный момент, по сравнению со стандартными инспекциями, этот плагин обнаруживает в 3-4 раза больше клонов, предоставляет более удобный интерфейс и доступен для некоммерческой версии IntelliJ IDEA. Основные возможности плагина Duplicate Detector: По сути, существует лишь один способ удаления клонов — обобщить схожую функциональность. Для этого можно создать вспомогательный метод или класс, или попробовать выразить один дубликат через другой. При этом не стоит забывать, что рефакторинг делается для повышения качества кода. Поэтому к нему лучше подходить творчески, так как иногда проблема может быть шире или уже, или вообще заключаться в чем-то другом. Давайте рассмотрим несколько конкретных примеров из популярного фреймворка Spring. Для этого воспользуемся средой разработки IntelliJ IDEA и плагином Duplicate Detector. Среда разработки IntelliJ IDEA и плагин Duplicate Detector предоставляют множество возможностей, которые упростят рефакторинг клонов. Например, довольно много функций можно найти в контекстном меню Refactor или в подсказках к инспекциям кода (Alt + Enter в рамках инспекции).     
  В данном примере фрагменты кода почти идентичны. Главные отличия касаются только строк 4 и 9, в которых изменяются значения полей. В таких случаях на практике мало что можно сделать. Как вариант, можно попробовать выделить функциональные интерфейсы и использовать лямбды. Однако при таком рефакторинге код не обязательно станет короче, а главное, понятнее. Вместо этого давайте еще раз внимательно рассмотрим код дубликатов. Судя по всему, главная задача ветки else — это загрузка класса. Было бы логичным, для начала, выделить такую загрузку в виде отдельного метода. Главным преимуществом такого рефакторинга является его простота. Метод loadClass() не нуждается в дополнительном пояснении, а о его поведении можно догадаться просто по его названию.   Рассмотрим более неоднозначный пример. На первый взгляд кажется, что код в нем является практически идентичным. Однако на самом деле методы setSource(), setElementTypeName(), setMergeEnabled() не являются частью общего интерфейса или класса. Конечно, можно создать новый интерфейс или расширить уже имеющийся. Но этого может быть недостаточно, если у Вас нет доступа к классам ManagedList и ManagedSet, чтобы связать их с этим интерфейсом. В этом случае, Вам также придется создавать собственные wrapper'ы этих классов. В итоге, такой рефакторинг породит много новых сущностей. В нашем случае — это два класса и один интрефейс. Из-за этого код программы станет только сложнее, поэтому создание собственных wrapper'ов имеет смысл только если подобная ситуация встречается не в первый раз.   Обратите внимание на последние части, связанные с переменной user. Несмотря на их небольшое отличие, они также относятся к клонам. Такое может происходить по нескольким причинам. Например, если один фрагмент сначала скопировали, а уже затем изменили (исправили ошибку). Или, например, если фрагменты разрабатывались отдельно, различие в них может быть связано с невнимательностью или незнанием одного из разработчиков. По сути, отличие первого фрагмента, с учетом реализации метода hasLength(), заключается в дополнительной проверке !user.isEmpty(). В данном конкретном примере это может оказаться и не настолько критичным. Тем не менее, сам пример хорошо иллюстрирует суть проблемы. 
  В этом примере клонами являются конструкторы. Дублирование конструкторов часто происходит, если клоны на самом деле — это целые классы. В подобных случаях обычно или создают новый общий суперкласс, или пробуют выразить один из существующих классов через другой. Иногда наследование также используют и для рефакторинга отдельных фрагментов кода. Это позволяет в новых вспомогательных методах получить доступ к полям класса, а сами эти поля исключить из списка явных параметров. Однако, несмотря на то, что от этого сигнатура метода становится проще, как правило, такой рефакторинг — не очень удачная идея. Наследование может сильно запутать логику программы и принести больше вреда, чем пользы, особенно, если оно используется не по своему прямому назначению. Исходные файлы: Файлы после рефакторинга: Главными отличиями класса SingleCharWildcardedPathElement являются: Наибольшие сложности возникают с методом matches. Однако, если обратить внимание, что строки 81 и 90, в сущности, занимаются сравнением символов, то и этот метод легко обобщается. 
  Иногда различия между клонами могут касаться конструкторов, методов или целых фрагментов кода. В этом случае обобщение можно сделать с помощью функциональных интерфейсов: Function, BiFunction, Comparator, Supplier, Consumer или других. Единственное ограничение заключается в том, что в параметризуемых фрагментах должны совпадать типы входных и выходных данных. В данном примере можно удачно обобщить способ создания объектов ConsumeMediaTypeExpression и ProduceMediaTypeExpression при помощи одного функционального параметра BiFunction. Однако иногда одного такого параметра оказывается недостаточно, и их требуется больше. К таким случаям следует отнестись с осторожностью, так как уже даже два функциональных параметра могут сильно усложнить работу с методом. В качестве заключения хотелось бы напомнить, что проблема дублирования кода не настолько однозначна, как может показаться. Иногда дублированный код нельзя эффективно удалить, иногда вообще не он является настоящей проблемой. Тем не менее, по сути, клоны являются признаком не очень качественного исходного кода. Поэтому их удаление желательно, но не должно становится навязчивой идеей. Удаление клонов должно преследовать конкретную цель — сделать код программы проще и понятнее. Подводя итоги, мне бы хотелось выделить несколько полезных принципов, которые могут помочь в борьбе с клонами: