Статьи

Справочные графики или «эскизы элементов» как инструменты для рефакторинга устаревшего кода

Первоначально автор Джон Гаймон

Обложка «Эффективная работа с устаревшим кодом» Майкла Фезерса
Часть кода, с которым мы работаем в 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

Образец графика № 1

Все части, которые мы хотим изменить, выделены красным, а все части, которые относятся к частям, которые мы хотим изменить, выделены оранжевым. Это дает нам представление о масштабах желаемых изменений, а также о планах постепенного выполнения этих изменений. Мы видим, что мы не можем вносить изменения B#get_stateбез внесения изменений B#munge_stateи B#get_all_the_state. Тесты для этих методов также должны быть изменены или удалены полностью. Существующие тесты для оранжевых блоков не должны быть изменены, но, вероятно, нужно будет написать новые, чтобы охватить функциональность старых красных блоков. Ни код, ни тестовые изменения для черных ящиков не должны быть необходимыми, поскольку оранжевые ящики должны изолировать все изменения в их интерфейсах и скрыть их от несвязанного кода.

Прорабатывая рефакторинг, я возвращаюсь к файлу точек и постоянно обновляю его с учетом моих изменений. Это отличный способ отслеживать мой прогресс, и он не дает мне заблудиться, что является очень реальным риском для крупных рефакторингов сложного кода. Мне нравится проверять файл Graphviz dot в системе контроля версий. Таким образом, если я слишком сильно застряну, я всегда могу вернуться к графику и посмотреть, актуален ли он. Также приятно иметь возможность вернуться и посмотреть на свой прогресс. Отзывы, которые вы получаете от просмотра изменений в графике, могут помочь вам найти области для дальнейшего развития (Это похоже на цикл обратной связи TDD.) Если график начинает становиться более изогнутым, вам, возможно, придется переосмыслить изменение кода.

Как только мы все закончим, наш график должен выглядеть так:

Пример графика 2

Недавно добавленные зеленые поля — это либо новые записи, либо старые записи, которые заполнены. На данный момент наш код должен выглядеть следующим образом:

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. Вот тизер:

Пример графика 3

Если вам интересно, файл точек для этих графиков можно найти в истории git для агента Ruby . Вы можете просмотреть историю и воссоздать график в каждой точке.

Если вы хотите узнать больше об этом предмете, я рекомендую вам прочитать книгу Майкла Фезера для получения дополнительной информации об этом и других методах работы с кодом, который подвергся разрушительному воздействию времени и энтропии.