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

Glamorous Toolkit, формованата среда за разработка, се доставя с обширна поддръжка за софтуерен анализ за различни езици. Тези анализи могат да бъдат комбинирани и интегрирани с останалата среда, за да помогнат на разработчиците да оценят собствената си система.

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

Частта за софтуерно моделиране е базирана на FAMIX, семейство от мета-модели за представяне на различни факти за софтуерни системи. FAMIX е разработен в контекста на платформата за анализ на Moose. Glamorous Toolkit интегрира FAMIX и ядрото на Moose в цялостната среда за разработка.

При проверка на архитектурата

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

Въпреки това, ако нашите предположения за архитектурата на системата не са в синхрон с реалността от кода, можем лесно да вземем грешни решения за това как да развием системата. Това има по-голям шанс да се случи, докато архитектурата на нашите системи остава описана само в уикита, текстови документи или презентации. Можем да намалим този шанс, като вземем нашите архитектурни решения и ги изразим като конкретни анализи, които можем да изпълним автоматично, за да проверим дали нашите предположения все още съответстват на реалността, открита в кода.

Работен пример

Като първоначален казус за изразяване на архитектурни ограничения ние използваме ArgoUML, Java приложения с отворен код за създаване на UML диаграми. Използваме версия 0.34 на това приложение, което има около 2400 класа и ~200 000 реда Java код.

За тази система създаваме архитектурен отчет, съдържащ три ограничения:

  • В системата не се използват отхвърлени класове;
  • UI widgets се поставят в специални пакети за UI;
  • Модулите, прилагащи 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 MB). Можем да проверим папката 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 ] ]

Чрез прототипиране на изпълнението в Playground можем директно да видим списъка с обекти, върнат от ограничението, и да се уверим, че е добре.

За да приложим ограничението, първо създаваме подклас на ArgoUMLConstraint с име 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 ]

Спираме до тук с внедряването и проверяваме ограничението. Можем да създадем и инспектираме ограничението директно в Playground. Обектите с ограничения имат изглед на инспектор, показващ списъка с обекти, които не спазват това ограничение. В този случай виждаме в този изглед 10-те класа, които не са отхвърлени и използват отхвърлени класове.

ArgoUMLDeprecatedClassesWithNoDeprecatedClients new
  model: model

Осигуряване на правилното разположение на класовете

За второто ограничение нека приложим по-малко общо, което е по-специфично за нашата система.

ArgoUML е приложение с графичен потребителски интерфейс. Ако проверим неговия списък с пакети, забелязваме, че свързаните с UI класове са поставени в UI пакети, които съдържат компонента 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“.

Вече можем да направим прототип на анализа в Playground. Като първа стъпка извличаме всички класове джаджи от модела.

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' ].

След това можем също да проверим това ограничение в Playground, за да се уверим, че работи.

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 система. Архитектурният доклад се състои от набор от ограничения, изразени чрез персонализирани анализи. След това можем автоматично да изпълним тези анализи и да ги проверим спрямо кода. Това е първата стъпка, за да се уверим, че нашите предположения относно архитектурата на системата не излизат от синхрон с реалността от кода.