Friday, 25 September 2026

Complexity is fixed only the shape moved

Part of a series on how software architecture shapes AI driven code degradation. The previous ten posts published every metric one at a time. This post uses them to make a single argument.

Tesler was right

Larry Tesler's conservation law says that a problem carries an irreducible amount of complexity. You can move it between the user and the program or between one part of the program and another but you cannot delete it. Fred Brooks says the same thing from the other side. Essential complexity belongs to the problem. Accidental complexity belongs to the solution and only the accidental part is available to be argued about.

This experiment can test that directly because the problem is held fixed. Sixty change requests, identical in both arms, identical in all four conditions. If conservation holds the total amount of complexity in the finished code base should be roughly the same in all eight cells no matter which architecture absorbed it and no matter how the agent was coached.

It is.

amountarchjust-solvecohesiongatedformulaspread, all 8
total_ccSpring65269464664215%
OfficeFloor656681641597
halstead_volumeSpring215,300220,800209,000206,1007%
OfficeFloor216,600221,500214,100207,800
ck_wmc_totalSpring76282676175020%
OfficeFloor738766725679
pmd_cognitive_totalSpring32329329328318%
OfficeFloor319289310268
java_locSpring2,6232,7532,5792,47820%
OfficeFloor2,4512,4922,4182,253

Mean over the last twelve of sixty change requests, averaged over ten runs. Spread is (max minus min) divided by the mean, over all eight cells.

Four independent operationalisations of "amount". Control flow, program vocabulary, weighted methods, and cognitive nesting. They use different theories and different tools. They agree. The finished application carries about six hundred and fifty points of cyclomatic complexity and about two and a half thousand lines of Java and it did not matter which architecture held it and it did not matter what the agent was told.

That is the essential complexity of this problem. It is the floor. No prompt reached under it.

Total cyclomatic complexity across four conditions and two architectures

Eight series that will not separate. This is what a conserved quantity looks like. Click for full size.

Those are totals for the finished system, which is the thing you have to maintain. It is worth also asking what the sixty rules themselves added because the two applications do not start from the same place. Spring's baseline is 413 points of cyclomatic complexity and OfficeFloor's is 361, so a matching total is not automatically a matching amount of work.

complexity added by the sixty rulesjust-solvecohesiongatedformulaspread
Spring total_cc23928523623520%
OfficeFloor total_cc29632128024128%
Spring halstead_volume56,50062,30051,10048,50025%
OfficeFloor halstead_volume58,80063,50056,20050,80022%

Final phase mean minus that run's own checkpoint 1 value, per run, then averaged. Spread is over the four conditions for that architecture.

The conservation claim survives this stricter test and it picks up an honest qualifier on the way through. The spread widens from 7% on totals to between 20% and 28% on what was added, so the amount is steady rather than fixed. And OfficeFloor added more complexity than Spring in every single condition. That is the price of a composed pipeline and it is a real cost visible before any argument about placement begins. OfficeFloor's advantage is not that it carries less. It is that its baseline was lower and it stayed where it was put.

So the only question left is where it goes

If the amount is fixed then every argument about architecture is an argument about arrangement. That is Brooks' point restated as an engineering problem. You cannot negotiate the essential part, so the whole craft lives in the accidental part, which here means one thing: where the six hundred and fifty points of complexity end up sitting.

Arrangement needs its own measures and they are not the same measures as amount. An arrangement metric has to answer "how evenly is this spread" while staying blind to "how much of it there is". Three families do that:

  • Concentration of code. Take every file's share of the total complexity and measure the inequality. ccdist_file_top1 is the share held by the single heaviest file. ccdist_file_hhi is the Herfindahl index, which is the sum of the squared shares. ccdist_file_gini is the Gini coefficient, which is scale free so it sees shape alone and cannot be improved by splitting files.
  • Concentration of change. Hassan's change entropy applied cumulatively from the first commit. cum_change_top1 is the share of all sixty rules' edits that landed in one file. cum_change_entropy_norm is the same information as a spread, where 1.0 is perfectly even. This is the one that speaks directly to maintenance because it describes the file you will be opening again next week.
  • Reach. node_cc_median follows the call graph out from the endpoint and adds up the complexity you have to read to change one rule. ck_cbo_mean and propagation_cost ask the same question through coupling.

