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

  • Берем из соседнего скрипта код инициализации. Ctrl+c Ctrl+v.
  • Ищем код по репозитрию, который загружает данные из нужного источника. Ctrl+c Ctrl+v. Если чего-то не хватает - дописываем, в старый не переносим.
  • Ищем код, который делает похожие преобразования. Повезло и нашли? Конечно же, Ctrl+c Ctrl+v.
  • А теперь так же копипастим код для экспорта данных.Ctrl+c Ctrl+v, традиционно.

Коммитим, ревью не делаем, и в продакшен.

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

Решение очевидно: нужно вынести дублирующиеся части в библиотеку. Но вот как их найти, не просматривая глазами весь репозиторий или творя непотребства с утилитой diff? Оказалось, для Python'а есть решение - утилита clonedigger (а еще она умеет Java), которая умеет сравнивать код на уровне AST. Ставится просто через pip:

workon clonedigger
pip install clonedigger

Запускается тривиально:

cd <project_dir>
clonedigger -l python .

На выходе получается красивый html-файл, содержащий замечания о синтаксических ошибках (да, у нас и такое было) и сообщения о том, какие пары фрагментов исходного кода больше похожи на копипасту, чем на что-то хорошее. Если html не устраивает (например, если захочется результат куда-то вывести в другом виде или дополнительно обработать), есть возможность получить XML (тоже не сахар, но уже лучше) через указание опции --cpd-output. Html-файл в моем случае выглядел так:

Дублироване

Самое замечательное, что clonedigger позволяет найти не только совсем одинаковые фрагменты, но и незначительно отличающиеся друг от друга (например, как на рисунке выше).

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

P.S Ирония судьбы заключается в том, что код самого clonedigger им же никогда, видимо, не проверялся, а многие части можно использовать в качестве наглядного пособия для курса "лучшие антипатерны разработки на Python и не только".


Comments

comments powered by Disqus