Oracle, Java and OpenJDK are registered trademarks of Oracle America Inc. and/or its affiliates. Linux is a trademark of Linus Torvalds. UNIX is a registered trademark of X/Open Company Limited. Windows, Excel are registered trademarks of Microsoft, Inc. MOF, UML and XMI are either registered trademarks or trademarks of the Object Management Group, Inc. MagicDraw is a trademark or registered trademark of No Magic, Inc. XML is a trademark of the World-Wide Web Consortium. SDMetrics is a registered trademark of Jürgen Wüst. All other product names and company names mentioned herein are the property of their respective owners.
All rights reserved. No part of this manual may be reproduced, in any form or by any means, without permission in writing from the author.
Part I of this manual addresses all users of SDMetrics. Part II addresses power users who want to define metrics or design rules of their own, and/or adapt SDMetrics to a specific XMI® exporter.
Part I - Basic SDMetrics Usage | |
---|---|
Section 2 "Installation" | describes the installation of SDMetrics. |
Section 3 "Getting Started" | is a brief guided tour of SDMetrics, taking you through the steps to calculate a set of metrics for your UML designs. |
Section 4 "The SDMetrics User Interface" | describes all features of the SDMetrics user interface in detail. |
Section 5 "Running SDMetrics from the Command Line" | shows how SDMetrics can be run from a command line. |
Section 6 "Design Measurement" | discusses general design measurement principles and provides guidelines how to interpret measurement data. |
Part II - Advanced SDMetrics Features | |
Section 7 "SDMetrics Metamodel and XMI Transformation Files" | explains how to define the UML metamodels used by SDMetrics, and how SDMetrics extracts UML design information from XMI files. |
Section 8 "Defining Custom Design Metrics and Rules" | shows how metrics and design rules are defined in SDMetrics, and how you can define your own UML design metrics and rules. |
Section 9 "Extending the Metrics and Rule Engine" | describes how to extend the calculation capabilities of the metrics engine itself. |
Part III - Appendices | |
Appendix A: "Metamodels" | shows SDMetrics' metamodels for UML1.x and UML2.x. |
Appendix B: "List of Design Metrics" | lists the design metrics that ship with SDMetrics. |
Appendix C: "List of Design Rules" | lists the design rules that ship with SDMetrics. |
Appendix D: "List of Matrices" | lists the relation matrices that ship with SDMetrics. |
Appendix E: "Project File Format Definitions" | is a reference to the metamodel, metric definition, and XMI transformation file formats. |
If you do not already have a JRE installed on your machine, obtain a JRE of your choice for Java 8 or higher (for example, the AdoptOpenJDK with Hotspot JVM from https://adoptopenjdk.net/) and follow the installation instructions provided with your JRE distribution.
To install SDMetrics, simply unpack the compressed archive (zip file) you received or downloaded to a folder of your choice. Among others, you should find these artefacts in the folder:
File/folder name | Purpose |
---|---|
SDMetrics.jar | The SDMetrics program files. |
sdmetrics.bat | Script to start SDMetrics on Windows platforms. |
sdmetrics.sh | Script to start SDMetrics on Unix/Linux platforms. |
Readme.html | Latest program version information. |
License.txt | The SDMetrics end user license agreement. |
SDMetricsUserManual.html | Opens the SDMetrics User Manual in your web browser. |
manual/ | Contains the SDMetrics User Manual files. |
bin/ | Any extensions your create for SDMetrics go in here. |
Make sure you have read and agreed with the license agreement before using SDMetrics.
If you choose to use the old and new version of SDMetrics in parallel, put the new version in a new installation directory of its own.
In either case, your application preferences will automatically be taken over from the old version, with two exceptions:
On a command line prompt, start SDMetrics with the command:
java -jar "/path/to/SDMetrics.jar"The name of the Java class to launch SDMetrics is com.sdmetrics.SDMetrics, so the following command will also start SDMetrics:
java -classpath "/path/to/SDMetrics.jar" com.sdmetrics.SDMetrics
Figure 1: SDMetrics main window
Calculating a set of metrics takes four steps:
Click the button
or select "Project -> Edit Project Settings" from the menu bar.
Figure 2: Project File Settings Dialog
You specify the XMI file to read in the "XMI Source File" box at the top of the project settings dialog. Click the topmost "Browse..." button and use the file chooser dialog to select your XMI file.
At this point, you don't need to worry about the remaining options in the Project File Settings dialog; leave everything at "Automatic" and "use default", and click the "OK" button to confirm your selection.
Step 2: Calculate the metrics
After you specified your project files, select
"Project->Calculate Metrics" from the menu bar, or click the
button on
the tool bar. SDMetrics will read your XMI design file and calculate
the metrics. This is a fully automated process that usually takes a few seconds.
You can monitor its progress on the status bar.
Figure 3: Table view of calculated metrics
Step 3: Explore the metric data
To explore the metric data, SDMetrics provides several views of the data, for instance:
Step 4: Export the metrics data for further processing
You can export the metric data tables for further processing with your
favorite spreadsheet software or statistical software packages.
Go back to the "Metric Data Tables" view and click the button on the upper right corner of the view. This opens the data export dialog
window.
Figure 4: Data Export Dialog
Here you can specify whether to write the data tables to one or several output files, and select the file format for the output files (tab-separated text, HTML, etc).
This completes the brief guided tour of some of SDMetrics' most important features. The following section describes these and all other features of SDMetrics in detail.
Figure 5: SDMetrics Project Files
Figure 6: Project File Settings Dialog
On the file settings dialog, you specify the project files to use. From top to bottom:
XMI Source File
To specify the XMI source file,
Name | Description |
---|---|
Default UML 1.x and XMI 1.0 | XMI 1.0 import and metrics for UML 1.x models |
Default UML 1.x and XMI 1.1-1.3 | XMI 1.1/1.2/1.3 import and metrics for UML 1.x models |
Default UML 2.x and XMI 2.x | XMI 2.x import and metrics for UML 2.x models |
When you select the radio button "Automatic", SDMetrics will automatically determine the most suitable project file set based on the XMI version and exporter of the XMI source file at hand. When you select the radio button "Custom", SDMetrics will always use the project file set selected from the dropdown list. This feature is useful when you have created your own project file sets (see Section 4.16.1 "Project File Sets"), or if SDMetrics has problems determining the XMI version of your XMI source file, or if SDMetrics cannot match the XMI version to one of the available project file sets.
Most of the time, you will want to use a project file set "as is". You can, however, replace any or all files of the project file set with modified or custom project files of your own, e.g., to calculate a different set of design metrics.
Override XMI Transformations File
If you do not wish to use
the XMI transformation file of the applicable project file set, you
can specify an alternative XMI transformation file to use here.
Select the "from file" radio
button, and specify your transformation file in the text field next to
it (using the browse button, the list of previously used XMI transformations,
or drag and drop the file into the dialog).
See Section 7.2 "XMI Transformation Files" for more information on how to create your own XMI
transformation files.
Override Metamodel Definition File
To use a modified or custom metamodel of your own, select the "from file"
radio button, and specify your metamodel definition file in the text
field next to it (using any of the means described above).
See Section 7.1 "SDMetrics Metamodel" for
more information on how to create your own metamodel definitions.
Metrics Definition File
To use a metrics definition file of your own, select the "from
file" radio button, and specify your metrics definition file in the
text field next to it (using any of the means described above).
See Section 8 "Defining Custom Design Metrics and Rules" for more information on how to create your own
metrics definition files.
The fully qualified name of a model element is the name of element, prepended by the fully qualified name of element's owner and a period. Note that SDMetrics element filters use the period as namespace separator, not the double colon (::) of the UML.
The # character serves as wildcard that matches any name. For instance, the filter #.org.xml matches the package org.xml and all elements contained within, regardless of the design model name.
Figure 7: Filter Settings Dialog
The right hand side of the window shows the current list of filters. To add a new filter to the list, enter the filter string in the text field on the left side and click the "Add" button (or press the enter or return key). Click the "Replace" button to replace the currently selected filter on the list with the contents of the text field. The "Delete" button removes the currently selected filter on the list.
The meaning of the radio buttons and checkboxes is as follows:
To save the current project settings, select "Project -> Save Project Settings" from the main menu. This opens a standard file chooser dialog where you can specify the configuration file to write the settings to.
If an error occurs during data processing (e.g., a file was not
found, metric calculation failed due to a semantic error in a
custom metric definition file), the calculation will
abort and you are prompted with a description and location of the
error. After you fixed the error (e.g., specified the correct project
file, corrected the metric definition file), press the
button again
to restart the calculation.
Upon successful completion, you can explore the metric data in the main window. SDMetrics provides several views:
View name | Description |
---|---|
Metric Data Tables | Presents metric data as a set of tables |
Histograms | Displays histograms for the metrics |
Kiviat Diagrams | Displays Kiviat diagrams for the model elements |
Rule Checker | Shows design rule violations for the UML model |
Design Comparison | Compares metric values to those of a second UML model |
Relation Matrices | Shows relations such as "class uses class", "actor associated with use case" |
Descriptive Statistics | Concise summary of the descriptive statistics for the metric data |
Graph Structures | Shows circular dependencies and connected components in model element relation graphs |
Model | Displays the UML model in a tabular format |
Catalog | Shows the definitions of all metrics, design rules, and relation matrices |
Log | Keeps a log of previous calculation runs |
To open a view, use the "Views" menu from the menu bar. The following views are also accessible via toolbar
buttons: Metric Data Tables (),
Histograms (
),
Kiviat Diagrams (
),
and the Rule Checker view (
).
Figure 8: Common controls used in most views
Figure 9: Table view of metrics
For large tables, use the vertical and horizontal scroll bars of the metrics table to see more columns and rows. The left hand column showing the element names always remains visible in the table view. To adjust its width, drag the separator bar between the two table sides to a suitable position.
The context menu of the table cells of the right hand side table provides links to
Percentiles
This option highlights values above or below a given percentile. For instance, you may choose to highlight, in each column, the metric values equal or above the 95th percentile for the respective metric. This highlights the top 5% metric values in each column.
You pick the percentile you wish to highlight from the top of the highlight dropdown list in the control panel. If you choose a percentile above the median (50th percentile), all values above that percentile are highlighted. If you choose a percentile below the median, the values below that percentile are highlighted.
SDMetrics calculates percentiles using the empirical distribution function with averages. The list of percentiles is configurable, see Section 4.16.2 "Percentiles".
Distance from the mean
This option highlights metric values with a certain distance from the mean value of the metric. The distance is expressed in multiples of the metric's standard deviation. For example, you may choose to highlight metric values larger than the mean plus four times the standard deviation of the metric. Simply select the appropriate multiple from the highlight dropdown list.
"Distance from the mean" is meaningful for metrics defined on an interval or ratio scale. "Percentiles" are meaningful for all ordinal scale metrics.
To remove any highlighting from the table, choose the first entry "nothing" from the highlight dropdown list.
Figure 10: Histogram view
Select the metric to display from the dropdown list on the left control
panel, or use the buttons
to select the first/previous/next/last metric
on the list, respectively. Press the "Show full definition" button to
view the detailed definition of the metric in the measurement catalog
(see Section 4.13 "The View 'Catalog'").
The table below the metric definition shows some descriptive statistics for the metric:
Figure 11: Kiviat Diagram View
Select the element to display from the tree at the top of the control
panel. You can adjust the size of the tree with the separator bars
below and to the right. Use the buttons or the cursor keys
to navigate through the tree.
The graph on the right hand side is a so-called Kiviat diagram, showing the measurement values of all metrics for the selected element. Each axis (or ray) of the graph represents one metric, as labeled in the graph. The measurement scale of each axis is the range of the metric: the minimum value is located in the center, the maximum value at the outer end. The axes are linearly scaled.
The thick line connects the measurement values of the selected element for each metric on the axes. If the element has many relatively large values, the area enclosed by the thick line will be large. So the size of the enclosed area serves as an indicator of the criticality of the element.
Note that metrics with non-numerical values, or metrics that do not vary at all, are not suitable for the graph and therefore omitted. Also, the Kiviat graph can only be shown for elements with at least three suitable metrics. If you see a "Diagram not available" message instead of a graph, there are less than three suitable metrics for the selected element type.
Below the element tree you can choose to display information about metric percentiles of the selected model element, or to display the descriptive statistics for a metric shown in the graph.
Percentiles
The percentiles table shows, for various percentiles (column "Prctl."), the number of metrics for which the measurement values exceed the percentile for the selected element (column "Count"). Assuming we mostly deal with metrics where higher values indicate lower quality, a design element should be considered critical if a larger number of metric values for the element are in the upper percentiles (e.g., 90th, 95th).
The percentiles table also controls which percentiles of the metrics are displayed in the graph. Check the boxes in the column "Draw" for the percentiles to display. This gives an indication how the measurement values of the selected element compare to all other elements. Column "Color" indicates the color for each percentile on the graph.
Descriptive Statistics
If you click near one of the axis in the Kiviat diagram, a short definition of the metric and its descriptive statistics with minimum and maximum values will be displayed on this tab.
The context menu of the graph provides links the measurement catalog (see Section 4.13 "The View 'Catalog'") and histogram (see Section 4.5 "The View 'Histograms'") for the metric of the nearest axis.
The 'Rule Checker' view displays design rule violations in a table. Each row of the table represents a violation of a design rule by a design element.
Figure 12: Design Rule Checker
The meaning of the columns is as follows:
Sort the table of design rule violations by columns, e.g., to quickly find all rule violations for a particular design element, all violations of a particular rule, or to sort the rule violations by their severity or category. Section 4.3.1 "Common controls in views" describes how to sort tables.
The measurement catalog indicates the applicable types of models for each rule. With the design rule filter, you can instruct SDMetrics to only check and report design rules that apply to your model at hand.
Writing Rule Filters
Table 3 shows some example filters and explains their function. For the example, we assume we have four application areas defined:
analysis
(rules for analysis models)
design
(rules for design models)
realtime
(rules for models of real-time systems)
pedantic
(rules about obsessive details)
Example Filter | Explanation |
---|---|
design |
To only check the rules of one application area, simply specify the
name of that application area. The example filter only checks rules that are applicable at the design phase. |
design&realtime |
To check rules applicable to all of several areas, combine the areas
with the & operator. The example filter only checks rules that are applicable to real-time systems at the design phase. |
analysis|design |
To check rules applicable to at least one of several areas, combine
the areas with the | operator. The example filter only checks rules that are applicable at the analysis phase or at the design phase. |
!analysis |
To check rules that do not apply to a particular area, precede the
name of the area with the ! operator. The example filter only checks rules that do not apply to the analysis phase. |
'design' |
Some rules do not explicitly define an application area. Such rules
are implied to apply to all areas. Therefore, the
filter design will also check rules that do not specify
any application area at all.To instruct SDMetrics to only check rules that explicitly are defined for an application area, put the application area in single quotes. The example filter only checks design rules which explicitly list "design" among their application areas. |
design& | Using parentheses, you can define arbitrarily complex rule filters. The example filter checks all rules applicable to the design phase that are also not explicitly defined for real-time systems or are pedantic. |
Applying Rule Filters
To apply a filter, specify it in the "filter" text field at the top of the rule checker view, and press return or click the "Apply" button. To clear the filter, and show all design rule violations regardless of their application area, click the "Clear" button.
If your filter contains a syntax error (illegal operation, unmatched parentheses etc.) you will get an error message and the rules will not be checked. If your filter specifies an application area that is not defined explicitly by at least one rule, SDMetrics will warn you about this, but will check the rules anyway.
UML 1.x
To allow a model element e to violate a rule named rulename, define a tagged value for e, where the tag name is violates_rulename (please refer to the manual of your UML modeling tool for details on how to add tagged values). The value of the tagged value pair is ignored by SDMetrics. You can use the value for instance to document the reason why the model element is allowed to violate the rule.
For example, if after a design review a particular class is allowed to violate rules named "GodClass" and "MultipleInheritance", you add two tagged values to the class, one with tag violates_GodClass and the other with the tag violates_MultipleInheritance. From then on, violations of these two rules will no longer be reported for that class (and only these two rules, and only for that class).
UML 2.x
UML 2 no longer has tagged values as they are known in UML 1.x, but
every model element can own comments. To exempt a model element from
one or more rules, add one or more comments where the body text
contains the string violates_rulename for each rule the model
element is allowed to violate. Please refer to the manual of your UML
modeling tool for details on how to add comments. An example comment
body text could be:
"violates_GodClass, violates_MultipleInheritance: confirmed in
review"
The comment's body can contain additional text before, after, or between the violates_rulename tags. You can use this to document the reason why the model element is allowed to violate the rule.
Customizing rule exemption annotations
If the above methods to mark elements as exempt from certain rules do not suit you, the method is customizable to some degree, see Section 8.3.6 "Exempting Approved Rule Violations".
Figure 13: Descriptive statistics view
Each row of the table represents one design metric, the columns provide the descriptive statistics (minimum, maximum, mean, standard deviation, and several percentiles). The context menu of the table links to the histogram view for the selected metric (Section 4.5 "The View 'Histograms'"), and its full definition in the measurement catalog view (Section 4.13 "The View 'Catalog'").
Comparing subsequent versions of a design. Object-oriented systems are usually developed in an incremental, iterative fashion. After a design phase at the beginning of an iteration, you can compare the current design to the previous iteration:
The design comparison view provides two sets of tables: metric deltas and descriptive statistics. By default, the tables show the difference of measurement values between the first and second design (value in first design minus value in second design). To show the relative difference of the value in the second design as percentage of the value in the first design, select the "Show relative deltas" radio button next to the element type dropdown list.
The context menu of the right hand side table links to the detailed definition of the selected metric in the measurement catalog (see Section 4.13 "The View 'Catalog'").
For non-numerical metrics, the metric columns show the metric values for the second design. The value is preceded by a "=" if the metric value is unchanged from the first design, or a "#" if the metric value has changed. Changed values are also indicated by the color red.
When comparing designs, we will usually have elements in the first design that have been deleted in the second design, and elements added to second design that were not present in the first design. In the "Name" column, added and deleted elements are indicated by their color (green for added elements, red for deleted elements). The last column "AD_Status" also shows the status of each element: A for added, D for deleted, and empty for elements present in both designs. Use this column to sort elements by their status, and to identify the element status when exporting the table data to a file.
Figure 15: Comparing descriptive statistics
The meaning of the first four columns is as follows:
To improve the element matching for renamed elements, you can define
explicit element mappings. Click the button to open the element mappings dialog.
Figure 16: Element Mappings
Enter the old name of the renamed element in the first design in the upper left text field. Enter the new name of the element in the second design in the upper right text field. Press "Add" to add the mapping to the list of mappings below.
To remove a mapping from the list, select the mapping on the list and
then press the "Delete" button. To remove all mappings, press the
"Clear all" button. Click the button to save the current list of mappings to a file, and
the
button to load a
previously saved list. Or drag and drop the mapping file anywhere into
the mappings dialog or the design comparison view.
Mapping Examples
Figure 17: Relation matrix
The rows of a relationship matrix show the source design elements from which the relationship originates, the columns contain the target design elements of the relation. The table cells indicate the presence or number of relationships between the respective source and target design element.
The dropdown list above the table shows the available relation matrices. Select the relation matrix for display from this list.
Relation matrices can become quite large. Use the vertical and
horizontal scroll bars of the table to see more columns and rows, as
needed. The left hand column showing the source element names always
remains visible. To adjust its width, drag the separator bar between
the two table sides to a suitable position. The column header shows
the names of the target elements. To increase or decrease the height
of the column header, use the and
buttons
above the table. You can adjust the width of
the table columns with the
and
buttons.
The "Show full definition" button above the matrix opens the measurement catalog with a detailed definition of the currently selected matrix (see Section 4.13 "The View 'Catalog'").
Appendix D: "List of Matrices" describes the relation matrices that SDMetrics calculates. Note that the relation matrices view will only show relation matrices that are not empty. A matrix is empty if
You can define and display additional relation matrices of your own, the procedure is described in Section 8.4 "Definition of Relation Matrices".
The view contains two register tabs, one for viewing cycles, the other for viewing connected components.
Figure 18: Graph Structures View - Cycles
There is one graph for each design rule that checks for cycles. From the dropdown list at the top, you can select the design rule for which you want to show the cycles. Click the "Show full definition" button to open the definition of the rule in the measurement catalog (Section 4.13 "The View 'Catalog'").
The tree below initially shows all model elements that depend on at least one other model element. The type of model elements and nature of the dependencies are defined by the selected design rule. Expand the model elements to the see on which other elements they depend. The icon next to each model element indicates its cycle status:
Use the "Reverse direction" button in the upper right corner to switch to a new tree that reverses the direction of the dependencies. Expanding a model element e shows all model elements that depend on e.
Figure 19: Graph Structures View - Connected Components
From the dropdown list at the top, you can select the metric for which you want to show the connected components. Click the "Show full definition" button to view the definition of the metric in the measurement catalog (Section 4.13 "The View 'Catalog'").
The tree on the left hand side shows the model elements for which the connected components are calculated. Column "#CC" indicates the number of connected components for each model element. If you select a model element in the tree, the connected components will be shown in a tree structure on the right hand side.
The top level nodes of the right hand side tree represent the connected components. The child nodes represent the model elements of each connected component.
Figure 20: The View 'Model'
Figure 20 shows, for example, the table for metamodel element "package" with three of its attributes "id", "name", and "context" (see Section 7.1 "SDMetrics Metamodel" and Appendix A: "Metamodels"). The table provides the XMI id, name, and a reference to its owner (XMI id of the owner) for all packages of the UML model.
Likewise, you obtain tables for all other model element types with their respective attributes. As a result, you have a complete representation of your UML design in a table format (complete as far as SDMetrics' design measurement and rule checking requirements are concerned).
The context menu of the table provides quick access to the Kiviat diagram of the selected element,
as well as simple navigation feature for cross-reference attributes. Via the context menu for cross-reference
attributes you can find and show each model element referenced by the cross-reference attribute.
With the and
arrows in the upper right corner
of the model view you can move back and forth within the previously selected model elements.
The most important feature of this view is the save button
to export the tables to files (see Section 4.15.1 "Exporting Data Tables").
These files are easy to parse. This feature is useful if you want
to build a custom application that performs operations on your UML
designs, using the flexible XMI import capabilities of SDMetrics.
The model view also comes in handy when you create your own custom metamodels and XMI transformations (see Section 7 "SDMetrics Metamodel and XMI Transformation Files"). You can use this view to quickly verify if your XMI transformations work as intended.
Figure 21: The View 'Catalog'
Selecting tables
From the dropdown list at the top of the catalog, you can select the following items to explore:
Click any row in the table to see a detailed description of the selected item in the lower part of the window.
The detailed descriptions often contain cross-references to other
metrics, rules, or matrices, include literature references, or
reference terms from the glossary. These references are hyperlinks that
take you to the full definition of the referenced item.
Use the and
arrows in the upper right corner of the catalog view to
move back and forth within the previously visited definitions.
Sorting tables
You can sort each table by its columns, e.g., to quickly find all metrics for a particular domain, all rules of certain severity level, and so on. Section 4.3.1 "Common controls in views" describes how to sort tables.
Figure 22: The 'Catalog' View
The log shows which project files have been selected by SDMetrics for processing and why, as well as the calculations that have been performed. You can use the output of the log to document calculation runs performed via the GUI.
The copy button copies the current contents of the log view to the clipboard. The clear buttons deletes all log entries from the view.
When the "Append" checkbox is selected, the log entries of subsequent calculation runs will be appended to the log. When the "Append" radio button is deselected, the log will be cleared automatically before each new calculation run.
Via the save button you can save the current
log entries to a ".txt" or ".log" file.
Figure 23: Data Export Dialog
To specify the export, you have a number of options:
Select export to one file or separate files. Usually, you will have several data tables, one for each element type. Here you specify if you want to store all tables in one file, or have each table written to a separate file.
Sanitize data. This option is only applicable to the data tables of the metric data tables view. The first table column of those tables shows the names of the design elements that were analyzed. Check this option to suppress the output of the element names. This can be useful if, for confidentiality reasons, you want to pass on or publish the metric data without disclosing the element names.
Select the file name. Enter the name of the file to write the
data to. If you chose to export the data to separate files, the name
you enter here will serve as a base name for the files created.
Example: You have tables for classes, packages, and interfaces, and
enter "data" as file base name. This will create three
files:
Select the file format (Files of type). The following formats are available from the "Files of type" dropdown list:
Figure 24: Export Graphs Dialog
The options for the graph export are:
Graph type to export You can export one of the three available graph types at a time:
Save to file: Specify a file base name for the graph and HTML files to be written. SDMetrics will extend the base name with element type names, metric names, and the proper file extension. For example, if you specify the base name "C:\graphs\model", exporting all graphs will generate files named "C:\graphs\model_Class_NumOps.svg", "C:\graphs\model_Package.html", and so on.
Finally, to export the graph(s) as specified above, press the "Save graphs" button. The "Close" button closes the dialog without exporting the graphs.
Note that exporting a large number of graphs in PNG or JPG format can take some time, you can monitor the progress on the status bar of the main window.
When SDMetrics analyzes an XMI file, it retrieves the XMI version from the file, as well as the name and version of the XMI exporter that created the file. SDMetrics then selects from the list of available project file sets the one that best matches the specification of the XMI file at hand.
If you downloaded updated project files from the SDMetrics web site www.sdmetrics.com, or wrote your own project files, and want to use these files by default, you can define new project file sets or change the existing ones. That way, you do not have to specify your custom project files over and over again in the project settings dialog.
Open the preferences dialog and select the tab sheet "Project File Sets":
Figure 25: Project File Set Preferences
The tab sheet shows a list of all available project file sets. You can add, modify, copy, import, and delete entries from the list.
Figure 26: Project File Set Editor
The parameters of a project file set are as follows:
If the Zip archive contains project file sets you have already installed, you will be asked what to do with the duplicates. You can either
To copy the settings of an existing project file set, select its entry on the list and then click the "Copy" button. This opens the editor window where you can modify the details. When you close the editor window with the "OK" button, a new entry with the modified settings will be added to the list; the originally selected entry remains unchanged.
To delete an entry from the list, select the entry and click the "Delete" button.
The "Restore..." button resets the default project file sets to the "factory settings", that is, the default XMI transformations and associated metamodel and metric definition files that originally ship with SDMetrics.
Note that your custom project file sets will remain unaffected by this operation.
Figure 27: Percentiles Tab Sheet
The tab sheet shows the current list of percentiles. To add a percentile, type the percentile in the text field labeled "Add new value", and press the enter (or return) key, or click the "Add" button. The percentiles you enter must be valid floating-point numbers between 0 and 100. Always use a dot as the decimal point.
To delete percentiles, select the percentiles to delete on the list. You can select multiple percentiles by holding down the shift or control key on the keyboard while selecting list elements. Click the delete button to remove the selected percentiles from the list.
Note: the new percentiles settings become effective when the next set of metrics is calculated.
Figure 28: Output Settings
File encoding
Specifies the character encoding of all non-binary output file formats (TXT, CSV, HTML, ODS, XML, SXC and SVG). The dropdown list shows all available character encodings for your platform. Default is ISO-8859-1 (also known as Latin-1). Choose an alternative encoding if your UML model element names contain characters not available in ISO-8859-1.
CSV column separator
By default, the CSV (comma separated vector) format uses the comma to delimit the values in a row of data. If you require a different delimiter, you can enter it here.
Quotes in CSV/text
The TXT and CSV output formats use certain characters as column and line delimiter. This leads to a conflict if a value to be written itself contains one of these delimiters: writing the value "as is" destroys the overall table structure of the output file.
A standard solution to resolve this conflict is to put quotes (") around the values - characters between quotes will not be interpreted as column or line delimiters. If the value to be put in quotes itself contains a quote character, that quote character is doubled in the output file (the value 5"10 thus becomes "5""10").
SDMetrics offers three strategies to deal with conflicting values in CSV and TXT output formats:
With this option enabled, SDMetrics will ask for your permission before overwriting existing files when you export data tables or graphs. This setting only affects the GUI, not the command line operation (which will always overwrite without asking).
Figure 29: Appearance Dialog
Sizes
Click the up and down arrows to increase or decrease the size of the font and the icons in SDMetrics' main window, menus, and dialogs. You can judge the resulting size by the preview fields next to the size adjustment controls.
Make sure to change the sizes in small steps and "within reason". Too large or too small settings may render the application difficult or impossible to use.
Note: you need to restart SDMetrics for the new size settings to become effective.
Look and Feel
To adjust the overall appearance - or look and feel - of SDMetrics, you have the following choices:
Design Comparison Colors
Here you can adjust the colors used to indicate added and deleted elements or increased or decreased measurement values for design comparisons (see Section 4.9 "The View 'Design Comparison'").
Click the button for the color you like to change. This opens a color chooser dialog where you can select the new color.
Figure 30: Behavior Dialog
Auto-size table columns to optimal width
When this option is enabled, the table columns in all data tables will
individually be sized to minimal width. When this option is disabled,
all table columns in a table initially have equal width.
Start application with maximized window
When this option is enabled, SDMetrics' main window will be fully
maximized on startup. Deselect this option to launch SDMetrics in a
smaller window.
Enable File Drag and Drop
File drag and drop is a convenient way to select an XMI or other
project file (see Section 4.2.1 "Specifying Project Files") for analysis: simply drag the file
from an external file system browser/explorer and drop it into
SDMetrics' main window.
File drag and drop may not work on all combinations of operating system platforms and Java virtual machines. If file drag and drop causes problems on your system, you can disable this feature here. Note: You need to restart SDMetrics for changes to this setting to become effective.
Help Launch Configuration
The help launch configuration specifies the web browser to open the user manual with, and the location of the user manual.
The SDMetrics command line syntax is:
java com.sdmetrics.SDMetrics -xmi xmifile [-proj projfile] [-customPF pfsetname] [-meta mmfile] [-trans transformationfile] [-metrics metricsfile] [-filter filterstring]* [-nonmatching] [-ignore] [-compare 2ndxmifile] [-mapping mapfile] [-relative] [-gHisto] [-gCumDist] [-gKiviat] [-gFormat format] [-gHTMLPerDiag] [-gHTMLPerType] [-gHTMLForAll] [-gUseFrames] [-gWidth width] [-gHeight height] [-model] [-stats] [-relmat] [-rules [-rulefilter filter]] [-nometrics] [-s] [-one] [-f format] basenameThe arguments in square brackets [] are optional. When followed by an asterisk (*), the bracket contents can be repeated an arbitrary number of times, otherwise, at most one occurrence is allowed. The meaning of the arguments is as follows.
Project file settings:
To summarize, external attributes are inherently relevant to the stakeholders in a software system, but can be measured directly only late in the development process. Internal attributes are early available but are not inherently meaningful. They become meaningful only when they are seen as indicators of (or in relation to) external attributes. Besides early availability, the advantages of design measurement are:
Design size metrics measure the size of design elements, typically by counting the elements contained within. For example, the number of operations in a class, the number of classes in a package, and so on.
Impact on quality
Size metrics are good candidates for developing cost or effort estimates for implementation, review, testing, or maintenance activities. Such estimates are then used as input for project planning purposes and the allocation of personnel.
In addition, large sized design elements (e.g., big classes or packages) may suffer from poor design. In an iterative development process, more and more functionality is added to a class or package over time. The danger is that, eventually, many unrelated responsibilities are assigned to a design element. As a result, it has low functional cohesion. This in turn negatively impacts the understandability, reusability, and maintainability of the design element.
Therefore, interfaces and implementations of large classes or packages should be reviewed for functional cohesion. If there is no justification for the large size, the design element should be considered for refactoring, for instance, extract parts of the functionality to separate, more cohesive classes.
Empirical results
Empirical studies consistently confirm the importance of size as the main cost driver in a software project. Size metrics are also consistently good indicators of fault-proneness: large methods/classes/packages contain more faults. However, since size metrics systematically identify large design elements as fault-prone, these metrics alone are not suitable to find elements with high fault density.
Coupling is the degree to which the elements in a design are connected.
Impact on quality
Coupling connections cause dependencies between design elements, which, in turn, have an impact on system qualities such as maintainability (a modification of a design element may require modifications to its connected elements) or testability (a fault in one design element may cause a failure in a completely different, connected element). Thus, a common design principle is to minimize coupling.
Most coupling dependencies are directed - the coupling usually defines a client-supplier relationship between the design elements. Therefore, it is useful to distinguish import coupling ("using", "fan-out") and export coupling ("used", "fan-in"), which we discuss in the following.
Import coupling measures the degree to which an element has knowledge of, uses, or depends on other design elements. High import coupling can have the following effects:
Export coupling measures the degree to which an element is used by, depended upon, by other design elements. High export coupling is often observed for general utility classes (e.g., for string handling or logging services) that are used pervasively across all layers of the system. Thus, high export coupling is not necessarily indicative of bad design.
Again, an important issue to consider here is stability. High export coupling elements that are likely to change in the future can have a large impact on the system if the change affects the interface. Therefore, high export classes should be reviewed for anticipated changes, to ensure that these changes can implemented with minimal impact.
Empirical results
Coupling metrics have consistently been found to be good indicators of fault-proneness. It seems worthwhile to investigate different dimensions of coupling: import and export coupling, different coupling mechanisms, distinguishing coupling to COTS libraries and application-specific classes/packages. Coupling metrics are suitable to identify design elements with high fault density. Therefore, coupling metrics greatly help to identify small parts of a design that contain a large number of faults.
Inheritance-related metrics are concerned with aspects such as
Deep inheritance structures are hypothesized to be more fault-prone. The information needed to fully understand a class situated deep in the inheritance tree is spread over several ancestor classes, thus more difficult to overview.
Similar to high export coupling, a modification to a design element with a large number of descendents can have a large effect on the system. Make sure the interface of the class is stable, or that anticipated modifications can be added without affecting the inheritance hierarchy at large.
Empirical results
Empirical studies show that effects of the use of inheritance on system qualities such as fault-proneness vary greatly. Depending on factors such as developer experience, system quality can benefit or suffer from the use of inheritance, or be unaffected by it.
Thus, inheritance metrics should not be relied on for decision making before their impact on system quality is not demonstrated in a given development environment. Extant inheritance metrics per se are not suitable to distinguish proper use of inheritance from improper use.
Also, inheritance is not very frequently used in designs. Typically, only a small percentage of the classes in a system will participate in inheritance relationships. As a consequence, inheritance-related metrics tend to have low variance and are difficult to use (see Section 6.3.1 "Descriptive Statistics").
Complexity measures the degree of connectivity between elements of a design unit. Whereas size counts the elements in a design unit, and coupling the relationships/dependencies leaving the design unit boundary, complexity is concerned with the relationships/dependencies between the elements in the design unit. For instance, counting the number method invocations among the methods within one class can be considered a measure of class complexity, or the number of transitions between the states in a state diagram.
Impact on quality
High complexity of interactions between the elements of a design unit can lead to decreased understandability and therefore increased fault-proneness. Also, testing such design units is more difficult.
Empirical results
In practice, complexity metrics are often strongly correlated with size measures. Large design units that contain many design elements within are also more likely to have a large number of connections between the design elements.
Thus, while complexity metrics are good indicators of qualities such as fault-proneness, they provide no new insights in addition to size metrics.
Cohesion is the degree to which the elements in a design unit (package, class etc.) are logically related, or "belong together". As such, cohesion is a semantic concept.
Cohesion metrics have been proposed which attempt to approximate this semantic concept using syntactical criteria. Such metrics quantify the connectivity (coupling) between elements of the design unit: the higher the connectivity between elements, the higher the cohesion.
Cohesion metrics often are normalized to have a notion of minimum and maximum cohesion, usually expressed on a scale from 0 to 1. Minimum cohesion (0) is assumed when the elements are entirely unconnected, maximum cohesion (1) is assumed when each element is connected to every other element.
Not normalized metrics are based on counts of connections between design elements in a unit (e.g., method calls within a class). As such, not normalized metrics are conceptually similar to complexity metrics.
Impact on quality
A low cohesive design element has been assigned many unrelated responsibilities. Consequently, the design element is more difficult to understand and therefore also harder to maintain and reuse. Design elements with low cohesion should be considered for refactoring, for instance, by extracting parts of the functionality to separate classes with clearly defined responsibilities.
Empirical results
In practice, cohesion metrics are only of limited usefulness:
The range and distribution of a metric determines the applicability of subsequent analysis techniques. Low variance metrics do not differentiate design elements very well and therefore are not likely to be useful predictors. Descriptive statistics allow us to determine if the data collected from two or more projects are comparable, stem from similar populations. If not, this information will likely be helpful to explain different findings across projects.
SDMetrics calculates and displays descriptive statistics for design metrics, see Section 4.5 "The View 'Histograms'" and Section 4.8 "The View 'Descriptive Statistics'".
Redundant metrics provide no new design information. They can be discarded without loss of information, and should be discarded to facilitate the use of measurement data for decision making. Design measurement tools often come with a large set of metrics, and you should expect many of metrics to be redundant. SDMetrics is no exception here.
The difficulty is that, depending on design practices used in a development environment, two metrics may be redundant in one software system, but not in another. There is no such thing as a canonical set of non-redundant metrics that captures all important design properties and is valid for all systems. Therefore, SDMetrics opts for a rich set of metrics, to lower the risk of missing important design aspects, at the prize of some redundancy among the metrics.
Techniques such as principal component analysis (PCA) can be used to identify and eliminate redundant metrics. PCA is a standard technique to identify the underlying, orthogonal dimensions (which correspond to properties that are directly or indirectly measured) that explain relations between the variables in a data set. Examples of the application of PCA are demonstrated in [BWDP00,BWL01].
Using a technique such as PCA within a few projects, you can identify a reduced set of largely orthogonal metrics for your development environment.
An admissible interpretation is therefore to sort the model elements by a metric, and review the design of the model elements with the highest values: Are those high values justified, should the model element be considered critical?
There is no definitive answer to the question how many elements to choose for review from the top of the sorted list. One strategy could be to select so-called "outliers". Another strategy could be to select a certain number or percentage of model elements from the top, based on available resources for reviewing.
SDMetrics supports this kind of interpretation of measurement data. The histograms in the metric view allow you to visually identify outliers (Section 4.5 "The View 'Histograms'"). In the table view, you can sort the elements by a metric, and highlight elements in the upper percentiles for a metric (Section 4.4 "The View 'Metric Data Tables'").
What about thresholds?
One recurring suggested use of design metrics is that they can be used to build simple quality benchmarks based on thresholds. If a design metric exceeds a certain threshold, the design element is either rejected and must be redesigned, or at least flagged as "critical". It is difficult to imagine why a threshold effect would exist between, for example, size metrics and fault-proneness. This would imply a sudden, steep increase in fault-proneness in a certain size value range, something that would be difficult to explain. Also, empirical data does not support this idea [BEGR00].
Figure 31: Benchmarking
As an example, Figure 31 shows a (fictitious) distribution of the number of operations invoked from within a class. The vertical axis indicates, for each value on the horizontal axis (number of invoked operations), the percentage of classes that have that particular value. In the example, the distribution of the new classes follows closely the distribution of the benchmark, except for values 7 and 8, which occur more frequently. Such deviations from expected distributions pinpoint potential risk areas. These areas could then be inspected to verify whether such a deviation is justified, or if a redesign of that part should be considered.
The example also illustrates why the examination of distributions provides more information than simply defining thresholds for each metric that are not to be exceeded. In the example, the number of invoked operations is not exceedingly high for the candidate design - the benchmark suggests 10 as an upper value. A simple threshold test would have missed the potential design problem for the cluster of classes with 7 and 8 operation calls.
In the following, we describe how to build and use a prediction model for class fault-proneness from the structural properties of a class. Figure 32 depicts the steps involved in building the prediction model.
Figure 32: Building a Prediction Model
The starting point is a system design that has been created in the past. We apply SDMetrics to the design to obtain structural properties data for the classes in the design, collected from the various diagram types (class, object, collaboration, sequence, and state diagrams). In addition, fault data (e.g., from inspections, testing, or post-release faults) has to be collected and the faults per class recorded.
We now have a set of classes enriched with structural properties data and fault data. On this data set we perform a statistical analysis (e.g. classification or regression analysis) to identify relationships between fault data and structural properties. The result of this analysis is a prediction model, e.g., in the form of a regression equation. The prediction model computes a predicted fault-proneness or predicted number of faults from the structural properties of a class. This model can be used to make predictions for new classes, as depicted in Figure 33.
The starting point in applying the prediction model is a new design candidate. We apply SDMetrics to this design to again obtain the structural properties measurement data for the classes. This data is then fed into the prediction model. Using the now known relationship between the structural properties and faults, the prediction model calculates, for instance, for each class a probability that a fault will be found in the class upon inspection.
Figure 33: Using the Prediction Model
The output of the prediction model is useful for decision-making. For instance, we may decide that for classes with a high predicted fault-proneness, say, above 25%, the class design shall undergo quality assurance (QA) activities such as inspections, extensive unit testing etc. Or, we may rank the classes by their predicted fault-proneness, and select the highly fault-prone classes from the top of the list for QA activities, until the allocated resources for QA are depleted.
Thus, the effort for QA activities can be focused on the classes that are more likely to contain faults. The benefits are manifold:
Prediction models for other system qualities can be built in the same way, for example, models to predict implementation and test effort from design size.
The advantage of using prediction models is that they provide a mapping from hard-to-interpret design measurement data ("size=12, coupling=7, ...") to easily interpreted external quality data ("predicted fault-proneness of class X: 78%", "predicted effort to implement package Y: 104 person hours"). The result is an absolute, quantitative statement (within certain error bars) about the external quality of a system, expressed in the same unit in which the external quality is measured.
Also, prediction models address the problem that a complex system quality attribute such as fault-proneness is influenced by many factors - various dimensions of size, coupling and so on. Approaches such as quality benchmarks, which investigate one design metric at a time to characterize fault-proneness, fail to take the combined effect of all factors into account. Prediction models provide a sound method to combine these multiple factors into one cohesive model.
Empirical evidence shows that highly accurate prediction models can be built from structural properties, and that they are beneficial in highlighting trouble areas, as well as in supporting project planning and steering [BWDP00, BWL01, BMW02, CK98, NP98, LH93].
XMI (the XML Metadata Interchange format), yet another standard of the OMG, is a mechanism to create a textual (XML) representation of models based on the MOF, such as the UML. XMI defines a set of production rules that prescribe how to serialize the elements of a model to XML, and how to generate a DTD or schema for the XML files thus generated. However, this standard is not immediately suitable for the definition of design metrics for a number of reasons:
When would I need to worry about all this?
Knowledge of the SDMetrics metamodel is needed if you want to define new metrics or rules of your own (see Section 8 "Defining Custom Design Metrics and Rules"). The metric and rule definitions make copious references to the elements and relationships defined in the metamodel.
In addition, knowledge of XMI transformation files is needed if
The SDMetrics metamodel is defined in an XML file of the following structure (for a formal definition, see Appendix E: "Project File Format Definitions"):
<sdmetricsmetamodel version="2.0" > <modelelement name="element1"> <attribute name="attr1" type="data" multiplicity="one" /> <attribute name="attr2" type="ref" multiplicity="many" /> .. </modelelement> <modelelement name="element2" parent="element1"> <attribute name="attr3" type="extref" multiplicity="one" /> ... </modelelement> ... </sdmetricsmetamodel>The metamodel definition file is a list of metamodel element definitions enclosed in <sdmetricsmetamodel> tags. The attribute "version" indicates the version number of the oldest version of SDMetrics with which the file can be used.
A metamodel element definition is enclosed in <modelelement> tags. The required attribute name specifies the name of the metamodel element (e.g., class, operation etc). The optional attribute parent specifies a parent metamodel element; the inheritance mechanism will be explained at the end of the section. Stored with each metamodel element is a set of metamodel attributes representing data fields and cross-references to other model elements. These are specified in a list of <attribute> definitions. We distinguish data attributes, cross-reference attributes, and extension references.
<modelelement name="operation"> <attribute name="id" /> <attribute name="name" /> <attribute name="context" type="ref" /> <attribute name="visibility" /> </modelelement>An operation has four single-valued attributes: data attributes id, name, and visibility, and cross-reference attribute context. In the example, the meaning of the attributes is as follows:
Note that extension reference attributes must be single-valued. A metamodel element can have at most one extension reference attribute.
Metamodel inheritance
To simplify the specification of metamodels, a metamodel element may inherit the attributes of another metamodel element. The parent metamodel element is specified via the parent attribute in the metamodel element definition:
<modelelement name="element2" parent="element1"> <!-- additional attributes of element2 (optional) --> </modelelement>In the example, an instance of model element "element2" has all attributes of "element1", and possibly additional attributes.
By default, all metamodel elements inherit from a special metamodel element named sdmetricsbase, which must be defined explicitly in every metamodel definition file. In the default metamodel that is shipped with SDMetrics, the sdmetricsbase element defines the attributes "id", "name", and "context" which all elements must possess. See Appendix A: "Metamodels" for a list of all metamodel elements and a description of their attributes.
Note that parent model elements must be defined before any of their child elements in the metamodel definition file. Consequently, the "sdmetricsbase" model element must be the first one defined in the file.
If the parent model element defines an extension reference attribute, the child model elements inherit the extension reference and therefore cannot define new extension references of their own.
<xmitransformations version="2.0" > <xmitransformation ...xmitransformation attributes... /> <trigger ...trigger attributes... /> <trigger ...trigger attributes... /> ... </xmitransformation> <xmitransformation ...xmitransformation attributes... /> <trigger ...trigger attributes... /> ... </xmitransformation> ... </xmitransformations>The transformation file defines a list of xmitransformation elements, each of which has a list of triggers. Each xmitransformation element provides XMI information for one SDMetrics metamodel element, each trigger provides XMI information for one attribute of the metamodel element.
The root element xmitransformations encloses the list of XMI transformations. The "version" attribute (required) indicates the version number of the oldest version of SDMetrics with which the XMI transformation file can be used.
Below is the representation of an operation as expressed in an XMI 1.0 file. The places that contain information we are interested in are set in boldface:
<Foundation.Core.Operation xmi.id="xmi.1632"> <!-- 1. operation id --> <Foundation.Core.ModelElement.name>printStackTrace <!-- 2. operation name --> </Foundation.Core.ModelElement.name> <Foundation.Core.ModelElement.visibility xmi.value="public"/> <!-- 3. operation visibility --> <Foundation.Core.ModelElement.isSpecification xmi.value="false"/> <Foundation.Core.Feature.ownerScope xmi.value="instance"/> <Foundation.Core.BehavioralFeature.isQuery xmi.value="false"/> <Foundation.Core.Operation.isAbstract xmi.value="false"/> <Foundation.Core.Feature.owner> <Foundation.Core.Classifier xmi.idref="xmi.1605"/> <!-- 4. operation context --> </Foundation.Core.Feature.owner> ... </Foundation.Core.Operation>The following XMI transformation describes how the required information is retrieved from an XMI 1.0 document:
<xmitransformation modelelement="operation" xmipattern="Foundation.Core.Operation" recurse="true"> <trigger name="id" type="attrval" attr="xmi.id" /> <trigger name="name" type="ctext" src="Foundation.Core.ModelElement.name" /> <trigger name="visibility" type="cattrval" src="Foundation.Core.ModelElement.visibility" attr="xmi.value"/> <trigger name="context" type="gcattrval" src="Foundation.Core.Feature.owner" attr="xmi.idref"/> </xmitransformation>The XMI transformation is enclosed within the xmitransformation tags. The opening tag takes the following attributes:
<trigger name="id" type="attrval" attr="xmi.id" />The "attr" attribute of the trigger indicates the XML attribute we are interested in. In most cases, XML attributes only store single values. Exceptions are element references. For example, a partition ("swimlane") in an UML1.x activity graph may be serialized in XMI1.2 as follows:
<UML:Partition name="mySwimlane" xmi.id="xmi12" contents="xmi35 xmi61 xmi115" />The XML attribute "contents" contains the XMI IDs of the states of the partition, separated by spaces. In the SDMetrics metamodel, the model element "partition" has a multi-valued attribute "contents" (see Appendix A.1 "Metamodel for UML 1.3/1.4"). With the trigger
<trigger name="contents" type="attrval" attr="contents" />SDMetrics will extract each part of the XML attribute, and store it as separate value in the multi-valued SDMetrics metamodel attribute.
<trigger name="name" type="ctext" src="Foundation.Core.ModelElement.name" />we retrieve the value for attribute "name" from the text enclosed within the Foundation.Core.ModelElement.name tags. The child element is specified via the "src" attribute of the trigger.
<trigger name="visibility" type="cattrval" src="Foundation.Core.ModelElement.visibility" attr="xmi.value"/>The "src" and "attr" attributes of the trigger specify the element/attribute we are interested in.
We can also use this trigger for multi-valued attributes. For example, a partition in a UML2 activity may be serialized in XMI2.0 as follows:
<group xmi:type='UML:ActivityPartition' name='mySwimlane' xmi:id='xmi12'> <containedNode xmi:idref='xmi35'/> <containedNode xmi:idref='xmi61'/> <containedNode xmi:idref='xmi115'/> </group>In the SDMetrics metamodel for UML2, the model element "activitygroup" that stores partitions has a multi-valued attribute "nodes" (see Appendix A.2 "Metamodel for UML 2.x"). With the trigger
<trigger name="nodes" type="cattrval" src="containedNode" attr="xmi:idref" />SDMetrics will pick up each "containedNode" child XML element, and store the values of their "xmi:idref" attributes in the multi-valued SDMetrics metamodel attribute "nodes" of the activity partition.
<trigger name="context" type="gcattrval" src="Foundation.Core.Feature.owner" attr="xmi.idref"/>The "src" attribute of the trigger specifies the child element name, the "attr" attribute of the trigger specifies the attribute of the grandchild element we want to access. Note that we do not specify the element name of the grandchild. For single-valued attributes such as "context", the trigger always accesses the first child of the specified child element, regardless of its name.
We can also use the trigger for multi-valued attributes. Revisiting the example of UML1.x activity partitions from Section 7.2.2.1 "Trigger Type "attrval"", an XMI1.2 exporter can also serialize swimlanes as follows:
<UML:Partition' name='mySwimlane' xmi.id='xmi12'> <UML:Partition.contents> <UML:ModelElement xmi.idref='xmi35'/> <UML:ModelElement xmi.idref='xmi61'/> <UML:ModelElement xmi.idref='xmi115'/> </UML:Partition.contents> </UML:Partition>The trigger
<trigger name="contents" type="gcattrval" src="UML:Partition.contents" attr="xmi.idref"/>will visit each child XML element of the UML:Partition.contents element, and store the values of their "xmi.idref" attributes in the multi-valued attribute "contents" of SDMetrics' "partition" metamodel element.
The "gcattrval" trigger has one more function that is not related to extracting values from XML attributes. If a model element is defined as child element of the XML element specified by the trigger's "src" attribute, the trigger will store a cross-reference to that model element with the attribute for which the trigger is defined. Consider the following example of entry and exit actions of a state in UML1.4:
<UML:SimpleState xmi.id = 'id127' name = 'full'> <UML:State.entry> <UML:CallAction xmi.id = 'id128' name = 'anon' isAsynchronous = 'false'> ... </UML:CallAction> ... (more entry actions) </UML:State.entry> <UML:State.exit> <UML:CallAction xmi.id = 'id129' name = 'anon' isAsynchronous = 'false'> ... </UML:CallAction> </UML:State.entry> ... </UML:SimpleState>The entry and exit actions are defined as child elements of the UML:State.entry and UML:State.exit elements. The "state" element of SDMetrics' metamodel for UML1.x has two multi-valued cross-reference attributes "entryaction" and "exitaction". For these, we define the following triggers:
<xmitransformation modelelement="state" xmipattern="UML:SimpleState" recurse="true"> <trigger name="entryaction" type="gcattrval" src="UML:State.entry" attr="xmi.idref" /> <trigger name="exitaction" type="gcattrval" src="UML:State.exit" attr="xmi.idref" /> ... </xmitransformation>With these triggers, SDMetrics will store cross-references to the model elements defined as children of the UML:State.entry and UML:State.exit XML elements in the "entryaction" and "exitaction" attributes of SDMetrics model element "state", respectively. Note that the values of the "attr" attributes of the triggers play no role in this.
<xmitransformation modelelement="state" xmipattern="Behavioral_Elements.State_Machines.SimpleState"> ... <trigger name="kind" type="constant" attr="simple"/> </xmitransformation> <xmitransformation modelelement="state" xmipattern="Behavioral_Elements.State_Machines.FinalState"> ... <trigger name="kind" type="constant" attr="final"/> </xmitransformation>The "attr" attribute specifies the value to be inserted for the metamodel attribute ("simple" for SimpleState, "final" for FinalState).
<xmitransformation modelelement="model" xmipattern="Model_Management.Model"> ... <trigger name="context" type="ignore" /> </xmitransformation>A trigger of type "ignore" requires no further attributes.
Composite aggregations in XMI 2.x
In XMI 2.x, the elements that play the parts in composite aggregations between metamodel elements are serialized in such a way that their types are not specified explicitly in the XMI file. For example, the type "Class" in the UML2 metamodel has a composite 1:n aggregation to the type "Property". The role name for the parts is "ownedAttribute". A class named "aClass" with two attributes "attr1" and "attr2" is serialized as follows:
<uml:Model xmi:version="2.0" xmi:id="xmi.1" name='aModel' ... <ownedMember xmi:type='uml:Class' xmi:id='xmi.42' name='aClass'> <ownedAttribute xmi:id='xmi.43' name='attr1' type='xmi.2001'/> <ownedAttribute xmi:id='xmi.44' name='attr2' type='xmi.1138'/> </ownedMember> ... </UML:Model>The name of the XML elements defining the attributes "attr1" and "attr2" is the role name "ownedAttribute" of the aggregation in the metamodel. We need to provide SDMetrics with the information that "ownedAttribute" associates classes with elements of type "Property". This can be done with the "xmi2assoc" trigger as follows:
<xmitransformation modelelement="class" xmipattern="uml:Class"> <trigger name="properties" type="xmi2assoc" attr="ownedAttribute" src="uml:Property"/> ... </xmitransformation> <xmitransformation modelelement="property" xmipattern="uml:Property"> ... </xmitransformation>The attribute "attr" of the trigger specifies the role name of the aggregation that is also the name of the XML element defining the part. The attribute "src" specifies the XMI pattern of the XMI transformation that should be used to process the part definition. The attribute "name" specifies the name of the (multi-valued) SDMetrics metamodel cross-reference attribute that stores the reference(s) to the part(s).
If a part is a subtype of the associated class, the XMI exporter must indicate the type of the part with the "xmi:type" attribute. If such a type is specified, SDMetrics will of course use the XMI transformations defined for that type.
The "src" attribute is optional if the type of the parts in the UML2 metamodel is abstract. In that case, the concrete type of the part must always be indicated in the XMI file with the xmi:type attribute. In the example above, the UML2 metamodel class "Model" has a composite aggregation with the abstract class "PackageableElement"; the role name of the parts is "ownedMember". The following XMI transformation collects all members of the model:
<xmitransformation modelelement="model" xmipattern="uml:Model"> <trigger name="members" type="xmi2assoc" attr="ownedMember" /> </xmitransformation>Associations in XMI 2.x
The "xmi2assoc" trigger can also be used to capture plain associations that are not aggregations. For example, the UML2 metamodel class "InstanceSpecification" has an association with class "Classifier", the role name is "classifier". To serialize an instance specification with several classifiers, an XMI exporter has two options:
<!-- first option: --> <ownedMember xmi:type='uml:InstanceSpecification' xmi:id='xmi.42' name='myInstance'> <classifier xmi:idref='xmi.43'/> <classifier xmi:idref='xmi.44'/> </ownedMember> <!-- second option: --> <ownedMember xmi:type='uml:InstanceSpecification' xmi:id='xmi.42' name='myInstance' classifier='xmi.43 xmi.44'/>The following XMI transformation will handle both options with one trigger:
<xmitransformation modelelement="instancespec" xmipattern="uml:InstanceSpecification"> <trigger name="classifiers" type="xmi2assoc" attr="classifier" /> </xmitransformation>Here we assume that the SDMetrics metamodel type "instancespec" has a multi-valued cross-reference attribute "classifiers" that stores the references to the classifiers of the instance specification.
If we take again the example of states in UML1.x activity partitions, we could add a cross-reference attribute "inPartition" to metamodel element "state", and populate that attribute with information in which partition a state lies as follows:
<xmitransformation modelelement="activitygraph" xmipattern="UML:ActivityGraph"> <trigger name="contents" type="attrval" attr="contents" linkbackattr="inPartition"/> <trigger name="contents" type="gcattrval" src="UML:Partition.contents" attr="xmi.idref" linkbackattr="inPartition" /> </xmitransformation>The semantic of the "linkbackattr" is as follows. When a trigger retrieves information for a cross-reference attribute (such as "contents" for partitions), we check if the referenced element has a cross-reference attribute of the name specified by the "linkbackattr" (i.e., attribute "inPartition" that we defined for states). If so, we set the value of that attribute to point back to the referencing model element (the partition containing the state).
<UML:Class xmi.id="cls1" name="Rectangle" />or like this:
<UML:Class xmi.id="cls1"> <UML:ModelElement.name>Rectangle</UML:ModelElement.name> </UML:Class>Therefore, the XMI transformations for these XMI versions have two triggers for most attributes, one trigger for each option. In the above example, the triggers to retrieve the class names are:
<xmitransformation modelelement="class" xmipattern="UML:Class"> <trigger name="name" type="attrval" attr="name" /> <trigger name="name" type="ctext" src="UML:ModelElement.name" /> ... </xmitransformation>Multiple triggers are also useful to support modeling tools that deviate in minor ways from the UML standards. For example, some modeling tools denote the parent/child elements in a UML1.x generalization by "supertype" and "subtype", instead of the proper "parent" and "child". In many cases, you can simply add a trigger for the nonstandard way of representing the attribute, and a single XMI transformation file is thus capable of handling various XMI exporters.
If you define a trigger for an inherited attribute, all inherited triggers for that attribute are overridden, and only the newly defined trigger(s) for that attribute are used for the child element.
Special care needs to be taken if there are multiple xmitransformations defined for the parent metamodel element. In that case, one xmitransformation is chosen arbitrarily to determine the inherited triggers for the child metamodel element. Therefore, you have to make sure that any of the possible xmitransformations is suitable to provide the inherited triggers for the child. Otherwise, explicitly provide the proper definitions of all triggers for the child metamodel element.
<xmitransformation modelelement="taggedvalue" xmipattern="UML:TaggedValue" requirexmiid="false"> <trigger name="tag" ... ... </xmitransformation>If attribute "requirexmiid" is set to "false", SDMetrics recognizes XML elements which have no XMI ID, as long as the XML element specifies no XMI IDREF cross reference either (attribute "xmi.idref" or "xmi:idref"). This is necessary because XMI 1.x exporters frequently serialize element cross references in a way that would otherwise trigger the creation of a new, unwanted, and empty UML model element for each such cross reference.
On rare occasions, you may need to extract information from XML elements that have no XMI ID but do have an XMI IDREF. You can achieve this with the optional attribute "allowxmiidref" as follows:
<xmitransformation modelelement="eaextensionelement" xmipattern="element" requirexmiid="false" allowxmiidref="true"> <trigger name="elementref" type="attrval" attr="xmi:idref" /> <trigger name="name" ... /> ... </xmitransformation>By default, "requirexmiid" is "true" and "allowxmiidref" is "false".
It is possible to make the use of an XMI transformation conditional. You may specify a condition for the attributes of the XMI element that must be fulfilled for a XMI transformation to become effective. With this feature, you can filter certain XMI elements, or you can conditionally map one XMI element onto different metamodel element types, depending on the values of its attributes.
For example, the MagicDraw™ UML modeling tool uses a proprietary XMI extension to encode diagram information as follows:
<mdElement elementClass = 'DiagramData' xmi.id = 'ID2546'> <parentID xmi.idref = 'ID0002' /> <type>Class Diagram</type> <mdElement elementClass = 'DiagramView' xmi.id = 'ID2547'> <elementID xmi.idref = 'ID2546' /> <zoomFactor xmi.value = '1.0' /> <mdOwnedViews> <mdElement elementClass = 'ClassView' xmi.id = 'ID2548'> <elementID xmi.idref = 'ID00dd' /> <geometry>165, 16, 358, 60</geometry> </mdElement> <mdElement elementClass = 'ClassView' xmi.id = 'ID2549'> <elementID xmi.idref = 'ID15f2' /> <geometry>40, 170, 600, 111</geometry> </mdElement> ...The XML element mdElement represents both diagrams and diagram elements, indicated by the value of attribute elementClass. The value "DiagramData" denotes a diagram (and we can then extract additional information on diagram type and owner). A value other than "DiagramData" denotes a diagram element (which contains a cross-reference to the UML model element it represents). To define diagram-specific metrics, we must be able distinguish diagrams from diagram elements:
<xmitransformation modelelement="diagram" xmipattern="mdElement" condition="elementClass='DiagramData'" recurse="true"> <trigger name="style" type="ctext" src="type" /> <trigger name="context" type="cattrval" src="parentID" attr="xmi.idref" /> </xmitransformation> <xmitransformation modelelement="diagramelement" xmipattern="mdElement" condition="elementClass!='DiagramData'"> <trigger name="element" type="cattrval" src="elementID" attr="xmi.idref" /> </xmitransformation>The first xmitransformation for model element "diagram" specifies a condition that the "mdElement" XML element must have an attribute elementClass of value "DiagramData". The second xmitransformation for model element "diagramelement" picks up all mdElement instances with an elementClass different from "DiagramData". As a result, "mdElement" DiagramData elements are stored as "diagram" metamodel elements, all others are stored as instances of "diagramelement".
SDMetrics uses the following strategy when searching a suitable XMI transformation for an XML element in the XMI source file:
The SDMetricsML is based on the XML. The metric definition file is an XML file of the following format (for a formal definition of the file format, see Appendix E: "Project File Format Definitions"):
<sdmetrics version="2.3" ruleexemption="taggedvalue" exemptiontag="tagname" > <metric name="met1" ...> <'metric definition' ...> </metric> <set ... name="set1" ...> <'set definition' ...> </set> <metric name="met2" ...> <'metric definition' ... > </metric> <matrix name="matrix1" ...> <'matrix definition' ... > </matrix> <rule name="rule1" ...> <'rule definition' ... > </rule> <wordlist name="list1" ...> <'wordlist definition' ... > </wordlist> <reference tag="ref1"> bibliographic citation #1 </reference> <term name="term1"> definition of term </term> ... </sdmetrics>The "version" attribute (required) of the root element sdmetrics indicates the version number of the oldest version of SDMetrics with which the metric definition file can be used. The remaining two attributes instruct the design rule checker how to access tagged values, this will be discussed later (Section 8.3.6 "Exempting Approved Rule Violations").
The file contains a list of definitions of metrics, as well as sets (sets of UML elements, sets of values), design rules and word lists (see Section 4.7 "The View 'Rule Checker'"), relation matrices (see Section 4.10 "The View 'Relation Matrices'"), literature reference and glossary terms. The following sections describe the definition of metrics, sets, rules, and relation matrices in detail.
<metric name="metricname" domain="metricdomain" category="metriccategory" internal="true/false" inheritable="true/false"> <description>Description of the metric.</description> <'metric definition' ...> </metric>The attributes in the opening "metric" tag are as follows:
Following the metric tag is an optional description of the metric, which will be shown in the measurement catalog (see Section 4.13 "The View 'Catalog'"). Section 8.6 "Writing Descriptions" explains how to write metric descriptions.
Following the description is an XML element that defines the calculation procedure for the metric. We describe each calculation procedure in detail in the following subsections.
<metric name="NumElements" domain="class"> <description>The number of elements in the class.</description> <projection relation="context" /> </metric>The metric NumElements is defined for elements of type "class". It counts, for a given class, all elements which have a reference attribute "context" that points to that class. Attribute "context" specifies the owner of a model element. So this metric counts all model elements of which the class is the owner, that is, the number of elements in the class. Those elements can be of any type, e.g., operations, attributes, or other classes (inner classes).
<metric name="NumMembers" domain="package"> <description>The number of owned members of the package.</description> <projection relset="ownedmembers" /> </metric>Sources for sets can be
For a projection, exactly one of the attributes "relation" or "relset" must be specified.
<metric name="NumOps" domain="class"> <description>The number of operations in the class.</description> <projection relation="context" target="operation" /> </metric>This only counts elements of type "operation" whose "context" is the given class, i.e., the number of operations of the class.
Filtering for several types
If you want to count elements of several types, specify the additional types separated by a "|". For example, to count the classes, interfaces, and data types in a package, we write:
<metric name="NumEl" domain="package"> <projection relation="context" target="class|interface|datatype" /> </metric>Filtering for subtypes
In SDMetrics' metamodel for UML2, type "class" is the parent type of several subtypes such as "usecase", "actor", "component". The target filter target="class", however, only accepts elements of type "class". Elements whose type is a subtype of type "class" will not be accepted. Thus, the following metric "NumCls" only counts the classes in the package, not including actors, use cases, and so forth:
<metric name="NumCls" domain="package"> <projection relation="context" target="class" /> </metric>To also accept elements of direct or indirect subtypes of the type, put a "+" prefix in front of the type name:
<metric name="NumClsTypeEl" domain="package"> <projection relation="context" target="+class" /> </metric>This will count all classes, actors, usecases, components, and other elements in the package whose type is a direct or indirect subtype of "class".
You can combine subtype filtering with filtering for several types. For example, target="+class|interface|+datatype" will accept classes and datatypes as well as their subtypes, and interfaces, but not any subtypes of "interface".
Figure 34: Example dependency links
Class c is the client in three dependency relationships with three suppliers: a package and two other classes. A UML 1.x dependency element links the client and supplier via its cross-reference attributes "depclient" and "depsupplier" (cf. metamodel element dependency in Appendix A.1 "Metamodel for UML 1.3/1.4"). We can specify a projection for the "depclient" relation for dependency elements:
<metric name="Dependencies" domain="class"> <description>The number of dependencies in which the class participates as client.</description> <projection relation="depclient" target="dependency" /> </metric>This projection retrieves the elements of type "dependency" where the given class is the client. For class c, this would get us d0, d1, and d2. So far so good, however, we are probably more interested in actual supplier elements. This is where attribute "element" comes in:
<metric name="SupplierElements" domain="class"> <description>The supplier elements of which the class is a client.</description> <projection relation="depclient" target="dependency" element="depsupplier" /> </metric>By specifying the attribute "element", the projection does not access the dependency element, but the element referenced via the "depsupplier" relation specified by the "element" attribute. In Figure 34 above, this gives us p, s1, and s2. In other words, the suppliers we want. To filter for suppliers of a certain type, we specify the additional attribute "eltype" that indicates the type of elements we are interested in:
<metric name="SupplierClasses" domain="class"> <description>The supplier classes of which the class is a client in a dependency.</description> <projection relation="depclient" target="dependency" element="depsupplier" eltype="class" /> </metric>This projection now only returns supplier classes (s1 and s2 for class c in the above example).
Note that the value of the "element" attribute is a metric expression. In addition to cross-reference attributes, you can specify arbitrary metric expressions that return model elements (see Section 8.5.2.2 "Special Operators").
As with the "target" attribute, you can filter for several element types and/or subtypes with the "eltype" attribute. Just separate the additional element types with a "|", and precede types whose subtypes should also be admitted with a "+", for example: eltype="+class|interface|+datatype".
<metric name="NumPublicOps" domain="class"> <description>The number of public operations in the class.</description> <projection relset="ownedoperations" condition="visibility='public'" /> </metric>The condition expression "visibility='public'" is evaluated for each element in the set, only operations that fulfill this condition are counted. The admissible operations for condition expressions are described in Section 8.5.4 "Condition Expressions".
If you combine the "condition" attribute with the "element" and "eltype" attributes, the expression will be evaluated for the model elements returned by those attributes, e.g.,
<metric name="AbstractDep" domain="package"> <description> The number of abstract classes the class depends on. </description> <projection relation="depclient" target="dependency" element="depsupplier" eltype="class" condition="abstract='true'"/> </metric>With attribute "targetcondition" you can specify a condition that is always evaluated for the element returned by the "target" attribute, even if the "element" and "eltype" attributes are used. The following example assumes that "stereotypename" is an attribute or metric that yields the name of the stereotype of a model element:
<metric name="Realize" domain="package"> <description>The number of classes the class realizes.</description> <projection relation="depclient" target="dependency" targetcondition="stereotypename='realize'" element="depsupplier" eltype="class" /> </metric>This metric only counts dependencies with stereotype <<realize>> that have a class as supplier.
With the "scope" attribute, you can filter for elements that fulfill a condition for the scope:
<metric name="SameScope_SupplierClasses" domain="class"> <description>The number of supplier classes of a client class defined in the same scope.</description> <projection relation="depclient" target="dependency" element="depsupplier" eltype="class" scope="same"/> </metric>For a given client class, this projection only accesses supplier classes that are in the same scope as the current class. You can specify one of the following values for the "scope" attribute:
In the above cases, we compared the scope of two elements. There are four more values we can specify for scope: idem/notidem, and containedin/notcontainedin. These test if the element for which the metric is calculated is/is not the scope of the projected element.
For example, we want to define a package coupling metric that counts the number of stimuli sent to instances of classes in the package, from instances of classes outside the package (i.e., stimuli that cross the package boundary).
We assume we have already defined for packages the set "StimRecvSet" of stimuli received by object instances of classes in the package. We need to check, for each stimulus in that set, if type of the sender is a class that is not defined in the current package:
<metric name="StimRecv_ex" domain="package"> <description>The number of stimuli received by object instances of classes outside the package.</description> <projection relset="StimRecvSet" target="stimulus" element="stimsender.objtype.context" scope="notidem" /> </metric>The element attribute accesses the package of the class of the instance that sent the stimuli. The scope attribute value "notidem" only accepts elements that are not identical to the current element for which the metric is calculated. So we only count stimuli where the context of the class of the sender is not the current package - i.e. stimuli sent from outside the package.
To further illustrate the new scope comparisons, consider again the example package hierarchy in Figure 35. The additional values for attribute scope are:
<metric name="NumOps" domain="package"> <projection relation="context" target="class" sum="NumOps" /> </metric>The expression "NumOps" is evaluated for each class in the package. This metric returns the number of operations in the classes in a package.
To return the maximum or minimum of the values evaluated for the sum expression, specify the additional attribute "stat". The stat attribute can take values "max", "min" or "sum" (default value). For example:
<metric name="MaxNumOps" domain="package"> <description>The maximum number of operations in a class of the package.</description> <projection relation="context" target="class" sum="NumOps" stat="max" /> </metric>When you combine "sum" with the "element" and "eltype" attributes, the sum will be taken over the elements returned by those attributes. For example:
<metric name="SupplierOperations" domain="class"> <description>The number of public operations in the supplier classes of a client class.</description> <projection relation="depclient" target="dependency" element="depsupplier" eltype="class" sum="NumPubOps"/> </metric>
Setting the value of "recurse" to true changes the meaning of a metric m for elements of type t as follows: in addition to processing the projection attributes as usual, apply metric m to all "compatible" elements in the projection, and add the result to the return value of the metric. An element is "compatible" if its type is t, or if its type is a subtype or supertype of t and metric m is defined for the type.
<metric name="NumCls_tc" domain="package"> <description>The number of classes in a package, its subpackages, and so on.</description> <projection relation="context" target="class" recurse="true"/> </metric>Without the "recurse" attribute being set to true, the above metric would just count the number of classes in a package. With recurse set to true, metric NumCls_tc is in addition recursively applied to all subpackages that are in the context of a package, and the values added up. As result, you not only obtain the number of classes in the package, but also in its subpackages, sub-subpackages, and so on. In Figure 35, the metric NumCls_tc yields the following values:
Package | NumCls_tc |
---|---|
package1 | 6 |
package1.1 | 3 |
package1.1.1 | 1 |
package1.2 | 1 |
Table 4: Example package metrics
As another example, the number of descendents of a class can be defined as follows:
<metric name="NumDesc" domain="class"> <description>The number of descendents of a class.</description> <projection relation="genparent" target="generalization" element="genchild" eltype="class" recurse="true"/> </metric>Without the "recurse" attribute, this metric would count the number of children of a class. With "recurse" being set to true, the metric NumDesc is recursively evaluated for each child class, and added to the total.
Care must be taken when the "recurse" attribute is combined with the filter attributes (target, targetcondition, eltype, element, condition, scope). The filter attributes are NOT evaluated for the selection of elements on which to apply the metric recursively. The metric is always recursively applied to all elements in the unfiltered projection that are compatible with the element for which the metric is calculated. Consider this example:
<metric name="NumDesc_SameScope" domain="class"> <projection relation="genparent" target="generalization" element="genchild" eltype="class" scope="same" recurse="true"/> </metric>This will not work. While only child classes in the same scope are counted, the metric is recursively applied to all child classes (regardless of their scope), and the number of their "same scope descendents" is added to the total. To define the intended metric, you would first define the set (see Section 8.5.3 "Set Expressions") of descendents of the class, and then define a projection to filter the classes with the desired scope in that set.
The "nesting" attribute takes the values "true" or "false" (default). When "nesting" is set to true for a project metric m for type t, the metric is recursively applied to all "compatible" elements in the projection. An element is "compatible" if its type is t, or if its type is a subtype or supertype of t and metric m is also defined for the type. The maximum value of all values returned, increased by one, is the value of the metric. Example:
<metric name="DIT" domain="class"> <description>Depth of the class in the inheritance tree.</description> <projection relation="genchild" target="generalization" element="genparent" eltype="class" nesting="true"/> </metric>Without the nesting="true" attribute, the above metric would simply count the number of parent classes of a class. With "nesting" set to true, the meaning of the metric changes as follows: calculate the DIT for all parents of the class, take the maximum DIT over all parent classes, increase it by one. As a result, this metric calculates the longest path from the class to the root in the inheritance graph.
The "nesting" attribute cannot be combined with the "sum" attribute or the "recurse" attribute. It can, however, be combined with all other attributes, including the filter attributes (target, targetcondition, element, eltype, scope, condition). Recursive calls are not applied to elements that do not fulfill the filter criteria.
function projection(modelelement me) is: // init result value result := 0 // retrieve elements specified by attribute "relation" if(relation specified) rel := all elements in relation with me else rel := result of set expression applied to me while(still elements in rel): el := next element in rel // filter "target" attribute and condition if(target specified and el is not one of the types) goto invalid if(targetcondition specified and el does not fulfill it) goto invalid // handle indirect relations (attributes "element" and "eltype") if(element specified) el := el.element if(eltype specified and el is not one of the types) goto invalid endif // filter "condition" attribute if(condition specified and el does not fulfill it) goto invalid // filter "scope" attribute if(scope specified and el does not fulfill scope) goto invalid if(nesting) if(el is compatible with me) result:=max(result, projection(el)+1) continue // next iteration of the while loop else if(sum specified) if(stat="max") result:=max(result,sum(el)) else if(stat="min") result:=min(result,sum(el)) else result:=result+sum(el) // default no attribute "stat" else result:=result+1 invalid: if(recurse) if(el is compatible with me) result:=result+projection(el) endwhile return result endfunction
<metric name="AvgOps" domain="package"> <description>The average number of operations in the classes of the package.</description> <compoundmetric term="NumOps/NumCls" fallback="0"/> </metric>The term attribute defines the metric expression to be evaluated, which is the return value of the metric. The optional fallback expression is used if the metric expression returns a "NaN" (not a number) or infinity. In that case, the fallback expression is evaluated and returned as metric result. This is useful to catch, e.g., divisions by zero and still have a well-defined metric result.
A second version of the compound metric allows the conditional evaluation of expressions:
<metric name="accessedOps" domain="class"> <description>The number of public operations of the class, if it has any clients, or 0 else.</description> <compoundmetric condition="NumClients>0" term="NumPubOps" alt="0" /> </metric>For the conditional version of the compound metric, a conditional expression (condition) and an alternative metric expression (alt) is specified in addition to the term attribute. Their meaning is as follows. First, the condition expression is evaluated. If it evaluates to "true", the term expression is evaluated and returned as value of the metric (value 1 is returned if no term expression is specified). If the condition expression is "false", the alt metric expression is evaluated and returned as result of the metric (value 0 is returned if no alt expression is specified).
<metric name="visibility" domain="class"> <description>The visibility of the operation.</description> <attributevalue attr="visibility"/> </metric>An attributevalue element has three attributes:
<metric name="typename" domain="parameter"> <description>The parameter type name.</description> <attributevalue element="parametertype" attr="name"/> </metric>
The nesting procedure has one required attribute, relation, specifying the relation which establishes the nesting relationship. Usually, this will be the "context" relation. For example:
<metric name="NestingLevel" domain="package"> <nesting relation="context" /> </metric>The calculation procedure is as follows: retrieve the element that is referenced by the specified relation (here: context). If this element is "compatible", recursively calculate the metric for that element, increase by one, and return that value. Else (context element is not of the same type, e.g., top level packages defined in the namespace of the model), the metric yields 0.
An element is "compatible" with nesting metric m of domain type t if the type of the element is t, or if its type is a subtype or supertype of t and metric m is defined for the type.
For a typical package hierarchy, the results are (column NestingLevel):
Package | NestingLevel | LNest |
---|---|---|
javax | 0 | 2 |
javax.swing | 1 | 1 |
javax.swing.event | 2 | 0 |
How does the nesting procedure is differ from projections using the nesting attribute? Projections navigate the specified relation in the opposite direction. A projection operates on all elements where the specified relation points to the current element for which the projection is calculated (navigation to the current element). The nesting procedure, on the other hand, simply navigates from the current element to the element referenced by the specified relation (navigation away from the current element). Consequently, a metric LNest defined as a projection as follows would count the levels of nesting within a package (as shown in column "LNest" of Table 5):
<metric name="LNest" domain="package"> <description>Levels of package nesting in this package.</description> <projection relation="context" target="package" nesting="true" /> </metric>
<metric name="Signature" domain="operation" > <description>The signature of an operation.</description> <signature set="OPParameters" element="parametertype" /> </metric>The elements that constitute the parameter list are specified by attribute set. Each element in the set can be subjected to the usual filters (target, element, eltype, condition, targetcondition, scope). In the above example, set OPParameters is defined to be the set of parameters of the operation, and we include the type of each parameter in the signature instead of the parameter itself.
The result is a string that contains the name of the operation, and the IDs of the types of the parameters in the order the parameters are defined in the XMI source file, for example 'getBalance(xmi4893,xmi238011)'. With these strings you can build, for instance, signature sets of the operations of a class and define metrics dealing with method overriding.
To obtain more human readable signature strings, you can further define the presentation of the elements on the signature list. The following definition generates signature strings such as 'getBalance[account:int; time:TimeStamp]':
<metric name="Signature2" domain="operation" > <description>Operation signature with brackets and parameter names/types in the clear.</description> <signature prologue="name+'['" set="OPParameters" value="name+':'+parametertype.name" separator="'; '" epilogue="']'" /> </metric>The attributes to control the presentation are:
The procedure has four attributes:
<metric name="InhHierarchies" domain="package"> <description>The number of inheritance hierarchies.</description> <connectedcomponents set="Classes" nodes="Parents+Children" /> </metric>The following example uses an undirected graph and only counts connected components with at least two nodes (i.e., ignoring isolated classes that do not participate in any inheritance relationship):
<metric name="InhHierarchies" domain="package"> <description>The number of non-trivial inheritance hierarchies.</description> <connectedcomponents set="Classes" nodes="Parents" undirected="true" minnodes="2" /> </metric>
The following example uses the "filtervalue" procedure to access the data value of a particular tagged value pair. We assume elements of type "taggedvalue" have two metrics "TagName" and "TagData", which yield the name and data value of the tagged value, respectively.
<metric name="Author" domain="class"> <description>Author of the class.</description> <filtervalue relation="context" target="taggedvalue" condition="tagName='Author'" value="tagData"/> </metric>The above metric takes the first tagged value with tag name 'Author' that it finds, evaluates the expression specified by the "value" attribute for that tagged value, and returns the result of that expression as the result of the metric.
The "filtervalue" procedure has the following attributes:
For example, to count for a package the number of operations that are defined in interfaces, classes (including inner classes), subsystems etc. of the package and all of its subpackages and subsystems, sub-subpackages and so on, at any level, we can simply write:
<metric name="NumOps_tc" domain="package"> <description>The number of operations defined in the package, its subpackages, etc.</description> <subelements target="operation"/> </set>One could achieve the same result with projections, however, it would require the definition of several helper metrics to gather operations in interfaces, classes, etc. separately and take the sum of these metrics.
The subelements procedure takes the following, optional, attributes:
<metric name="width_string" domain="diagramelement"> <substring source="geometry" separator="','" position="2"/> </metric>Attribute source specifies a metric expression that yields the string we want to dissect. Attribute separator specifies the separator string, which can be of any length. This will usually be just a string constant, so it needs to be enclosed in single quotes (see Section 8.5.1.1 "Constants"). In our example, the separator is a single comma, which will split the string "206, 320, 69, 13" into four parts: "206", "320", "69", and "13". Leading or trailing whitespaces are automatically trimmed from each substring.
With the position parameter, we tell SDMetrics which substring we want; the first substring is at position 0, position 2 yields the third value, "69". With negative positions -1, -2, etc. we can access the last substring, last but one substring, and so on, without having to know how many substrings there are. The default value for attribute position is -1 (return the last substring).
Often we do not want to use the substring as is, but process it further, e.g., retrieve its numerical value. We can do this directly with the optional attribute result. The following metric extracts the first value of a comma-separated list and converts it to a number:
<metric name="left" domain="diagramelement"> <substring source="geometry" separator="','" position="0" result="parsenumber(_value)"/> </metric>Attribute result specifies a metric expression, variable _value contains the value of the substring we extracted.
In a source string such as "Left=620 ; Top=743 ; Right=732 ;", the beginning and end of the interesting substrings are delimited by different separators. The optional argument endseparator deals with this situation:
<metric name="top" domain="diagramelement"> <substring source="location" separator="'Top='" endseparator="';'" result="parsenumber(_value)"/> </metric>The separator 'Top=' cuts up the source string into two substrings: "Left=620 ;" and "743 ; Right=732 ;". Of these, we use the second (last) substring, because the position argument is missing. The attribute endseparator="';'" instructs SDMetrics to cut off the substring at the first semicolon. After automatic trimming of whitespace, this leaves us with the string "743", which then gets converted to the number 743.
The "substring" procedure has the following attributes:
We distinguish two types of sets:
The distinctions element/value set and multi/regular set are orthogonal. You can have regular element or value sets as well as multisets of elements and multisets of values.
A set is defined with an XML element as follows:
<set name="setname" domain="setdomain" multiset="false|true" inheritable="true/false"> <description>Description of the set</description> <'set definition' ...> </set>The attributes in the opening "set" tag are:
Following the "set" tag is an optional description of the set, enclosed in <description> tags. Set descriptions are currently not shown to the end user, and mostly serve as comments for the maintainer of the metric definition file. See Section 8.6 "Writing Descriptions" how to write (set) descriptions.
Following the description is an XML element that defines the calculation procedure for the set. We describe these calculation procedures in detail in the following subsections.
<set name="Children" domain="class"> <description>The set of children of the class.</description> <projection relation="genparent" target="generalization" element="genchild" eltype="class"/> </set>The following example defines a multiset containing the supplier classes to which a class has a dependency link. If a class has multiple dependency links to the same supplier class, that supplier class will occur multiple times in the resulting set.
<set name="SuppClasses" domain="class" multiset="true"> <description>The supplier classes of which the class is a client in a dependency.</description> <projection relation="depclient" target="dependency" element="depsupplier" eltype="class" /> </set>The following attributes we know from projections for metrics (see Section 8.1.1 "Projection") are also available for set projections:
For example, we can easily change the definition of the set of children classes from above to yield the element set of descendents classes:
<set name="Descendents" domain="class"> <description>The set of descendents of a class.</description> <projection relation="genparent" target="generalization" element="genchild" eltype="class" recurse="true"/> </set>For each child class, the set "Descendents" is evaluated, and the union of all these sets is taken. The child classes themselves also are included in the set. This effectively produces the set of descendents for a class.
In the following example, we assume we have defined two sets for classes:
<set name="AssCls" domain="package"> <projection relation="context" target="class" set="AssCls+DepCls" /> </set>The sets referenced in the set expression may be either all element sets, or all value sets (see also Section 8.2.1.4 "Attribute "valueset""). The resulting set will be of the same type as the constituent sets.
When exclude_self is set to true, the element for which the set is calculated is not included in the result set. For example, assume we want to determine, for a state in a state diagram, the set of states that can be reached from that state:
<set name="Reachable_States" domain="state"> <description>The set of reachable states.</description> <projection relation="transsource" target="transition" element="transtarget" eltype="state" recurse="true" exclude_self="true" /> </set>By setting exclude_self to true, the state for which the set is calculated is not included in the result set, even if the state-transition graph has loops back to the state.
For example, if a metric "Signature" for operations contains the signature string of the operation, you can define the set of signatures for the operations of a class as follows:
<set name="Sigs" domain="class"> <description>The set of signatures of the operations in the class.</description> <projection relation="context" target="operation" valueset="Signature" /> </set>The metric expression "Signature" is evaluated for each operation; the resulting values are stored in the set.
Attribute valueset may be used in combination with all other attributes except set or exclude_self.
For example, to define the set of all actors in a model, we simply write:
<set name="ActorSet" domain="model"> <description>The set of actors defined in the model.</description> <subelements target="actor"/> </set>The subelements procedure for sets takes the following, optional, attributes:
The SDMetricsML defines a design rule with an XML element like follows:
<rule name="rulename" domain="ruledomain" category="rulecategory" severity="ruleseverity" applies_to="ruleapplication" disabled="false|true" inheritable="true/false" > <description>Description of the rule.</description> <'rule definition'" /> </rule>The attributes of the enclosing "rule" tag are:
applies_to="design"
or
applies_to="analysis,realtime"
.Following the rule description is an XML element that defines the check the rule performs. There are a number of procedures to choose from:
For example, consider a rule stating that an abstract class should have child classes. The abstract class itself cannot be instantiated, so child classes are required for the class to become useful in the system. That rule can be defined as follows:
<rule name="NoSpec" domain="class" category="Completeness" severity="2-med"> <description>Abstract class has no child classes, must be specialized to be useful.</description> <violation condition="abstract='true' and NOC=0" /> </rule>The condition expression condition checks if the class is abstract, and the number of children of the class (metric NOC) is zero.
The "violation" procedure has two attributes;
You can use this procedure to check for cyclic dependencies between model elements. For example, the following rule checks for cycles in the inheritance graph for classes:
<rule name="CyclicInheritance" domain="class" category="Inheritance" severity="1-high"> <description>Class inherits from itself!</description> <cycle nodes="Parents" /> </rule>The domain of the rule is "class", so the classes of the model constitute the nodes of the graph. The required set expression nodes yields, for each node, the set of connected nodes to which there is an edge. In our example, set "Parents" is the set of parents of the class.
To report the SCCs found, each model element in a SCC receives a label of the form "cyc# c (n nodes)", where c is the number of the SCC, and n is the number of nodes in the SCC.
The rule reports a violation for each model element in each SCC. The value of the rule shown to the user is the label of the model element. That way, users can tell which elements belong to which connected component.
In some cases, you may only want to report SCCs of a certain size, for instance, SCCs with three or more nodes. To this end, the cycle procedure has a second, optional attribute minnodes, which specifies the minimum number of nodes an SCC must have to be reported. The default is 1 (nodes having an edge back to themselves).
<rule name="MissingGuard" domain="state" category="Correctness" severity="1-high"> <description>If there are two or more transitions from a choice state, they all must have guards.</description> <projection precondition="kind='choice' and Outgoing>1" relation="transsource" target="transition" condition="Guards=0" </rule>Attributes relation, target, and condition define the projection, and are processed exactly the same way as we know them from set projections. In the above example, the projection yields, for a state, the set of outgoing transitions that have no guard.
In the context of a rule, a projection can have three additional attributes:
<rule name="DupName" domain="state" category="Correctness" severity="1-high"> <description>The compound state has two or more states of the same name.</description> <projection relation="context" target="state" condition="name!=''" valueset="name" mincnt="2" /> </rule>For rule "DupName", the projection defines the value set of names of the states of a compound state (omitting anonymous states). By setting mincnt="2", only names that occur two times or more will be reported.
Note the following restrictions that apply in the context of a rule projection:
For example, assume we have a value set "AttrNameSet" defined for classes, which is a multiset of the names of the attributes of the class. We can then define a rule to check for duplicate names:
<rule name="DupAttrNames" domain="class" category="Correctness" severity="1-high"> <description>The class has two or more attributes with identical names.</description> <valueset set="AttrNameSet" mincnt="2" /> </rule>The procedure evaluates the set expression for the class, and reports all elements that occur at least twice. The value of the rule is the value of the reported element in the value set; in our example this is the duplicate attribute name. In addition to the set attribute, the valueset procedure accepts the attributes mincnt and precondition, which have the same meaning as they have for rule projections (see Section 8.3.3 "Projection for Rules").
To check model element names for keywords, we define a list of keywords in the metric definition file, and add a rule that checks for names on the list:
<wordlist name="CPP" ignorecase="false" > <entry word="auto" /> <entry word="bool" /> <entry word="break" /> ... </wordlist> ... <rule name="Keyword" domain="class" category="Naming" severity="1-high"> <description>Class name is a reserved C++ keyword.</description> <violation condition="name onlist CPP" value="name" /> </rule>The word list is enclosed in wordlist tags. Attribute name defines a name for the keyword list, which is used later to reference the list. Attribute ignorecase controls if uppercase and lowercase letters should be distinguished when searching for a word on the list. For programming languages that are case-insensitive, set ignorecase to "true". By default, ignorecase is "false" (i.e., case-sensitive search).
The word list contains a list of entry elements. An entry has one required attribute word that contains one word of the list.
To check if a word is on a list, there is a special operator onlist which you can use in condition expressions (see Section 8.5.4 "Condition Expressions"). In the condition expression of rule "Keyword" above, the left hand operator yields the name of the class, the right hand operator is the name of the word list to check.
Note that while the main purpose of the word list feature is to define design rules checking for keywords, there are other possible uses:
This information is specified in two attributes of the XML root element of the metric definition file, for example:
<sdmetrics version="2.1" ruleexemption="taggedvalue" exemptiontag="tagname" > ...
<matrix name="matrixname" from_row_type="rowtype" to_col_type="columtype" row_condition="..." col_condition="..." > <description>Description of the matrix.</description> <'matrix definition' ...> </matrix>The attributes in the opening "matrix" tag are:
The matrix definition is an XML element that defines the calculation procedure establishing the relationship from source elements to target elements in the matrix. The available procedures are the same ones that are available for set definitions (see Section 8.2 "Definition of Sets").
The definition below calculates a relation matrix showing UML abstractions for classes, i.e., which classes implement which interfaces:
<matrix name="UMLAbstr" from_row_type="class" to_col_type="interface"> <description>UML abstractions: implementation of interfaces.</description> <projection relation="client" target="abstraction" element="supplier" /> </matrix>The evaluation procedure for this matrix definition is as follows. For a source element in a given matrix row, the projection is evaluated, as described in Section 8.2.1 "Projection". The number of occurrences of each target element in that projection is counted, and entered in the respective column of the relation matrix. This procedure is repeated for all rows of the matrix. Thus, we obtain in every table cell, a count of the number of relationship links from the source element to the target element.
In the context of a relation matrix definition, the set projection attribute recurse is not allowed.
As another example, assume we have defined, for a class, the set "AssEl_out" of elements associated with the class by bidirectional or navigable outgoing associations. We can then simply create a relation matrix showing these associations as follows:
<matrix name="ICAssoc_Cls" from_row_type="class" to_col_type="class"> <description>Import coupling via associations between classes.</description> <projection relset="AssEl_out" /> </matrix>The evaluation procedure for this matrix definition is straightforward. For a source element in a given matrix row, the specified set expression "relset" is evaluated, as described in Section 8.2.1 "Projection". For each target element, the value of the respective column of the relation matrix is the cardinality of the target element in the set. This procedure is repeated for all rows of the matrix.
Continuing the association matrix example from above, we may want to reduce the size of the matrix by dropping empty rows (classes without outgoing associations), and empty columns (classes without incoming associations). Assume we have defined two helper metrics "Num_AssEl_out" and "Num_AssEl_in", to count for a class the number of outgoing and incoming associations, respectively. We add row and column conditions to the matrix definition as follows:
<matrix name="ICAssoc_Cls" from_row_type="class" to_col_type="class" row_condition="Num_AssEl_out>0" col_condition="Num_AssEl_in>0"> <description>Import coupling via associations between classes.</description> <projection relset="AssEl_out" /> </matrix>Then, only classes with one ore more outgoing associations (Num_AssEl_out>0) will be included in the rows of the matrix, which eliminates empty rows. Likewise, the column condition Num_AssEl_in>0 eliminates empty columns.
A string constant is a sequence of characters, enclosed in single quotes, for example: 'public'. A string constant may contain any sequence of digits (0-9), letters (a-z, A-Z), whitespace, and the underscore (_).
Number constants (integer or floating point) are denoted in the usual way:
An identifier is a letter or underscore (a-z, A-Z, _), followed by any sequence of digits (0-9), letters, and underscores. Examples of valid identifiers are: i, visibility, NumOps, set2, Metric1_a, _principal.
By convention, attribute names start with a lowercase letter, metric and set names with an uppercase letter, and variables with an underscore. This avoids name conflicts between metrics or sets, attributes, and variables.
Variables
Metric, set, and rule procedures can define variables that hold values with special meanings for the calculation. We access those values in metric, set, and condition expressions. SDMetrics defines two standard variables _self and _principal.The variable _self yields the model element for which the expression is evaluated. The variable _principal yields the model element for which the current metric, set, or rule is evaluated. Variables _principal and _self need not be the same element. In fact, the variable _principal should only be used if it is not the same element as _self.
Consider the following example. The metric counts, for a class A the supplier classes on which A depends that have more operations than A:
<metric name="SuppliersWithMoreOperations" domain="class"> <description>The number of classes this class depends on that have more operations.</description> <projection relation="depclient" target="dependency" element="depsupplier" eltype="class" condition="_self.NumOps > _principal.NumOps"/> </metric>The condition expression is evaluated, in turn, for each supplier class of class A. The variable _self therefore refers to the supplier class. The variable _principal always refers to class A for which the metric is calculated.
Note: the condition expression in the above example could have also been written as "NumOps > _principal.NumOps", because the identifier NumOps will be interpreted as a metric of the model element for which the expression is calculated. In fact, variable _self is rarely needed in practice. Useful applications include
Math functions
1. The "dot" operator
In an expression term, it is sometimes useful to refer to attribute or metric values of related elements. This can be accomplished with the dot operator.
For example, to access the name of an operation's owner, we simply write "context.name". The left hand side expression "context" refers to the owner of the operation, which has to be some model element (e.g., a class). For that model element, the right hand side expression "name" is evaluated, which yields the value of its attribute "name".
In general, evaluating expression a.b for a model element e works as follows:
If we want to determine the package in which a class resides, we usually access it via its "context" attribute. However, the owner of a class need not be a package. For an inner class, for example, the "context" attribute yields its containing outer class.
The "upto" operator in the expression "context upto (typeof(self)='package')" follows the "context" attribute, across several model elements if necessary, until it encounters a model element of type package.
In general, evaluation of the expression a upto b for a model element e works as follows:
Similar to the "upto" operator, the "topmost" operator successively evaluates a condition expression for a chain of model elements. Whereas the "upto" operator returns the first element that fulfills the condition expression, the "topmost" operator returns the last element in the chain that fulfills the condition expression.
The "topmost" operator in the expression "context topmost (typeof(self)='package')" yields the top-level package that contains the element.
In general, evaluation of the expression a topmost b for a model element e works as follows:
An example for a valid set expression is ((A+B)-context.C)*D.
In addition, you can use the following operators on the indicated types:
An example of a condition expression with logical operators is: (NumOps>5) & (!(NumAttr=3 | islowercase(name))
Below is an example of a full-fledged metric description. Descriptions of rules and matrices follow the exact same scheme.
<metric name="DIT" domain="class"> <description>The depth of the class in the inheritance tree.((p)) This is calculated as the longest glossary://Path/path/ from the class to the root of the inheritance tree. The DIT for a class that has no parents is 0.((p)) ((ul)) ((li))Defined in ref://CK94/. ((li))See also metric://class/CLD/. ((/ul)) </description> <projection relation="genchild" target="generalization" element="genparent" eltype="class" nesting="true"/> </metric> <reference tag="CK94">S. Chidamber, C. Kemerer, "A Metrics Suite For Object Oriented Design", IEEE Trans. Software Eng., vol. 20, no. 6, pp. 476-493, 1994. </reference> <term name="Path">A sequence of adjacent nodes in a graph. The sequence of nodes n(1), n(2), ..., n(m) is a path if there is an edge from n(i) to n(i+1) for all i from 1 to m-1. <term>Below we explain the various features available for writing descriptions.
HTML markup
The detailed description is meant to be rendered in HTML. Therefore,
you can use HTML markups to format your text. However, because the
description is embedded in an XML file, you cannot use the plain HTML
tags. They would interfere with the XML structure. Instead of
enclosing your HTML tags with the usual < and > brackets, use
two subsequent opening and closing parentheses, like so: Text in
((i))italics((/i)) or ((b))boldface((/b))
.
The example for metric DIT above uses paragraphs and bulleted lists to format the text.
Brief description and full description
The first sentence of the description should be a short definition that states the basic idea what the metric (or rule or matrix) is about. It should be brief enough to fit in a single line (about 80 characters, but that is no hard limit).
This first sentence must be terminated by a period, and must not use any HTML markup. The brief descriptions are used in various windows of SDMetrics' GUI, to provide short explanations of metrics, rules, and matrices. The description then continues with additional explanations, and notes about the metric. Together with the brief description, this constitutes the full description shown in the measurement catalog.
Cross-references
You can reference other metrics, rules, or matrices in the description.
metric://<domain>/<name>/
. For example, to
cross-reference a metric NumOps
for elements of type
class
, write metric://class/NumOps/
. Note
the terminating slash at the end of the locator.
rule://<domain>/<name>/
. For example, rule
CyclicInheritance
for classes is referenced by
rule://class/CyclicInheritance/
.
matrix://<matrix>/
. A matrix named "Associations"
would be referenced by matrix://Associations/
.Literature references
To provide literature references, you define a bibliographic citation
as in the above example for metric DIT. The citation is enclosed in a
reference
XML element. The element has one required
attribute, tag
, which provides a handle for the
citation.
You can then reference the citation in your metric, rule, and matrix
descriptions with a locator of the form
ref://<reference_tag>/
, for example
((li))Suggested in ref://CK94/.((/li))
. In the
measurement catalog display, the locator will be replaced by a
hyperlink that takes the user to the bibliographic citation of the
specified reference.
Glossary terms
If your description uses any terms that you feel need additional explanation, you can define these terms in a glossary. The above example for metric DIT shows a definition for the term "Path".
The term definition is enclosed in a term
XML
element. The element has one required attribute, name
,
which is the term that is defined. You can then use the term in your
description with a locator as follows:
glossary://<Name of term>/<hyperlink text>/In the full description as shown to the user, the locator will be replaced by a hyperlink that takes the user to the definition of the term. If the name of the glossary term and the hyperlinked text to be shown are identical, you can leave the hyperlinked text empty, like so:
This is a glossary://WFR// of the UML
(note the two
slashes after "WFR").
The profile mechanism is a lightweight extension mechanism; it does not allow for modifying existing meta-classes or creating new, "first-class citizen" meta-classes in the UML metamodel. UML profiles can, in theory, be easily imported and exchanged between UML tools, and dynamically applied, combined, and retracted from a UML model.
The most prominent example of a UML profile is probably the Systems Modeling Language (SysML), maintained by the Object Management Group [OMG10]. SysML is an extension of the UML to better support systems engineering. Other profiles maintained by the OMG include MARTE (Modeling and Analysis of Real-time and Embedded Systems), UTP (UML Testing Profile), and SoaML (Service oriented architecture Modeling Language).
How are UML profiles relevant to design quality measurement?
If you extensively use profiles in your models, it is useful to define design metrics and rules that take profile extensions into account. A SysML model, for example, contains elements such "blocks", "requirements", or "test cases", and relationships between them such as "allocate", "refine", or "verify", all defined by the SysML profile. As a SysML user, you may therefore be interested in the number of blocks and requirements in the packages, measure the size of blocks in terms of their part-, reference-, and value-properties, define rules such as "requirements that are not further decomposed or refined must be verified by a test case", or create a relation matrix showing the allocation of activities to blocks.
The following extract of an XMI file shows a SysML model with a package named "Specifications". The package contains a requirement "XYZ" with Id "R1" and text "The system shall do XYZ":
<xmi:XMI xmi:version="2.1" ...> <uml:Model name = "Sample" xmi:id = "xmi1"> <packagedElement xmi:type="uml:Package" xmi:id="xmi2" name="Specifications"> <packagedElement xmi:type="uml:Class" xmi:id="xmi3" name="XYZ" /> </packagedElement> ... </uml:Model> <sysml:Requirement base_Class="xmi3" xmi:id="xmi7" Text="The system shall do XYZ" Id="R1" /> </xmi:XMI>As a consequence of the UML's lightweight extension approach, two XML elements are needed to represent the requirement:
<modelelement name="requirement"> <attribute name="base" type="ref">The extended class.</attribute> <attribute name="text">Text of the requirement.</attribute> <attribute name="reqid">ID of the requirement in the model.</attribute> </modelelement>The XMI transformation to process the <sysml:Requirement> XML element looks like this:
<xmitransformation modelelement="requirement" xmipattern="sysml:Requirement"> <trigger name="base" type="attrval" attr="base_Class" /> <trigger name="text" type="attrval" attr="Text" /> <trigger name="reqid" type="attrval" attr="Id" /> </xmitransformation>With these definitions in place, the SysML requirement of the SysML model is represented by two elements in SDMetrics: one "class" element and one "requirement" element.
How do we know if a class in the model represents a SysML requirement? The class represents a SysML requirement if there is a "requirement" element in the model whose reference attribute "base" points to the class. Using the "filtervalue" procedure (Section 8.1.7 "Value Filter"), we can define a "helper" metric that yields the requirement element extending the class, if one exists:
<metric name="Reqmt" domain="class" internal="true"> <filtervalue relation="base" target="requirement" /> </metric>We can then use this metric to identify classes that represent requirements. For example, to count the number of requirements in a package, we count all classes where metric "Reqmt" is not empty:
<metric name="ReqCount" domain="package"> <projection relation="context" target="class" condition="Reqmt!=''" /> </metric>
The following XMI serialization of a SysML model contains a requirement "ABC" with Id "R2". The requirement is decomposed into two sub-requirements "A" and "B" with Ids "R2.1" and "R2.2", respectively:
<xmi:XMI xmi:version="2.1" ...> <uml:Model name = "Sample" xmi:id = "xmi1"> <packagedElement xmi:type="uml:Package" xmi:id="xmi2" name="Specifications"> <packagedElement xmi:type="uml:Class" xmi:id="xmi3" name="XYZ" /> <packagedElement xmi:type="uml:Class" xmi:id="xmi4" name="ABC"> <nestedClassifier xmi:type="uml:Class" xmi:id="xmi5" name="A" /> <nestedClassifier xmi:type="uml:Class" xmi:id="xmi6" name="B" /> </packagedElement> </packagedElement> ... </uml:Model> <sysml:Requirement base_Class="xmi3" xmi:id="xmi7" Text="The system shall do XYZ" Id="R1" /> <sysml:Requirement base_Class="xmi4" xmi:id="xmi8" Text="The system shall do such and such" Id="R2" /> <sysml:Requirement base_Class="xmi5" xmi:id="xmi9" Text="The system shall do A" Id="R2.1" /> <sysml:Requirement base_Class="xmi6" xmi:id="xmi10" Text="The system shall do B" Id="R2.2" /> </xmi:XMI>From the XMI serialization we can see that the SysML uses class nesting to model requirement decomposition. The class to represent the decomposed requirement owns the classes representing the sub-requirements as nested classifiers. We could therefore define a metric counting the sub-requirements like so:
<metric name="SubRequirements" domain="class"> <projection relset="nestedclassifiers" target="class" condition="Reqmt!=''" /> </metric>Defining the metric with the domain "class" has the disadvantage that the metric is calculated for all classes, even those that do not represent requirements at all. A better solution is to define the metric for the domain "requirement":
<metric name="SubReqCount" domain="requirement"> <projection relset="base.nestedclassifiers" target="class" condition="Reqmt!=''" /> </metric>The output of this metric in the metric data tables is not very satisfactory, though:
Because the requirement elements in the model are unnamed and outside the scope of the UML model, we cannot tell from the metric output to which requirement the data belongs.
To improve this situation, SDMetrics' metamodel facility provides extension references. If the extending model element contains an extension reference, SDMetrics moves the extending model element into the same namespace as the extended model element, and copies the name from the extended element.
To use this feature for our SysML requirements, we change the type of cross-reference "base" from "ref" to "extref", thus declaring it an extension reference:
<modelelement name="requirement"> <attribute name="base" type="extref" /> <attribute name="text" /> <attribute name="reqid" /> </modelelement>This instructs SDMetrics to move the requirement model elements into the same namespace as the class they extend, and copy the name of the extended class. Each requirement element thus has the same owner and the same name as the class that represents the requirement. With this modification, the extending elements are easier to handle.
One immediate benefit of extension references is that the elements are
easier to trace in the metric data tables:
Figure 37: Metric output for requirements with extension reference
Another benefit is that we can simplify some metric and rule definitions. Take for example the metric "ReqCount" defined earlier to count the number of requirements in a package. Because the packages now own the requirement elements, we can define the metric in a more straightforward manner without resorting to 'helper' metrics:
<metric name="ReqCount" domain="package"> <projection relation="context" target="requirement" /> </metric>
<metric name="NumCls" domain="package"> <projection relation="context" target="class" /> </metric>Applied to our sample package with four requirements, this metric will include the class elements to represent the requirements in its class count. Depending on our measurement goals, this may not be what we want. We could of course adjust the definition of the metric to only count classes that do not represent requirements:
<metric name="NumClsExceptRequirements" domain="package"> <projection relation="context" target="class" condition="Reqmt=''"/> </metric>This approach is possible, but ugly. If we add support for SysML blocks or other types that also extend "class", we would need to adjust the definition of metric again to exclude classes representing blocks, etc.
To better deal with these issues, SDMetrics provides a mechanism to merge the extended element with its extending element to form a single model element that combines all the information from both elements. To enable this mechanism, we have to define the type of the extending element as a subtype of the extended element.
In our SysML requirements example, we therefore change the definition of type "requirement" by declaring its parent to be type "class":
<modelelement name="requirement" parent="class"> <attribute name="base" type="extref" /> <attribute name="text" /> <attribute name="reqid" /> </modelelement>If SDMetrics finds a model element of type t ("requirement" in our example) with an extension reference, and the type of the extended element is a supertype of t ("class" in our example), then SDMetrics will merge the two elements into one element of type t. The merged element copies all attribute values of the extended element, and the extended element is removed from the model. We are left with one element of type t that assumes the place of the extended element.
Applied to our sample model and XMI file, we end up with a model that contains four requirement elements, no classes, and maintains the expected containment hierarchy:
Model "Sample" Package "Specifications" Requirement "XZY" with Id "R1" Requirement "ABC" with Id "R2" Requirement "A" with Id "R2.1" Requirement "B" with Id "R2.2"The package "Specification" no longer contains elements of type "class", and metric NumCls will yield 0. Model element type "requirement" has become a full, first-class citizen. We can identify requirements simply by the type of the model element, the need for helper metrics is eliminated.
Using reference extensions with inheritance, it is very easy to define metrics that take more complex relationships into account. For example, the SysML defines a relationship "satisfy" to identify the model elements that fulfill a given requirement. The more elements are needed to satisfy a requirement, the more difficult the requirement will be to change. We are therefore going to define a metric that counts the number of elements that satisfy a requirement.
Technically, the SysML defines the "satisfy" relationship as a stereotype that extends the meta-class "abstraction". We therefore create a new model element type "satisfy" with an extension reference as a subtype of "abstraction" (see Appendix A.2 "Metamodel for UML 2.x"):
<modelelement name="satisfy" parent="abstraction"> <attribute name="base" type="extref" /> </modelelement>The type "satisfy" inherits the multi-valued reference attributes "client" and "supplier" from "abstraction". For the XMI transformation, we only have to provide a trigger for the new extension reference "base":
<xmitransformation modelelement="satisfy" xmipattern="sysml:Satisfy"> <trigger name="base" type="attrval" attr="base_Abstraction" /> </xmitransformation>We can then define our metric to count the number of elements that satisfy a requirement:
<metric name="Satisfiers" domain="requirement" category="Coupling (export)"> <description>The number of elements that satisfy this requirement.</description> <projection relation="supplier" target="satisfy" sum="size(client)"/> </metric>
If, for example, the distinction between SysML FlowPorts and plain UML ports is only relevant for a few of your metrics and rules, and you can treat them equally as plain UML ports most of the time, option 1 or 2 is appropriate.
If so, you have to choose option 2 or 3.
In case of SysML requirements, for example, the characteristic of the "class" element changes considerably. Unlike regular classes, requirements can't have operations or attributes, do not participate in associations, can't be generalized, and are usually not instantiated. They are actually not much like classes at all.
In such situations, option 3 is appropriate, because it eliminates the "class" element representing the requirement from the model, and existing metrics and rules that involve classes will ignore the extended element.
Like requirements, a SysML block extends UML meta-class "class". Unlike requirements, however, SysML blocks maintain most of the class features from UML. Yet, as blocks are the central modular structure units of the SysML, the set of metrics and rules to calculate for blocks will probably be quite different from the set of class metrics and rules. Option 3 is therefore the best choice for the central concepts of the profile.
If the stereotype extends two or more meta-classes (for example, SysML TestCase can extend "operation" or "behavior"), you can use option 3 for at most one of the extension, and must use option 1 or 2 for all others. This is because SDMetrics' metamodeling facility does not support multiple inheritance.
On the SDMetrics web site, you can download a set of project files for SysML 1.2, with metrics and rules that cover block definition diagrams, internal block diagrams, parametric diagrams, and requirement diagrams.
The SDMetricsML definitions are read by the "metrics engine", a subsystem of SDMetrics that interprets the metric definitions and calculates the metrics for a UML model accordingly. Likewise, the "rule engine" checks the design rules defined through SDMetricsML for a UML model.
Occasionally, the capabilities of the SDMetricsML/metrics/rule engine may be insufficient or inefficient for your measurement needs. For these situations, the metrics engine and rule engine provide a plugin mechanism to extend their calculation capabilities and the SDMetricsML. The plugins add
This section describes how to develop such plugins for the metrics and rule engine. The plugins are implemented in the Java programming language. Writing a plugin thus requires Java skills and a Java compiler.
<set name="ParaTypeSet" domain="operation"> <description>The set of parameter types of an operation.</description> <projection relation="context" target="parameter" element="parametertype" /> </set>What we would need then is a metric that takes each pair of operations of a class, and counts those pairs that have a parameter type in common. None of the existing metric procedures in SDMetrics accomplish this, but if we had such a procedure, the definition of the metric could look something like this:
<metric name="LCOM_Parametertypes" domain="class"> <description>Lack of cohesion in operations based on operation parameter types.</description> <pairwise relation="context" target="operation" paircondition="size(_first.ParaTypeSet * _second.ParaTypeSet)=0" /> </metric>The idea is that the metric obtains the set of operations of a class (as we would in a standard projection), and then evaluate the "paircondition" expression for each operation pair. The variables "_first" and "_second" in the pair condition refer to the first and second operation of the pair. The given expression evaluates to true if the intersection of the parameter types of the operations at hand is empty.
To make the procedure more flexible, we could have it support a few more attributes:
packacke com.acme; import java.util.Collection; import java.util.Comparator; import com.sdmetrics.math.ExpressionNode; import com.sdmetrics.metrics.*; import com.sdmetrics.model.ModelElement; 01 public class MetricProcedurePairwise extends MetricProcedure { @Override 02 public Object calculate(ModelElement element, Metric metric) throws SDMetricsException { 03 ProcedureAttributes attributes = metric.getAttributes(); 04 Variables vars = new Variables(element); 05 Collection<ModelElement> set = getRelationOrSet(element, attributes, vars); 06 if (set == null) 07 return Integer.valueOf(0); 08 ExpressionNode pairCondition = attributes.getExpression("paircondition"); 09 boolean allTuples = attributes.getBooleanValue("tuples", false); 10 boolean withSelf = attributes.getBooleanValue("withself", allTuples); 11 FilterAttributeProcessor fap = getFilterAttributeProcessor(attributes); 12 SummationHelper sum = new SummationHelper(getMetricsEngine(), attributes); 13 Comparator<ModelElement> comparator = ModelElement.getComparator(); 14 for (ModelElement first : fap.validIteration(set, vars)) { 15 vars.setVariable("_first", first); 16 for (ModelElement second : fap.validIteration(set, vars)) { 17 int comp = comparator.compare(first, second); 18 if (comp == 0 && !withSelf) 19 continue; 20 if (comp > 0 && !allTuples) 21 continue; 22 vars.setVariable("_second", second); 23 if (pairCondition == null || evalBooleanExpression(element, pairCondition, vars)) { 24 sum.add(second, vars); } } } 25 return sum.getTotal(); } }Let's go through the salient points of this implementation line by line:
<metricprocedure name="pairwise" class="com.acme.MetricProcedurePairwise" />Attribute name defines the name to denote the metric procedure in subsequent metric definitions, attribute class defines the fully qualified name of the metric procedure class.
We can then define metrics using our newly created metric procedure. Here's another example of a LCOM-style cohesion metric:
<metric name="LCOC_AttributeTypes" domain="package"> <description>The maximum number of attribute types shared by a pair of classes in a package.</description> <pairwise relation="context" target="class" sum="size(_first.AttrTypeSet * _second.AttrTypeSet)" stat="max" /> </metric>For SDMetrics to find our new metric procedure, we have to ensure that the class is included in the class path when we run SDMetrics. The simplest way to achieve this is by putting the class files in the "bin" folder of the SDMetrics installation directory. SDMetrics searches this directory for class files.
Within the "bin" directory, we have to create a structure of subfolders that reflects the packages of the classes we provide. The class file for class com.acme.MetricProcedurePairwise therefore will be located at the path bin/com/acme/MetricProcedurePairwise.class.
packacke com.acme; import java.util.Collection; import com.sdmetrics.math.ExpressionNode; import com.sdmetrics.metrics.*; import com.sdmetrics.model.ModelElement; 01 public class SetProcedureCondition extends SetProcedure { @Override 02 public Collection<?> calculate(ModelElement element, Set set) throws SDMetricsException { 03 ProcedureAttributes attributes = set.getAttributes(); 04 ExpressionNode condexp = attributes.getRequiredExpression("condition"); 05 ExpressionNode setExpr = attributes.getRequiredExpression("set"); 06 ExpressionNode altExpr = attributes.getRequiredExpression("alt"); 07 Variables vars = new Variables(element); 08 boolean condition = evalBooleanExpression(element, condexp, vars); 09 if (condition) return evalSetExpression(element, setExpr, vars); 10 return evalSetExpression(element, altExpr, vars); } }Some of the metrics engine API used here we already know from (Section 9.1.2 "Implementation of the Metric Procedure"), so we focus the discussion of this implementation on new features:
For further examples on how to programmatically manipulate regular and multisets, see Section 9.6 "Set Functions".
To use our new set procedure, we register it with the metrics engine as follows:
<setprocedure name="conditionalset" class="com.acme.SetProcedureCondition" />Again, we deploy the class file of the procedure class in the "bin" folder of our SDMetrics installation (path com/acme/SetProcedureCondition.class). After that, we can write set definitions using the new set procedure. For example:
<set name="InterestingObjects" domain="class"> <conditionalset condition="'DataStore' in StereoTypeNames" set="AttributeSet" alt="OperationSet" /> </set>Note: every set procedure can also be used to define relation matrices (cf. Section 8.4 "Definition of Relation Matrices"). This includes custom set procedures you defined yourself.
To define the contents of the approved list of verbs and objects, we will use word lists (see Section 8.3.5 "Word lists"). The definition of the rule including the word lists could look something like this:
<wordlist name="VerbList"> <entry word="create"/> <entry word="read"/> <entry word="update"/> <entry word="delete"/> </wordlist> <wordlist name="ObjectList"> <entry word="bank account"/> <entry word="customer"/> <entry word="credit"/> </wordlist> <rule name="ActionNames" domain="action"> <description>Checks that action names contain only approved verbs and objects.</description> <verbobject term="name" condition="!(_verb onlist VerbList) or !(_object onlist ObjectList)" value="'Illegal name: '+name" /> </rule>Attribute term is a metric expression that yields the string to be checked. The attribute can be optional, when omitted, the procedure will check the name of the model element.
The procedure then has to extract the verb and object part from the result of the term expression, and pass the values as variables _verb and _object to the condition expression. If the expression evaluates to true, the procedure reports a violation, the value of the violation, as usual, given by the value expression.
Here is a possible implementation of such a rule procedure:
import com.sdmetrics.math.ExpressionNode; import com.sdmetrics.metrics.*; import com.sdmetrics.model.ModelElement; 01 public class RuleProcedureVerbObject extends RuleProcedure { @Override 02 public void checkRule(ModelElement element, Rule rule) throws SDMetricsException { 03 String name = element.getName(); 04 ProcedureAttributes attributes = rule.getAttributes(); 05 ExpressionNode term = attributes.getExpression("term"); 06 Variables vars = new Variables(element); 07 if (term != null) 08 name = evalExpression(element, term, vars).toString(); 09 int boundary = name.indexOf(' '); 10 if (boundary < 0) { 11 reportViolation(element, rule, "Element does not specify an object."); 12 return; } 13 String verb = name.substring(0, boundary); 14 String object = name.substring(boundary + 1); 15 vars.setVariable("_verb", verb); 16 vars.setVariable("_object", object); 17 ExpressionNode condition = attributes.getRequiredExpression("condition"); 18 if (evalBooleanExpression(element, condition, vars)) { 19 Object value = getRuleValue(element, attributes, vars); 20 reportViolation(element, rule, value); } } }The example illustrates a number of new features of the metrics and rule engine API:
<ruleprocedure name="verbobject" class="com.acme.RuleProcedureVerbObject" />After that, we can define rules using our new rule procedure, such as rule "ActionNames" from above.
For example, let's assume we have a modeling process that defines a stereotype 'foobar' for classes. Classes of this stereotype must either contain operations, or contain attributes, or specialize some other class. We wish to define a rule that checks this constraint for 'foobar' classes.
Checking each condition individually is simple. We can use SDMetrics' standard class metrics NumOps, NumAttr, and DIT (depth of inheritance tree), and compare their values to 0. However, the condition expressions to assert that exactly one of the conditions holds is cumbersome:
(NumOps!=0 and NumAttr=0 and DIT=0) or (NumOps=0 and NumAttr!=0 and DIT=0) or (NumOps=0 and NumAttr=0 and DIT!=0)The length of the condition expressions grows with the square of the number of individual conditions to check. The efficiency of evaluating this condition expressions is suboptimal, as each individual condition may be evaluated several times. A Boolean function that calculates the "exclusive or" (XOR) for any number of conditions would be convenient in this situation. The condition expression could then be boiled down to
xor(NumOps!=0, NumAttr!=0, DIT!=0)With this new XOR function, we could define our rule as follows:
<rule name="FooBarCondition" domain="class"> <description>Classes with stereotype foobar must either define operations, attributes, or specialize another class.</description> <violation condition= "('foobar' in StereotypeNames) and !xor(NumOps!=0, NumAttr!=0, DIT!=0)" /> </rule>The condition term of the rule first checks if the class is of stereotype 'foobar' (we assume value set
StereotypeNames
was defined elsewhere to contain
the names of the stereotypes of the class). If the stereotype condition holds, and
the XOR function returns false
, the condition term is true
and the rule violation will be reported.
true
if exactly one of the individual condition expressions is true
:
package com.acme; import com.sdmetrics.math.ExpressionNode; import com.sdmetrics.metrics.BooleanOperation; import com.sdmetrics.metrics.SDMetricsException; import com.sdmetrics.metrics.Variables; import com.sdmetrics.model.ModelElement; 01 public class BooleanOperationXOR extends BooleanOperation { @Override 02 public boolean calculateValue(ModelElement element, 03 ExpressionNode node, 04 Variables vars) 05 throws SDMetricsException { 06 int trueConditions = 0; 07 int index = 0; 08 while (index < node.getOperandCount() && trueConditions <= 1) { 09 if (evalBooleanExpression(element, node.getOperand(index), vars)) { 10 trueConditions++; } 11 index++; } 12 return trueConditions == 1; } }Let's go through the salient points of this implementation line by line:
element
is the model element for which to evaluate the function.
node
contains the operator tree for the function call.
The operands of the node represent the arguments passed into the function.
vars
contains the values of the variables defined for the
evaluation of the arguments to the function.
boolean
.
<booleanoperationdefinition name="xor" class="com.acme.BooleanOperationXOR" />Attribute name defines the name of the function to be used in condition expressions, attribute class defines the fully qualified name of the Boolean function class.
At runtime, the Boolean function class must be included in the class path of SDMetrics. The simplest way to achieve this is by putting the class files in the "bin" folder of the SDMetrics installation directory. SDMetrics searches this directory for class files.
Within the "bin" directory, we have to create a structure of subfolders that reflects the packages of the classes we provide. The class file for class com.acme.BooleanOperationXOR therefore will be located at the path bin/com/acme/BooleanOperationXOR.class.
max(NumClasses, NumInterfaces, NumUseCases)The following implementation of the MAX function calculates the value of each argument provided to the function, and returns the maximum value:
packacke com.acme; import com.sdmetrics.math.ExpressionNode; import com.sdmetrics.metrics.MetricTools; import com.sdmetrics.metrics.SDMetricsException; import com.sdmetrics.metrics.ScalarOperation; import com.sdmetrics.metrics.Variables; import com.sdmetrics.model.ModelElement; 01 public class ScalarOperationMax extends ScalarOperation { @Override 02 public Number calculateValue(ModelElement element, ExpressionNode node, Variables vars) throws SDMetricsException { 03 float result = Float.NEGATIVE_INFINITY; 04 for (int index = 0; index < node.getOperandCount(); index++) { 05 float value = ((Number) evalExpression(element, node.getOperand(index), vars)).floatValue(); 06 result = Math.max(result, value); } 07 return MetricTools.getNumber(result); } }Again, we discuss the implementation line by line:
<scalaroperationdefinition name="max" class="com.acme.ScalarOperationMax" />As with Boolean functions, we deploy the class file of the class in the "bin" folder of our SDMetrics installation (path com/acme/ScalarOperationMax.class). After that, we can write metric expressions using the new function. For example:
<metric name="DominantElementTypeSize" domain="package"> <description>Size of the package in terms of the number elements of the most dominant type in the package.</description> <compoundmetric term="max(NumClasses, NumInterfaces, NumDataTypes, NumUseCases)" /> </metric>
The symmetric difference of two sets A and B is the set of elements contained in either A or B, but not both. For regular sets, we could express the symmetric difference in terms of the existing set operations as (A+B)-(A*B) (i.e., the union of the sets without the intersection of the sets, see Section 8.5.3 "Set Expressions"). For multisets, however, we must take the cardinality of elements into account: the cardinality of an element in the symmetric difference is the absolute difference of the cardinality of the element in sets A and B. For example, if the cardinality of element e is five in set A and three in set B, the cardinality of element e in the symmetric difference is two. The formula (A+B)-(A*B) would yield cardinality (5+3)-3=5 for element e and therefore cannot be used for multisets.
The following implementation handles both regular and multisets.
packacke com.acme; import java.util.Collection; import java.util.Iterator; import com.sdmetrics.math.ExpressionNode; import com.sdmetrics.metrics.MetricTools; import com.sdmetrics.metrics.SDMetricsException; import com.sdmetrics.metrics.SetOperation; import com.sdmetrics.metrics.Variables; import com.sdmetrics.model.ModelElement; 01 public class SetOperationSymmDiff extends SetOperation { @Override 02 public Collection<?> calculateValue(ModelElement element, ExpressionNode node, Variables vars) throws SDMetricsException { 03 Collection<?> left = evalSetExpression(element, node.getOperand(0), vars); 04 Collection<?> right = evalSetExpression(element, node.getOperand(1), vars); 05 boolean isMultiSet = MetricTools.isMultiSet(right) || MetricTools.isMultiSet(left); 06 Collection<?> result = MetricTools.createHashSet(isMultiSet); // process elements from the first set 07 Iterator<?> it = MetricTools.getFlatIterator(left); 08 while (it.hasNext()) { 09 processElement(it.next(), result, left, right); } // process additional elements from the second set 10 it = MetricTools.getFlatIterator(right); 11 while (it.hasNext()) { 12 Object o = it.next(); 13 if (!left.contains(o)) { 14 processElement(o, result, left, right); } } 15 return result; } @SuppressWarnings({ "unchecked", "rawtypes" }) 16 private void processElement(Object o, Collection col, Collection<?> left, Collection<?> right) { 17 int leftCount = MetricTools.elementCount(left, o); 18 int rightCount = MetricTools.elementCount(right, o); 19 int count = Math.abs(leftCount - rightCount); 20 for (int i = 0; i < count; i++) 21 col.add(o); } }Once more, we discuss the salient features of this implementation, line by line.
<setoperationdefinition name="symmdiff" class="com.acme.SetOperationSymmDiff" />Again, we deploy the class file of the class in the "bin" folder of our SDMetrics installation (path com/acme/SetOperationSymmDiff.class). After that, we can write set expressions using the new function. For example:
<metric name="FooBar" domain="package"> <compoundmetric term="size(symmdiff(FooBarClassesSet, FooBazClassesSet))" /> </metric>
General guidelines
The above examples of custom procedures and functions did not cover the entire metrics and rule engine API. On the SDMetrics web site, you can
Column "Attributes" provides the name of the attributes, column "Type" indicates if the attribute is a data or cross-reference attribute, column "Mult." indicates the multiplicity of the attribute ("one" for single-valued, "many" for multi-valued attributes).
Attribute | Type | Multi. | Description |
---|---|---|---|
context | ref | one | Owner of the element in the UML model. |
id | data | one | Unique identifier of the model element. |
name | data | one | Name of the element in UML model. |
stereotypes | ref | many | The stereotypes of the model element. |
In Table 7, column "Model Element" lists all elements of the UML1.4 metamodel. The remaining columns provide the details about all further attributes the metamodel elements define in addition to the inherited attributes.
Attribute | Type | Multi. | Description |
---|---|---|---|
context | ref | one | Owner of the element in the UML model. |
id | data | one | Unique identifier of the model element. |
name | data | one | Name of the element in UML model. |
comments | ref | many | The comments for the model element. |
Table 9 lists all elements of the UML2.x metamodel, and the attributes they define in addition to those inherited from the parent.
Metric: NumAttr | Category: Size |
The number of attributes in the class. The metric counts all properties regardless of their type (data type, class or interface), visibility, changeability (read only or not), and owner scope (class-scope, i.e. static, or instance attribute). Not counted are inherited properties, and properties that are members of an association, i.e., that represent navigable association ends.
|
Metric: NumOps | Category: Size |
The number of operations in a class. Includes all operations in the class that are explicitly modeled (overriding operations, constructors, destructors), regardless of their visibility, owner scope (class-scope, i.e., static), or whether they are abstract or not. Inherited operations are not counted. |
Metric: NumPubOps | Category: Size |
The number of public operations in a class. Same as metric NumOps, but only counts operations with public visibility. Measures the size of the class in terms of its public interface.
|
Metric: Setters | Category: Size |
The number of operations with a name starting with 'set'. Note that this metric does not always yield accurate results. For example, an operation
|
Metric: Getters | Category: Size |
The number of operations with a name starting with 'get', 'is', or 'has'. Note that this metric does not always yield accurate results. For example, an operation
|
Metric: Nesting | Category: not specified |
The nesting level of the class (for inner classes). Measures how deeply a class is nested within other classes. Classes not defined in the context of another class have nesting level 0, their inner classes have nesting level 1, etc. Nesting levels deeper than 1 are unusual; an excessive nesting structure is difficult to understand, and should be revised. |
Metric: IFImpl | Category: Inheritance |
The number of interfaces the class implements. This only counts direct interface realization links from the class to the interface. For example, if a class C implements an interface I, which extends some other interfaces, only interface I will be counted, but not the interfaces that I extends (even though class c implements those interfaces, too). |
Metric: NOC | Category: Inheritance |
The number of children of the class (UML Generalization). Similar to export coupling, NOC indicates the potential influence a class has on the design. If a class has a large number of children, it may require more testing of the methods in that class. A large number of child classes may indicate improper abstraction of the parent class.
|
Metric: NumDesc | Category: Inheritance |
The number of descendents of the class (UML Generalization). Counts the number of children of the class, their children, and so on. |
Metric: NumAnc | Category: Inheritance |
The number of ancestors of the class. Counts the number of parents of the class, their parents, and so on. If multiple inheritance is not used, the metric yields the same values as DIT. |
Metric: DIT | Category: Inheritance |
The depth of the class in the inheritance hierarchy. This is calculated as the longest path from the class to the root of the inheritance tree. The DIT for a class that has no parents is 0. Classes with high DIT inherit from many classes and thus are more difficult to understand. Also, classes with high DIT may not be proper specializations of all of their ancestor classes.
|
Metric: CLD | Category: Inheritance |
Class to leaf depth. The longest path from the class to a leaf node in the inheritance hierarchy below the class. |
Metric: OpsInh | Category: Inheritance |
The number of inherited operations. This is calculated as the sum of metric NumOps taken over all ancestor classes of the class. |
Metric: AttrInh | Category: Inheritance |
The number of inherited attributes. This is calculated as the sum of metric NumAttr taken over all ancestor classes of the class.
|
Metric: Dep_Out | Category: Coupling (import) |
The number of elements on which this class depends. This metric counts outgoing plain UML dependencies and usage dependencies (shown as dashed arrows in class diagrams, usage with 'use' stereotype). |
Metric: Dep_In | Category: Coupling (export) |
The number of elements that depend on this class. This metric counts incoming plain UML dependencies and usage dependencies (shown as dashed arrows in class diagrams, usage with 'use' stereotype). |
Metric: NumAssEl_ssc | Category: Coupling |
The number of associated elements in the same scope (namespace) as the class. For instance, for a class that is defined in a package p, this counts only associations with classes, interfaces, etc. in the same package p. Such associations are encouraged, because they do not cross the package boundary, and contribute to the cohesion of the package. This metric does not distinguish plain, aggregate, or composite associations, as well as incoming, outgoing, or bidirectional associations; all such associations are counted. |
Metric: NumAssEl_sb | Category: Coupling |
The number of associated elements in the same scope branch as the class. For instance, for a class that is defined in a package p, this metric only counts associations with model elements
|
Metric: NumAssEl_nsb | Category: Coupling |
The number of associated elements not in the same scope branch as the class. For a class that is defined in a package p, this counts only associations with model elements in packages that neither contain p nor are contained by p. These are the least desirable associations, because they couple otherwise unrelated packages. Such associations cannot be avoided, but their use should be minimized.
|
Metric: EC_Attr | Category: Coupling (export) |
The number of times the class is externally used as attribute type. This is the number of attributes in other classes that have this class as their type.
|
Metric: IC_Attr | Category: Coupling (import) |
The number of attributes in the class having another class or interface as their type. |
Metric: EC_Par | Category: Coupling (export) |
The number of times the class is externally used as parameter type. This is the number of parameters defined outside this class, that have this class as their type.
|
Metric: IC_Par | Category: Coupling (import) |
The number of parameters in the class having another class or interface as their type.
|
Metric: Connectors | Category: Complexity |
The number of connectors owned by the class. |
Metric: InstSpec | Category: not specified |
The number of instance specification where the class is a classifier. Similar to export coupling, the more instances of the class there are, the larger the role of the class in the system. |
Metric: LLInst | Category: not specified |
The number of lifelines that represent a property of which this class is the type. Similar to export coupling, the more lifelines there are of the class, the larger the role of the class in the system. |
Metric: MsgSent | Category: Coupling (import) |
The number of messages sent. Counts the number of messages that instances of this class send to instances of other classes, or unclassified instances.
|
Metric: MsgRecv | Category: Coupling (export) |
The number of messages received. Counts the number of messages that instances of this class receive from instances of other classes or unclassified instances.
|
Metric: MsgSelf | Category: Complexity |
The number of messages sent to instances of the same class. Counts the number of messages that instances of this class send to themselves or to other instances of the same class.
|
Metric: Diags | Category: Diagram |
The number of times the class appears on a diagram. |
Metric: NumOps | Category: Size |
The number of operations in the interface.
|
Metric: EC_Attr | Category: Coupling (export) |
The number of times the interface is used as attribute type.
|
Metric: EC_Par | Category: Coupling (export) |
The number of times the interface is used as parameter type.
|
Metric: IC_Par | Category: Coupling (import) |
The number of parameters in the interface having an interface or class as their type.
|
Metric: Assoc | Category: Coupling |
The number of elements the interface has an association with. The metric counts incoming, outgoing, or bidirectional associations, aggregations and compositions to all kinds of elements. In practice, there should mostly be incoming associations (see AttrOnIF), so the metric has export coupling characteristics.
|
Metric: NumDirClients | Category: not specified |
The number of elements directly implementing the interface. This is the number of UML abstractions where this interface is the target.
|
Metric: NumIndClients | Category: not specified |
The number of elements implementing a descendent of the interface. An element implementing an interface is also an implementation of every ancestor of that interface. This metrics counts how many classes, components, etc., indirectly implement the interface via a descendent. Together with metric NumDirClients, this indicates the total number of realizations of the interface. |
Metric: NumAnc | Category: Inheritance |
The number of ancestors of the interface.
|
Metric: NumDesc | Category: Inheritance |
The number of descendents of the interface.
|
Metric: Diags | Category: Diagram |
The number of times the interface appears on a diagram. |
Metric: NumCls | Category: Size |
The number of classes in the package. Counts all classes, regardless of their visibility (public, protected, private, or package), or abstractness. |
Metric: NumCls_tc | Category: Size |
The number of classes in the package, its subpackages, and so on. This is the sum of metric NumCls for this package, and all its direct and indirect subpackages. |
Metric: NumOpsCls | Category: Size |
The number of operations in the classes of the package. This is the sum of metric NumOps, taken over all classes in this package, and more fine-grained measure of the size of the package. |
Metric: NumInterf | Category: Size |
The number of interfaces in the package. Like metric NumCls, this counts all interfaces, regardless of their visibility. |
Metric: R | Category: Complexity |
The number of relationships between classes and interfaces in the package. There is a dependency from class or interface C to class or interface D if
|
Metric: H | Category: Cohesion |
Relational cohesion. This is the average number of internal relationships per class/interface, and is calculated as the ratio of R+1 to the number of classes and interfaces in the package.
|
Metric: Ca | Category: Coupling (export) |
Afferent coupling. The number of elements outside this package that depend on classes or interfaces in this package. The dependencies considered are the same ones listed with metric R.
|
Metric: Ce | Category: Coupling (import) |
Efferent coupling. The number of elements outside this package that classes or interfaces in this package depend on. The dependencies considered are the same ones listed with metric R.
|
Metric: I | Category: not specified |
Instability or ease of change. This is the ratio of efferent coupling (metric Ce) to total coupling (Ce+Ce). Values of metric I range between 0 and 1. A value close to 0 indicates a package that does not rely much on other packages, but is heavily relied upon by other packages. Such a package should be stable, because it is hard to change: changes to the package potentially have a large impact on the model ("ripple effects"). A value close to 1 indicates a package that mostly relies on other packages, but that itself is not much relied upon. Such a package can be unstable, because it is easy to change: changes to the package are not likely to have a large impact on the model. |
Metric: A | Category: not specified |
Abstractness (or generality) of the package. This is the ratio of abstract classes and interfaces in the package to the total number of interfaces and classes in the package. Values range from 0 to 1. Zero indicates packages without interfaces or abstract classes, 1 indicates a package consisting only interfaces and abstract classes.
|
Metric: D | Category: not specified |
Distance from the main sequence. Package design should aim to strike a balance between instability and abstractness of the packages. A stable package should be abstract, so that changes to the package are merely extensions that do not affect existing clients of the package. An unstable package is easy to change and can therefore be concrete. The Stable-Abstractions-Principle (SAP) says that a package should be as abstract as it is stable. With abstractness measured by metric A, and stability measured by metric I, the SAP demands that A+I be close to 1. If you plot A vs. I in a graph, the "main sequence" is the theoretical optimal line where A+I=1. Metric D is the distance of the package from the main sequence, and is calculated as (A+I-1)*sqrt(2). Values range from -sqrt(2) to +sqrt(2). Values close to zero indicate packages that adhere to the SAP. A large negative value indicates a package that is concrete and stable (A and I close to 0). Such a package can be "painful" because it is not extensible and prone to change. A large positive value indicates a package that is abstract and unstable. Such a package is extensible but has few dependents, and is therefore useless.
|
Metric: DN | Category: not specified |
Normalized distance D' from the main sequence. This is a variation of metric D that has been normalized to range between 0 and 1. It is calculated as |A+I-1|. Values close to zero indicate packages that adhere to the SAP (see metric D).
|
Metric: Nesting | Category: Nesting |
Nesting level of the package in the package hierarchy. Top level packages have nesting level 0, their subpackages are at level 1, and so on. |
Metric: ConnComp | Category: Cohesion |
The connected components formed by the classes and interfaces of the package. The classes and interfaces of a package, and their dependencies, form a graph. This metric counts the number of connected components of that graph. Ideally, all classes and interfaces of the package should be related directly or indirectly, so that there is only one connected component. If there are two or more connected components, you may consider moving some classes or interfaces to other packages, or splitting up the package.
|
Metric: Dep_Out | Category: Coupling (import) |
The number of UML dependencies where the package is the client. See also Dep_Out for classes. |
Metric: Dep_In | Category: Coupling (export) |
The number of UML dependencies where the package is the supplier. See also Dep_In for classes. |
Metric: DepPack | Category: Coupling (import) |
The number of packages on which classes and interfaces of this package depend. A package P depends on a package P' if
This metric is similar to metric Ce, the difference is that Ce counts the individual classes or interfaces that are depended upon. |
Metric: MsgSent_Outside | Category: Coupling (import) |
The number of messages sent to instances of classes outside the package. The metric counts, for instances of classes of this package, the messages they send to instances of classes from other packages. That is, outgoing messages that cross the package boundary. Note that messages to unclassified instances are not counted here. |
Metric: MsgRecv_Outside | Category: Coupling (export) |
The number of messages received by classifier instances of classes outside the package. The metric counts, for instances of classes of this package, the messages they receive from instances of classes from other packages. That is, incoming messages that cross the package boundary. Note that messages from unclassified instances are not counted here. |
Metric: MsgSent_within | Category: Complexity |
The number of messages sent between classifier instances of classes in the package. The metric counts, for instances of classes of this package, the number of messages they send to themselves or other instances of classes from this package. Note that messages to unclassified instances are not counted here. |
Metric: Diags | Category: Diagram |
The number of times the package appears on a diagram. |
Metric: LifeLines | Category: Size |
The number of lifelines participating in the interaction. |
Metric: Messages | Category: Complexity |
The number of messages sent within the interaction. |
Metric: SelfMessages | Category: not specified |
The number of messages that objects in the interaction send to themselves. The focus of sequence diagrams should be on object interactions. A sequence diagram with a large number of self messages may indicate that the modeler attempted to model object internal algorithms. |
Metric: CombinedFragments | Category: Size |
The number of combined fragments in the interaction. |
Metric: Operands | Category: Size |
The number of interaction operands of the combined fragments in the interaction. |
Metric: Height | Category: not specified |
The maximum number of messages on any of the lifelines of interaction. Very long and busy lifelines with lots of messages attached to them may indicate objects that have too many responsibilities. |
Metric: CombinedFragmentNesting | Category: not specified |
The maximum nesting level of combined fragments in the interaction. Excessive nesting of combined fragments makes sequence diagrams harder to read and understand. Consider extracting such combined fragments into sequence diagrams of their own. |
Metric: NumAss | Category: not specified |
The number of associations the use case participates in. Counts incoming, outgoing, and bidirectional associations. |
Metric: ExtPts | Category: not specified |
The number of extension points of the use case. |
Metric: Including | Category: Coupling (import) |
The number of use cases which this one includes. |
Metric: Included | Category: Coupling (export) |
The number of use cases which include this one. |
Metric: Extended | Category: not specified |
The number of use cases which extend this one. |
Metric: Extending | Category: not specified |
The number of use cases which this one extends. |
Metric: Diags | Category: Diagram |
The number of times the use case appears on a diagram. |
Metric: Trans | Category: Complexity |
The number of transitions in the state machine. Internal transitions are not included in this count.
|
Metric: TEffects | Category: Complexity |
The number of transitions with an effect in the state machine. |
Metric: TGuard | Category: Complexity |
The number of transitions with a guard in the state machine.
|
Metric: TTrigger | Category: Complexity |
The number of triggers of the transitions of the state machine.
|
Metric: States | Category: Size |
The number of states in the state machine. This includes pseudo states, as well as composite and concurrent states of the statemachine, and recursively the states they contain, at all levels of nesting. Submachine states count as "one", the states in statemachines they reference are not included.
|
Metric: SActivity | Category: Size |
The number of activities defined for the states of the state machine. This counts entry, exit, and do activities (or interactions or statemachines) defined for the states. The states considered are those counted by metric States.
|
Metric: CC | Category: Complexity |
The cyclomatic complexity of the state-transition graph. This is calculated as Trans-States+2.
|
Metric: Actions | Category: Size |
The number of actions of the activity. Includes actions in all activity groups (partitions, interruptible regions, expansion regions, structured activities including conditional, loop, and sequence nodes), and their subgroups, sub-subgroups, etc. |
Metric: ObjectNodes | Category: Size |
The number of object nodes of the activity. Counts data store, central buffer, and activity parameter nodes in all activity groups and their subgroups etc. (see Actions). |
Metric: Pins | Category: Size |
The number of pins on nodes of the activity. Counts all input, output, and value pins on all nodes and groups of the activity. |
Metric: ControlNodes | Category: Size |
The number of control nodes of the activity. Control nodes are initial, activity final, flow final, join, fork, decision, and merge nodes. The metric also counts control nodes in all activity groups and their subgroups etc. (see Actions). |
Metric: Partitions | Category: Size |
The number of activity partitions in the activity. |
Metric: Groups | Category: Size |
The number of activity groups or regions of the activity. Counts interruptible and expansion regions, structured activities, conditional, loop, and sequence nodes, at all levels of nesting. |
Metric: ControlFlows | Category: Complexity |
The number of control flows of the activity. Includes contained edges in all activity groups and their subgroups etc. (see Actions) |
Metric: ObjectFlows | Category: Complexity |
The number of object flows of the activity. Includes contained edges in all activity groups and their subgroups etc. (see Actions) |
Metric: Guards | Category: Complexity |
The number of guards defined on object and control flows of the activity. Includes contained edges in all activity groups and their subgroups etc. (see Actions). |
Metric: ExcHandlers | Category: Complexity |
The number of exception handlers of the activity. Includes exception handlers for all nodes in all activity groups and their subgroups etc. (see Actions). |
Metric: NumOps | Category: Size |
The number of operations of the component.
|
Metric: NumComp | Category: Size |
The number of subcomponents of the component. Counts components directly owned by this component; sub-sub-components etc. are not included in this count. |
Metric: NumPack | Category: Size |
The number of packages of the component. This only counts packages directly owned by the component; any sub-packages etc. of these packages are not included in this count. |
Metric: NumCls | Category: Size |
The number of classes of the component. This only counts classes directly owned by the component; nested classes, classes in packages, subcomponents, etc. are not included in this count.
|
Metric: NumInterf | Category: Size |
The number of interfaces of the component. This only counts interfaces directly owned by the component; interfaces in packages, sub-components, etc. are not included in this count.
|
Metric: NumManifest | Category: not specified |
The number of artifacts of which this component is a manifestation.
|
Metric: Connectors | Category: Complexity |
The number of connectors owned by the component. |
Metric: ProvidedIF | Category: Inheritance |
The number of interfaces the component provides. A component provides an interface if there is a plain or interface realization to the interface, or if a port of the component provides the interface.
|
Metric: RequiredIF | Category: not specified |
The number of interfaces the component requires. The component requires an interface if there is a dependency, usage dependency, or an association to the interface, or if a port of the component requires the interface. |
Metric: Dep_Out | Category: Coupling (import) |
The number of outgoing UML dependencies (component is the client). See also Dep_Out for classes. |
Metric: Dep_In | Category: Coupling (export) |
The number of incoming UML dependencies (component is the supplier). See also Dep_In for classes. |
Metric: Assoc_Out | Category: Coupling (import) |
The number of associated elements via outgoing associations. Takes associations, aggregations, and compositions with navigability away from the component into account, i.e., elements the component knows. |
Metric: Assoc_In | Category: Coupling (import) |
The number of associated elements via incoming associations. Takes associations, aggregations, and compositions with navigability to the component into account, i.e., elements that know the component. |
Metric: Diags | Category: Diagram |
The number of times the component appears on a diagram. |
Metric: Type | Category: not specified |
The type of node (regular, execution environment, or device). |
Metric: NumOps | Category: Size |
The number of operations of the node.
|
Metric: NumComp | Category: Size |
The number of components located on the node. Counts components directly owned by this node; sub-components etc. are not included in this count. |
Metric: NumNodes | Category: Size |
The number of subnodes of the node. Counts nodes directly located on this node; sub-sub-nodes etc. are not included in this count. |
Metric: NumArt | Category: Size |
The number of artifacts deployed on the node. This metric counts
|
Metric: NumPack | Category: Size |
The number of packages of the node. This only counts packages directly owned by the node; any sub-packages etc. of these packages are not included in this count. |
Metric: AssEl | Category: Coupling |
The number of elements the node is associated with. Takes incoming, outgoing, and bidirectional communication paths, associations, aggregations, and compositions into account. |
Metric: Diags | Category: Diagram |
The number of times the node appears on a diagram. |
Metric: Type | Category: not specified |
The type of diagram (class diagram, sequence diagram, etc.). |
Metric: Elements | Category: Size |
The total number of design elements on the diagram. See diagram metrics. |
Metric: Classes | Category: Size |
The number of classes on the diagram. See diagram metrics. |
Metric: Interfc | Category: Size |
The number of interfaces on the diagram. See diagram metrics. |
Metric: Packages | Category: Size |
The number of packages on the diagram. See diagram metrics. |
Metric: Assoc | Category: Complexity |
The number of associations on the diagram. See diagram metrics. |
Metric: Genrs | Category: Complexity |
The number of generalizations on the diagram. See diagram metrics. |
Metric: Deps | Category: Complexity |
The number of UML dependencies and UML usage dependencies on the diagram. See diagram metrics. |
Metric: IfRealize | Category: Complexity |
The number of interface realizations on the diagram. See diagram metrics. |
Metric: InstSpec | Category: Size |
The number of instance specifications on the diagram. See diagram metrics. |
Metric: Lifelines | Category: Size |
The number of lifelines on the diagram. See diagram metrics. |
Metric: Connectors | Category: Complexity |
The number of connectors on the diagram. See diagram metrics. |
Metric: Messages | Category: Complexity |
The number of messages on the diagram. See diagram metrics. |
Metric: Actors | Category: Size |
The number of actors on the diagram. See diagram metrics. |
Metric: UseCase | Category: Size |
The number of use cases on the diagram. See diagram metrics. |
Metric: ExtPts | Category: Size |
The number of extension points on the diagram. See diagram metrics. |
Metric: Extends | Category: Complexity |
The number of use case extensions on the diagram. See diagram metrics. |
Metric: Includes | Category: Complexity |
The number of use case includes on the diagram. See diagram metrics. |
Rule: Unnamed | Category: Completeness |
Severity: 1-high | Applies to: all areas |
Class has no name. Give the class a descriptive name that reflects its purpose. Unnamed classes will cause problems during code generation.
|
Rule: Unused | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The class is not used anywhere. The class has no child classes, dependencies, or associations, and it is not used as parameter or property type. You'll probably still need to model the clients of the class, or else consider deleting the class from the model. Note: for models that were reverse-engineered from source code, this rule may falsely report many classes as "unused". This happens for classes that are only referenced in method implementations, e.g., via local variables.
|
Rule: NotCapitalized | Category: Naming |
Severity: 3-low | Applies to: all areas |
Class names should start with a capital letter. This naming convention is a recommended style guideline in the UML standards [OMG03], [OMG05]. |
Rule: GodClass | Category: Style |
Severity: 2-med | Applies to: all areas |
The class has more than 60 attributes and operations. Also known as blob classes, large classes are likely maintenance bottlenecks, sources of unreliability, and indicate a lack of (object-oriented) architecture and architecture enforcement. Consider refactoring the class to split it up into smaller classes. |
Rule: Keyword | Category: Naming |
Severity: 2-med | Applies to: design |
Class name is a Java or C++ keyword. Using programming language keywords for class names will cause problems during code generation. Find another name for the class. Capitalizing the name will also help, see rule NotCapitalized.
|
Rule: MultipleInheritance | Category: Style |
Severity: 3-low | Applies to: all areas |
Use of multiple inheritance - class has more than one parent. The use of multiple inheritance is controversial. Some OO programming languages do not support multiple inheritance. Review the class design to confirm that the use of multiple inheritance is justified.
|
Rule: SpecLeafClass | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Class is marked as leaf, but it has child classes. Leaf classes cannot have any child classes. This is a WFR of the UML. |
Rule: NoSpec | Category: Completeness |
Severity: 2-med | Applies to: all areas |
Abstract class has no child classes. Abstract classes cannot be instantiated. Without specializations that can be instantiated, the abstract class is useless.
|
Rule: CyclicInheritance | Category: Inheritance |
Severity: 1-high | Applies to: all areas |
Class inherits from itself directly or indirectly. The inheritance graph must be a tree, no cycles are allowed.
|
Rule: ConcreteSuper | Category: Style |
Severity: 1-high | Applies to: all areas |
The abstract class has a parent class that is not abstract. This is bad design. A child class should be substitutable for the parent class. Since the parent class can be instantiated, but not the child class, substitution is not possible anymore.
|
Rule: DupOps | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Class has duplicate operations. There are two or more operations with identical signatures (i.e., operation name and list of parameter types). Operation signatures must be unique within the class.
|
Rule: DupAttrNames | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The class has two or more properties with identical names. Attribute names must be unique within the class.
|
Rule: AttrNameOvr | Category: Naming |
Severity: 2-med | Applies to: all areas |
The class defines a property of the same name as an inherited attribute. During code generation, this may inadvertently hide the attribute of the parent class. Consider changing the name of the attribute in the child class.
|
Rule: DescendentRef | Category: Style |
Severity: 1-high | Applies to: all areas |
The class references a descendent class via associations, UML dependencies, attribute or parameter types. This is poor design. A class c should be oblivious of its descendent classes. The reference to the descendent class and the inheritance links back to class c effectively form a dependency cycle between these classes. Redesign this to eliminate the need for the reference to the descendent class. |
Rule: DepCycle | Category: Style |
Severity: 2-med | Applies to: all areas |
The class has circular references. Circular dependencies should be avoided. The classes participating in the cycle cannot be tested and reused independently. The more classes participate in the cycle, the worse the problem is, especially if the classes reside in different packages (see also rule DepCycle for packages). Consider revising the design to eliminate the cycle.
|
Rule: Unnamed | Category: Completeness |
Severity: 1-high | Applies to: all areas |
Interface has no name.
|
Rule: Unused | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The interface is not used anywhere. The interface is not implemented anywhere, has no associations, and is not used as parameter or attribute type.
|
Rule: NotCapitalized | Category: Naming |
Severity: 3-low | Applies to: all areas |
Interface names should start with a capital letter.
|
Rule: Keyword | Category: Naming |
Severity: 2-med | Applies to: design |
Interface name is a Java or C++ keyword; find another name for it.
|
Rule: PubOpsOnly | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The interface has operations that are not public. All operations in interfaces must have public visibility.
|
Rule: PubAttrOnly | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The interface has attributes that are not public. All attributes in interfaces must have public visibility.
|
Rule: AttrOnIF | Category: Style |
Severity: 3-low | Applies to: all areas |
The interface has attributes or outgoing associations. Interfaces can have attributes and outgoing associations since UML2.0. This rather appears to be a concession to certain component technologies, and should otherwise be avoided.
|
Rule: Unnamed | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The data type has no name.
|
Rule: Keyword | Category: Naming |
Severity: 2-med | Applies to: design |
Data type name is a Java or C++ keyword; find another name for it.
|
Rule: NoQuery | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The data type has an operation that is not marked as a query. All operations of a data type must be queries.
|
Rule: Unnamed | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The attribute has no name.
|
Rule: Capitalized | Category: Naming |
Severity: 3-low | Applies to: all areas |
Attribute names should start with a lowercase letter. This is a recommended style guideline in the UML standards [OMG03], [OMG05].
|
Rule: Keyword | Category: Naming |
Severity: 2-med | Applies to: design |
Attribute name is a Java or C++ keyword.
|
Rule: PublicAttr | Category: Style |
Severity: 2-med | Applies to: all areas |
Non-constant attribute is public. External read/write access to attributes violates the information hiding principle. Allowing external entities to directly modify the state of an object is dangerous. State changes should only occur through the protocol defined by the interfaces of the object. Make the attribute private or protected.
|
Rule: NoType | Category: Completeness |
Severity: 2-med | Applies to: design |
The attribute has no specified type. Without a type, the attribute has no meaning in design, and code generation will not work. Specify a type for the attribute.
|
Rule: Unnamed | Category: Completeness |
Severity: 1-high | Applies to: all areas |
Operation has no name.
|
Rule: Capitalized | Category: Naming |
Severity: 3-low | Applies to: all areas |
Operation names should start with a lower case letter. This is a recommended style guideline in the UML standards [OMG03], [OMG05]. In many programming languages, constructors have the same name as their class, thus starting with upper case letters. Therefore, operations with the same name as their class are not reported. |
Rule: Keyword | Category: Naming |
Severity: 2-med | Applies to: design |
Operation name is a Java or C++ keyword. Using programming language keywords as operation names will cause problems during code generation. Find another name for the operation.
|
Rule: AbstractOp | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Operation is abstract, but its owner class is not abstract. In many programming languages, a class is abstract if at least one of its operations is abstract. Either make the owner class abstract, or provide an implementation for the operation. |
Rule: LongParList | Category: Style |
Severity: 2-med | Applies to: all areas |
The operation has a long parameter list with five or more parameters. Long parameter lists are difficult to use, and likely to change more frequently. Change the design to pass one or more objects to the operation that encapsulate the required parameters. Note: the rule only considers in, out, and inout parameters; the return parameter (if any) is not counted.
|
Rule: MulReturn | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The operation has more than one return parameter. Many programming languages only support one return parameter per operation. Change some return parameters to out or inout parameters, or return one object that encapsulates all return parameters.
|
Rule: DupName | Category: Naming |
Severity: 1-high | Applies to: all areas |
The operation has two or more parameters with identical names. Parameters must have unique names to distinguish them. This is a WFR of the UML.
|
Rule: Query | Category: Style |
Severity: 2-med | Applies to: all areas |
The operation name indicates a query, but it is not marked as a query. The operation name suggests this is a getter (see Getters). Mark the operation as query to indicate that it does not change the owner's state. |
Rule: Unnamed | Category: Completeness |
Severity: 1-high | Applies to: all areas |
Parameter has no name. Note that this rule does not check return parameters, as they are unnamed in most programming languages.
|
Rule: NoType | Category: Completeness |
Severity: 2-med | Applies to: all areas |
The parameter has no specified type.
|
Rule: Keyword | Category: Naming |
Severity: 2-med | Applies to: design |
Parameter name is a Java or C++ keyword. Return parameters are not checked, as they are unnamed in many programming languages. Also, some modeling tools assign the name 'return' as default name to return parameters.
|
Rule: Unnamed | Category: Completeness |
Severity: 1-high | Applies to: all areas |
Package has no name.
|
Rule: Capitalization | Category: Naming |
Severity: 3-low | Applies to: all areas |
Package name has upper case letters. A common naming convention is that package names use all lower case letters.
|
Rule: Keyword | Category: Naming |
Severity: 2-med | Applies to: design |
Package name is a Java or C++ keyword.
|
Rule: EmptyPackage | Category: Completeness |
Severity: 2-med | Applies to: all areas |
The package has no contents. Add model elements to the package, or delete it from the design. |
Rule: DupClsName | Category: Naming |
Severity: 1-high | Applies to: all areas |
The package has two or more classes or interfaces with identical names. This will cause problems during code generation. Rename the classes or interfaces so that the names are unique.
|
Rule: DepCycle | Category: Style |
Severity: 1-high | Applies to: all areas |
The package has circular dependencies to other packages. Cycles in the package dependency graph should be avoided. The packages participating in the cycle cannot be tested, reused, or released independently. The more packages participate in the cycle, the worse the problem is. Other design guidelines such as the Stable-Dependencies Principle (see rule SDP1) are also invariably violated. Revise the design to eliminate the cycle. |
Rule: SDP1 | Category: Style |
Severity: 2-med | Applies to: all areas |
Package violates the Stable-Dependencies Principle (SDP). The package depends on another package, P that is less stable than itself (as measured by metric I). Package P is less stable and therefore more liable to change than this package. A change to P may ripple to this package. This is undesirable because this package is more stable and therefore harder to change. Therefore, the Stable-Dependencies Principle says that dependencies should run in the direction of stabilities. |
Rule: SDP2 | Category: Style |
Severity: 2-med | Applies to: all areas |
Package violates the Stable-Dependencies Principle (SDP). The package depends on another package that is less abstract than itself. The Stable-Abstractions-Principle (SAP) says that a package should be as abstract (as measured by metric A) as it is stable (as measured by metric I). The Stable-Dependencies Principle (SDP) says that dependencies should run in the direction of stabilities. Therefore, dependencies should run in the direction of abstraction: a package should be more abstract than the packages it depends on. |
Rule: AggEnds | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The binary association has two composite or shared aggregation ends. A binary association may have at most one shared (hollow diamond) or composite (filled diamond) aggregation end. This is a WFR of the UML. |
Rule: NaryAggEnds | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The n-ary association has a composite or shared aggregation end. Three (or more)- way associations must not indicate shared or composite aggregation.
|
Rule: NaryNavEnds | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The n-ary association indicates a navigable association end. Three (or more)- way associations must not indicate navigability at any of the association ends.
|
Rule: LooseEnd | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The association has one or more ends not connected to a model element. Check the ends of the association, and attach the loose end(s) to the proper model element(s), or remove the association from the model. |
Rule: NaryAgg | Category: Style |
Severity: 3-low | Applies to: design |
The association has three or more association ends. People are often confused by the semantics of n-ary associations. N-ary associations have no representation in common programming languages. The suggestion is therefore to remodel the n-ary association using several plain associations. |
Rule: SpecAgg | Category: Style |
Severity: 3-low | Applies to: all areas |
The association is a specialization of another association. People are often confused by the semantics of specialized associations. The suggestion is therefore to model any restrictions on the parent association using constraints.
|
Rule: AssocClass | Category: Style |
Severity: 3-low | Applies to: design |
Avoid association classes. Association classes have no representation in common programming languages. They defer the decision which class(es) eventually will be responsible to manage the association attributes. The recommendation is to remodel the association class to only use regular classes and binary associations. |
Rule: NoChild | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The child end of the generalization is not connected to a model element. Check the generalization and attach the child end to the proper model element, or remove the generalization from the model. |
Rule: NoParent | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The parent end of the generalization is not connected to a model element. Check the generalization and attach the parent end to the proper model element, or remove the generalization from the model. |
Rule: TypeMismatch | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Parent and child of the generalization are not of the same type. The child must be of the same type as the parent. This is a WFR of the UML.
|
Rule: NoSupplier | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The supplier end of the interface realization is not connected to an interface. For a loose supplier end, either attach the end to the proper interface or delete the interface realization from the model. If the supplier end is connected to anything else but an interface, connect it to the proper interface, or consider replacing the interface realization with a plain UML generalization, realization, or dependency. |
Rule: NoInterface | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The client end of the interface realization is not connected to a model element. Check the interface realization and attach the client end to the proper model element, or remove the interface realization from the model. |
Rule: NoSupplier | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The supplier end of the dependency link is not connected to a model element. Check the dependency and attach the supplier end to the proper model element, or remove the dependency link from the model. |
Rule: NoClient | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The client end of the dependency link is not connected to any model element. Check the dependency and attach the client end to the proper model element, or remove the dependency link from the model. |
Rule: Alternatives | Category: Style |
Severity: 3-low | Applies to: all areas |
The interaction models alternative sequences. The interaction contains a combined fragment with 'alt', 'opt', or 'break' operator to model alternative execution sequences. The purpose of a sequence diagram is to show one scenario, not a set of different possible sequences. Activity diagrams are better suited for that purpose. Consider using several sequence diagrams showing one scenario each, or use an activity diagram.
|
Rule: NoAssoc | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The actor is not associated with any use cases, classes, or subsystems. Without such associations, the actor is useless. Associate the actor with one or more use cases, or delete it from the model.
|
Rule: NaryAssoc | Category: Correctness |
Severity: 2-med | Applies to: all areas |
The actor participates in an n-ary association. An actor can only participate in binary associations. Replace the n-ary association with several binary associations.
|
Rule: Unnamed | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The actor has no name.
|
Rule: Unused | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The use case is not used. The use case is not associated with any actors, or included in or extending other use cases. Such a use case is useless. Associate it with an actor, attach it to another use case, or delete it from the model.
|
Rule: DupExPoint | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The use case has two or more extension points of the same name. Rename the extension points so that they all have unique names.
|
Rule: NoName | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The use case has an extension point without a name. Check the extension points of the use case and make sure they all have a name.
|
Rule: NaryAssoc | Category: Correctness |
Severity: 2-med | Applies to: all areas |
The use case participates in an n-ary association. A use case can only participate in binary associations. Replace the n-ary association with several binary associations.
|
Rule: Unnamed | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The use case has no name.
|
Rule: CyclicIncludes | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Use case directly or indirectly includes itself. A use case cannot include use cases that directly or indirectly include it. Remove some include links to break the cycle.
|
Rule: FunctionalDecomp | Category: Style |
Severity: 2-med | Applies to: all areas |
Use case both includes and is included in other use cases. Several levels of include relations between use cases indicate a functional decomposition, which should not be part of requirements analysis.
|
Rule: Extends | Category: Style |
Severity: 3-low | Applies to: all areas |
The use case is extending another use case. The semantics of the extend relationship between use cases are often misunderstood, and there are no definite criteria when to use "extend" and when to use "include" relationships. The suggestion is to avoid using "extend" relationships in favor of the more intuitive "include". |
Rule: RegularTransition | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The protocol state machine contains a transition that is not a protocol transition. Protocol state machines can only contain protocol transitions. Check the transitions and make sure they are all protocol transitions.
|
Rule: ProtocolTransition | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The state machine contains a protocol transition. Protocol transitions can only occur in protocol state machines. Check the transitions and make sure they are all regular transitions.
|
Rule: HistoryState | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The protocol state machine contains a history state. Protocol state machine cannot have deep or shallow history states. Check the state machine and remove all history states.
|
Rule: StatesWithActivities | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The protocol state machine contains states with entry/exit/doactivities. States in protocol state machines must not have any entry, exit, or do activities. Check the states and remove the activities.
|
Rule: TransWithEffects | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The protocol state machine contains transitions with effects. Transitions in protocol state machines must not have any effects. Check the transitions and remove the effects.
|
Rule: TooManyInitialStates | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The region has two or more initial states. A region can have at most one initial state. Check the region and remove the surplus initial states.
|
Rule: InitialAndFinalStates | Category: Style |
Severity: 2-med | Applies to: all areas |
There is no initial or final state for the state machine. The top-level region of a state machine should have one initial state and at least one final state so that the state machine has a well-defined beginning and end.
|
Rule: DupName | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The region has two or more states of the same name. Distinctive states should have distinctive names. Duplicate names can also cause problems during code generation.
|
Rule: EmptyRegion | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The region has no states. Add states to the region, or remove the region from the model. |
Rule: DeepHistory | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The region has two or more deep history states. A region can have at most one deep history state. Check the region and delete the surplus history states.
|
Rule: ShallowHistory | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The region has two or more shallow history states. A region can have at most one shallow history state. Check the region and delete the surplus history states.
|
Rule: NoIncoming | Category: Completeness |
Severity: 1-high | Applies to: all areas |
State has no incoming transitions. Without incoming transitions, the state can never be reached. Add one or more transitions to the state.
|
Rule: NoOutgoing | Category: Completeness |
Severity: 1-high | Applies to: all areas |
State has no outgoing transitions. Without outgoing transitions, the state can never be left. Check if this is merely an oversight or the actually intended behavior. In the former case, add the missing outgoing transition(s). In the latter case, consider adding an outgoing transition to a final state.
|
Rule: IllegalJoin | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Join states must have two or more incoming and exactly one outgoing transition.
|
Rule: IllegalFork | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Fork states must have exactly one incoming and two or more outgoing transitions.
|
Rule: IllegalChoice | Category: Correctness |
Severity: 1-high | Applies to: all areas |
A choice or junction state must have at least one incoming and one outgoing transition.
|
Rule: MissingGuard | Category: Correctness |
Severity: 1-high | Applies to: all areas |
If there are two or more transitions from a choice state, they all must have guards. A choice state realizes a dynamic conditional branch; the guards are required to evaluate the branch conditions. Check the outgoing transitions and add the missing guard(s).
|
Rule: IllegalInitial | Category: Correctness |
Severity: 1-high | Applies to: all areas |
An initial state must have no incoming and exactly one outgoing transition.
|
Rule: IllegalFinal1 | Category: Correctness |
Severity: 1-high | Applies to: all areas |
A final state cannot have any outgoing transitions. Remove the outgoing transitions from the model.
|
Rule: IllegalFinal2 | Category: Correctness |
Severity: 1-high | Applies to: all areas |
A final state cannot have any regions or entry/exit/state behavior. Check the state and remove all regions, entry, exit, and do activities.
|
Rule: IllegalEntryExit | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Entry or exit point is not owned by a top-level region. Move the entry or exit point to the top-level region of the state machine.
|
Rule: IllegalHistory | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The history state has two or more outgoing transitions. A history state can have at most one outgoing transition. Check the history state and remove the surplus outgoing transitions.
|
Rule: Unnamed | Category: Completeness |
Severity: 3-low | Applies to: all areas |
State has no name. While the UML allows for anonymous states, adding a descriptive name to the state increases the readability and understandability of the diagram.
|
Rule: BadForkOutgoing | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Transitions from fork states must not have a guard or triggers. Check the outgoing transitions and remove the guards and triggers.
|
Rule: ForkTargetStates1 | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Fork state has transitions to states in identical regions. The transitions from a fork state must target states of different regions of a concurrent state. Check the outgoing transitions and make sure they all target different regions.
|
Rule: ForkTargetStates2 | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Fork state has transitions to states in different concurrent states. The transitions from a fork state in a state machine must target regions of the same concurrent state. Check the outgoing transitions and make sure they all target the same concurrent state.
|
Rule: JoinSourceStates1 | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Join state has transitions from states in identical regions. The transitions to a join state must originate from states of different regions of a concurrent state. Check the incoming transitions and make sure they all come from different regions.
|
Rule: JoinSourceStates2 | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Join state has transitions from states in different concurrent states. The transitions to a join state in a state machine must originate from regions of the same concurrent state. Check the incoming transitions and make sure they all come from the same concurrent state.
|
Rule: BadForkTarget | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Transitions from fork states in a state machine must not target a pseudo state. Check the outgoing transitions and make sure they do not point to any pseudo states.
|
Rule: BadJoinSource | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Transitions to join states in a state machine must not originate from a pseudo state. Check the incoming transitions and make sure they do not come from pseudo states.
|
Rule: BadIncoming | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Transitions to join states must not have triggers or guards. Check the incoming transitions and remove all triggers and guards.
|
Rule: BadOutgoing | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Transitions from pseudo states must not have any triggers. Check the outgoing transitions and remove all triggers and guards.
|
Rule: MissingTarget | Category: Correctness |
Severity: 1-high | Applies to: all areas |
State has an outgoing transition not attached to a target state. Check the outgoing transitions of the state and attach any loose ends to the proper target states.
|
Rule: MissingSource | Category: Correctness |
Severity: 1-high | Applies to: all areas |
State has an incoming transition not attached to a source state. Check the incoming transitions of the state and attach any loose ends to the proper source states.
|
Rule: EmptyGroup | Category: Completeness |
Severity: 2-med | Applies to: all areas |
The activity group does not contain any nodes or subgroups. Add nodes to the activity group, or delete it from the model. |
Rule: NoExpansionNode | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The expansion region has no expansion node. An expansion region must have at least one input expansion node. This is a WFR of the UML. |
Rule: Unnamed | Category: Completeness |
Severity: 3-low | Applies to: all areas |
Action has no name. Give the action a descriptive name that describes its purpose. |
Rule: IsolatedAction | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The action has neither incoming nor outgoing edges. If nothing goes in or comes out of the action, the action is useless. Add the missing edges, or delete the action. |
Rule: IllegalInitial | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The initial node has incoming edges our outgoing object flows. Check the initial node to make sure it has no incoming edges, and that all outgoing edges are control flows.
|
Rule: IllegalFinal | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Final nodes must not have any outgoing edges. Check the node and remove the outgoing edges.
|
Rule: IllegalJoin1 | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Join nodes must have exactly one outgoing edge.
|
Rule: IllegalJoin2 | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Outgoing edge of join is the wrong type. If all of the join node's incoming edges are control flows, the outgoing edge must be a control flow. If there is at least one incoming object flow, the outgoing edge must be an object flow.
|
Rule: IllegalFork1 | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Fork nodes must have exactly one incoming edge.
|
Rule: MixedEdgeTypes | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Edges from or to decision, merge, or fork nodes must be of the same type. The incoming and outgoing edges of a decision, merge, or fork node must either be all control flows or all object flows. A mixture of control and object flows attached to one such node is not allowed.
|
Rule: ForkOut | Category: Completeness |
Severity: 2-med | Applies to: all areas |
Fork nodes should have two or more outgoing edges. Otherwise, there is no fork. Check the node and add the missing outgoing edges.
|
Rule: IllegalDecision | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Decision nodes must have exactly one incoming edge.
|
Rule: DecisionOut | Category: Completeness |
Severity: 2-med | Applies to: all areas |
Decision nodes should have two or more outgoing edges, each with a guard. Otherwise, there is no decision. Check the node and add the missing edges and/or guards.
|
Rule: IllegalMerge | Category: Correctness |
Severity: 1-high | Applies to: all areas |
Merge nodes must have exactly one outgoing edge.
|
Rule: NumIncoming | Category: Completeness |
Severity: 2-med | Applies to: all areas |
Merge and join nodes should have two or more incoming edges. Otherwise, there is nothing to merge/join. Check the node and add the missing incoming edges.
|
Rule: IsolatedNode | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The control node has neither incoming nor outgoing edges.
|
Rule: Unnamed | Category: Completeness |
Severity: 3-low | Applies to: all areas |
The object node has no name.
|
Rule: InOrOut | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The activity parameter node has both incoming and outgoing flows. Activity parameter nodes must have either only incoming or only outgoing edges, but not both at the same time. Check the edges on the node and make sure they're all pointing in the same direction.
|
Rule: IsolatedNode | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The object node has neither incoming nor outgoing edges.
|
Rule: NoIncoming | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The input pin has no incoming edges. Input pins require incoming edges to provide values to their actions. Add an incoming edge to the pin. |
Rule: NoOutgoing | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The output pin has no outgoing edges. Output pins require outgoing edges to deliver values provided by their actions. Add in outgoing edge to the pin. |
Rule: IllegalInputPin | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The input pin has outgoing edges. Only input pins on structured activity nodes can have outgoing edges. Check the pin and remove any outgoing edges.
|
Rule: IllegalOutputPin | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The output pin has incoming edges. Only output pins on structured activity nodes can have incoming edges. Check the pin and remove any incoming edges.
|
Rule: IllegalValuePin | Category: Correctness |
Severity: 1-high | Applies to: all areas |
The value pin has an incoming edge. Value pins provide their actions with constant values and thus require no incoming edges. Check the pin and remove any incoming edges.
|
Rule: DanglingCtrlFlow | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The control flow has no source or target node, or both. Check the control flow and attach the proper source and target node, or delete the edge from the model. |
Rule: DanglingObjectFlow | Category: Completeness |
Severity: 1-high | Applies to: all areas |
The object flow has no source or target node, or both. Check the object flow and attach the proper source and target node, or delete the edge from the model. |
Matrix: Actor-Usecase | Row type: actor | Column type: usecase |
Association relationships between actors and use cases. Shows which actor participates in which use case. A "1" indicates the actor in that row participates in the use case in that column. |
Matrix: Class_Gen | Row type: class | Column type: class |
Generalization relationships between classes (from child to parent). Shows which class a given class inherits from. A "1" indicates the class in that row is a child of the class in that column. |
Matrix: Class_Assoc | Row type: class | Column type: class |
Association relationships between classes. The numbers indicate how many associations the class in a row has with classes in the column. All associations (or aggregations or compositions) with navigability from the row class to the column class are counted. |
Matrix: Package_Dependencies | Row type: package | Column type: package |
Dependencies due to class/interface usage between packages. Shows on which packages a given package depends on. A "1" indicates the package in that row has a class or interface that uses a class or interface of the package in that column.
|
Matrix: Messages_Sent | Row type: lifeline | Column type: lifeline |
Messages sent between lifelines. The numbers indicate how many messages the lifeline in a row sends to the lifelines in the columns. |
Metamodel definition file format
<!ELEMENT sdmetricsmetamodel ( modelelement+ ) > <!ATTLIST sdmetricsmetamodel version CDATA #REQUIRED> <!ELEMENT modelelement (#PCDATA|attribute)*> <!ATTLIST modelelement name CDATA #REQUIRED parent CDATA "sdmetricsbase"> <!ELEMENT attribute (#PCDATA)> <!ATTLIST attribute name CDATA #REQUIRED type (ref|extref|data) "data" multiplicity (one|many) "one">XMI transformation file format
<!ELEMENT xmitransformations ( xmitransformation+ ) > <!ATTLIST xmitransformations version CDATA #REQUIRED requirexmiid (true|false) "true"> <!ELEMENT xmitransformation (trigger*) > <!ATTLIST xmitransformation modelelement CDATA #REQUIRED xmipattern CDATA #REQUIRED recurse (true|false) "false" requirexmiid (true|false) #IMPLIED allowxmiidref (true|false) #IMPLIED condition CDATA #IMPLIED> <!ELEMENT trigger EMPTY> <!ATTLIST trigger name CDATA #REQUIRED type (attrval|cattrval|gcattrval|ctext|reflist|constant|ignore|xmi2assoc) #REQUIRED attr CDATA #IMPLIED src CDATA #IMPLIED linkbackattr CDATA #IMPLIED>Metric definition file format
<!ELEMENT sdmetrics (metric|set|matrix|rule|wordlist|reference|term| metricprocedure|setprocedure|ruleprocedure)+ > <!ATTLIST sdmetrics version CDATA #REQUIRED ruleexemption CDATA #IMPLIED exemptiontag CDATA #IMPLIED exemptionvalue CDATA #IMPLIED> <!ELEMENT metric (description?,(projection|setoperation|compoundmetric| attributevalue|nesting|signature|connectedcomponents|count|compare| subelements|filtervalue|substring))> <!ATTLIST metric name CDATA #REQUIRED domain CDATA #REQUIRED inheritable (true|false) "false" category CDATA #IMPLIED internal (true|false) "false"> <!ELEMENT set (description?,(projection|compoundset|subelements|compare))> <!ATTLIST set name CDATA #REQUIRED domain CDATA #REQUIRED inheritable (true|false) "false" multiset (true|false) "false"> <!ELEMENT matrix (description?,(projection|setoperation|compoundset))> <!ATTLIST matrix name CDATA #REQUIRED from_row_type CDATA #REQUIRED to_col_type CDATA #REQUIRED> <!ELEMENT rule (description?,(violation|cycle|projection|compoundset| valueset|compare))> <!ATTLIST rule name CDATA #REQUIRED domain CDATA #REQUIRED inheritable (true|false) "false" category CDATA #IMPLIED severity CDATA #IMPLIED applies_to CDATA #IMPLIED disabled (true|false) "false"> <!ELEMENT wordlist (entry*)> <!ATTLIST wordlist name CDATA #REQUIRED ignorecase (true|false) "false"> <!ELEMENT entry EMPTY> <!ATTLIST entry word CDATA #REQUIRED> <!ELEMENT description (#PCDATA)> <!ENTITY % scopes "same|other|higher|lower|sameorhigher|sameorlower|samebranch| notsamebranch|containedin|notcontainedin|idem|notidem" > <!ENTITY % filteratts 'target CDATA #IMPLIED targetcondition CDATA #IMPLIED element CDATA #IMPLIED eltype CDATA #IMPLIED scope (%scopes;) #IMPLIED condition CDATA #IMPLIED' > <!ENTITY % sumatts 'sum CDATA #IMPLIED stat (sum|min|max) "sum"' > <!ENTITY % ruleatts 'precondition CDATA #IMPLIED mincnt CDATA #IMPLIED value CDATA #IMPLIED' > <!ELEMENT projection EMPTY> <!ATTLIST projection relation CDATA #IMPLIED relset CDATA #IMPLIED %sumatts; set CDATA #IMPLIED valueset CDATA #IMPLIED exclude_self (true|false) "false" recurse (true|false) "false" nesting (true|false) "false" %ruleatts; %filteratts;> <!ELEMENT compoundmetric EMPTY> <!ATTLIST compoundmetric term CDATA #IMPLIED condition CDATA #IMPLIED alt CDATA #IMPLIED fallback CDATA #IMPLIED> <!ELEMENT attributevalue EMPTY> <!ATTLIST attributevalue attr CDATA #REQUIRED element CDATA #IMPLIED> <!ELEMENT signature EMPTY> <!ATTLIST signature set CDATA #REQUIRED name CDATA #IMPLIED prologue CDATA #IMPLIED value CDATA #IMPLIED separator CDATA #IMPLIED epilogue CDATA #IMPLIED %filteratts;> <!ELEMENT connectedcomponents EMPTY> <!ATTLIST connectedcomponents set CDATA #REQUIRED nodes CDATA #REQUIRED> <!ELEMENT valuesetcount EMPTY> <!ATTLIST valuesetcount set CDATA #REQUIRED> <!ELEMENT count EMPTY> <!ATTLIST count term CDATA #REQUIRED set CDATA #REQUIRED relset CDATA #REQUIRED %filteratts;> <!ELEMENT compare EMPTY> <!ATTLIST compare term CDATA #IMPLIED set CDATA #IMPLIED with CDATA #IMPLIED comp CDATA #IMPLIED return_element (true|false) "false" exclude_self (true|false) "true" %ruleatts; %filteratts;> <!ELEMENT setoperation EMPTY> <!ATTLIST setoperation set CDATA #REQUIRED %sumatts; %filteratts;> <!ELEMENT nesting EMPTY> <!ATTLIST nesting relation CDATA #REQUIRED> <!ELEMENT subelements EMPTY> <!ATTLIST subelements set CDATA #IMPLIED exclude_self (true|false) "true" valueset CDATA #IMPLIED %filteratts; %sumatts;> <!ELEMENT filtervalue EMPTY> <!ATTLIST filtervalue relation CDATA #IMPLIED relset CDATA #IMPLIED value CDATA #IMPLIED %filteratts;> <!ELEMENT substring EMPTY> <!ATTLIST substring source CDATA #REQUIRED separator CDATA #REQUIRED position CDATA #IMPLIED endseparator CDATA #IMPLIED limit CDATA #IMPLIED result CDATA #IMPLIED> <!ELEMENT compoundset EMPTY> <!ATTLIST compoundset set CDATA #REQUIRED cum CDATA #IMPLIED valueset CDATA #IMPLIED exclude_self (true|false) "false" %ruleatts; %filteratts;> <!ELEMENT violation EMPTY> <!ATTLIST violation condition CDATA #REQUIRED value CDATA #IMPLIED> <!ELEMENT valueset EMPTY> <!ATTLIST valueset set CDATA #REQUIRED precondition CDATA #IMPLIED mincnt CDATA #IMPLIED> <!ELEMENT cycle EMPTY> <!ATTLIST cycle nodes CDATA #REQUIRED minnodes CDATA #IMPLIED> <!ELEMENT reference (#PCDATA|description)*> <!ATTLIST reference tag CDATA #REQUIRED> <!ELEMENT term (#PCDATA|description)*> <!ATTLIST term name CDATA #REQUIRED> <!ELEMENT metricprocedure EMPTY> <!ATTLIST metricprocedure name CDATA #REQUIRED class CDATA #REQUIRED> <!ELEMENT setprocedure EMPTY> <!ATTLIST setprocedure name CDATA #REQUIRED class CDATA #REQUIRED> <!ELEMENT ruleprocedure EMPTY> <!ATTLIST ruleprocedure name CDATA #REQUIRED class CDATA #REQUIRED>Grammar of expressions in metric definitions
The grammar for expressions in metric and set definitions is presented here using an extended Backus-Naur Formalism (EBNF). Note that the production rule FCT_LITERAL for function names recognized by the expression parser only lists the default functions provided by SDMetrics.
Expression = UptoExpression. UptoExpression = OrExpression {"upto" OrExpression | "topmost" OrExpression}. OrExpression = AndExpression {"|" AndExpression | "or" AndExpression}. AndExpression = EqualExpression {"&" EqualExpression | "and" EqualExpression}. EqualExpression = RelationalExpression ["!=" RelationalExpression | "=" RelationalExpression]. RelationalExpression = AdditiveExpression [ "<" AdditiveExpression | ">" AdditiveExpression | "<=" AdditiveExpression | ">=" AdditiveExpression |"lt" AdditiveExpression | "le" AdditiveExpression | "gt" AdditiveExpression | "ge" AdditiveExpression | "->" AdditiveExpression | "in" AdditiveExpression | "startswith" AdditiveExpression | "endswith" AdditiveExpression | "onlist" AdditiveExpression]. AdditiveExpression = MultiplicativeExpression { "+" MultiplicativeExpression | "-" MultiplicativeExpression}. MultiplicativeExpression = PowerExpression { "*" PowerExpression | "/" PowerExpression } PowerExpression = DotExpression {"^" DotExpression | "->" DotExpression |"in" DotExpression }. DotExpression = UnaryExpression {"." UnaryExpression}. UnaryExpression = "+" UnaryExpression | "-" UnaryExpression | "!" UnaryExpression | "not" UnaryExpression | FCT_LITERAL ExpressionList | "(" Expression ")". ExpressionList = "(" Expression {"," Expression} ")". Constant = STRING_LITERAL | IDENTIFIER | INTEGER_LITERAL | FLOATING_POINT_LITERAL. FCT_LITERAL = "ln" | "exp" | "sqrt" | "abs" | "ceil" | "floor" | "round" | "startswithcapital" | "startswithlowercase" | "islowercase" | "tolowercase" | "length" | "size" | "flatsize" | "isunique" | "typeof" | "instanceof" | "qualifiedname". INTEGER_LITERAL = DECIMAL_LITERAL. DECIMAL_LITERAL = DIGIT {DIGIT}. FLOATING_POINT_LITERAL= ({DIGIT} "." DIGIT {DIGIT} [EXPONENT]) | DIGIT {DIGIT} [EXPONENT]. EXPONENT = ("e"|"E") ["+"|"-"] DIGIT {DIGIT}. STRING_LITERAL = "'" {LETTER|DIGIT} "'". IDENTIFIER = LETTER {LETTER|DIGIT}. LETTER = "_" | "A".."Z" | "a".."z". DIGIT = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9".