Every one of these can move a long way while total_cc does not move at all. That is the property that makes them the right instruments here.

The result: one architecture moved and one did not

Here is the whole finding in one figure. Each row is a metric. Each marker is one condition. The horizontal position is that condition's value divided by the same architecture's own control, so 1.0 means the intervention changed nothing. The number at the end of each bar is the spread across the four conditions.

Amount conserved in both architectures, organisation moved in Spring only

Top panel is amount, where nothing should move. Bottom panel is organisation. Normalising to each architecture's own control is deliberate, because the question here is how far each one moved, not where it started. The absolute levels are in the table below. Click for full size.

The top panel is the conservation result again. Every marker sits on the line for both architectures under every condition.

The bottom panel is the argument. Read the orange rows first. A plain English request for cohesion cut Spring's share of change in one file by two thirds, from 0.344 to 0.110. The formula in the prompt cut its change concentration, cum_change_hhi, by a factor of four. Spring's arrangement is different under every condition, and dramatically so.

Now read the blue rows. They are short. Under the same four interventions, OfficeFloor's arrangement barely registered that anything had been asked of it.

organisation metricSpring spreadOfficeFloor spreadratio
cum_change_top1110%11%10.0
cum_change_entropy_norm24%3%9.2
cum_change_hhi142%20%7.3
mi_mean5%1%5.6
ccdist_file_top187%17%5.3
ck_cbo_mean14%3%4.2
ccdist_file_gini34%8%4.1
total_files37%9%4.0
ccdist_file_hhi106%28%3.7
node_cc_median87%24%3.5
wmcdist_class_hhi100%29%3.4
propagation_cost36%12%2.9
and, for contrast, the amount metrics from the first table
halstead_volume7%6%1.1
total_fns15%16%1.0
total_cc8%13%0.6

Spread is (max minus min) divided by the mean of the four conditions, for that architecture's final phase. Ratio is Spring's spread divided by OfficeFloor's. A ratio near 1 means the intervention moved both equally.

The pattern is clean. On amount the ratio is about 1. Both architectures are equally immovable, which is the conservation law. On organisation, the ratio runs from 3 to 10. One architecture is plastic and the other is not.

Spring's best result is where OfficeFloor started

The interventions worked. That needs saying plainly because it is the strongest thing an opponent of this thesis can say. Spring got structurally better under all three.

The question is what "better" converged on.

metricSpring, controlSpring, bestOfficeFloor, control
cum_change_top10.3440.1100.106
ccdist_file_top10.1970.0780.086
ccdist_file_hhi0.07270.02320.0217
wmcdist_class_hhi0.05540.01910.0182
cum_change_entropy_norm0.7210.9210.886

"Spring, best" is whichever of the three interventions scored best on that metric. OfficeFloor's column is its plain control, with no intervention at all.

Read the right hand column. That is OfficeFloor with no prompt, no tool and no formula. It is the same place Spring arrives after three rounds of coaching.

Spring can be made to distribute its complexity. It has to be asked. OfficeFloor distributes because there is nowhere else for the complexity to go.

Why the two architectures behave differently

A Spring endpoint is a method. A rule is a statement you add to it. Nothing in the framework says where that statement goes, so every rule is a fresh decision made sixty times. Sixty decisions is sixty opportunities for the surrounding instruction to change the answer. That is exactly what the data shows and it is why the same architecture produced four different shapes under four different prompts.

An OfficeFloor endpoint is a graph of wired functions. A rule is a new function and a new edge. The composition is the restriction. There is no version of "add a rule" that concentrates it because the only available move is additive. A prompt asking for cohesion is asking for something the architecture has already done.

Look at what the escape route cost Spring. Under the formula condition Spring's count of container dispatched classes went from 3 to 11.2 per run. Those are advice classes, aspects, filters and entity listeners. They are the framework's own way of running code without anybody calling it. OfficeFloor's count is exactly 1.0 in all four conditions and it never moved.

Container dispatched classes across four conditions

