What extraction cannot slim, and whether function injection can: a pre-registered study of 580 Java repositories
Analyze & explore related research in Conceptio
Connect this record directly into your research workflow or AI agent. Explore citations across open repositories, query full text, and export structured proof bundles.
What extraction cannot slim, and whether function injection can A pre-registered study of 580 Java repositories Daniel Sagenschneider OfficeFloor daniel@officefloor.net doi:10.5281/zenodo.23991507
arXiv:2611.10545v1 [cs.SE] 7 Nov 2026
November 2026
Abstract A method of 26 code lines or more reads as a Long Method, and Extract Method is the usual fix. Some long methods have no single-exit fix: a return cannot leave an extracted piece, a piece hands back one value, and some methods are knotted so that every cut is blocked. Function injection, as the OfficeFloor framework does it, cuts differently: each piece becomes a function the framework injects and sequences, an early return ends a function, and a function hands on several values. The framework’s author pre-registered a test of whether it takes apart what extraction cannot. A stratified random sample of 580 open-source Java repositories, each pinned before AI coding assistants were in general use, was scanned by released tools whose cost models were fixed before the sample was drawn: 3,026,902 methods, 168,466 of them long. Single-exit extraction cannot slim 22.8% of the long methods. The registered hypothesis, that at least a quarter of those stuck methods are ones only function injection takes apart, is refuted: 22.2% (95% CI 20.3 to 24.3). Nearly half of the stuck methods are early-return methods the plan counts as conventionally fixable, and the split takes three in four of those apart as they stand. Where extraction would do, the split chooses injection and costs less for 29.0% against extraction wrapped as one function and 17.2% against plain extraction, almost always as a straight sequence of functions. An exploratory reading of the quarter of the stuck methods that neither takes apart finds a third of it tables and literals, long for their data rather than their logic, and most of the rest blocked by a jump inside a loop, by a nested statement too large to cut, or by values too many for any cut. Three registered studies are proposed: the classes as rules in the tools, a refinement per class, and the same measures on code written with AI assistants.
1
Introduction
Fowler catalogued the Long Method and named its fix, Extract Method [6, 8]. An earlier study of this author’s found that the length at which reviewers call a method a Long Method is 26 code lines, lines that hold code with blank and comment lines not counted [16]. A tool, SinewSlim, followed: for every method of 26 lines or more it searches for a set of extractions that brings the method, and every extracted piece, to 25 lines or fewer, pricing each cut by what crosses it and taking the segmentation whose widest cut is narrowest. Tools that find Extract Method opportunities are not new [19, 9]; SinewSlim’s difference is that it prices every cut and says when no cut is feasible, and that is where this paper starts. Some long methods cannot be slimmed by extraction into single-exit pieces at all. A return cannot leave such a piece, so a method of guard clauses blocks every cut that contains one. An extracted piece hands back one value, so a run of statements that produces two needs a record to carry them, at a cost of its own. Some methods are knotted so tightly that every cut needs more parameters than a reader should follow. These methods are called stuck here, and stuck means stuck under extraction as SinewSlim models it: 1
one exit, one output and at most five parameters a piece. The first finding of this paper is the count of them: on 580 repositories, 22.8% of the long methods are stuck, between a fifth and a quarter. Function injection cuts a method differently. In OfficeFloor, a framework the author created, a function is an ordinary Java method whose arguments the framework supplies, as dependency injection does [7], and whose next function the framework chooses: the developer writes the pieces and the wiring between them, and the framework sequences them. An early return becomes a flow from its function to the method’s end; an if can become a decision with a flow per branch; a function hands on several values, because the next function asks the framework for them; the instrument used here lets a function take up to five such values as parameters. So a method extraction cannot slim might still come apart as functions. The author expected it would, often. That expectation is exactly what a reader should not take on trust from the framework’s author, and this paper is the pre-registered test of it: the sample’s seed, the hypotheses, the bar and the rule for reading the result were committed before any repository of the sample was read, every cost comes from released products whose cost models were fixed at or before registration, and every figure is reported whichever way it points. Each repository of the sample is pinned to its last commit before 1 December 2020, six months before GitHub Copilot’s technical preview of 29 June 2021 [3], so that no method in the sample was written with the AI coding assistants in general use since. What the paper reports. Four things. The count of the long methods single-exit extraction cannot slim, by cause, on a seeded random sample of 580 repositories. The registered test of whether function injection takes apart a quarter of them, which it fails: 22.2% (95% CI 20.3 to 24.3), the bar 0.7 points above the interval, with a reading, not registered, of where the shortfall sits. An estimate of how often, where extraction would do, the split a developer would make chooses injection and costs less under cost models fixed at or before registration, with the shape of the plans it chooses and the mechanism, derived from the two cost models, that decides them. And an exploratory reading of the quarter of the stuck methods that no split takes apart, from which three further studies are proposed. Every reason the split fails and every method where it does worse are in the replication package, from which every figure here is regenerated by script.
2
Background
Extraction, and its cost. SinewSlim reads a method’s facts (its statements, the values each declares, reads and writes, the jumps that leave each run of statements) and searches for the segmentation S into contiguous runs whose extraction leaves every piece and the remainder at or under the target. A piece p ∈ S is priced by its seam, what crosses its boundary: its ip inputs, passed as parameters, and its op outputs, handed back; and the piece itself costs 1, for its name and its call. The extraction cost of a segmentation is (
Cext (S) =
X
ip + r(op ) + 1 ,
p∈S
|
{z
seam of p
r(o) =
}
o, 3o,
o ≤ 1, o ≥ 2 (a record).
(1)
A run is feasible with ip ≤ 5, op ≤ 1 and no jump leaving it other than a trailing return. The inputs count every value a piece reads that was set before it, the method’s own parameters included, except a value declared before the piece with no value or a literal one and first used inside it, which moves into the piece; the remainder, what is left in the method, is not priced. The search takes the segmentation with the smallest worst seam maxp (ip + r(op )), then the fewest pieces |S|, then the smallest Cext : it minimises the widest cut first and the total last, so SinewSlim’s segmentation is not in general the one of least cost, a point Section 8 returns to. A method is slimmable when a segmentation exists; slimmable but locked when every piece and the remainder are bound to 2
the class’s fields or methods, so that a split moves nothing out of the class; slimmable only with a record when a piece must hand back several values (op ≥ 2, priced at 3op ); and not slimmable otherwise, with the obstacle named. The last two statuses are what this paper calls stuck. The injection split. OfficeFloorSlim, the second product, reads the same facts and searches for a partition P of the method’s control flow into functions F joined by edges E, each function at or under the target, with every line in some function. Its cost model was decided before any of its figures was set beside SinewSlim’s: Cinj (P ) =
X
df + 1
+ |E|,
(2)
f ∈F
where df , the function’s seam, counts the values computed inside the method that f declares as its parameters, so that a value used by several later functions costs 1 in each function that declares it; values from outside the method, its parameters and whatever the framework injects, are free and not counted, and what a function returns to the framework is not counted either; a value declared earlier with no value or a literal one and first used inside the function moves into it, as into SinewSlim’s pieces, and is not declared. Each function costs 1 and each edge, a flow between functions, costs 1; a function may carry at most five values, df ≤ 5. A return before a function’s end is one flow to the method’s end, however many such returns the function holds; a throw becomes an escalation; and an if or switch may become a decision function in F whose branches are edges in E. Loops, lambda bodies and labelled statements are never cut between functions. The injection split chooses among feasible partitions by SinewSlim’s own rule: the smallest worst seam maxf df , then the fewest functions, then the smallest Cinj . Its worst seam is the counterpart of SinewSlim’s widest piece, with one difference that matters for reading them side by side: SinewSlim’s ip counts the method’s own parameters when a piece reads them, and df does not, since the framework injects them. The combined split. No OfficeFloor developer would wire a ten-line calculation through the framework as functions and flows when extracting a method would do. The combined split is what a developer would do: functions where the flow divides, extracted methods inside them, its rule settled on the day of registration, before the sample was drawn. Its outer search is the injection split’s; a function whose body is over the target is allowed when SinewSlim’s search, under its defaults, slims that body as a method of its own, its declared values its parameters. A body SinewSlim slims only with a record is refused. Writing Sf for SinewSlim’s segmentation of the body of f (empty when the body is at or under the target), a plan is priced by the sum Ccomb (P ) = Cinj (P ) +
X
Cext (Sf ),
(3)
f ∈F
the injection total of (2) plus the extraction cost of (1) of the pieces inside the functions. Unlike the two splits it is built from, the combined split chooses by the total: the plan with the smallest P Ccomb ; on a tie, the fewer pieces f |Sf |, then the fewer functions |F |. The whole method as one function, slimmed by SinewSlim, is always a candidate: with |F | = 1, no edge and nothing carried, Cinj = 1 and Ccomb = Cext (S) + 1. So wherever SinewSlim slims a method the combined split has a plan, and minP Ccomb (P ) ≤ Cext (S) + 1. That holds by construction and is not a finding; a one-function plan is reported as extraction only and counted as SinewSlim’s. What the two models price. Equations (1) and (2) price the same thing, data crossing a boundary, by different rules, and the rules say in advance where each is cheaper. Extraction prices a computed value twice: once on its way out of the piece that made it, as an output, and once on its way into each piece that reads it, as an input. A piece that hands back two or more values prices each three times, as a field of a record. The remainder is free: a 3
value that flows back into what stays in the method costs nothing on arrival. A method’s own parameters are priced like any other input, once into each piece that reads them. And each piece costs 1, for its name and its call. Injection prices a computed value once, in each function that declares it as a parameter, and nothing on its way out. A method’s parameters are never priced, since the framework injects them wherever they are read, and what a function returns to the framework is not priced either. Each function costs 1, and each flow between functions costs 1, whether or not anything crosses it. So a boundary that one computed value crosses costs the same both ways, an output and an input against a declared value and a flow, and a chain of k single-value stages costs within 1 of the same under both. The models part in four places. A flow is cheaper where several values cross one boundary, each priced once against twice or three times; where a method’s parameters are read past the first cut, which extraction prices and injection does not; and where what the last piece hands back is the method’s own result, which extraction prices as an output and injection returns for free. It is dearer where a boundary carries nothing, since the flow still costs 1 and an empty seam costs 0. The second of these is what the example build below shows. Which of them the long methods of the sample contain is what Section 4 measures. Two worked examples. Figure 1 gives two methods written for this paper and priced by the pinned instrument; the comments in the code mark the cuts it reports and name each piece and function as the equations below do: EXTRACT for a piece SinewSlim would extract, FUNCTION for a function of the combined split, and INJECTION for a function of the injection split alone where that differs. The costs below are the instrument’s, set out term by term under Equations (1) to (3). The first, describe, is 32 code lines of guard clauses, and the point of it is that one model has a price and the other has none. Every run of more than a line or two contains a return, so no single-exit piece is feasible and Equation (1) has nothing to sum. The injection split makes two functions, the guards and the rest, each declaring nothing; the guards’ early returns are one flow to the method’s end, the returns of price are its own end and cost nothing, and one flow joins the two: Cext (describe) has no value: no feasible segmentation S (SinewSlim’s obstacle: jumps), Cinj (describe) = (0 + 1) + (0 + 1) + |{z} 2 = 4. | {z } check
| {z } price
|E|
Its jumps ignored, the method would slim, so the plan puts it in category 1 of the four defined after the examples, conventionally fixable, and credits the split with it only in the post hoc reading. The second, build, is 40 lines in three stages: the first produces records and skipped, read by what follows, and the parameter options is read throughout. SinewSlim extracts the two loops, parseRows taking rows, options, records and handing back skipped, and summarise taking options, records, summary and handing back nothing: Cext (build) = (3 + r(1) + 1) + (3 + r(0) + 1) = 9, |
{z
parseRows
}
|
{z
summarise
Ccomb (one function) = 9 + 1 = 10.
}
The widest piece is parseRows, seam 4. The injection split alone, choosing by worst seam first, cuts at both stage boundaries so that no function declares more than one value, and pays a flow at each. Its three functions are marked INJECTION in the figure: prepare holds the two declarations and declares nothing; tally holds both loops and declares records alone, since skipped, declared with a literal and first used inside it, moves in, as it moves into SinewSlim’s parseRows, which hands it back; and report builds the report and declares summary: Cinj (build) = (0 + 1) + (1 + 1) + (1 + 1) + |{z} 2 = 7. | {z } prepare
| {z } tally
4
| {z } report
|E|
Table 1: The terms. Term
Meaning
Code line Long method Piece, remainder Seam, width
A line holding code; blank and comment lines are not counted A method of 26 code lines or more; the target for any part is 25 An extracted run of statements, and what stays in the method What crosses a piece’s boundary, inputs plus outputs, Eq. (1); the width of a segmentation is its worst seam A piece handing back two or more values, priced at 3 each SinewSlim has a segmentation; locked when it moves nothing out of the class Slimmable only with a record, or not slimmable: no single-exit segmentation A piece of the injection split, and an edge between two of them, Eq. (2) The computed values a function takes as parameters; the most any function declares A function made of an if or switch, whose branches are flows; a throw Functions and flows alone, chosen by worst seam first Functions where the flow divides, SinewSlim’s pieces inside them, chosen by the sum, Eq. (3) A combined plan of one function: SinewSlim’s segmentation, plus 1 Where SinewSlim slims a method: the combined split chooses injection and its sum is below, or equal to, SinewSlim’s cost plus 1 A stuck method in category 3: a plan under the OfficeFloor column and no conventional fix Of a stuck method, tested in the order 2, 1, 3, 4: a record slims it; extraction slims it once its jumps are ignored; only the split has a plan; nothing has a plan A plan with no decision function, or with one; either may have early returns
Record Slimmable, locked Stuck Function, flow Declares, worst seam Decision, escalation Injection split Combined split Extraction only Improved, tied Rescued Categories 1 to 4
Sequence, branching
The combined split, choosing by the total, cuts once, before the second stage; the second function declares records, skipped, and both bodies are at or under the target, so no piece is extracted inside either: Ccomb (build) = (0 + 1) + (2 + 1) + |{z} 1 + | {z } parse
| {z }
summarise
|E|
0
= 5,
P |{z} f
Cext (Sf )
a saving of 5 against extraction wrapped as one function and 4 against plain extraction. The 4 is the three parameters, options priced into each piece and rows into the first, which the split never prices; summary, which extraction separates from the loop that fills it and the split keeps with it; and records, an input to both pieces but declared by one function; against those five the split pays 1 for its flow. The categories. A stuck method is put in one category, tested conventional first, so that function injection is credited only with what nothing simpler fixes: category 2 if a record slims it; else category 1 if SinewSlim’s defaults slim it once its jumps are ignored, the guard-clause methods a developer could restructure; else category 3 if the OfficeFloor column has a plan; else category 4, which needs a redesign. Category 3 is the rescue, and the plan’s hypothesis reads it under the combined split. Table 1 collects the terms used from here on. Related work. Finding Extract Method opportunities is a settled line of work. JDeodorant finds them by slicing [19], JExtract by the dependencies among a method’s statements [17], SEMI by the functional relevance of neighbouring statements [2], GEMS by a learned model [21], and Haas and Hummel by a scored search over long methods [9]. Maruyama’s block-based slicing [12] and Komondoor and Horwitz’s semantics-preserving extraction [10] take runs that are not contiguous or that jump, which is where a single-exit piece fails; IDEs do the same for one early 5
public String describe(Order order, Customer customer, Catalog catalog) { // FUNCTION check: a return ends the flow , else next if (order == null) { return "no order"; } if (order.getLines().isEmpty()) { return "empty order"; } if (customer == null) { return "no customer"; } if (!customer.isActive()) { return "inactive customer"; } // END FUNCTION: passes nothing on // FUNCTION price: declares nothing int total = catalog.totalOf(order. getLines()); if (total == 0) { return "free"; } int discount = customer.discountFor(total ); if (discount > total) { return "invalid discount"; } int due = total - discount; String currency = customer.getCurrency();
public Report build(List<String> rows, Options options) { // FUNCTION parse: declares nothing // INJECTION prepare: declares nothing List<Record> records = new java.util. ArrayList<>(); int skipped = 0; // END INJECTION: passes on records // INJECTION tally: declares records // EXTRACT parseRows(rows, options, records) for (String row : rows) { String[] cells = row.split(options. separator()); if (cells.length < 3) { skipped++; continue; } Record record = new Record(); record.setKey(cells[0].trim()); record.setValue(Integer.parseInt(cells [1].trim())); record.setUnit(cells[2].trim()); record.setSource(options.source()); records.add(record); } // END EXTRACT: returns skipped // END FUNCTION: passes on records, skipped // FUNCTION summarise: declares records, skipped Summary summary = new Summary(); // EXTRACT summarise(options, records, summary) for (Record record : records) { if (record.getValue() < options.minimum ()) { summary.addInvalid(record.getKey()); continue; } summary.addTotal(record.getValue()); summary.addUnit(record.getUnit()); summary.addSource(record.getSource()); summary.incrementCount(); } // END EXTRACT: returns nothing summary.setSkipped(skipped); // END INJECTION: passes on summary // INJECTION report: declares summary Report report = new Report(); report.setTitle(options.title() + ": " + summary.getCount() + " records"); report.setTotal(summary.getTotal()); report.setUnits(summary.getUnits()); report.setSources(summary.getSources()); report.setInvalid(summary.getInvalid()); report.setSkipped(summary.getSkipped()); report.setGenerated(System.currentTimeMillis ()); report.setFormat(options.format()); report.finish(); return report; // END INJECTION: returns report // END FUNCTION: returns report }
if (currency == null) { return "no currency"; } String note = customer.noteFor(due); if (note == null) { return currency + " " + due; } return currency + " " + due + " (" + note + ")"; // END FUNCTION: returns the String }
Figure 1: Two worked examples, priced by the pinned instrument; the comments mark the cuts it reports, named as in the equations of the text, and are not code lines. Left, describe: stuck under single-exit extraction (obstacle jumps); the injection split and the combined split both make the 2 functions marked FUNCTION, with 2 flows, carrying nothing, total 4. Right, build: SinewSlim extracts the 2 pieces marked EXTRACT, each to the method named, at a cost of 9; the injection split alone makes the 3 functions marked INJECTION with 2 flows, total 7; the combined split makes the 2 functions marked FUNCTION with 1 flow, the second declaring records, skipped, total 5.
6
return by handing back a flag the caller tests. JExtract is the nearest in what it considers: it shares SinewSlim’s candidate space, contiguous runs with one exit, but scores a run by how far its variables, types and packages differ from the rest of the method and returns the best three, so it has no length target and no verdict when nothing is feasible. Each of these proposes cuts; none counts what no cut can do across a corpus, and that is the count this paper makes, under one tool’s rules. Studies of refactoring as practised find that most of it is done by hand and that Extract Method is among the refactorings developers apply most [14]; asked why, developers name reuse and the readability of a long method [18]; and RefactoringMiner finds the refactoring in commit history at scale [20]. The Long Method is among the smells whose diffuseness across releases Palomba et al. measured [15], and detectors for it, by threshold or learned, are compared by Fontana et al. [5]. A length threshold for a long method is usually a statistic over a corpus [11]; the 26 used here is what reviewers call long [16]. Cognitive complexity [1], read in Section 6, measures how hard a method is to follow rather than how long it is. Function injection has an older relative in flow-based programming [13], where a program is a graph of processes passing packets along connections, and a nearer one in dependency injection [7]; what OfficeFloor adds is that the pieces are methods of ordinary classes and the sequencing is the framework’s. Pre-registration is newer to software engineering than to the sciences it came from; registered reports, in which the plan is reviewed before the data is read, are described by Ernst and Baldassarre [4], and this study follows their form without a reviewer. The author knows of no prior count of how many long methods a flow of functions can take apart that extraction cannot. What was known before the plan. The combined split had been run on one repository, OfficeFloor’s own, written by the author, before the hypotheses were written: it rescued 12 of 119 stuck methods (10.1%), and of the 327 long methods SinewSlim slims it chose injection and cost less for 95 (29%). That one repository pointed against the rescue hypothesis. The plan records the author’s expectation all the same: that code written by many hands, before AI assistants, would show more methods that only injection takes apart than the framework’s own code does. The 29% is why the second question has no threshold: a share set after seeing it would test nothing.
3
Method
The analysis plan was registered on 1 November 2026 with every decision settled, before the sample was drawn, by commit bcb5d48 of the study’s public repository. Every change since is a dated amendment that records what had been seen when it was made. The plan, the pinned values, the step scripts and the amendments are in that repository, named in the ancillary data, and the plan as it stood at that commit is deposited with the data (see Data availability). Everything below is as registered unless marked post hoc or exploratory. The sample. An independent tool, open-source-repository-sampler 0.3.0, drew a seeded, stratified simple random sample of GitHub repositories with the plan’s seed, 20261101, chosen as one nobody had drawn before; the study’s scripts refuse any other seed. The frame is Java repositories created before 1 December 2020 in three popularity tiers, high (over 5,000 stars), mid (1,000 to 5,000) and low (200 to 999), drawn equally. Each repository is pinned to the newest commit on its default branch’s first-parent history committed before 1 December 2020, and judged there: at least 50 functions in at least 15 files, repositories owned by the author or by OfficeFloor excluded. Table 2 gives each tier’s population, what was drawn and what was usable; population estimates weight each repository by its stratum’s population over its usable count. The draw finished on 2 November 2026 and is committed: 757 drawn, 580 usable, 177 excluded by the criteria, 0 lost to errors.
7
Table 2: The sample: each tier’s population before the as-of date, what was drawn, what met the criteria, and the stratum weight (population over usable) used for population estimates. Tier
Stars
High Mid Low
over 5,000 1,000 to 5,000 200 to 999
All
Population
Drawn
Usable
Excluded
Weight
546 2,539 8,818
253 252 252
224 191 165
29 61 87
2.4 13.3 53.4
11,903
757
580
177
The instruments. Every cost comes from released products; the study computes none. OfficeFloorSlim 0.5.2, built on SinewSlim 0.2.3, was run once per repository at its pinned commit, from a jar whose digest is pinned in the study. It reports, for every long method, SinewSlim’s assessment under SinewSlim’s defaults (trigger 26, target 25), the injection split and the combined split, each with its plan or its reason for having none, and the method’s category under each. The scope is production Java: every .java file, leaving out src/test, src/it, build output, generated directories and any directory named test or tests. The instrument was re-pinned three times before any figure of the plan’s measures had been computed, twice for speed and once for a fault in an intermediate release. The release used reproduces the first registered release’s results on every scan compared, and differs from the faulty release in exactly the 2 methods the amendment names; the plan’s amendments record each change and its evidence. The measures and the analysis. Per repository and pooled, the plan fixes the long methods by SinewSlim’s status and their slim cost, the stuck methods and those each split has a plan for, the stuck methods by category under each split, the worst seam, the number of parts and the largest part side by side where both SinewSlim and the injection split have a plan, each split’s reasons where it has no plan, and, of the long methods SinewSlim slims, those where the combined split chooses injection and its sum is below SinewSlim’s cost plus 1 (improved) or equal to it (tied), with the saving. The two total costs are never compared: they count different things, and the weight that would turn one into the other is the very thing a sceptical reader would say was chosen to favour function injection. The unit is the repository. Every interval is a percentile bootstrap over repositories within strata, 10,000 resamples with the plan’s seed, at 95%; the bootstrap applies no finite population correction, and the high tier’s draw is 46% of its population, so the intervals there are, if anything, wide. Each headline figure is reported pooled, as a population estimate weighted by stratum with its interval, per tier, and as the median of each repository’s own share, so that a few very large repositories cannot quietly carry a pooled figure. The hypotheses. Each is read from the 95% interval of its population estimate. H1, the rescue, is confirmatory: category 3 under the combined split is at least 25% of the stuck methods and at least 30 methods, a bar fixed before any scan; supported if the whole interval is at or above 25% and the count reaches the minimum, refuted if the whole interval is below, inconclusive otherwise. H2, the improvement, is an estimate with no threshold: the share of the long methods SinewSlim slims where the combined split chooses injection and costs less; ties are reported beside it and never counted, because the tie-break favours injection.
4
Results
Every usable repository was scanned (580 of 580, none failed) on 3 November 2026; 173 files could not be parsed and were left out. Figure 2 gives the whole count at a glance and Table 3 the corpus. Of 3,026,902 methods with a body, 168,466 (5.6%) are long. SinewSlim slims 130,033 of them (77.2%) with one return per piece, at a median slim cost of 3 (quartiles 2 and 6), the cost being
8
168,466 long methods, 5.6% of the 3,026,902 with a body
SinewSlim slims 130,033 (77.2%)
Stuck: 38,433 (22.8%)
105,869 slimmable, 24,164 locked to the class
3,727 only with a record, 34,706 not at all
1: jumps ignored 17,123 (44.6%)
Injection Tied, settled Extraction chosen and by the alone chosen cheaper tie-break 68,359 (52.6%) 35,618 (27.4%) 26,056 (20.0%) the combined split, where extraction would do
2: a record 3,727 (9.7%)
3: the rescue 8,524 (22.2%)
4: a redesign 9,059 (23.6%)
the categories under the combined split, tested conventional first
Figure 2: The count. Every long method of the 580 repositories, by SinewSlim’s status; the methods it slims, by what the combined split chooses; the stuck methods, by category under the combined split. Shares are pooled over the sample. Table 3: The long methods by SinewSlim’s status, per tier and pooled. The last two statuses are the stuck methods. SinewSlim’s status
High
Mid
Low
All
Share
1,509,345 70,918
938,580 58,536
578,977 39,012
3,026,902 168,466
5.6% of methods
Slimmable Slimmable, but locked to its class Slimmable only with a record (stuck) Not slimmable (stuck)
44,038 10,380 1,467 15,033
36,010 8,913 1,424 12,189
25,821 4,871 836 7,484
105,869 24,164 3,727 34,706
62.8% 14.3% 2.2% 20.6%
Stuck
16,500
13,613
8,320
38,433
22.8% of long
Methods with a body Long (26 code lines or more)
SinewSlim’s total for the whole segmentation rather than any one seam. The rest, 38,433 (22.8%), are stuck: 3,727 slim only with a record and 34,706 not at all. The shares are alike across the tiers. The reach, and H1. Table 4 gives every headline figure and Table 5 the stuck methods by category under each split. The combined split has a plan for 24,327 of the 38,433 stuck methods, 63.3% (95% CI 60.1 to 66.5) of the population, and the injection split alone for 21,329 (55.5%). But having a plan is not the rescue. Tested conventional first, 17,123 of the stuck methods (44.6%) would slim by extraction once their jumps were ignored, and 3,727 (9.7%) with a record. What is left is category 3, the methods only function injection takes apart: 8,524 under the combined split, 22.2% (95% CI 20.3 to 24.3) of the stuck population, and 7,515 under the injection split alone. The interval’s upper bound, 24.3%, lies below the registered quarter. H1 is refuted by the plan’s rule, and by a small margin: the bar is 0.7 points above the interval. The margin holds under the two readings that could move it, both in Section 8: with the test code the scope let through left out the upper bound is 24.7%, and with every method the instrument’s fault cost category 3 restored the pooled share would be 22.7% against 22.2%; that interval was not recomputed, and a shift of that size leaves the upper bound, 24.3%, under the quarter. The count is far above the minimum of 30; it is the share that falls short. The tiers agree (22.6%, 21.5% and 22.4%), and the median repository’s own share, 17.4% over 470 repositories with a stuck method, is lower still, so the large repositories carry the pooled figure upward, not down. The stuck methods are concentrated, the ten repositories with the most holding 38.0% of them, and in 124 of those 470 repositories (26.4%) no stuck method is rescued at all; the rescue share of the ten largest runs from 16.9% to 33.9%, around the pooled figure rather than above it. The remaining 9,059 stuck methods (23.6%) have no plan from either side; Section 6 reads them. 9
Table 4: The headline figures: the count over its denominator, the pooled share, the population estimate weighted by stratum with its 95% bootstrap interval over repositories within strata, and the median of each repository’s own share over the repositories with a method in the denominator. Figure Stuck methods the injection split has a plan for Category 3 under the injection split Stuck methods the combined split has a plan for Category 3 under the combined split, the rescue: H1 Improved: injection chosen and cheaper, of what SinewSlim slims: H2 Tied on the sum, settled by the tie-break
Count / of
Pooled
Population (95% CI)
Median
Repos.
21,329 / 38,433
55.5%
55.5% (52.0 to 59.0)
60.0%
470
7,515 / 38,433
19.6%
19.2% (17.5 to 21.3)
14.5%
470
24,327 / 38,433
63.3%
63.3% (60.1 to 66.5)
66.7%
470
8,524 / 38,433
22.2%
22.2% (20.3 to 24.3)
17.4%
470
35,618 / 130,033
27.4%
29.0% (24.3 to 34.6)
21.5%
559
26,056 / 130,033
20.0%
20.9% (19.1 to 23.0)
19.1%
559
Table 5: The stuck methods by category, tested conventional first, under the injection split and under the combined split. Category 3 under the combined split is H1’s count. Category of a stuck method
Injection split Count of stuck
Combined split Count of stuck
1: slims by extraction once its jumps are ignored (conventionally fixable) 2: slims by extraction with a record 3: a plan under the OfficeFloor column only (the rescue) 4: no plan from either (needs a redesign)
17,123
44.6%
17,123
44.6%
3,727 7,515
9.7% 19.6%
3,727 8,524
9.7% 22.2%
10,068
26.2%
9,059
23.6%
Stuck methods
38,433
38,433
Why the combined split fails. Table 6 gives every reason, under each split. A loop longer than the target is the reason for 43.5% of the 14,106 stuck methods the combined split has no plan for: a loop whose body SinewSlim cannot slim inside the function either. A single statement longer than the target, usually a switch of many short cases, a try with resources or a finally, and a switch that falls through account for most of the rest. A jump crossing every cut, the obstacle that most often stops extraction, stops the combined split on only 484: on jumps, the split does what it was built to do. That is more than the 177 the injection split alone stops at for the same reason: 189 of them are the fault of Section 8, and the rest are methods the injection split stops at a loop or a statement over the target, which the combined split may take whole as a function body, so that the obstacle it reports moves to the jump crossing every cut outside it. Where extraction would do: H2. Of the 130,033 long methods SinewSlim slims, the combined split chooses injection and costs less than SinewSlim’s cost plus 1 for 35,618: 29.0% (95% CI 24.3 to 34.6) of the population, a median repository share of 21.5%, close to the 29% seen on OfficeFloor’s own code before the plan. Another 26,056 (20.9%) tie on the sum and go to injection by the tie-break, and 52.6% are left to extraction alone. Among the improved methods the saving is a median of 2 (quartiles 1 and 4), and 39.2% of them save exactly 1, so that their sum equals SinewSlim’s cost: the extra function has paid for itself and no more. That plus 1 is the cost of the method as one function, which a developer who keeps the method as a method never pays, so the registered figure is the improvement against extraction inside OfficeFloor. Against plain extraction, the sum strictly below SinewSlim’s cost itself, the share is 21,655 of the 130,033: 17.2% 10
Table 6: Why the split has no plan: each reason’s count among the stuck methods and among all long methods, under the injection split and under the combined split.
Reason
Injection split Stuck Share All long
Combined split Stuck Share All long
A loop longer than the target A single statement longer than the target A try with resources or a finally A switch whose cases fall through More than 5 carried values in every plan A jump crossing every cut
8,265 2,839
48.3% 16.6%
18,528 6,553
6,133 2,609
43.5% 18.5%
6,133 2,611
2,491 1,714 1,618
14.6% 10.0% 9.5%
8,544 1,962 1,921
1,753 1,634 1,493
12.4% 11.6% 10.6%
1,753 1,634 1,493
177
1.0%
226
484
3.4%
498
Without a plan
17,104
37,734
14,106
14,122
(95% CI 14.6 to 20.1), a median repository share of 11.9%. The second reading was not registered; a reader who takes H2 as “costs less than extraction” should take that one. Where both can split. Where SinewSlim and the injection split both have a plan, 111,583 methods, the split’s worst seam carries fewer values than SinewSlim’s widest piece for 63.3%, the same for 32.2% and more for 4.5%; the 4,993 methods where it carries more are listed in the analysis, every one, and 84.1% of them are larger by a single value. The comparison leans towards the split by definition, since a function’s seam leaves out the method’s parameters and a piece’s width counts them, and the scans cannot say by how much; and the split gets there by cutting more, more functions than SinewSlim makes pieces for 66.8%. Post hoc. Nothing in the two paragraphs that follow was registered. Each was made after the registered analysis had been read, and each is a reading of the same scans, not a new measure. Post hoc 1: where the shortfall sits. The categories credit injection only with what no restructuring could reach, but the combined split has a plan for 76.0% of the category 1 methods and 74.9% of category 2, taking them apart as they stand, with no guard inverted and no record written. Read over the 17,583 stuck methods that nothing conventional fixes, categories 3 and 4 together, category 3 is 48.5% (population 47.2% (95% CI 43.4 to 51.2), median repository 50.0%): Table 5 with a different denominator, not a new measure. The registered reading stands; this one says where the shortfall against the quarter comes from. Post hoc 2: the shape of the plans injection wins. Every plan records its functions, each a run, a decision or a handler, and its links, each a next, a flow or a return, so a plan can be read as a straight sequence of functions or as branching, with or without early returns (Table 7). Where extraction would do and injection is chosen and cheaper, the plan is a sequence in 96.4% of the 35,618 methods (17.1% of them with early returns), and the tied plans are sequences in 97.7%. Where injection rescues a stuck method, the plan branches in 64.3% of the 8,524, and 10.0% are sequences with early returns. Read as a population, the long methods for which a straight sequence of functions is the cheaper split under the registered models, the plan a sequence and either cheaper than extraction wrapped as one function or the only plan there is, are 23.8% (95% CI 19.2 to 29.8) of the long methods (37,381 in the sample), or 14.8% (95% CI 12.2 to 18.0) against plain extraction, a saving of 2 or more. Nearly all are methods extraction would also split; the ancillary data has the figure with ties and for plans of any shape.
11
Table 7: Post hoc: the shape of the combined split’s plan where it uses injection, read from the plan’s functions and links. A sequence has no decision function; early returns are return links from a function other than the last. Plans
Count
Sequence
Sequence, early returns
Branching
Branching, early returns
Improved (injection chosen and cheaper) Tied (chosen by the tie-break) Rescued (category 3, combined split)
35,618
28,252 (79.3%)
6,088 (17.1%)
1,062 (3.0%)
216 (0.6%)
26,056 8,524
21,644 (83.1%) 2,189 (25.7%)
3,807 (14.6%) 852 (10.0%)
564 (2.2%) 3,849 (45.2%)
41 (0.2%) 1,634 (19.2%)
5
Discussion
The author expected function injection to take apart a good share of what extraction cannot, and set a quarter as the bar. On 580 repositories of code written before AI assistants were in general use, the registered reading is 22.2%, and the interval excludes the quarter, by 0.7 points. The expectation was right in direction against the one repository seen beforehand (10.1% on the framework’s own code) and wrong in size against the bar. A reader sceptical of OfficeFloor should take the figure as it stands: function injection is not a general fix for stuck methods. The post hoc reading says where the shortfall sits. Nearly half of the stuck methods (44.6%) are methods SinewSlim would slim if their jumps were ignored: the guard-clause and early-return methods, of which describe in Figure 1 is one. The plan put them in category 1, conventionally fixable, which was the reading least favourable to injection and the right one to register, and it stands. What that category assumes is that ignoring a jump stands for a refactoring a developer would make. The nearest ones are inverting the guards, threading a flag through nested ifs, or extracting the run with a flag the caller tests and returns on, as an IDE does for one early return; the study prices none of them, and the first two many would call the worse design. The combined split handles the jump as it stands, and takes apart three in four of those methods without restructuring them. Of the stuck methods that nothing conventional fixes at all, it takes apart about half. That is the clearest pattern in the data: single-exit extraction, as SinewSlim models it, cannot deal with an early return, and a flow can. What is left after that, 23.6% of the stuck methods, has no plan from either side, and Section 6 reads what it holds. The shape of the plans separates the two results by mechanism. Where injection is chosen over extraction, the method is almost always a sequence: stages in order, each consuming what the one before produced, sometimes leaving early. The reading of Equations (1) and (2) in Section 2 says where such a sequence is cheaper than a method lifted into pieces, and it is not at every hand-off: where several values cross one boundary, where a method’s parameters are read past the first cut, and where the last piece hands back the method’s own result; build in Figure 1 shows the second. The saving is small because most boundaries carry one or two values: a median of 2, and 39.2% of the improved methods save exactly 1, the wrapper function’s own cost. The claim the data supports is that the long methods people write are often pipelines, and that under the registered cost models a pipeline whose stages hand on more than one value, or read the method’s parameters throughout, is cheaper written as a sequence of functions than contained in a method and lifted into pieces. The scans do not record which source each saving comes from, so their shares are not known. The rescue has the other mechanism: the methods only injection takes apart are mostly branching, an if or switch whose arms become flows, 29.8% of them with early exits, which is where extraction’s single-exit piece fails outright. As a population, the methods for which a sequence of functions is the cheaper split are 23.8% of the long methods, or 14.8% against plain extraction. At the margin, a saving of 1, the two designs cost the same under the registered models, and what the sequence adds is not priced. Under injection the caller becomes configuration: the order of the functions and what each receives are declared, rather than read from statement order and the local variables that carry one stage’s product to the next. Whether a declared flow is easier to follow than the method it replaces is a
12
Table 8: Exploratory: the category 4 methods by class, the largest classes grouped by family. Top ten is the share of the class held by the ten repositories with the most of it, beside the same repositories’ share of all long methods (what size alone predicts); dispersion is 1 when the class is spread as the long methods are and higher when clustered; cognitive per line is the class’s median cognitive complexity [1] per code line. Class Tables and literals A switch of short cases, too many together A switch that is a flat table (every case 3 lines or fewer) An array initialiser A long argument list A builder chain A string concatenation A constructor that only delegates A body of one statement Dense logic A break or continue inside a loop A return inside a loop A return and a break or continue inside a loop A return inside a try An if that passes values on An if that passes values on and jumps Too many values at every cut, even at a limit of 10 Dispatch and nesting An else-if chain (dispatch) A switch with a case over the cap A nested try over the cap A nested loop over the cap Data within reach of a higher limit A plan at a limit of 6 to 10 Only the blocking body slims, at a limit of 6 to 10 All other classes Category 4
Methods
Share
Repos.
Top ten
Size predicts
Dispersion
Cognitive per line
3,030 1,028
33.4% 11.3%
142
45%
32%
4.2
0.21
346
3.8%
97
47%
30%
1.8
0.12
416 390 388 260 81 121 2,213 377 199 156
4.6% 4.3% 4.3% 2.9% 0.9% 1.3% 24.4% 4.2% 2.2% 1.7%
44 58 76 65 19 35
87% 65% 59% 64% 86% 60%
18% 13% 15% 20% 22% 8%
11.5 9.9 5.7 11.4 1.7 2.3
0.00 0.00 0.00 0.00 0.00 0.00
98 72 56
50% 55% 56%
30% 32% 28%
1.5 0.9 1.6
0.57 0.57 0.63
170 170 97 1,044
1.9% 1.9% 1.1% 11.5%
48 57 43 149
63% 56% 62% 47%
31% 30% 27% 34%
1.3 1.0 1.3 3.1
0.41 0.53 0.59 0.45
773 375 154 129 115 2,193 1,134 1,059
8.5% 4.1% 1.7% 1.4% 1.3% 24.2% 12.5% 11.7%
107 53 26 43
44% 60% 88% 61%
32% 28% 24% 15%
3.9 1.4 1.8 2.4
0.44 0.41 0.08 0.47
850
9.4% 329
39%
38%
7.8
0.35
9,059
question about reading, which nothing here measures; the 37,381 methods are where such a study would look. Every method here was written by people, before AI coding assistants were in general use; whether code written with assistants is stuck as often, and in the same ways, is a study for which the figures here are the baseline.
6
What the redesign list holds
Nothing in this section was registered or bears on a registered figure. It was read after the results, to decide what the tools should do with the methods they stop at, by a separate step whose harness, scripts and results are in the study’s repository; Section 7 says what follows from it. The 9,059 category 4 methods, 23.6% of the stuck methods and 5.4% (95% CI 4.2 to 7.1) of the long methods, are those SinewSlim cannot slim and the combined split has no plan for. A harness re-read 9,057 of them from source with a parser of its own and classed each by the innermost obstacle; at the scan’s limit of five it gives none a combined plan, so it reproduces the scan, and it refines the instrument’s reason into the classes of Table 8. Three causes. 50.5% of the list (4,579 methods, in 280 repositories) is indivisible: one statement over the cap that nothing inside can move. 35.7% (3,237) is data: every cut needs more than five values. 13.7% (1,243) is control: a return, break or continue crosses every cut. The instrument’s reason jumps also covered 219 methods where nothing can be cut at all, a body of one statement, a constructor that only delegates, or returns that belong to a lambda; the harness moves these to indivisible, and no conclusion below depends on the move.
13
Table 9: Exploratory: of the category 4 methods, how many have a plan when the limit of five is raised, under the combined split, under SinewSlim alone (its parameters a piece) and under the injection split alone (its carried values a function). Limit 5 6 7 8 10
Combined split has a plan Methods Share 0 398 691 890 1,134
0.0% 4.4% 7.6% 9.8% 12.5%
SinewSlim slims Methods Share 0 308 593 852 1,213
0.0% 3.4% 6.5% 9.4% 13.4%
Injection split has a plan Methods Share 189 439 645 786 1,011
2.1% 4.8% 7.1% 8.7% 11.2%
Tables and literals. A third of the list, 3,030 methods (33.4%), is tables and literals: switch statements of short cases (1,028) or that are flat tables (346), array initialisers, long argument lists, builder chains, string concatenations, constructors that only delegate, and bodies of a single expression. They are long for their data or their options, not their logic: their cognitive complexity is zero or near it, 0.12 and 0.21 per line for the two kinds of switch against 0.35 for the list as a whole. Of the 1,528 switches in the list (16.9%), 1,374 are long only because of how many options they have; only the 154 with a case over the cap have a body to split. Read as methods of their own, the blocking statements of the literal classes take data in and call nothing internal (80% of the array initialisers, 62% of the string concatenations), where those of the logic classes hand on to other internal code (90% of the long switch cases, 83% of the nested tries): the logic has seams, and is blocked by jumps and carried values, not by a lack of them. Arguably the tables are not long methods in the sense the trigger intends. Dense logic. The rest is where a split would help a reader, and the harness says why it fails. In the control group, 91% (1,128 of 1,243) have the blocking jump inside a loop, try or switch: 834 in a loop, 197 in a try, 97 in a switch. The injection split cuts a sequence of steps, and a step whose body breaks out of its enclosing loop or returns from the method cannot become a function, so the loop stays whole. Only 81 have the jump at the method’s top level, 55 of them long branching methods with early returns. These are the densest methods in the study, 0.57 to 0.63 cognitive complexity per line in the loop classes. Of the 750 methods blocked by an if (8.3%), 375 are else-if chains that dispatch; of the 375 others, 300 (80%) pass values on from their branches, jump, or both, and they are long and dense (median 141 to 186 lines, 0.53 to 0.59 per line). A branchy leaf, an if that passes nothing out and jumps nowhere, is the rarest kind (75, 0.8% of the list). What blocks an if is its outputs, not its branching. The limit of five. The data group was re-run with the limit raised (Table 9). Six gives the combined split a plan for 398 of the list (4.4%), the largest single gain; ten for 1,134 (12.5%). Within the data group, 35% (1,134) gain a plan at some limit up to ten; for 33% (1,059) only the blocking statement slims as a body of its own and the method as a whole still has no plan; and 32% (1,044) have no plan even at ten. The last are the most complex of the data classes (0.45 per line, median 95 lines) and need a different split, not a looser limit. SinewSlim alone gains at about the same rate (308 at six), so a higher limit moves categories 1 to 3 as well as 4, which is why Section 7 proposes sweeping it over the whole population rather than raising it here. Complexity. At the same length, category 4 is no more complex than the methods injection rescues. Standardised to category 4’s length distribution, its mean cognitive complexity is 46.2, against 45.9 for category 3, 59.6 for categories 1 and 2, and 35.4 for the methods SinewSlim slims; per line, 0.35 against 0.33 and 0.47. What keeps a method in the list is its shape, not its complexity, and the list holds two families: the tables, with no complexity to speak of, and the dense logic, at about 0.45 to 0.63 per line. 14
Where the list is. The list is in 329 repositories, so it is widespread, and it is also clustered beyond what repository size explains. Its dispersion index, the chi-square of each repository’s count against the count its long methods predict, over the repositories with a long method less one, is 7.8, where 1 would mean spread as the long methods are; the ten repositories with the most hold 39.0%, close to the 38.3% their share of long methods predicts, so the clustering is not only the largest few; and 32 repositories whose standardised residual against the pooled rate exceeds 3.9, about the Bonferroni bound for 580 tests at the plan’s level, hold 40% of the list from 22% of the long methods. The classes split by how they are spread (Table 8). The literal classes are concentrated: the ten repositories with the most array initialisers hold 87% of them where size predicts 18%, a dispersion of 11.5, and the long argument lists, string concatenations and nested tries (88% in ten repositories) are alike, which looks like generated code or a house style of building data in code. The logic classes are spread as the long methods are, flat switches at 1.8 and ifs that pass values on at 1.0, which looks like ordinary ways of writing code. A reading by domain, coded by keyword from each repository’s description and topics, is in the ancillary data; its intervals are wide. Two repositories, OpenLiberty/open-liberty, apache/hive, ran out of the call-resolution budget the statement readings use, so their internal calls are undercounted; the complexity figures do not depend on call resolution.
7
What follows
The redesign list is where both tools stop, and Section 6 says it is not one thing. Three studies follow from it, each to be registered as this one was, by a dated commit of its plan, seed and reading rule in a public repository before its sample is drawn. Study 1: the classes as rules. The exploratory classes move into the instrument as rules with fixed thresholds, so that a method the split cannot plan is reported with its class and the advice that fits it. Two hypotheses: that a fresh sample drawn the same way gives the same class shares, within the intervals here; and that reviewers shown a method with its class accept it, the tables first, since if reviewers do not call a table a long method the trigger itself changes. Study 2: a refinement per class, and what is left. Each class gets the change that reaches it, tested on the methods already listed. Table 10 gives the classes of Table 8 with the rule the harness finds each by and what the study does with it: named exits for a jump inside a loop, try or switch (1,128 methods); the branches of an if that passes values on as functions with the value as the flow’s argument (267); and for data coupling (3,237) the limit of five swept from three to ten over the whole population, since Section 6 shows a higher limit moves categories 1 to 3 as well as 4. Each refinement is read by how many of its class become slimmable and by how cognitive complexity per line moves, since a rewrite that slims by growing denser has not helped. What is left when every class has had its refinement is the proposal for a definition of a god method that needs no human judgement: a method over the cap that no split at the chosen limit and no rule-following rewrite takes apart. The hypothesis is that the residual is under one in a hundred long methods, and dense logic rather than data. Study 3: the measures on code written with assistants. A sample drawn by the same design from repositories whose code was written after assistants were in general use, scanned with the refined instrument, and its long share, stuck share, class shares and residual compared with the figures here, which are its baseline. The claim is bounded to decomposition: the measures say nothing about how methods couple to one another or whether the code will change well.
15
Table 10: The classes of the redesign list, the rule the exploratory harness finds each by, and what the second study does with it. The root cause is the instrument’s reason: a statement over the cap that nothing inside can move (indivisible), more than five carried values in every plan (data), or a jump crossing every cut (control); a control method whose jump crosses no cut moves to indivisible. The rules are applied in the order listed within each family, to the innermost statement over the cap; a cut is a boundary between statements. Class
Rule
Refinement
Tables and literals (indivisible) A constructor that The body is a single super(...) or only delegates this(...) call A body of one One top-level statement that holds no others: statement a return or an expression A switch that is a flat A switch with every case three lines or fewer table A switch of short A switch with no case over the cap and a cases case over three lines An array initialiser An expression whose array initialiser spans at least half its lines A builder chain An expression with a chain of four or more calls A string An expression concatenating five or more concatenation string literals A long argument list An expression with a call of eight or more arguments Dense logic (control and data) A break or continue The blocking jump leaves a loop, or targets a inside a loop; a loop or switch nested in it; by the kind of return inside a loop; jump both A return inside a try The same, with a try An if that passes An if with fewer than two else if arms, values on values set in its branches read after it, and no jump leaving it An if that passes The same, with a jump leaving it values on and jumps Too many values at The instrument’s reason is carried values, and every cut, even at a the combined split has no plan at any limit up to ten limit of ten Dispatch and nesting (indivisible) An else-if chain An if with two or more else if arms A switch with a case over the cap A nested try or loop over the cap
A switch with a case over the cap The innermost statement over the cap is a try or a loop whose body nothing can move
Data within reach of a higher limit (data) A plan at a limit of 6 The combined split has a plan at the first to 10 limit from six to ten Only the blocking No plan at ten, but SinewSlim slims the body slims at 6 to 10 blocking statement as a body of its own at some limit from six to ten
16
Out of the trigger, by the first study: a table to leave in place or move to configuration
Named exits: the jump becomes the function’s named exit, wired in configuration The same The branches become functions, the value the flow’s argument The same A different split, not a looser limit; what the sweep leaves
Classed and reported by the first study; no refinement The case extracted as a method of its own Classed and reported by the first study; no refinement The limit swept from three to ten over the whole population The same
8
Threats to validity
Stuck is relative to the extraction SinewSlim searches: contiguous pieces with one exit, one output and at most five parameters. An IDE’s Extract Method takes a run with an early return by handing back a flag or an Optional that the caller tests and returns on, and a developer can write such a piece by hand; SinewSlim does not search that form, so a method stuck here may come apart under an extraction it does not price. Category 1, 44.6% of the stuck methods, is where this matters, and the count of what nothing conventional fixes is a count under one tool’s rules. The limits every count rests on, five parameters a piece, five carried values a function, a trigger of 26 and a target of 25, are fixed points: the trigger is the author’s own earlier finding, and Section 6 shows a limit of six gives a plan to 4.4% of the redesign list, so each would move the categories; Section 7 proposes sweeping the limit for that reason. The split is a projection: none of this code was written for function injection, so a plan is what the split would cost, not what anyone paid, and the migration is not analysed here. The injection split’s limits decide much: loops and lambda bodies are never cut between functions, a choice made before the plan for the framework’s own reasons, and Section 6 shows it is where most of the control group sits. The baseline H2 is read against is chosen by a different rule from the plan it is set beside. SinewSlim takes the segmentation with the smallest worst seam first and the smallest cost last, and the combined split takes the plan with the smallest sum; where a segmentation of wider seam and lower cost than SinewSlim’s exists, it is not the baseline, so H2 leans towards injection by the width of that gap, which the scans do not record. The combined split’s tie-break favours injection, so ties are reported apart and never counted, and its one-to-one weight between the two totals is a choice; a reader who weighs a flow dearer than a parameter would see fewer improved methods. H2’s baseline of SinewSlim’s cost plus 1 includes a function a developer outside OfficeFloor never pays for, so the reading against SinewSlim’s cost itself is reported beside it. The cost models price what crosses a boundary and nothing about how the result is read, so whatever a declared flow adds to understanding, or takes away, is outside every figure here. The instrument has one fault in how it builds a function body’s facts, found after the registered analysis and reported to its project. The combined split has no plan for 16 of the 130,033 methods SinewSlim slims (0.012%), which the plan says cannot happen by construction, and no plan for 189 of the 7,515 methods the injection split has a plan for, all with the reason jumps; both lists are in the ancillary data, and both count against injection. The most the second could add to H1 is every lost method restored to category 3, 22.7% pooled, under the quarter. The split also charges a flow for an early return in a function that otherwise falls off the method’s end, where the flow goes where falling off the end goes; it can only overstate injection’s cost. The scope’s globs let through test code in directories named otherwise (test-applications, internalClusterTest): 8,191 long methods (4.9%) in 80 repositories. With every method under a directory whose name contains test left out, the rescue is 22.7% (95% CI 21.0 to 24.7) and the improvement 29.5% (95% CI 24.8 to 35.1), and neither reading changes. Generated code predates AI assistants and is in the sample wherever its directory is not named generated; the concentrated literal classes of Section 6 are where it shows, and the longest method in the sample, 5,976 lines, is a table in plantuml/plantuml. Stars measure attention, not craft, and a repository’s tier is its popularity now, not at the as-of date. Assistants older than Copilot existed before the as-of date, so the sample is code written before AI code generation was in general use, not before any of it. The registration is a commit in a repository the author controls, pushed to GitHub on 1 November 2026; the evidence of its date is GitHub’s record of that push and of the commits that follow it, not an independent registry’s timestamp. A reader who wants more than that has the plan as registered, deposited with the data, and the scripts, from which every step from the draw to this paper’s numbers is repeated without the author. The exploratory reading is one pass over one list by its author, its class thresholds were chosen by eye to sort that list and are not yet validated, which is the first proposed study’s job. The studies of Section 7 are proposals, not findings. The study is Java only, and the author created the framework and both products: this is 17
why the sample was drawn by a separate tool with a seed fixed in the plan, why every cost is the products’ and fixed before the plan, why the two totals are never compared, and why every reason the split fails and every method where it does worse is reported.
9
Conclusion
On 580 open-source Java repositories as they stood before AI coding assistants were in general use, 22.8% of the long methods cannot be slimmed by single-exit Extract Method as SinewSlim models it. The registered hypothesis that function injection takes apart at least a quarter of them is refuted: it takes apart 22.2% (95% CI 20.3 to 24.3), the bar 0.7 points above the interval. A reading not registered says where the shortfall sits: nearly half of the stuck methods are early-return methods the plan counted as conventionally fixable, and the split takes three in four of those apart as they stand. Where extraction would do and injection is chosen instead, the method is almost always a pipeline, cheaper under the registered cost models as a sequence of functions because a value crossing a boundary is priced once as a declared value and twice as an output and an input, and a parameter read throughout is priced by extraction alone. The quarter of the stuck methods that no split takes apart is, on an exploratory reading, a third tables and literals that are long without being complex, and otherwise logic blocked by a jump inside a loop or try, by an if whose branches pass values on, or by more values than any cut may carry; three studies are proposed to turn that reading into rules, a refinement per class, and a baseline against code written with assistants. The author expected more of the rescue, and the plan was written so that the data could say so. Data availability. Every number here is produced by a script from the committed analysis, and every table is a generated fragment; the ancillary data holds the registered plan with its amendments, the sample with its derivation, the run log, the registered analysis with every perrepository figure and every method where the split’s worst seam is larger, the post hoc readings with the methods they list, the exploratory reading of category 4 with its tables and domain coding, and the two worked examples with their scan. The raw scans are reproduced by the study’s scripts from the pinned sample and the pinned jar, so a replication needs no cooperation from the author.
Competing interests and funding The author created OfficeFloor, SinewSlim and OfficeFloorSlim, and has a commercial interest in OfficeFloor’s adoption. No external funding was received.
References [1] G. A. Campbell, “Cognitive complexity: an overview and evaluation,” in Proceedings of the 2018 International Conference on Technical Debt (TechDebt 2018), ACM, pp. 57–58. [2] S. Charalampidou, A. Ampatzoglou, A. Chatzigeorgiou, A. Gkortzis and P. Avgeriou, “Identifying extract method refactoring opportunities based on functional relevance,” IEEE Transactions on Software Engineering, vol. 43, no. 10, pp. 954–974, 2017. [3] N. Friedman, “Introducing GitHub Copilot: your AI pair programmer,” The GitHub Blog, 29 June 2021. [4] N. A. Ernst and M. T. Baldassarre, “Registered reports in software engineering,” Empirical Software Engineering, vol. 28, no. 2, article 55, 2023.
18
[5] F. Arcelli Fontana, M. V. Mäntylä, M. Zanoni and A. Marino, “Comparing and experimenting machine learning techniques for code smell detection,” Empirical Software Engineering, vol. 21, no. 3, pp. 1143–1191, 2016. [6] M. Fowler, Refactoring: Improving the Design of Existing Code. Addison-Wesley, 1999. [7] M. Fowler, “Inversion of control containers and the dependency injection pattern,” martinfowler.com, 2004. [8] M. Fowler, Refactoring: Improving the Design of Existing Code, 2nd ed. Addison-Wesley, 2018. [9] R. Haas and B. Hummel, “Deriving extract method refactoring suggestions for long methods,” in Software Quality Days (SWQD 2016), LNBIP vol. 238, Springer, 2016, pp. 144–155. [10] R. Komondoor and S. Horwitz, “Semantics-preserving procedure extraction,” in Proceedings of the 27th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL 2000), pp. 155–169. [11] M. Lanza and R. Marinescu, Object-Oriented Metrics in Practice. Springer, 2006. [12] K. Maruyama, “Automated method-extraction refactoring by using block-based slicing,” in Proceedings of the 2001 Symposium on Software Reusability (SSR 2001), pp. 31–40. [13] J. P. Morrison, Flow-Based Programming: A New Approach to Application Development, 2nd ed. CreateSpace, 2010. [14] E. Murphy-Hill, C. Parnin and A. P. Black, “How we refactor, and how we know it,” IEEE Transactions on Software Engineering, vol. 38, no. 1, pp. 5–18, 2012. [15] F. Palomba, G. Bavota, M. Di Penta, F. Fasano, R. Oliveto and A. De Lucia, “On the diffuseness and the impact on maintainability of code smells: a large scale empirical investigation,” Empirical Software Engineering, vol. 23, no. 3, pp. 1188–1221, 2018. [16] D. Sagenschneider, “What reviewers call a Long Method is a length: a pre-registered test of coupling divergence as a god-unit signal, and what it settled instead,” 2026. https://doi.org/10.5281/ zenodo.23749773 [17] D. Silva, R. Terra and M. T. Valente, “Recommending automated extract method refactorings,” in Proceedings of the 22nd International Conference on Program Comprehension (ICPC 2014), pp. 146– 156. [18] D. Silva, N. Tsantalis and M. T. Valente, “Why we refactor? Confessions of GitHub contributors,” in Proceedings of the 24th ACM SIGSOFT International Symposium on Foundations of Software Engineering (FSE 2016), pp. 858–870. [19] N. Tsantalis and A. Chatzigeorgiou, “Identification of extract method refactoring opportunities for the decomposition of methods,” Journal of Systems and Software, vol. 84, no. 10, pp. 1757–1782, 2011. [20] N. Tsantalis, M. Mansouri, L. M. Eshkevari, D. Mazinanian and D. Dig, “Accurate and efficient refactoring detection in commit history,” in Proceedings of the 40th International Conference on Software Engineering (ICSE 2018), pp. 483–494. [21] S. Xu, A. Sivaraman, S.-C. Khoo and J. Xu, “GEMS: an extract method refactoring recommender,” in Proceedings of the 28th IEEE International Symposium on Software Reliability Engineering (ISSRE 2017), pp. 24–34.
19
Related documents
- Simulating Termination Analyzer applied to the HP counter-example input#74964
- RepairFormer: Automated Repair of Structured Inputs Using Transformers#1216376
- Locate, Then Reason: End-to-End Patch Presence Testing in Stripped Binaries#1390017
- Locate, Then Reason: End-to-End Patch Presence Testing in Stripped Binaries#1381165
- Simulating Termination Analzyers overcome conventional halting problem proof method#732947
Retrieved via Conceptio: every document is proof-bundled with source, license, and retrieval metadata.
Read the Docs → · View CLI source on GitHub · Conceptio Open Knowledge Archive