Pull to refresh

Интегрируем Copy-Paste-Detection в Xcode, и не только

Development for iOS *
Сегодня ночью, проводя очередной code-review в наших проектах, наткнулся на большой кусок проявления чистейшего, кристализованного копипаста. Он не очень пришелся мне по душе, и как-то сразу всплыл вопрос:" А много ли копипаста у нас в проектах"? Google is my friend, поэтому решение нашлось очень быстро у jkennedy1980, который использовал CPD (copy paste detector), который входит в PMD (Pretty Much Done || Project Mess Detector || Programming Mistake Detector || ... ). В общем случае, CPD умеет находить copy-paste сходу для ряда языков(cpp, cs, java, php, ruby, ecmascript) и относительно легко расширяется, но мне же был нужен Objective-C. Как раз такой вариант и был у jkennedy1980, который использовал CPD в автоматической сборке jenkins'ом. Это в общем случае очень хорошо для любого проекта на любом языке, когда jenkins внедрен в процесс разработки, все права выставлены, и все знают где, когда и что надо нажимать. В случае же, когда разработчики не знают про jenkins, либо знают, но он где-то там далеко, такой метод мягко говоря не подходит. Xcode для iPhone/iOS разработчиков, все же, как-то ближе, и хотя для него все еще нельзя написать плагин,

Немного лирики


Заранее оговорюсь о том, что некоторые пункты по установке CPD, будут дублировать то, что написано в оригинале у jkennedy1980. То есть кому интересна интеграция jenkins + cpd — можно сразу идти сюда. Тех же, кому больше интересна интеграция Xcode + cpd — прошу оставаться с нами.
Для тех, кому совсем лень читать про настроку, и кто хочет попробовать все и сразу: ссылка на Xcode проект, в котором «все уже украдено до нас».

Качаем CPD


Скачиваем PMD, в котором находится CPD, который нам, собственно и нужен. В моем случае, мною была скачана версия 4.6.2.

Качаем ObjectiveC Tokenizer


CPD по умолчанию не поддерживает Objective-C, но благодаря Mike Hall, который описал грамматику ObjectiveC для JavaCC, и все тому же jkennedy1980, мы можем получить замечательный Objective-C Tokenizer, который расширит функциональность CPD еще одним языком программирования. Скачиваем и его с github.

Ничего лишнего


Собираем закачанные файлы в нужную нам папку, и пробуем CPD на каком-нибуть проекте-жертве.
java
-Xmx512m
-classpath pmd-4.2.5.jar:ObjCLanguage-0.0.1-SNAPSHOT.jar
net.sourceforge.pmd.cpd.CPD
--minimum-tokens 100
--files [Path to XCode project classes]
--language ObjectiveC
--encoding UTF-8
--format net.sourceforge.pmd.cpd.XMLRenderer > cpd-output.xml


Параметры сами по себе интуитивно понятные, единственным более-менее вопросным с первого взгляда параметром является --minimum-tokens. В двух словах, это количество токенов, при повторении которых кусок кода может считаться copy-pasted. Поставите значение 1 — весь код будет сплошным копипастом, Поставите значение в 100500 — есть вероятность, что не будет найдено ни одного. Значение 100 подобрано опытным путем, согласно коэффициентам, взятым из справочника Стеля.

После успешного запуска, мы получим cpd-output.xml вида

<duplication lines="23" tokens="110">
<file line="20" path="/.../CPDObjective-C/CopyPastedFiles/AnotherSimpleClass.m"/>
<file line="13" path="/../CPDObjective-C/CopyPastedFiles/SimpleClass.m"/>
<codefragment>
<![CDATA[
- (void)someMethod {
   
   for (int i = 0 ; i < 10; i++) {
      for (int j = 0; j < 10; j ++) {
         NSLog(@"This is incorrect");
         NSLog(@"This is incorrect");
         NSLog(@"This is incorrect");
         NSLog(@"This is incorrect");
         ...


С него-то мы и будем брать данные для показывания их в Xcode

Интеграция с Xcode


Для того, интегрировать ХCode и CPD, мы добавим в Build Phases таргета проекта, Run Script фазу, условно состоящую из нескольких частей:
  1. Собственно вызов cpd
  2. Парсинг cpd-output.xml
  3. Вывод в «правильном формате»

Реализацию парсера для XML я приведу немного позже, а сначала я расскажу о «правильном формате». «Правильный формат» — в данном случае это формат, в котором можно выводить сообщения в Run Script фазе, которые бдут обработаны Xcode'ом. В общем случае он выглядит примерно так:
[full-path-to-file]:[line-number]:[column-number]: warning: [Message]
[full-path-to-file]:[line-number]:[column-number]: error: [Message]

Задача скрипта-программы будет сводиться к преобразованию cpd-output.xml в «правильный формат».
Написать скрипт/программу, которая будет читать исходный XML, и выдавать на выходе нужные нам строки — несложно, но, все же, для примера выкладываю проект, который после сборки запускает скомпилированного себя же, чтобы проверить свои исходники на предмет copypast'а.

А вот и пару скринов, как это выглядит на практике.
В Issue Navigator:


Внутри файла:


P.S.


Чаще всего copy-paste — это зло, с которым надо бороться. Иногда с ним и не стоит бороться. Всяко бывает. Но, если все же прийдется, теперь у вас есть возможность интегрировать CPD в Xcode.

P.S.S.


«Правильный формат» для Xcode был найден на просторах интернета случайным образом. Интересную информацию о том, что еще можно сделать из Run Script фазы, чтобы ее результат был обработан Xcode — можно писать в комментариях

Последнее слово


Код, представленный в проекте не является номинантом на премию «Лучший код года». Автор пишет под MacOSX раз в сто лет, и просто показал метод расширения функциональности Xcode. А еще автор сознательно сделал так, что, проект с некоторой долей вероятности может не собраться на некоторых системах. Это не баг, это фича(с).
Tags:
Hubs:
Total votes 25: ↑23 and ↓2 +21
Views 4.3K
Comments Comments 17