The paper argues that an architecture does not change how much complexity a problem carries, only where that complexity is placed. The Discussion puts the reason in one line: the composition is the restriction, there is no version of add a rule that concentrates it. That is true. This blog is about why I believe it is true. The short answer is that in OfficeFloor control flow and data flow are two separate graphs.
Where I noticed it
I have been building a diagram tool for IntelliJ that opens an OfficeFloor endpoint and draws it. The surprising part, obvious in hindsight, is that it needs two overlays and not one. One overlay draws control flow, where the next function and the branches direct. The other draws data flow, the variables a function reads and writes and the value a function returns to the next one. They are different shapes over the same set of functions. Building a tool that had to keep them apart made concrete something the experiment had already measured without naming.
One graph or two
In a Spring endpoint the handler method is both a control node and a data node at once. The call graph and the data passing graph are largely the same edges. To get a value from the first step to the fourth you thread it through the second and the third even when they never use it. So control and data are one graph. When the data of the fourth step needs a change, it changes the first three.
Dependency injection softens this. Spring's @Service collaborators are decoupled objects, injected rather than constructed in place. They provide partial separation and is likely part of why the Spring arm held up as well as it did. What dependency injection does not separate is the orchestration itself, the order the steps run in and the values handed between them, which still lives in the call sequence and its arguments. That is the coupling that remains, and it is the one continuation injection removes.
OfficeFloor keeps the two apart. Sequencing is continuations, the next and the flows, so you splice a step into the order by adding an edge and not by editing the caller. Data is variables, matched by type and qualifier through a shared namespace, so a function sources what it needs and publishes what it produces without any neighbour changing its signature. A rule is therefore a new function and a new edge, and nothing around it moves.
Why that is the mechanism and not a side detail
For add a rule to stay additive, which is the whole of the additive arm's behaviour, the new function has to do two things without touching anything already there. It has to take its place in the order, and it has to get its inputs and hand on its outputs. Those are exactly the two decouplings. Remove either one and add a rule stops being additive.
Two places the numbers already show it
I did not measure the decoupling directly, and the paper does not claim to. But two of its results are fingerprints of it.
The first is the escape from the call graph. Under the disclosed formula the Spring arm's count of container dispatched classes, the aspects, filters and listeners, rose from 3.0 to 11.2 per run. That is the agent reaching for Spring's own decoupling machinery to get complexity off a call graph that resists being spread out, because the call graph couples control and data and will not distribute cleanly. The OfficeFloor arm's count is exactly 1.0 in all five conditions. It never escapes, because the ordinary way it is written is already off the call graph. The flat 1.0 is the decoupling showing up as a relief valve that never has to open.
The second is the wiring the study deliberately does not score. OfficeFloor composes its endpoint in YAML, and the study keeps YAML in a separate pool so a wiring based design cannot dilute a per line metric. That guard is correct, and it cuts both ways, because the composition is where the additive arm puts the structure that makes it additive, and no complexity metric scores it. Read through the mechanism, that guard is the placement result stated at its most literal. The coordination of control and data has not left the system. It has moved out of the Java the call graph metrics can see and into the composition they cannot. The amount is conserved, as the paper shows. What the decoupling changes is where the coordination lives.
This is seen in looking an end point requiring more than one entity:
Decoupling is not the same as granularity
One honest sharpening. The decoupling is necessary for the additive behaviour but it is not the whole of it. You could decouple control from data and still write one large function that reads and writes many variables, which would be decoupled and concentrated at once. So there are two knobs, not one. The decoupling is the substrate, and the granularity, how finely the rules are spread across functions, is the placement that rides on it.
The experiment separates them cleanly, almost by accident. None of the four external instructions moved the additive arm's placement. The fifth lever, an independent AI reviewer reasoning about the code, did, and it did so by merging functions and collapsing edges while leaving coupling, file count and per node complexity untouched. In other words the one lever that moved the additive arm moved its granularity and never its decoupling. The decoupling held under all five conditions. That is the sense in which it is the substrate. It is the part nothing in the experiment could move.

No comments:
Post a Comment