Часть кода, с которым мы работаем в New Relic, существует довольно давно. К сожалению, не все это осталось чистым и хорошо выровненным с течением времени. Если вы не будете осторожны, даже самый чистый код может уступить энтропии. И без активных усилий по противодействию этому вы можете получить код, который кажется не поддерживаемым.
К счастью, есть некоторые инструменты, которые мы можем использовать, чтобы помочь нам внести ясные, организованные, тестируемые изменения даже в самые сложные кодовые базы. Одним из моих любимых является «Справочный график», слегка расширенная версия «Эскиза функций», описанного в разделе « Эффективная работа с устаревшим кодом» . Общая идея заключается в том, что у вас есть набор изменений, которые вы хотите внести в свой код, поэтому вы отображаете все, что содержит любые ссылки на изменения, которые вы хотите внести.
Давайте рассмотрим следующий пример кода, в котором все классы и даже методы этих классов представляют собой запутанную путаницу перекрестных ссылок:
class A def report B.new.get_all_the_state end end class B def get_all_the_state [ munge_state, @state1, D.new.get_state ] end def munge_state get_state end def get_state @state0 end end class C def report D.new.get_state end end class D def get_state @state2 end end module E def self.report [ A.new.report, C.report ] end end
Допустим, мы решили, что хотим избавиться от B#get_all_the_state
метода и разделить его обязанности между классами «А» и «В». В этом надуманном примере легко найти подходящее решение, к которому мы должны приступить. Но в более сложном проекте может быть сложно узнать все последствия, которые одно простое изменение может иметь в вашем исходном коде. Графируя ссылку между методами и переменными экземпляра вокруг кода, который мы хотим изменить, мы можем получить хорошее представление о масштабах изменений, которые мы хотим внести. Визуальное представление о том, что ссылки между методами и переменными экземпляра в вашем коде могут дать вам хорошее представление о том, где скрываются ваши самые узкие узлы кода, и поможет вашей работе распутать их.
Я использую Graphviz для создания графиков. Он рендерит изображения из точечных файлов, которые вы используете для определения узла, стилизации их и определения их ассоциаций. Для приведенного выше кода это будет файл точек. После установки Graphviz вы можете создать график ниже с помощью этой команды:
dot before.dot -T png -o before.png
|
Все части, которые мы хотим изменить, выделены красным, а все части, которые относятся к частям, которые мы хотим изменить, выделены оранжевым. Это дает нам представление о масштабах желаемых изменений, а также о планах постепенного выполнения этих изменений. Мы видим, что мы не можем вносить изменения B#get_state
без внесения изменений B#munge_state
и B#get_all_the_state
. Тесты для этих методов также должны быть изменены или удалены полностью. Существующие тесты для оранжевых блоков не должны быть изменены, но, вероятно, нужно будет написать новые, чтобы охватить функциональность старых красных блоков. Ни код, ни тестовые изменения для черных ящиков не должны быть необходимыми, поскольку оранжевые ящики должны изолировать все изменения в их интерфейсах и скрыть их от несвязанного кода.
Прорабатывая рефакторинг, я возвращаюсь к файлу точек и постоянно обновляю его с учетом моих изменений. Это отличный способ отслеживать мой прогресс, и он не дает мне заблудиться, что является очень реальным риском для крупных рефакторингов сложного кода. Мне нравится проверять файл Graphviz dot в системе контроля версий. Таким образом, если я слишком сильно застряну, я всегда могу вернуться к графику и посмотреть, актуален ли он. Также приятно иметь возможность вернуться и посмотреть на свой прогресс. Отзывы, которые вы получаете от просмотра изменений в графике, могут помочь вам найти области для дальнейшего развития (Это похоже на цикл обратной связи TDD.) Если график начинает становиться более изогнутым, вам, возможно, придется переосмыслить изменение кода.
Как только мы все закончим, наш график должен выглядеть так:
Недавно добавленные зеленые поля — это либо новые записи, либо старые записи, которые заполнены. На данный момент наш код должен выглядеть следующим образом:
class A def report [ B.new.zeros, ones ] end def ones B.new.ones end end class B def zeros @state0 end def get_state @state1 end end class C def report @state2 end end module E def self.report [ A.new.report, C.report ] end end
Хотя надуманные примеры хороши для демонстрации концепции, они не очень хорошо показывают, насколько хорошо эта техника работает на практике. По моему опыту, создание графа для нетривиального фрагмента кода занимает около двух-трех часов. Я считаю, что это хорошо проведенное время, и я считаю, что держать его в курсе, так как я работаю, на удивление ненавязчиво. На самом деле, я с нетерпением жду внесения своих постепенных изменений, а затем вижу, как график очищается и корректируется во время работы.
Недавно я пересмотрел систему настройки агента Ruby. Вот тизер:
Если вам интересно, файл точек для этих графиков можно найти в истории git для агента Ruby . Вы можете просмотреть историю и воссоздать график в каждой точке.
Если вы хотите узнать больше об этом предмете, я рекомендую вам прочитать книгу Майкла Фезера для получения дополнительной информации об этом и других методах работы с кодом, который подвергся разрушительному воздействию времени и энтропии.