Files
codeql/java/ql/lib/semmle/code/java/metrics/MetricPackage.qll
2022-08-24 16:36:15 +01:00

299 lines
9.5 KiB
Plaintext

/**
* Provides classes and predicates for computing metrics on Java packages.
*/
import semmle.code.java.Package
import MetricElement
import MetricRefType
import semmle.code.java.Dependency
import MetricCallable
/** This class provides access to metrics information for packages. */
class MetricPackage extends Package, MetricElement {
/** Gets the percentage of lines in this package that consist of comments. */
override float getPercentageOfComments() {
exists(float n |
n = this.getTotalNumberOfLines() and
n > 0 and
result = 100 * (this.getNumberOfCommentLines() / n)
)
}
/** Gets the number of lines of code in this package. */
override int getNumberOfLinesOfCode() {
// Refer to `numlines(...)` directly to avoid invalid recursive aggregate.
result =
sum(CompilationUnit cu, int lines |
cu.getPackage() = this and numlines(cu, _, lines, _)
|
lines
)
}
/** Gets the number of lines of comments in this package. */
override int getNumberOfCommentLines() {
result =
sum(CompilationUnit cu, int lines |
cu.getPackage() = this and numlines(cu, _, _, lines)
|
lines
)
}
/** Gets the total number of lines in this package, including code, comments and whitespace-only lines. */
override int getTotalNumberOfLines() {
result =
sum(CompilationUnit cu, int lines |
cu.getPackage() = this and numlines(cu, lines, _, _)
|
lines
)
}
/** Gets the total number of reference types in this package. */
int getNumberOfTypes() { result = count(RefType t | t.getPackage() = this) }
/** Gets the total number of callables declared in a type in this package. */
int getNumberOfCallables() {
result = count(Callable m | m.getDeclaringType().getPackage() = this)
}
/**
* Gets the number of public callables declared in a type in this package.
* This is an indication of the size of the API provided by this package.
*/
int getNumberOfPublicCallables() {
result =
sum(MetricRefType t, int toSum |
t.getPackage() = this and
toSum = t.getNumberOfPublicCallables()
|
toSum
)
}
/** Gets the total number of fields declared in a type in this package. */
int getNumberOfFields() { result = count(Field f | f.getDeclaringType().getPackage() = this) }
/**
* Afferent Coupling (incoming dependencies).
*
* The afferent coupling of a package is the number of types
* outside this package that depend on types inside this package.
*
* A high afferent coupling is an indication that the package
* has many responsibilities; it may thus be a good idea to
* consider whether these responsibilities can be separated into
* separate packages.
*
* Afferent coupling of packages is also used to define other
* package metrics, such as the instability metric.
*/
int getAfferentCoupling() {
result =
count(RefType t |
t.getPackage() != this and
exists(RefType s | s.getPackage() = this and depends(t, s))
)
}
/**
* Efferent Coupling (outgoing dependencies)
*
* The efferent coupling of a package is the number of types inside
* this package that depend on types outside this package.
*
* A high efferent coupling indicates that it may be advisable to
* merge this package with others.
*
* Efferent coupling of packages is also used to define other
* package metrics, such as the instability metric.
*/
int getEfferentCoupling() {
result =
count(RefType t |
t.getPackage() = this and
exists(RefType s | s.getPackage() != this and depends(t, s))
)
}
/** Efferent Coupling (outgoing dependencies) to the specified package. */
int getEfferentCoupling(Package p) {
p != this and
result =
count(RefType t |
t.getPackage() = this and
exists(RefType s | s.getPackage() = p and depends(t, s))
)
}
/**
* Instability metric.
*
* Instability is a measure of how likely a package is to be influenced
* by changes to other packages. If this metric value is high, it is easily
* influenced, if it is low, the impact is likely to be minimal. Instability
* is estimated as the number of outgoing dependencies relative to the total
* number of dependencies.
*
* A paradigmatic stable package is `java.lang`: A lot of other packages depend
* on it, but it does not itself depend on many other packages. Changing
* other parts of a program is thus highly unlikely to affect `java.lang`.
*/
float getInstability() {
exists(int ecoupling, int sumcoupling |
ecoupling = this.getEfferentCoupling() and
sumcoupling = ecoupling + this.getAfferentCoupling() and
sumcoupling > 0 and
result = ecoupling / sumcoupling.(float)
)
}
/**
* Abstractness metric.
*
* Abstractness measures the proportion of abstract types in
* a package relative to the total number of types in that package.
* A highly abstract package (where the metric value is close 1)
* that is furthermore unstable is likely to be useless: the
* class hierarchy has been over-engineered, and all those
* abstract types are not heavily used.
*
* One should try to design packages that are abstract in
* proportion to their incoming dependencies, and concrete in
* proportion to their outgoing dependencies. Such an approach
* is likely to facilitate making changes to the code.
*
* See _Java Design: Objects, UML, and Process_ by R. C. Martin
* for further discussion of these metrics and their use.
*/
float getAbstractness() {
exists(int i, int j |
i = count(RefType t | t.getPackage() = this) and
j = count(RefType t | t.getPackage() = this and t.isAbstract()) and
result = j / i.(float) and
i > 0
)
}
/**
* Distance from Main Sequence.
*
* This measure intends to capture the tradeoff between abstractness
* and instability: the ideal situation occurs when the sum of
* abstractness and instability is one. That is, a package is
* completely abstract and stable (abstractness=1 and instability=0)
* or it is concrete and unstable (abstractness=0 and instability=1).
* We thus measure the distance from that ideal situation.
*/
float getDistanceFromMain() {
exists(float r | r = this.getAbstractness() + this.getInstability() - 1 |
r >= 0 and result = r
or
r < 0 and result = -r
)
}
/**
* How many types in this package the specified reference type depends on.
*
* The specified reference type must also belong to this package.
*/
float countDependencies(RefType t) {
t.getPackage() = this and
result = count(RefType s | s.getPackage() = this and depends(t, s))
}
/**
* Relational Cohesion.
*
* Relational Cohesion measures how well a package p hangs together.
* That is, for each type in p we measure how many types within p
* it depends on, and we take the average. To cater for the case
* that there is only one type in the package (and then obviously
* the package is quite cohesive!) we add 1 to the resulting average.
*
* The recommended range for this metric is between 1.5 and 4.0:
* too low a cohesion indicates that the package is a "kitchen sink"
* containing too many unrelated types. On the other hand, a cohesion
* that is too high indicates a package that is too complex, with
* too many internal dependencies.
*
* Note: some tools compute this metric not by counting types, but
* by counting the different ways one type depends on another.
* While this is appropriate in a tool that distinguishes between
* associations, generalizations and use links, it is less appropriate
* for metrics that are directly computed from code.
*/
float relationalCohesion() {
result =
1 +
avg(RefType t, float toAvg |
t.getPackage() = this and
toAvg = this.countDependencies(t)
|
toAvg
)
}
/**
* Gets a dependency of this element, for use with John Lakos's "level metric".
*/
override MetricElement getADependency() {
exists(RefType s, RefType t |
s.getPackage() = this and
t.getPackage() = result and
depends(s, t) and
result != this
)
}
/** Cyclic package dependencies: a source package dependency of this package. */
MetricPackage srcDep() {
result = this.getADependency() and
result.fromSource() and
this.fromSource()
}
/**
* Cyclic package dependencies: a member of the cycle to which this package belongs.
*/
MetricPackage getACycleMember() {
result.srcDep*() = this and
this.srcDep*() = result and
result.fromSource()
}
/** Cyclic package dependencies: the size of the cycle to which this package belongs. */
int getCycleSize() { result = count(this.getACycleMember()) }
/**
* Cyclic package dependencies: whether this package is considered to be a
* representative member of the cycle to which it belongs.
*/
predicate isRepresentative() {
this.getName() =
min(MetricPackage p, string toMin |
p = this.getACycleMember() and
toMin = p.getName()
|
toMin
)
}
/**
* Average Fan-In.
*
* The fan-in of a package is the average efferent coupling over all callables in that package.
*/
float getAverageFanIn() {
result =
avg(RefType t, MetricCallable c, int toAvg |
(c = t.getACallable() and t.getPackage() = this) and
toAvg = c.getAfferentCoupling()
|
toAvg
)
}
}