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

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

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

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

О проверке архитектуры

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

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

Пример выполнения

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

Для этой системы мы создаем архитектурный отчет, содержащий три ограничения:

  • В системе не используются устаревшие классы;
  • Виджеты пользовательского интерфейса помещаются в специальные пакеты пользовательского интерфейса;
  • Модули, реализующие расширения UML для разных языков программирования, не имеют взаимозависимостей.

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

Настраивать

Чтобы начать проверку этих ограничений, нам нужно загрузить модель ArgoUML в Glamorous Toolkit. Мы достигаем этого с помощью файла MSE, который должен быть экспортирован внешним экспортером. Вы можете загрузить исходный код 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

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

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

Анатомия архитектурного ограничения

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

1. Опишите ограничение

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

2. Убедитесь, что его можно проверить

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

3. Привяжите заинтересованное лицо

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

4. Сформулируйте анализ.

Когда у нас есть описание ограничения и способ идентифицировать все соответствующие сущности, мы можем перейти к определению анализа, который выражает это ограничение. Мы должны сформулировать анализ так, чтобы он возвращал список объектов (например, методов, классов, пакетов), которые не соблюдают ограничение. Таким образом, если анализ возвращает пустой список, ограничение проходит. Ограничение считается невыполненным, если анализ возвращает одну или несколько сущностей.

Реализация архитектурных ограничений

Давайте посмотрим на определение нескольких конкретных ограничений.

Ограничения в Glamorous Toolkit являются подклассами GtLeafConstraint; такого рода ограничения охватывают один-единственный аспект нашей системы. Чтобы лучше структурировать все ограничения, относящиеся к ArgoUML, мы создаем подкласс с именем ArgoUMLConstraint. В этом классе также хранится модель, к которой мы применяем ограничение.

GtLeafConstraint subclass: #ArgoUMLConstraint
 instanceVariableNames: 'model'
 classVariableNames: ''
 package: 'Argouml-ArchitectureRules'
ArgoUMLConstraint>>#model
 ^ model
ArgoUMLConstraint>>#model: aModel
 model := aModel

Чтобы создать фактическое ограничение, нам нужно переопределить следующие методы:

  • name: указывает имя ограничения;
  • description: Предоставляет описание ограничения;
  • stakeholder: указывает заинтересованную сторону, связанную с ограничением;
  • issues: вычисляет список объектов, которые не удовлетворяют ограничению;
  • status: Необязательный метод, указывающий, как обрабатывать случай отказа ограничения при выполнении ограничений как тестов. Например, мы могли бы не захотеть отмечать сборку как сломанную из-за сбоя ограничения, но все же может быть полезно знать список сущностей, которые не соблюдают это ограничение.

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

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

  1. Опишите ограничение. Чтобы упростить удаление устаревших классов, их не следует использовать из классов, которые сами по себе не являются устаревшими.
  2. Убедитесь, что он доступен для тестирования. Чтобы проверить это, нам нужно получить список классов из нашей системы и иметь возможность определять, помечен ли класс с помощью @Deprecated.. Для каждого устаревшего класса нам дополнительно необходимо получить доступ к списку классы, использующие его. Эти данные легко доступны в модели.
  3. Привяжите заинтересованную сторону. Мы делаем команду разработчиков заинтересованной стороной для решения этого ограничения.
  4. Сформулируйте анализ. Сначала мы формулируем анализ в виде предложения, а затем прототипируем реализацию на игровой площадке.

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

allDeprecatedClasses := model allModelClasses select: [ :each |
  each isAnnotatedWith: 'Deprecated' ].
allDeprecatedClasses select: [ :each |
  each clientTypes anySatisfy: [ :aClient |
    (aClient isAnnotatedWith: 'Deprecated') not ] ]

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

Чтобы реализовать ограничение, мы сначала создаем подкласс ArgoUMLConstraint named ArgoUMLDeprecatedClassesWithNoDeprecatedClients. Методы name и description реализовать несложно:

ArgoUMLConstraint 
 subclass: #ArgoUMLDeprecatedClassesWithNoDeprecatedClients
 instanceVariableNames: ''
 classVariableNames: ''
 package: 'Argouml-ArchitectureRules'
ArgoUMLDeprecatedClassesWithNoDeprecatedClients>>#name
 ^ 'Deprecated classes with no deprecated clients'
ArgoUMLDeprecatedClassesWithNoDeprecatedClients>>#description
 ^ 'To make deprecated classes easy to remove, they should not be used from classes that are not themselves deprecated.'

