Second in a series on how software architecture shapes AI-driven code degradation. The first run left a puzzle. This post solves it.
In the first run, one number refused to cooperate. Erosion.
Erosion was meant to be the headline. It should have shown the mutative architecture rotting while the additive one stayed flat. Instead it washed out. Both arms rose. The metric could not tell them apart.
So we ran the experiment again. Longer this time. Sixty changes per chain instead of twenty. And erosion did something worse than wash out. It gave the backwards answer. It said the additive architecture, OfficeFloor, was eroding faster than the mutative one, Spring.
That is the opposite of the whole thesis. It was also, as it turns out, a lesson in how a metric can measure the wrong thing while looking perfectly rigorous.
What erosion measures
Erosion comes from SlopCodeBench. The idea is simple. Give every function a complexity mass. Mass is cyclomatic complexity times the square root of its size. Then take the fraction of all that mass that sits in functions above a complexity of 10.
A high erosion score means most of your complexity lives in a few heavy functions. A low score means it is spread thin. It is a good idea. It is also, for this question, the wrong lens.
The backwards answer
We now test the arms properly. We fit a degradation slope to each arm. Then we bootstrap the difference between them. If the difference confidence interval excludes zero, the two arms really do erode at different rates.
For whole application erosion, the Spring minus OfficeFloor slope difference is negative. The interval excludes zero. In plain terms, OfficeFloor erodes significantly faster. The metric is not confused. It is confidently wrong.
Before blaming the architecture, we checked the obvious suspect. The denominator.
Was it the denominator? No.
Erosion is a ratio. A framework like OfficeFloor routes logic through YAML wiring. That would leave less functions (Java) in the denominator. A smaller denominator inflates the ratio. That would be a measurement artifact, not real erosion.
So we measured the total complexity mass at the start and end of a chain (the denominator).
| Spring base | OfficeFloor base | Spring final | OfficeFloor final | |
|---|---|---|---|---|
| Total complexity mass | 1099 | 927 | 2013 | 2050 |
The denominators are close. At the start they are within about sixteen percent. At the end they are almost identical. So the ratio is comparing like with like. The backwards answer is not a denominator trick. It comes from the top of the fraction. It comes from which functions crossed the threshold.
What actually drives erosion
Only a handful of functions in each code base ever cross a complexity of 10. So we listed them. Here is a representative chain.
| Spring (mutative) | OfficeFloor (additive) |
|---|---|
| addOwner, CC 23 (the endpoint handler) | soundexDigit, CC 19 |
| soundex, CC 19 | FlagPossibleDuplicate::service, CC 14 |
| soundexCode, CC 19 | soundex, CC 12 |
| normalizeTelephone, CC 12 |
Look at what fills these lists. Soundex. Phone number formatting. Duplicate detection. These are branchy little algorithms. A soundex coder is a big character switch. Phone formatting is a pile of conditionals. They are complex in any architecture.
Both arms had to implement the same rules. So both arms grew the same branchy helpers. The erosion score is mostly measuring those helpers. It is barely measuring the endpoint at all.
There is one real difference in that table. Spring's list contains addOwner, the endpoint handler, at complexity 23. That is the god method. That is the thesis made visible. But erosion buries it. It is one line among many.
Why this is the wrong lens
Here is the core problem. Erosion is blind to location. It cannot tell a god method from an isolated algorithm. A function at complexity 19 counts the same whether it is a bloated handler or a tidy, single-purpose soundex coder in its own class.
But location is the entire hypothesis. The claim is not that additive code has less complexity. The claim is that additive code puts complexity in small, separate, single-purpose units. A metric that ignores where complexity sits cannot test a claim about where complexity sits.
Worse, the additive style is penalised for good behaviour. OfficeFloor extracts each concern into its own function. When an extracted algorithm is genuinely branchy, it crosses the threshold on its own. Spring, meanwhile, can bury the same logic inside a larger method and split the counting differently. The threshold is a hard cliff at 10. Cross it and your entire mass counts. Stay under it and none of it does. That makes the score jumpy. On one chain Spring scored low. On the next it scored high. One to five functions decide the whole number.
The fix: scope it to the handler
The repair is small. Stop measuring erosion across the whole app. Measure it only inside the endpoint handler's own class. That is the surface the thesis is about. It excludes the shared soundex and phone helpers, which live in their own classes, in both arms.
Now the metric behaves.
| Arm | Erosion slope | 95% CI |
|---|---|---|
| OfficeFloor (additive) | 0 | [0, 0] |
| Spring (mutative) | 0.0033 | [0.0016, 0.0049] |
OfficeFloor is flat. Dead flat. Its handler class never erodes, because new rules attach as new wired functions elsewhere. Spring climbs and the interval excludes zero. The two intervals do not overlap. Same raw metric. Same data. Correct answer, once you point it at the right surface.
The lesson
A metric can be significant and still be wrong. Whole application erosion cleared every statistical bar. It had a tight slope. Its between arm difference excluded zero. And it pointed the wrong way, because it was answering a different question than the one we asked.
The failure was not noise. It was validity. The metric was dominated by architecture neutral leaf algorithms, and it was blind to the one thing under test. No amount of extra data would have fixed that.
So the discipline is not only "run the statistics." It is "check what your metric can actually see." Erosion could not see location. Our whole hypothesis was about location.
One note to avoid confusion. Other recent work uses the word "architectural erosion" too. Slater's study means something different by it. There, erosion means violating the layers of a fixed hexagonal design. Here, erosion means complexity concentrating into heavy functions. Same word. Different measurement.
Whole application erosion is not useless. It stays in our results for comparison with SlopCodeBench. It is just not the decisive statistic here, and we no longer treat it as one.
The obvious next question is what to measure instead. If erosion cannot see where complexity lands, what can? That needs a metric built for the job. It also needs a better idea than complexity. That is the next post.
No comments:
Post a Comment