Пример формирования пользовательского анализа исходного кода

Glamorous Toolkit, гибкая среда разработки, поставляется с обширной поддержкой анализа программного обеспечения для различных языков. Эти анализы можно комбинировать и интегрировать с остальной средой, чтобы помочь разработчикам оценить свою собственную систему.

В этой статье мы покажем, как это работает, на классическом примере: оценка @Deprecated классов в системах Java. Мы выбрали эту задачу, потому что это одна из простейших эволюционных проблем, которую часто преподают на вводных курсах. Тем не менее, в нашем случае мы показываем, как решить эту проблему, не читая код, просто создавая индивидуальный интерактивный анализ.

Часть программного моделирования основана на FAMIX, семействе метамоделей для представления различных фактов о программных системах. FAMIX разработан в рамках Платформы анализа лося. Glamorous Toolkit интегрирует FAMIX и ядро ​​Moose в общую среду разработки.

Об устаревших классах

Отметка класса как устаревшего означает, что класс больше не считается важным и может прекратить свое существование в будущем. Это может помочь во многих случаях, например, при миграции API.

Представим, что у нас есть система, в которой несколько классов помечены как устаревшие. Первый вопрос: «Что из этого мы можем удалить?». Ответ на этот вопрос по существу сводится к поиску классов, помеченных знаком @Deprecated, а затем к выбору тех, которые нигде не используются (мы игнорируем отражение в этом упражнении). Как только мы ответим на первый вопрос, появится следующий: «Что нам делать с классами, которые мы не можем удалить прямо сейчас?». Обычный ответ на этот вопрос: «это зависит от обстоятельств».

В любом случае обе эти проблемы требуют анализа. Мы можем подойти к ним вручную и щелкнуть по IDE. Но вы здесь не для этого. Итак, давайте исследуем их по-другому.

Настраивать

В качестве исходного примера мы используем ArgoUML, Java-приложение с открытым исходным кодом для создания диаграмм UML. Мы используем версию 0.34 этого приложения, которая содержит около 2400 классов и ~ 200 тысяч строк кода Java. После того, как мы закончили создание анализа, мы также применим его ко второму проекту, а именно Apache Lucene Solr.

Чтобы начать наш анализ, нам нужно загрузить модель ArgoUML в Glamorous Toolkit. Мы достигаем этого с помощью файла MSE, который должен быть экспортирован внешним экспортером. Если вы читаете это руководство в Glamorous Toolkit, вы можете загрузить исходный код ArgoUML и уже созданный файл модели MSE, используя приведенный ниже фрагмент. Файл MSE был создан с использованием jdt2famix.

targetFolder := 'models' asFileReference ensureCreateDirectory.
archiveFileName := 'ArgoUML-0-34.zip'.
archiveUrl := 'https://dl.feenk.com/moose-tutorial/argouml/'.
ZnClient new
  url: archiveUrl, archiveFileName;
  signalProgress: true;
  downloadTo: targetFolder.
(ZipArchive new 
  readFrom: targetFolder / archiveFileName) 
    extractAllTo: targetFolder.
targetFolder

Фрагмент кода создает новую папку models в каталоге Glamorous Toolkit, а также загружает и извлекает уже подготовленный архив для этого руководства, который содержит код и файл модели MSE (размер архива составляет около 33 МБ). Мы можем проверить папку models, чтобы убедиться, что загрузка прошла успешно.

Мы можем загрузить эту модель, выполнив другой фрагмент кода.

modelFile := 'models' asFileReference
  / 'ArgoUML-0-34'
  / 'ArgoUML-0-34.mse'.
model := MooseModel new
  importMSEFromFile: modelFile.
model

Теперь у нас есть загруженная модель нашей системы, которую мы можем проверить и изучить.

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

Поиск устаревших классов

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

По умолчанию мы видим список всех классов из системы. На данный момент нас интересуют только устаревшие классы. Мы можем быстро выбрать только те; перетащите горизонтальную полосу внизу списка, чтобы отобразить игровую площадку, и введите:

