All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tngtech.archunit.library.metrics.ComponentDependencyMetrics Maven / Gradle / Ivy

Go to download

A Java architecture test library, to specify and assert architecture rules in plain Java - Module 'archunit'

There is a newer version: 1.4.0
Show newest version
/*
 * Copyright 2014-2022 TNG Technology Consulting GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.tngtech.archunit.library.metrics;

import java.util.Collection;
import java.util.Map;
import java.util.function.Function;

import com.google.common.collect.ImmutableMap;
import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.core.domain.JavaClass;

import static com.google.common.base.Preconditions.checkArgument;
import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;
import static com.tngtech.archunit.core.domain.JavaModifier.ABSTRACT;
import static com.tngtech.archunit.core.domain.JavaModifier.PUBLIC;

/**
 * Calculates architecture metrics as defined by Robert C. Martin in his book
 * "Clean architecture : a craftsman's guide to software structure and design".
*
* These metrics are calculated following these definitions for each component: *
    *
  • Efferent Coupling (Ce): The number of outgoing dependencies to any other component
  • *
  • Afferent Coupling (Ca): The number of incoming dependencies from any other component
  • *
  • Instability (I): {@code Ce / (Ca + Ce)}, i.e. the relationship of outgoing dependencies to all dependencies
  • *
  • Abstractness (A): {@code num(abstract_classes) / num(all_classes)} in the component
  • *
  • Distance from Main Sequence (D): {@code | A + I - 1 |}, i.e. the normalized distance from the ideal line between {@code (A=1, I=0)} * and {@code (A=0, I=1)}
  • *
*
* As an example take *

 * A -> B -> C
 * D -> B
 * 
* Then {@code Ce(B) = 1, Ca(B) = 2, I(B) = 1 / (1 + 2) = 0.33}. Assume {@code 1/3} of the classes in the component would * be abstract, then {@code A(B) = 0.33}, thus {@code D(B) = | 0.33 + 0.33 - 1 | = 0.33}. *

* Martin's thesis about these metrics is, that the more stable (low {@code I} value) a component gets, the higher the * Abstractness ({@code A} value) should be (thus the Distance from the Main Sequence). *

* Note: As an adjustment to the original definitions of these metrics we only consider public classes * to calculate the abstractness of a component. The background is, that these metrics analyse the maintainability * of components in relation to their dependencies. Internals of a component (non-public classes) do not * influence the coupling of two components, since there cannot be any dependencies to these components * from the outside. Thus they can be freely modified, no matter how many incoming dependencies there are, * or if they are abstract or not. */ @PublicAPI(usage = ACCESS) public final class ComponentDependencyMetrics { private final Map metricsByComponentIdentifier; ComponentDependencyMetrics(MetricsComponents components, Function> getDependencies) { MetricsComponentDependencyGraph graph = MetricsComponentDependencyGraph.of(components, getDependencies); ImmutableMap.Builder metricsByComponentIdentifierBuilder = ImmutableMap.builder(); for (MetricsComponent component : components) { metricsByComponentIdentifierBuilder.put(component.getIdentifier(), new SingleComponentMetrics(component, graph)); } this.metricsByComponentIdentifier = metricsByComponentIdentifierBuilder.build(); } /** * The {@link ComponentDependencyMetrics Efferent Coupling (Ce)} of the components. * * @see ComponentDependencyMetrics */ @PublicAPI(usage = ACCESS) public int getEfferentCoupling(String componentIdentifier) { return getComponentMetrics(componentIdentifier).getEfferentCoupling(); } /** * The {@link ComponentDependencyMetrics Afferent Coupling (Ca)} of the components. * * @see ComponentDependencyMetrics */ @PublicAPI(usage = ACCESS) public int getAfferentCoupling(String componentIdentifier) { return getComponentMetrics(componentIdentifier).getAfferentCoupling(); } /** * The {@link ComponentDependencyMetrics Instability (I)} of the components. * * @see ComponentDependencyMetrics */ @PublicAPI(usage = ACCESS) public double getInstability(String componentIdentifier) { return getComponentMetrics(componentIdentifier).getInstability(); } /** * The {@link ComponentDependencyMetrics Abstractness (A)} of the components. * * @see ComponentDependencyMetrics */ @PublicAPI(usage = ACCESS) public double getAbstractness(String componentIdentifier) { return getComponentMetrics(componentIdentifier).getAbstractness(); } /** * The {@link ComponentDependencyMetrics Normalized Distance from Main Sequence (D)} of the components. * * @see ComponentDependencyMetrics */ @PublicAPI(usage = ACCESS) public double getNormalizedDistanceFromMainSequence(String componentIdentifier) { return getComponentMetrics(componentIdentifier).getNormalizedDistanceFromMainSequence(); } private SingleComponentMetrics getComponentMetrics(String componentIdentifier) { SingleComponentMetrics result = metricsByComponentIdentifier.get(componentIdentifier); checkArgument(result != null, "Unknown component with identifier '" + componentIdentifier + "'"); return result; } private static class SingleComponentMetrics { private final int efferentCoupling; private final int afferentCoupling; private final double instability; private final double abstractness; private final double normalizedDistanceFromMainSequence; SingleComponentMetrics(MetricsComponent component, MetricsComponentDependencyGraph graph) { efferentCoupling = graph.getDirectDependenciesFrom(component).size(); afferentCoupling = graph.getDirectDependenciesTo(component).size(); instability = divideSafely(efferentCoupling, efferentCoupling + afferentCoupling, 1); ContainedPublicClasses classes = new ContainedPublicClasses(component); abstractness = divideSafely(classes.numberOfAbstractClasses, classes.numberOfAllClasses, 0); normalizedDistanceFromMainSequence = Math.abs(instability + abstractness - 1); } int getEfferentCoupling() { return efferentCoupling; } int getAfferentCoupling() { return afferentCoupling; } double getInstability() { return instability; } double getAbstractness() { return abstractness; } double getNormalizedDistanceFromMainSequence() { return normalizedDistanceFromMainSequence; } private static double divideSafely(int dividend, int divisor, double replacementValue) { return divisor != 0 ? ((double) dividend) / divisor : replacementValue; } private static class ContainedPublicClasses { final int numberOfAbstractClasses; final int numberOfAllClasses; ContainedPublicClasses(MetricsComponent component) { int numberOfAbstractClasses = 0; int numberOfAllClasses = 0; for (JavaClass javaClass : component) { if (javaClass.getModifiers().contains(PUBLIC)) { numberOfAllClasses++; if (javaClass.getModifiers().contains(ABSTRACT)) { numberOfAbstractClasses++; } } } this.numberOfAbstractClasses = numberOfAbstractClasses; this.numberOfAllClasses = numberOfAllClasses; } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy