The god class experiment found that complexity concentration finds god classes, but cannot tell a god class from a complex algorithm. This post is about the idea I think tells them apart, the sinew, and the experiment that has now started to test it. There are no results yet, only the idea, why it matters, and how it will be judged.
The god class experiment is finished and published on Zenodo. Its question was can we find god classes/methods by complexity concentration (from the understand complexity is conserved Conserved Amount, Negotiable Placement). The answer was that it nearly can but flags too much. Some were god classes/method. The others were classes that correctly compute complex algorithms.
So concentration is the right signal, but it is only half of one. It says where the complexity is. It does not say whether that place is healthy.
Two kinds of concentration
Think of a complex algorithm and a god class side by side. Both score high on cognitive complexity. Both stand out in their repository. The difference is in how they sit in the system.
The algorithm receives its inputs, works on them, and hands back a result. Data flows in and stops there. It is a leaf.
The god class is the opposite. It receives a request, and then it reaches out: it loads this, checks that, builds the other, passes the owner and the form and the clock and the result to half a dozen collaborators, and decides what happens next. Data flows in and fans out again, carried on the calls. It is a junction.
That is the distinction I want to measure, and the name I have for what makes the difference is a sinew.
What a sinew is
A sinew is data coupled to the control flow: data that reaches the code that needs it only by being passed down the call chain, method to method. The more data rides on the control flow, the stronger the sinew.
Here is the shape, in a deliberately ordinary Spring style handler:
@PostMapping("/owners/{ownerId}/pets")
public String addPet(@PathVariable int ownerId, PetForm form, BindingResult result, Model model) {
Owner owner = owners.findById(ownerId);
validate(owner, form, result);
if (result.hasErrors()) {
return populate(owner, form, model);
}
Pet pet = build(owner, form, clock.now());
owners.save(owner, pet);
notify(owner, pet, model);
return "redirect:/owners/" + ownerId;
}
Nothing here is bad code. But look at what travels. owner is passed to four calls. form to three. model and result are carried along so that something further down can write into them. Every method on those paths has the data in its signature, whether it uses all of it or only passes it on.
The name comes from the body. A sinew is what ties the muscle to the bone: it is what makes things move together. Data threaded through a call chain does exactly that to code. The methods on the path cannot move without each other.
Four harms
Strong sinews make code hard to change, in four ways:
- Ripple. Add a field, a parameter, a new piece of context, and every method that carries it changes, including the ones that only pass it on.
- Rigidity. A step cannot be inserted, removed or reordered without re-plumbing the signatures either side of it.
- Comprehension. To know where a value came from or where it goes, you follow the whole path.
- Attraction. Data in scope invites logic.
The fourth is the one that matters here. A method that already holds most of the data a change needs is where the next change is easiest to put. It does not have to be a decision. It is the cheaper path. So the next rule goes there, and the one after that, and the method that held the data now holds the logic too. Complexity collects where the data is.
Gravity
This is why I think of it as gravity. A junction with strong sinews has mass: it holds the data, so it attracts the logic. The logic it attracts brings more data with it, more parameters, more collaborators, more things passed on. That is more mass, and more attraction. The pull grows with what it has already pulled in.
And the pull works against the way out. The cost of refactoring a junction is the cost of cutting its sinews: every path the data rides has to be re-plumbed. So the bigger the junction gets, the more expensive it is to break up, and the more likely the next change is to land in it rather than around it. Past a certain point it is a black hole. Everyone knows it is a god class, nobody can afford to take it apart, and it keeps growing.
That is the reason to want to see it early. A junction that has just started to attract logic is a few signatures and a few calls. Refactoring it is an afternoon. The same junction a year and forty features later is a project. If a measure can say which concentrations are junctions, then it can say, while it is still cheap, which ones are going to keep growing.
Measuring it: coupling divergence
The measure began as coupling divergence: per method, the coupling going out against the coupling coming in. Coupling here is counted from what rides on the control flow. For a method with p parameters that makes calls into its own project, passing a(c) arguments on each call c:
- in = 1 + p (the call that reached it, plus the data that came with it)
- out = the sum over its calls of 1 + a(c) (each call it makes, plus the data it sends)
- d = out − in
That gives each method a role, by analogy with a pipe system:
| d | role | reading |
|---|---|---|
| greater than 0 | junction | creates and directs flow: hands on more data than it receives |
| 0 | pipe | passes data through |
| less than 0 | catchment | a leaf: receives data and processes it |
The addPet above is a junction. A parser that takes a string and returns a tree is a catchment.
And that is the classification. Complexity concentrated in a catchment is good concentration: leaf processing doing the work, which is where the complexity of an algorithm belongs. Complexity concentrated in a pipe or a junction is bad concentration: a unit that directs the data and also holds the logic. The gravity well.
There is a picture behind this from the earlier experiments. If complexity is conserved, and an architecture only moves it rather than removes it, then each method takes up area in proportion to its complexity, and the question is where the area sits. In a healthy system most of it sits in the catchments, and the junctions stay thin. A system out of balance puts its area in junctions.
None of the ingredients are new. Tramp data and stamp coupling are in Yourdon and Constantine's Structured Design from 1979, and a sinew is tramp data seen as a path. Henry and Kafura's information flow multiplied complexity by fan-in and fan-out in 1981. Lanza and Marinescu's God Class already combines complexity with access to foreign data. What I have not found is the combination: a signed balance of what a unit sends against what it receives, used to set complex leaves aside, on a scale that each codebase sets for itself.
The experiment
The concentration divergence study asks one question:
In Java, does weighting a unit's complexity by its coupling divergence recover the units people call god classes and methods better than complexity alone, by setting complex leaves aside?
It is built the way the god class study was, and on top of it. The complexity measures, the outlier rule and the statistics are the god class study's, installed from its released commit, so every baseline number is the number that study would produce on the same class. What is added is the coupling: a JavaParser measurer with its symbol solver, which resolves each call to see whether it lands in the project's own code.
The plan was written first and tagged in git (plan-v1) before any measurement code existed. Every change since is its own dated commit, stating what had been seen when it was made.
The things I most want to get right:
- People decide, not tools. The primary oracle is the MLCQ data set (Madeyski and Lewowski): professional developers reviewing real Java code and saying, without being given a definition, whether a class is a Blob or a method is a Long Method. PMD's GodClass is in the study too, but it already contains a coupling measure, so agreement with it would be partly circular. Only a person can say "this is a god class, that is just a hard algorithm".
- The base signal is chosen without the factor. On the explore half of MLCQ, the complexity signal is chosen by how well it does on its own. The divergence factor is never consulted, so it cannot pick a signal it happens to win on. The verdict is read once, on the confirm half.
- The rival explanations get their own arms. A class's divergence is a sum over its methods, so a bigger class tends to have a bigger divergence. If weighting by divergence helps, it could just be weighting by size. So the same weighting by the class's number of methods, which carries no coupling at all, is measured beside it. And the factor does two things at once, it exempts catchments and it amplifies junctions, so each is tested on its own. If the gain all comes from amplifying junctions, the claim that setting leaves aside is what helps is not supported, even if the headline is positive.
- More than one way to count. Three definitions of coupling run through everything: the one above, where a call is itself coupling; one where only the data counts; and one after Alan Kay, where the first item of a call is the message and coupling starts at the second.
- More than one population. A fresh random sample of Java repositories on a new seed (seeds 1 and 2 are used and seen), scored against the tool oracles; and Fontana's God Class, 420 classes judged by hand by a different team on the Qualitas Corpus, as a replication.
And there is a competing expectation I have to write down, because I saw it first. On the AI evolved petclinic code from the earlier experiments, it was the outgoing data on its own that carried the signal, and the balance against the incoming added little. If that holds here, what matters is how much a unit sends, and the sinew's balance is the wrong half of the idea. The plan reads that as its own outcome, not as a failure to be explained away.
As of today, the plan is registered, the measurer is built and validated on material already seen, the MLCQ repositories are being measured at the commits their reviewers labelled, and the fresh sample has been drawn. No result against any oracle exists yet.
What this experiment does not test
It looks at one checkout at a time. It asks whether sinews tell bad concentration from good, at the commit the reviewers labelled. It does not ask whether a junction found today grows into a god class next year.
That is deliberate. Before asking whether something shows early, there has to be a definition of the thing that holds up. If coupling divergence does tell god units from complex leaves, the next study takes it into git history and asks the question this post is really about: does a junction with strong sinews attract the next changes, and does it show while it is still small enough to refactor? That one will be registered on its own, judged by what actually happens to the code, not by agreement with another detector.
Where OfficeFloor comes in
I created OfficeFloor, and the sinew idea came out of building it, so I should say plainly where the interest is and how far the claim goes.
Most web frameworks are what I have been calling mutative. The framework gives you one hook per request: a controller method, a handler, a route function. Everything after that is yours to design. The control flow, the logic, which data goes where, all of it starts in that one method. That is a lot of freedom, and freedom is fine when someone is exercising judgement about it.
AI does not seem to. In the earlier experiments, and in others' work, AI tends to concentrate. Zhang, Wen and Tempero found that LLM generated object oriented designs put more of the burden into fewer classes, with the largest class holding a bigger share of the code, while smell detectors registered nothing unusual. He and colleagues found SonarQube's cognitive complexity rose and stayed up in projects after they adopted Cursor. A single handler hook with the data already in scope is exactly the place attraction works on. It is the junction, handed to you on the first line.
OfficeFloor separates the two graphs. The control flow between procedures is configured, and the data is injected. A procedure declares what it needs and what it produces, and does not receive it from whoever called it. Sketched, the first steps of the same handler:
public class AddPet {
public static void loadOwner(@HttpPathParameter("ownerId") int ownerId,
OwnerRepository owners, Out<Owner> owner) {
owner.set(owners.findById(ownerId));
}
@Next("invalid")
public static void validate(@Val Owner owner, PetForm form) { ... }
public static void build(@Val Owner owner, PetForm form, Clock clock, Out<Pet> pet) { ... }
}
No procedure passes owner to the next. It is a variable that each one that needs it asks for. So in the flow layer, between procedures, no data rides on the control flow, and a sinew cannot form there by construction. Inside a procedure it is ordinary Java, and sinews can form exactly as they do anywhere else, but they cannot reach past the procedure.
So here is the consideration, loosely, and not yet proven by anything. If sinews are where complexity concentrates into god classes, and if AI concentrates code in architectures that hand it one hook and a scope full of data, then an architecture that constrains sinews might be one that contains AI's tendency to concentrate. Not by stopping it from writing complex code, but by not giving the complexity a junction to collect in.
It also has to survive what I have already seen. On the petclinic runs, complexity concentrated about as much in OfficeFloor as in Spring: the Gini coefficient of method complexity rose from 0.23 to 0.34 in Spring and from 0.22 to 0.35 in OfficeFloor. What differed was where it sat. OfficeFloor started with 64% of its complexity in catchments, against Spring's 42%. So the claim, if it holds, is not that OfficeFloor code concentrates less. It is that its concentration lands in leaves rather than junctions, the good kind rather than the kind with gravity. That was one small application evolved by agents, the sixth way of measuring the same run, and it shaped the hypothesis rather than testing it.
That is three ifs, and the experiment that has started tests only the first. It samples repositories at random and does not compare frameworks. The OfficeFloor part is a direction, not a finding, and it has a trap I intend to stay out of. OfficeFloor code will behave differently under these measures, by design. If I only explain that difference after I see it, "OfficeFloor works differently" can explain anything, and the theory stops being testable. So when OfficeFloor code is measured, what it should look like will be written down first.
The short version
Complexity concentration finds god classes but cannot tell them from complex algorithms. The sinew is my candidate for the difference: data threaded down the control flow, which holds code together so that it cannot move apart, and which attracts the next piece of logic to wherever the data already is. A unit that sends out more data than it receives is a junction, and complexity in a junction is the kind that grows, pulling in more complexity and more coupling until it is too heavy to refactor. Complexity in a leaf is just an algorithm. A pre-registered experiment has started to test whether that distinction recovers the classes and methods people call god units better than complexity alone, judged by human reviewers, with size and fan-out given every chance to explain it away first. If it does, the next step is to see whether a junction shows early enough to cut before its gravity wins, and after that, whether an architecture that keeps data off the control flow keeps AI from building the junctions at all.
The plan, its amendments and the harness are public in the repository, with the commits that show when each was made.