self select: [ :each | each isAnnotatedWith: 'Deprecated' ]

Нажмите кнопку Inspect (или воспользуйтесь сочетанием клавиш: Ctrl + g в Windows и Linux и Cmd + g в Mac). В результате справа появляется новая панель, содержащая 25 классов, отмеченных как устаревшие.

Просто чтобы убедиться, что у нас действительно есть устаревшие классы, давайте исследуем один из них и выберем для просмотра исходный код.

Давайте ненадолго отступим. Мы видим 2 панели, но наша сессия проверки состоит из 5 панелей. Первая панель - это детская площадка. Остальные четыре представляют собой объекты, и инспектор предлагает несколько видов этих объектов. Кроме того, каждая панель также представлена ​​прямоугольником на полосе прокрутки сверху. Чтобы настроить количество видимых панелей, мы можем изменить их размер.

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

deprecatedClasses := model allModelClasses select: [:each |
  each isAnnotatedWith: 'Deprecated' ].

Этот фрагмент также определяет локальную переменную deprecatedClasses для хранения результата. Метод allModelClasses возвращает группу классов модели из модели. Мы продолжим использовать этот способ определения анализа, так как в конце весь наш анализ будет просто состоять из нескольких фрагментов кода.

Возвращаясь к нашей исходной проблеме, теперь у нас есть 25 классов, которые устарели в нашей системе. Далее нам нужно проверить, какие из них не используются. Или, если мы думаем с точки зрения клиентов и поставщиков, у каких из устаревших классов нет клиентских классов. Мы можем добавить еще один фрагмент в игровую площадку, который дополнительно фильтрует список, хранящийся в deprecatedClasses.

deprecatedClasses select: [ :each |
  each clientTypes isEmpty ].

Первая часть анализа сделана. У нас есть 14 классов, которые можно просто удалить, так как они не используются в системе. Остается 11 классов, которые нельзя удалить, потому что они все еще используются. Итак, что нам с этим делать?

stillUsedClasses := deprecatedClasses select: [ :each |
  each clientTypes isNotEmpty ].

Визуализация устаревших классов

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

Добавьте еще один фрагмент на игровую площадку и выполните:

