Сегодня ночью, проводя очередной 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 проект, в котором «все уже украдено до нас».
Скачиваем PMD, в котором находится CPD, который нам, собственно и нужен. В моем случае, мною была скачана версия 4.6.2.
CPD по умолчанию не поддерживает Objective-C, но благодаря Mike Hall, который описал грамматику ObjectiveC для JavaCC, и все тому же jkennedy1980, мы можем получить замечательный Objective-C Tokenizer, который расширит функциональность CPD еще одним языком программирования. Скачиваем и его с github.
Собираем закачанные файлы в нужную нам папку, и пробуем CPD на каком-нибуть проекте-жертве.
Параметры сами по себе интуитивно понятные, единственным более-менее вопросным с первого взгляда параметром является --minimum-tokens. В двух словах, это количество токенов, при повторении которых кусок кода может считаться copy-pasted. Поставите значение 1 — весь код будет сплошным копипастом, Поставите значение в 100500 — есть вероятность, что не будет найдено ни одного. Значение 100 подобрано опытным путем, согласно коэффициентам, взятым из справочника Стеля.
После успешного запуска, мы получим cpd-output.xml вида
С него-то мы и будем брать данные для показывания их в Xcode
Для того, интегрировать ХCode и CPD, мы добавим в Build Phases таргета проекта, Run Script фазу, условно состоящую из нескольких частей:
Реализацию парсера для XML я приведу немного позже, а сначала я расскажу о «правильном формате». «Правильный формат» — в данном случае это формат, в котором можно выводить сообщения в Run Script фазе, которые бдут обработаны Xcode'ом. В общем случае он выглядит примерно так:
Задача скрипта-программы будет сводиться к преобразованию cpd-output.xml в «правильный формат».
Написать скрипт/программу, которая будет читать исходный XML, и выдавать на выходе нужные нам строки — несложно, но, все же, для примера выкладываю проект, который после сборки запускает скомпилированного себя же, чтобы проверить свои исходники на предмет copypast'а.
А вот и пару скринов, как это выглядит на практике.
В Issue Navigator:
Внутри файла:
Чаще всего copy-paste — это зло, с которым надо бороться. Иногда с ним и не стоит бороться. Всяко бывает. Но, если все же прийдется, теперь у вас есть возможность интегрировать CPD в Xcode.
«Правильный формат» для Xcode был найден на просторах интернета случайным образом. Интересную информацию о том, что еще можно сделать из Run Script фазы, чтобы ее результат был обработан Xcode — можно писать в комментариях
Код, представленный в проекте не является номинантом на премию «Лучший код года». Автор пишет под MacOSX раз в сто лет, и просто показал метод расширения функциональности Xcode. А еще автор сознательно сделал так, что, проект с некоторой долей вероятности может не собраться на некоторых системах. Это не баг, это фича(с).
Немного лирики
Заранее оговорюсь о том, что некоторые пункты по установке 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 фазу, условно состоящую из нескольких частей:
- Собственно вызов cpd
- Парсинг cpd-output.xml
- Вывод в «правильном формате»
Реализацию парсера для 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. А еще автор сознательно сделал так, что, проект с некоторой долей вероятности может не собраться на некоторых системах. Это не баг, это фича(с).