The orange line under the formula condition is complexity leaving the call graph rather than leaving the codebase. Click for full size.

The file count tells the same story from the other end. Asked for structure Spring went from 63 files to 92. OfficeFloor sat between 168 and 184 in every condition because it was already there.

What this does not show

Five things because an argument that only reports its wins is not worth reading.

Function count is not conserved. Of the amount metrics total_fns is the weakest. The rules added between 93 and 168 functions depending on the condition, a spread of 61%. That is what you would expect because "split this into smaller pieces" is an instruction about function count and three of the four conditions were asking for exactly that. Counting functions measures the arrangement as much as the amount. The same is true of pmd_npath_total and halstead_effort, which are published in the amount group but compose super linearly inside a method, so they fall when a method is split. Control flow, volume and weighted methods are the amount metrics that actually hold still.

Function level distribution does not discriminate. ccdist_fn_gini moved 15% in Spring and 14% in OfficeFloor. cogdist_fn_gini moved 16% in both. The inequality of complexity across individual functions is an architecture neutral property and the interventions moved it equally in both arms. The signal lives at the file and class level, which is where the architectural decision actually is. If you only measured function level Gini you would conclude there is no effect here.

Correctness moved in both arms and it moved badly. The strict pass rate, meaning every test of every rule so far green, fell in both architectures under coaching. Spring went from 0.267 under the control to 0.108 under the cohesion prompt and 0.033 under the formula. OfficeFloor went from 0.325 to 0.050 under the cohesion prompt. Both architectures are plastic on correctness. The claim here is about structure only and structural stability did not buy safety. Asking an agent to restructure while it implements is expensive in both worlds.

Read those particular means with more suspicion than the rest of the page because the underlying distribution is bimodal and the mean sits in a gap where almost no run actually lands. In every one of the eight cells most runs finish at 0 or 0.167 and two or three finish near 0.9 and it is those few that hold the average up. Spring's control mean of 0.267 is three runs at 0, five at 0.167 and two at 0.917. The ordering between conditions survives this because the count of runs stuck at 0 moves the same way the mean does, from 3 of 10 under the control to 8 of 10 under the formula. The level does not. No typical run scores 0.267. You can see the whole distribution in the strip beneath the four panels of the strict pass figure, which is what that strip is for.

Two of the three interventions optimise a score this experiment defined. The impact metrics are not evidence for a claim about this experiment because the gated and formula conditions were built to move them. They are reported and they are excluded from the argument above. Every metric in the tables is either a published definition or a whole codebase count.

The handler metrics flatter OfficeFloor and should be read with care. wmc_handler moved 154% in Spring and 135% in OfficeFloor, which looks like a tie. It is not a tie in absolutes. Spring went from 126.5 to 22.6. OfficeFloor went from 1.4 to 5.5. A metric scoped to the entry handler cannot see a pipeline arm so it understates OfficeFloor's real cost. node_cc_median, which follows the calls is the honest version and is the one in the table.

What it means

Tesler's law is usually quoted as a warning about user interfaces. It is a stronger claim than that. Eight independent attempts landed on the same amount of complexity: within 7% of each other on total Halstead volume and within 28% on what the sixty rules added. Against organisation metrics that moved by 100% and more under the same interventions that is a quantity refusing to be argued with. You do not get to remove essential complexity. You get to decide where it lives.

That makes "how is it organised" the only real architectural question and this experiment puts a number on how that question gets answered in each of the two worlds.

In the mutative world the answer comes from outside the code. It came from the prompt, from the review tool, and from the published metric. Change any of those and the architecture changes with it. That is not a bug in Spring. It is what it means to have a framework that permits everything: the structure is whatever the last person to touch it decided and with an AI agent doing the touching sixty times, the structure is whatever you remembered to ask for.

In the additive world the answer comes from the composition itself. Better prompting did not improve OfficeFloor's structure because there was no slack for it to take up. It just kept doing the same thing.

There is a practical version of this. If your structure depends on prompting, reviews and metrics, then it depends on the discipline of whoever is holding them every single time forever. If your structure is a property of the composition it holds when nobody is watching.

No comments:

Post a Comment