Чтобы присоединить заинтересованное лицо, мы создаем экземпляр GtConstraintStakeholder и возвращаем его из метода stakeholder.

ArgoUMLDeprecatedClassesWithNoDeprecatedClients>>#stakeholder
 ^ GtConstraintStakeholder new 
     name: 'Development Team'

Теперь самый сложный метод issues, который реализует анализ. В этом случае мы можем просто повторно использовать код из Playground, где мы прототипировали ограничение.

ArgoUMLDeprecatedClassesWithNoDeprecatedClients>>#issues
  | allDeprecatedClasses |
  allDeprecatedClasses := self model allModelClasses select: [:each|
   each isAnnotatedWith: 'Deprecated' ].
  ^ allDeprecatedClasses select: [ :each |
      each clientTypes anySatisfy: [ :aClient |
        (aClient isAnnotatedWith: 'Deprecated') not ]

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

ArgoUMLDeprecatedClassesWithNoDeprecatedClients new
  model: model

Обеспечение правильного размещения занятий

Для второго ограничения давайте реализуем менее общее, более специфичное для нашей системы.

ArgoUML - это приложение с графическим пользовательским интерфейсом. Если мы проверим его список пакетов, мы увидим, что классы, связанные с пользовательским интерфейсом, помещаются в пакеты пользовательского интерфейса, которые содержат компонент ui в своем имени, например org.argouml.ui, org.argouml.notation.ui или org.argouml.ui.explorer.

Чтобы упростить идентификацию классов, связанных с пользовательским интерфейсом, и лучше структурировать код, мы решили, что все классы, представляющие графические виджеты, должны быть помещены в пакеты, содержащие компонент ui в своем имени. Это правило также позволяет этим пакетам содержать подпакеты. В зависимости от нашего контекста у нас также может быть более строгое правило. А пока мы выберем менее строгую версию. Мы рассматриваем как графические виджеты те классы, которые наследуются от java.awt.Component (здесь мы позже можем решить расширить это определение).

Начнем с создания класса для ограничения:

ArgoUMLConstraint subclass: #ArgoUMLWidgetsInTheWrongPackages
 instanceVariableNames: ''
 classVariableNames: ''
 package: 'Argouml-ArchitectureRules'

Далее мы проходим четыре шага для создания ограничения.

1. Опишите ограничение. Классы, представляющие виджеты (унаследованные от java.awt.Component), должны быть помещены в пакеты, которые содержат компонент ui в своем имени, чтобы улучшить модульность кода.

ArgoUMLWidgetsInTheWrongPackages>>#name
  ^ 'Widgets placed in the wrong package'
ArgoUMLWidgetsInTheWrongPackages>>#description
 ^ 'Classes representing widgets (inherit from ''java.awt.Component'') should be placed in packages that contain the ''ui'' component in their name to improve code modularity.'

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

3. Привяжите заинтересованное лицо. Как и в случае с предыдущим ограничением, команда разработчиков является заинтересованной стороной для этого ограничения. На этот случай давайте создадим более специализированный способ указания заинтересованной стороны. В предыдущем ограничении мы использовали непосредственно экземпляр типа GtConstraintStakeholder. Мы можем создать подкласс с именем ArgoUMLConstraintStakeholder и добавить специальный API для создания заинтересованных сторон.

GtConstraintStakeholder subclass: #ArgoUMLConstraintStakeholder
 instanceVariableNames: ''
 classVariableNames: ''
 package: 'Argouml-ArchitectureRules'
ArgoUMLConstraintStakeholder class
 instanceVariableNames: 'developmentTeam'
ArgoUMLConstraintStakeholder class>>#developmentTeam
  ^ developmentTeam ifNil: [
      developmentTeam := self new name: 'Development Team' ]

Теперь мы можем указать заинтересованное лицо, используя метод developmentTeam.

ArgoUMLWidgetsInTheWrongPackages>>#stakeholder
  ^ ArgoUMLConstraintStakeholder developmentTeam

Мы также можем провести рефакторинг метода заинтересованных сторон в предыдущем ограничении.

ArgoUMLDeprecatedClassesWithNoDeprecatedClients>>#stakeholder
  ^ ArgoUMLConstraintStakeholder developmentTeam

4. Сформулируйте анализ. И последнее, но не менее важное: мы должны провести анализ, проверяющий ограничение. Начнем с формулировки его в виде простого текста:

Из модели получите список всех классов, представляющих виджеты. Из этого списка выберите все классы, которые не помещены в пакет, содержащий компонент «ui».

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

allWidgetClasses := model allModelClasses select: [ :aClass |
  aClass superclassHierarchyGroup anySatisfy: [ :aSuperclass |
    aSuperclass mooseName = 'java::awt::Component'] ].

На втором этапе мы отклоняем все классы виджетов, которые содержат компонент ui в имени своего пакета. Есть несколько способов реализовать это, но пока давайте просто проверим, включает ли полное имя компонент, имя которого начинается с ui.

allWidgetClasses reject: [ :aClass |
  aClass namespaceScope mooseName includesSubstring: '::ui' ].

Теперь, когда у нас есть весь анализ, мы можем поместить его в метод issues.

ArgoUMLWidgetsInTheWrongPackages>>#issues
 | allWidgetClasses |
 allWidgetClasses := self model allModelClasses select: [ :aClass |
   aClass superclassHierarchyGroup anySatisfy: [ :aSuperclass |
     aSuperclass mooseName = 'java::awt::Component' ] ].
 ^ allWidgetClasses reject: [ :aClass |
     aClass namespaceScope mooseName includesSubstring: '::ui' ].

Затем мы также можем проверить это ограничение на игровой площадке, чтобы убедиться, что оно работает.

ArgoUMLWidgetsInTheWrongPackages new
 model: model

Проверка зависимостей между модулями

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

ArgoUML может генерировать диаграммы UML для систем, написанных на различных языках программирования. У каждого языка могут быть свои особенности. Для поддержки этого в ArgoUML есть базовый модуль и модули UML для разных языков программирования. Мы можем решить, что в нашем дизайне эти модули не должны иметь никаких зависимостей между собой; они должны просто зависеть от основного модуля. Мы хотели бы создать ограничение для обеспечения этого.

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

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

ArgoUMLConstraint subclass: #ArgoUMLInvalidUMLModulesDependencies
 instanceVariableNames: ''
 classVariableNames: ''
 package: 'Argouml-ArchitectureRules'

Давайте рассмотрим четыре шага по созданию ограничения.

1. Опишите ограничение. Модули UML для разных языков программирования не должны зависеть друг от друга.

ArgoUMLInvalidUMLModulesDependencies>>#name
  ^ 'Invalid dependencies between UML modules'
ArgoUMLInvalidUMLModulesDependencies>>#description
  ^ 'UML modules for different programming languages should not depend on one another.'

2. Убедитесь, что он доступен для тестирования. Чтобы проверить это, нам нужно получить список модулей UML и классы в каждом модуле. В этом случае мы рассматриваем модулем все классы из определенных пакетов. Для каждого класса нам необходимо получить доступ ко всем его зависимостям. Все эти данные мы можем получить из модели.

3. Привяжите заинтересованное лицо. Мы делаем архитектурную команду заинтересованным лицом для этого ограничения.

ArgoUMLConstraintStakeholder class>>#architectureTeam
  ^ architectureTeam ifNil: [
      architectureTeam := self new name: 'Architecture Team' ]
ArgoUMLInvalidUMLModulesDependencies>>#stakeholder
  ^ ArgoUMLConstraintStakeholder architectureTeam

4. Сформулируйте анализ. Есть другие способы, которыми мы могли бы провести этот анализ. Один из возможных способов:

Из модели получите список всех модулей UML. Для каждого модуля соберите список исходящих зависимостей. Выберите из каждого списка зависимости, где целью зависимости является другой модуль UML.

Давайте создадим прототип анализа на Playground. Мы не будем вдаваться в подробности проведения анализа. Мы начинаем с получения для каждого модуля UML списка интересующих нас пакетов. Это пакеты, которые начинаются с org.argouml.language, за которым следует имя языка (есть также другие пакеты, начинающиеся с org.argouml.language, которые не являются модулями UML)

moduleNames := #('cpp' 'csharp' 'java' 'php' 'sql').
modulePackages := model allModelNamespaces select: [ :aNamespace |
  | fullName |
  fullName := aNamespace mooseName.
  moduleNames anySatisfy: [ :moduleName |
    fullName beginsWith: 'org::argouml::language::', moduleName ] ].
modulesByName := modulePackages groupedBy: [ :aNamespace |    
  aNamespace withAllParentScopes reversed fourth mooseName ].

Второй шаг - получить для каждого модуля все его зависимости. Мы можем получить это, используя метод queryAllOutgoing, который возвращает все типы зависимостей, которые сущность имеет в модели. Затем мы выбираем как недопустимые те зависимости, которые относятся к другим модулям UML.

allInvalidDependencies := OrderedCollection new.
modulesByName keysAndValuesDo: [ :moduleName :packages |
 | moduleDependencies invalidDependencies otherModuleNames |
 moduleDependencies := packages flatCollect: #queryAllOutgoing. 
 otherModuleNames := modulesByName keys copyWithout: moduleName.
 
 invalidDependencies := moduleDependencies select: [ :aDependency |
  aDependency to asCollection anySatisfy: [ :aCandidate |
   otherModuleNames anySatisfy: [ :anotherModuleName |
    aCandidate mooseName beginsWith: anotherModuleName ] ] ].
 
 allInvalidDependencies addAll: invalidDependencies ].
allInvalidDependencies

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

Теперь мы можем взять предыдущие фрагменты и поместить их в метод ограничения:

ArgoUMLInvalidUMLModulesDependencies>>#issues
  |moduleNames modulePackages modulesByName allInvalidDependencies|
  moduleNames := #('cpp' 'csharp' 'java' 'php' 'sql').
  modulePackages := model allModelNamespaces select: [:aNamespace |
    | fullName |
    fullName := aNamespace mooseName.
    moduleNames anySatisfy: [ :moduleName |
      fullName beginsWith: 'org::argouml::language::', moduleName]].
  modulesByName := modulePackages groupedBy: [ :aNamespace |    
    aNamespace withAllParentScopes reversed fourth mooseName ].
  allInvalidDependencies := OrderedCollection new.
  modulesByName keysAndValuesDo: [ :moduleName :packages |
   | moduleDependencies invalidDependencies otherModuleNames |
   moduleDependencies := packages flatCollect: #queryAllOutgoing. 
   otherModuleNames := modulesByName keys copyWithout: moduleName.
 
   invalidDependencies := moduleDependencies select: [:aDependency |
     aDependency to asCollection anySatisfy: [ :aCandidate |
       otherModuleNames anySatisfy: [ :anotherModuleName |
         aCandidate mooseName beginsWith: anotherModuleName ] ] ].
 
    allInvalidDependencies addAll: invalidDependencies ].
  ^ allInvalidDependencies

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

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

modulePackages := model allModelNamespaces 
  select: #isArgoUMLUmlModulePackage.
modulesByName := modulePackages groupedBy: #argoUMlUmlModuleName.

Создание архитектурного отчета

До этого момента мы реализовали три архитектурных ограничения. В завершение мы заносим все в архитектурный отчет. Архитектурный отчет - это не что иное, как составное ограничение.

Мы создали отдельные ограничения, создав подкласс GtLeafConstraint. Чтобы создать архитектурный отчет, мы создаем подкласс GtConstrainerReport, который является подклассом GtCompositeConstraint с некоторыми вспомогательными методами. Поскольку нам нужно передать модель отдельным ограничениям, мы сохраняем ее в отчете.

GtConstrainerReport subclass: #ArgoUMLReport
 instanceVariableNames: 'model'
 classVariableNames: ''
 package: 'Argouml-ArchitectureRules'
ArgoUMLReport>>#model
  ^ model
ArgoUMLReport>>#model: aModel
  model := aModel
ArgoUMLReport class>>#onModel: aModel 
  ^ self basicNew  
      model: aModel;  
      initialize

Затем мы переопределяем метод build: и используем addConstraint: для добавления отдельных ограничений в отчет.

build: aComposite
 aComposite name: 'ArgoUML Report'.
 aComposite
  addConstraint:(ArgoUMLDeprecatedClassesWithNoDeprecatedClients new 
   model: self model);
  addConstraint: (ArgoUMLWidgetsInTheWrongPackages new 
   model: self model);
  addConstraint: (ArgoUMLInvalidUMLModulesDependencies new 
   model: self model)

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

report := ArgoUMLReport onModel: model.

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

ArgoUMLInvalidUMLModulesDependencies>>#status
  ^ GtNeutralConstraintStatus new

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

Выполнение архитектурного отчета в рамках непрерывной интеграции

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

$./glamoroustoolkit GlamorousToolkit.image eval "\
| modelFileName model|\
modelFileName := '.models/ArgoUML-0-34/ArgoUML-0-34.mse'\
model := MooseModel new \
importMSEFromFile: modelFileName asFileReference. \
ArgoUMLReport onModel: model.\
GtConstrainerHudsonReporter runOn: (ArgoUMLReport onModel: model)."

Ниже мы видим результаты выполнения нашего архитектурного отчета для ArgoUML на сервере Jenkins с использованием приведенного выше сценария. Только ограничения на размещение виджетов помечены как несоответствующие.

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

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