view := GtMondrian new.
view nodes 
  with: (stillUsedClasses, 
    (stillUsedClasses flatCollect: #clientTypes)) asSet.
view layout grid.
view

Это показывает нам простую визуализацию, содержащую 11 устаревших классов и их типы клиентов.

Получается интерактивная картинка. При нажатии на узел справа отображаются сведения о соответствующем классе.

Не совсем удобно, что все классы выглядят одинаково. Давайте различим устаревшие и нерекомендуемые классы:

view := GtMondrian new.
view nodes 
  shape: [ :each |
    BlElement new 
      size: 5@5;
      geometry: BlCircle new;
      background: ((each isAnnotatedWith: 'Deprecated')
        ifTrue: [ Color red ]
        ifFalse: [ Color gray ]) ];
  with: (stillUsedClasses, 
    (stillUsedClasses flatCollect: #clientTypes)) asSet.
view layout grid.
view

Хорошо, теперь мы видим классы, но каковы зависимости?

view := GtMondrian new.
view nodes 
  shape: [ :each |
    BlElement new 
      size: 5@5;
      geometry: BlCircle new;
      background: ((each isAnnotatedWith: 'Deprecated')
        ifTrue: [ Color red ]
        ifFalse: [ Color gray ]) ];
  with: (stillUsedClasses, 
    (stillUsedClasses flatCollect: #clientTypes)) asSet.
view edges connectFromAll: #clientTypes.
view layout grid.
view

В порядке. Возможно, если немного лучше расположить график…

view := GtMondrian new.
view nodes 
  shape: [ :each |
    BlElement new 
      size: 5@5;
      geometry: BlCircle new;
      background: ((each isAnnotatedWith: 'Deprecated')
        ifTrue: [ Color red ]
        ifFalse: [ Color gray ]) ];
  with: (stillUsedClasses, 
    (stillUsedClasses flatCollect: #clientTypes)) asSet.
view edges connectFromAll: #clientTypes.
view layout force.
view

В порядке. Намного лучше.

Мы можем выделить несколько различных ситуаций. Два устаревших класса (выделены красным) вызывают друг друга, и их можно просто удалить. Есть один нерекомендуемый класс, использующий два устаревших класса. И есть 3 устаревших класса, которые используются несколькими другими нерекомендуемыми. Эта фотография предлагает нам возможность выбрать наш путь, одновременно принимая во внимание больше деталей. Например, интересным кандидатом для исследования является один класс, использующий два устаревших.

Индивидуальная настройка системы

До сих пор созданное нами представление является общим и может применяться в любой системе Java. Тем не менее, мы часто можем получить больше информации, принимая во внимание информацию, относящуюся к системе, которую мы изучаем.

В частности, представление дает представление о том, удаление какого класса может быть более или менее затратным. Однако, чтобы оценить, что делать дальше, нам также понадобится представление о ценности.

Проект ArgoUML разделен на основные компоненты: модель и пользовательский интерфейс (UI). Следовательно, при рассмотрении анализа устаревания может быть полезно знать, принадлежат ли классы пользовательскому интерфейсу или нет. Чтобы показать это, мы можем создать конкретное представление, которое окрашивает границы классов пользовательского интерфейса в синий цвет. Чтобы идентифицировать классы пользовательского интерфейса, мы полагаемся на тот факт, что имя пакета содержит компонент ui в своем имени.

view := GtMondrian new.
view nodes 
  shape: [ :each |
    BlElement new 
      size: 5@5;
      geometry: BlCircle new;
      background: ((each isAnnotatedWith: 'Deprecated')
        ifTrue: [ Color red ]
        ifFalse: [ Color gray ]);
      border: (BlBorder paint: 
        ((each mooseName includesSubstring: '::ui')
          ifTrue: [ Color blue ]
          ifFalse: [ Color transparent ])) ];
  with: (stillUsedClasses, 
    (stillUsedClasses flatCollect: #clientTypes)) asSet.
view edges connectFromAll: #clientTypes.
view layout force.
view

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

В этой ситуации что важнее рефакторинга в первую очередь: пользовательский интерфейс или модель? По-разному! Конечно, это зависит от контекста системы. Мы не можем дать оценочное суждение извне (хорошо, иногда можем, но очень редко). Тем не менее, теперь у нас есть более подробный инструмент для ведения разговора.

Изучение различных взглядов

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

  • Они маленькие или большие?
  • У них много методов?
  • Используются ли многие из их методов?

Возможно, действительно используются только несколько методов из этих устаревших классов. Или что они содержат много используемых методов. Затем мы адаптируем нашу точку зрения, чтобы попытаться ответить на эти вопросы.

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

Также в последующих представлениях не требуется, чтобы чтение точно следовало коду представления. Основная идея, которую мы пытаемся донести, заключается в том, что анализ может быть настолько подробным, насколько нам нужно, если это помогает нам рассуждать о наших системах.

Насколько велики устаревшие классы?

До сих пор мы не учитывали размер классов. Мы можем исправить это, установив размер класса в зависимости от количества его методов.

view := GtMondrian new.
view nodes 
  shape: [ :each |
    BlElement new 
      size: (each numberOfMethods min: 50) asPoint;
      geometry: BlCircle new;
      background: ((each isAnnotatedWith: 'Deprecated')
        ifTrue: [ Color red ]
        ifFalse: [ Color gray ]);
      border: (BlBorder paint: 
        ((each mooseName includesSubstring: '::ui')
          ifTrue: [ Color blue ]
          ifFalse: [ Color transparent ])) ];
  with: (stillUsedClasses, 
    (stillUsedClasses flatCollect: #clientTypes)) asSet.
view edges connectFromAll: #clientTypes.
view layout force
  strength: 0.15;
  charge: -250;
  length: 70.
view

Какие устаревшие методы фактически используются?

Теперь мы видим, что некоторые устаревшие классы действительно довольно большие, а другие очень маленькие. Но сколько кода в этих классах на самом деле используется?

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

deprecateMethods := (stillUsedClasses flatCollect: #methods)
  select: [ :aMethod | aMethod clientTypes notEmpty ].
clientMethods := (deprecateMethods collect: [ :aMethod | 
  aMethod clientMethods reject: [ :each | 
    each parentType = aMethod parentType ] ]) flatten.
interestingMethods := deprecateMethods, clientMethods.
view := GtMondrian new.
view nodes 
  shape: [ :each |
    BlElement new 
      background: ((each isAnnotatedWith: 'Deprecated')
        ifTrue: [ Color lightRed ]
        ifFalse: [ Color veryLightGray ]) ];
  with: (stillUsedClasses, 
    (stillUsedClasses flatCollect: #clientTypes)) asSet;
  forEach: [ :aClass |
    view nodes
      shape: [ :each |
        BlElement new 
          size: 5@5;
          background: Color gray ];
      with:(aClass methods intersection: interestingMethods).
    view layout grid ].
view edges connectFromAll: #clientTypes.
view layout force
  strength: 0.15;
  charge: -250;
  length: 70.
view

Код этого представления начинает увеличиваться; однако мы также лучше понимаем способ использования устаревших классов. Мы заметили, что у двух устаревших классов есть много методов, вызываемых изнутри системы. Однако только несколько методов вызывают эти устаревшие методы.

Как используются устаревшие методы?

В устаревшем классе, который имеет много методов, какой метод мы должны сначала рассмотреть и попытаться понять? Мы могли бы начать с тех методов, у которых больше всего клиентов; может быть, с теми, у кого их всего несколько? Независимо от нашего выбора, нам сначала нужно определить использование этих методов. Поскольку поиск этого вручную может занять много времени, мы должны провести анализ.

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

view := GtMondrian new.
view nodes 
  shape: [ :each |
    BlElement new 
      padding: (BlInsets all: 2);
      constraintsDo: [:c | 
     c horizontal fitContent.
     c vertical fitContent ];
      background: ((each isAnnotatedWith: 'Deprecated')
        ifTrue: [ Color lightRed ]
        ifFalse: [ Color veryLightGray ]) ];
  with: (stillUsedClasses, 
    (stillUsedClasses flatCollect: #clientTypes)) asSet;
  forEach: [ :aClass |
    view nodes
      shape: [ :each |
        BlElement new
          margin: (BlInsets all: 2);
          geometry: BlCircle new;
          size: ((each parentType 
            isAnnotatedWith: 'Deprecated')
              ifTrue: [ (each clientMethods 
                intersection: clientMethods) size + 5  ]
              ifFalse: [ 5 ]) asPoint;
          background: Color gray ];
      with:(aClass methods intersection: interestingMethods).
    view layout rectanglePack ].
view edges connectFromAll: #clientTypes.
view layout force
  strength: 0.15;
  charge: -250;
  length: 70.
view

Представление выше показывает нам, что два устаревших метода используются чаще; Кроме того, похоже, что это не устаревший метод, который используется гораздо чаще, чем другие.

Давай попробуем другую систему

Прежде чем мы подведем итоги, давайте посмотрим, как будет выглядеть этот анализ, если мы применим его к другой системе. В данном руководстве эта система называется Apache Lucene Solr. Это гораздо большая система, чем ArgoUML, имеющая 11 тыс. Классов и примерно 1 млн строк кода Java. Для учебника мы используем версию, соответствующую хешу фиксации 52f2a77.

Если вы следуете руководству Glamorous Toolkit, вы можете повторить все предыдущие шаги, используя предварительно определенный архив lucene-solr.zip, доступный по адресу https://dl.feenk.com/moose-tutorial/lucene-solr/lucene- solr-52f2a77.zip (в архиве около 230 МБ). Далее мы сосредоточимся только на нескольких шагах.

Первоначально после загрузки модели мы снова можем увидеть подробности о системе.

Мы можем применить начальное представление, показывающее зависимости между устаревшими классами. Мы видим совсем другую ситуацию, чем в случае с ArgoUML.

Опять же, чтобы извлечь значение, нам нужно настроить представление для этой системы. Как следует из названия, система состоит из двух подсистем: Lucene и Solr. Давайте добавим синюю границу к классам, принадлежащим Lucene, и зеленую границу к классам из Solr.

view := GtMondrian new.
view nodes 
  shape: [ :each |
    | containerName borderColor |
    containerName := each container mooseNameWithDots.
    borderColor := Color gray.
    (containerName beginsWith: 'org.apache.lucene') ifTrue: [
     borderColor := Color blue ].
    (containerName beginsWith: 'org.apache.solr') ifTrue: [
     borderColor := Color green ].
    BlElement new 
      size: 5@5;
      geometry: BlCircle new;
      background: ((each isAnnotatedWith: 'Deprecated')
        ifTrue: [ Color red ]
        ifFalse: [ Color gray ]);
      border: (BlBorder paint: borderColor) ];
  with: (stillUsedClasses, 
    (stillUsedClasses flatCollect: #clientTypes)) asSet.
view edges connectFromAll: #clientTypes.
view layout force.
view

Теперь мы видим несколько кластеров клиентов, использующих устаревшие классы от Lucene. Мы также видим потенциально более проблемный устаревший класс, у которого есть клиенты как из Lucene, так и из Solr.

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

Теперь мы получаем более полную картину того, как устаревшие классы используются в Lucene-Solr. Мы видим, что в данном случае очень часто используются несколько методов. Также у клиентских классов есть только несколько методов, которые используют устаревшие методы.

Теперь мы можем использовать это представление в качестве ориентира при чтении кода.

Заворачивать

В этом руководстве мы исследовали создание анализа для исследования устаревших классов в системах Java. Даже для этой относительно небольшой задачи есть еще много других способов адаптации нашего анализа.

Например, как насчет устаревших методов, используемых в других устаревших методах? Может быть, мы не хотим их учитывать. Возможно, нас больше волнует размер методов и т. Д.. В зависимости от того, чего мы хотим достичь, для нас становятся важными разные виды информации. Во всех этих случаях мы можем адаптировать наши запросы и представления.

Понимание устаревших классов - это всего лишь простой пример. Разработка программного обеспечения связана с множеством других подобных проблем. И каждый из них доступен одинаково: начать с вопроса; создавать индивидуальные анализы, ориентированные на конкретные контекстные потребности; интерпретировать результаты; повторяйте, пока не будете уверены в том, что делать дальше. И все это без чтения (большого) кода. В этом суть гибкого развития.

Приложение

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

  • исходный код системы
  • все зависимости системы
  • Jdt2famix, инструмент, который берет исходный код и зависимости системы и создает файл MSE.

В конкретном случае ArgoUML мы должны сделать:

В результате получится ArgoUML-0-34/ArgoUML-0-34.mse файл с сериализованной моделью, который мы можем загрузить и изучить в Glamorous Toolkit. Если есть классы, которые не могут быть разрешены в системе, они записываются в файл ArgoUML-0-34/jdt2famix-import.problems.

Мы можем выполнить набор аналогичных шагов для Lucene Solr. В этом случае мы можем клонировать репозиторий и использовать Maven для получения всех зависимостей. Более подробную информацию о том, как это сделать, можно найти на веб-странице jdt2famix.