Part of a series on how software architecture shapes AI driven code degradation. This post explains a group of measurements on their own. Each metric gets its definition, its figure, and its numbers.
Call hops from a handling step
indirection_median · ↓ lower is better · harness breadth-first search over the call graph
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
How many calls deep you have to go, from a handling step, to reach the code that does the work. Every hop is a file a developer has to open. This is the sceptic's objection made numeric: you did not remove the complexity, you buried it behind indirection.
indirection_median = mediank ∈ reached depth(k)
Where. reached is every method reachable from the handling nodes. The handling nodes themselves have depth 0. Depth is assigned by breadth-first search, so it is the shortest path, not the longest. The harness also records how many methods were reached, as indirection_reached, which is the denominator.
In this harness. placement.indirection_stats, sharing the same call index as the comprehension metrics.
How to read it. Expected to favour the concentrated arm. Everything in one method is zero hops away. This is Brooks' accidental complexity as a number, and it is the honest price of decomposition.
Deepest call chain
indirection_max · ↓ lower is better · harness breadth-first search over the call graph
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
The longest shortest-path from a handling step to any reachable method. It is the worst-case navigation cost for one change.
Where. Same depth assignment as the median. Because every depth is a shortest path, this is the eccentricity of the reachable set rather than the length of the longest walk.
In this harness. Same single pass.
How to read it. A rising maximum with a flat median means one long tail appeared rather than a general deepening.
Share of reached methods more than two hops away
indirection_deep_share · ↓ lower is better · harness breadth-first search over the call graph
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
Of everything the request path reaches, what fraction is far enough away that you would not find it by reading the handler. It is a distribution-shape measure rather than an extreme: not how deep the deepest is, but how much of the system lives out in the far field.
Where. The threshold of 2 is a choice, and it is the only arbitrary constant in this group. It is meant as “further than the handler and the thing it obviously calls”.
In this harness. Same single pass.
How to read it. Expected to be a counter-signal, like the rest of this group.
Coupling between objects, average class
ck_cbo_mean · ↓ lower is better · Chidamber & Kemerer 1994, via CK
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
How many other classes a class depends on. Splitting one class into six creates coupling between the six that did not exist before, so this is expected to be a counter-signal.
ck_cbo_mean = mean over classes of CBO(C)
Where. A reference is any use of the other class: a field type, a parameter type, a local variable, a method call, a thrown exception. CK counts distinct classes, not distinct references, so calling one class fifty times still counts 1. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.
In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.
How to read it. If a distributed architecture keeps this flat while adding units, that is a real result in its favour, because it is the objection you would most expect to land.
Coupling between objects, worst class
ck_cbo_max · ↓ lower is better · C&K 1994, via CK
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
The single most entangled class in the codebase.
Where. Same CBO definition as the mean.
In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.
How to read it. Where the mean is diluted by many small classes, the maximum is not. A rising maximum with a flat mean means one class is becoming the hub.
Fan-out, average class
ck_fanout_mean · ↓ lower is better · CK
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
How many other classes a class calls out to. It is the directional half of coupling: not how entangled a class is, but how much it depends on others.
ck_fanout_mean = mean over classes of fanout(C)
Where. Unlike CBO this counts only outgoing references. CK records the incoming direction separately as fan-in, which the harness also stores.
In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.
How to read it. Rising fan-out with flat fan-in is the orchestrator shape: a class that coordinates rather than one that is depended upon.
Response for a class, average
ck_rfc_mean · ↓ lower is better · Chidamber & Kemerer 1994, via CK
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
How many distinct methods could end up executing in response to one message to this class. That is its own methods plus everything they call. It is roughly the size of the behaviour you have to consider when you call into a class, which makes it both a testability and a comprehension measure.
ck_rfc_mean = mean over classes of RFC(C)
Where. M(C) is the methods declared by C. R(m) is the set of methods invoked by m. CK uses the one-level version, which is the common implementation: it counts methods called directly by the class's own methods and does not recurse.
In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.
How to read it. It rises with both concentration and indirection, which makes it a useful tiebreaker between those two stories.
Response for a class, worst
ck_rfc_max · ↓ lower is better · C&K 1994, via CK
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
The class with the largest response set. The worst thing in the codebase to call into.
Where. Same one-level RFC definition as the mean.
In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.
How to read it. Read it beside the handler's own RFC below. If the worst class in the codebase is the handler, that is the thesis. If it is something else, say so.
Response set of the handler class
ck_handler_rfc · ↓ lower is better · CK, pinned to the handler class
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
How much behaviour is reachable from one call into the endpoint's class. It is closely related to the whole-path complexity in the comprehension group, but computed by a different tool, in units of methods rather than branches, and only one level deep.
Where. H is the handler class. One level only, so unlike the comprehension walk this does not follow the call graph transitively.
In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.
How to read it. A second opinion on the handler's comprehension load, from a tool with a different parser and a different definition.
Coupling of the handler class
ck_handler_cbo · ↓ lower is better · CK, pinned to the handler class
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
How many other classes the endpoint's own class depends on. A handler that collects dependencies is a handler that is accumulating responsibilities.
Where. Same CBO definition, scoped to the handler class.
In this harness. Computed by the CK tool, which parses source with Eclipse JDT. Only declared source classes are seen: library types are not, and neither is anything the parser fails on, so the class count in the validity group is worth checking beside any CK figure.
How to read it. In a pipeline architecture the dependencies move to the wiring, which is exactly the kind of relocation the framework-dispatch caveat is about. A very low figure here for an arm implementing sixty rules is a prompt to check the validity group.
Law of Demeter violations
pmd_demeter_violations · ↓ lower is better · Lieberherr & Holland 1989, via PMD
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
Places where code reaches through one object to get at another, as in a.getB().getC().doThing(). It is the classic symptom of a class knowing too much about its neighbours' internals, and it is another externally defined detector with thresholds nobody here chose.
this, a parameter, a locally created object, or a field of thisWhere. The law says a method may only call methods on: itself, its own parameters, objects it created, and its own fields. PMD's rule reports one violation per offending call site, so a single long chain can contribute several.
In this harness. PMD's LawOfDemeter rule, from the same single spawn as the complexity measures.
How to read it. It tends to rise with distribution, so treat it as a counter-signal. It is also a famously noisy rule, so read the trend rather than the absolute count.
Propagation cost
propagation_cost · ↓ lower is better · MacCormack, Rusnak & Baldwin 2006
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
If you change a random file, what fraction of the codebase could feel it? It is the density of the transitive closure of the file dependency matrix, and it is the one whole-architecture coupling number here with real pedigree in the modularity literature.
Where. n is the number of production Java files. reach(i) is the set of other files reachable from file i by following call edges transitively, so a file does not count itself. The numerator is therefore the number of ordered reachable pairs, and dividing by n2 gives the expected fraction of the system a random change can touch.
In this harness. placement.propagation_cost collapses the method call graph to a file graph, dropping self-edges, then runs a depth-first reach from every file.
How to read it. Lower means better modularised. Use the between-arm comparison at the same change request and nothing else.
Files reachable from a typical file
propagation_fanout_median · ↓ lower is better · MacCormack et al. 2006, unnormalised
Line is the mean of ten runs. Band is one standard deviation. Click for full size.
The raw count behind propagation cost. From one file, how many files can you reach by following dependencies. Because it is not normalised it cannot be improved by adding files, which makes it the honest version.
Where. Same file graph and same transitive reach as the propagation cost, with no n2 division. The harness also records the maximum and the file count.
In this harness. Same single pass as the propagation cost.
How to read it. If the normalised and unnormalised numbers tell different stories, the difference is packaging rather than structure. That comparison is the reason both are reported.
No comments:
Post a Comment