Technical Paper · H · Working Paper

Software metrics: rework, REP_CHURN, code churn and others — what they actually mean and what they really tell us

Most software metrics are words before they are numbers, and the words are used for several different things. This paper takes the metrics CodeDelta reports, says what each word means out in the world, fixes what it measures here, gives the formula, and states what a high or low figure tells you and what it does not, with one real figure for each.

Working paper · 24 September 2026 · one real run (FFmpeg 6.0 → 7.0, CodeDelta 2.1.1 build 49) and figures from the published studies

Abstract

Ask a search engine what rework means in software and the answer mixes deployment statistics, process measures and a vague “percentage of code recently modified”. The same is true of churn, which can mean lines touched, files touched, staff leaving, or customers leaving. A metric is only useful once its definition is fixed, its unit is named, and its number can be reproduced by someone else. This paper does that for the family of measurements CodeDelta reports on source code: the three line counts (LOC, SLOC, LLOC); code churn and its three parts; REWORK, the share of change made in place; REP_CHURN, the share made by replacement; TRUE_CHURN, churn with generated files taken out; moved code; data churn; and the separate tier for text and data files. For each, the paper gives the everyday meanings the word carries elsewhere, the definition used here, the formula, what the figure tells you and what it does not, and one measured value from a public codebase. The closing section is the argument of the whole set: none of these numbers means anything on its own, and all of them mean something against a baseline measured the same way.

01Why the words come first

A metric has three parts: a word, a definition, and a number. Software has plenty of the first and third and is careless with the second. “Rework” in the DORA metrics [1] is a property of deployments: the share of releases that had to be redone because of an incident in production. “Rework” in an estimating spreadsheet is hours spent on a task a second time. “Rework” on a code-quality dashboard is often lines modified within some recent window, with the window unstated. All three are legitimate measures of something. None of them is a measure of the source code, and a reader who sees a rework figure with no definition cannot know which one they are looking at.

The metrics below are all measures of source code, computed by comparing two versions of a tree. Each has a fixed definition and a formula, each is reported in the same unit, and each number here can be reproduced by anyone with the two versions and the tool. That is the only sense in which the paper claims they are the real meanings: not that other meanings are wrong, but that these are stated.

02One real run, from which every figure below is read

Everything in the sections that follow is illustrated from one run of the released tool, CodeDelta 2.1.1 (build 49), comparing two public releases of FFmpeg, 6.0 and 7.0, on a laptop: 8,580 file pairs, 37 seconds. This is the engine's own output, unedited. The scan header first, then the results block:

  Old: FFmpeg-Old
  New: FFmpeg-New
  Old: 8038 files | New: 8430 files
  Not measured (unrecognised types, symlinks): old 1, new 3 files - (no ext) x2, .pnm x1
  File coverage (new side): 100.0% of 8433 files
  Excluded by skip dirs (new side): 0 files
  Matched: 8580 file pairs

Results:
  Files:     8580 total | 2563 changed | 542 new | 150 deleted | 5325 unchanged

  LLOC change metrics (primary):
    CHG_LLOC: 11810   DEL_LLOC: 30698   ADD_LLOC: 88078   CHURN: 130586
    TRUE_CHURN: 130559  (CHURN minus generated churn)
    REWORK: 9.0% (1 statement in 11 edited in place)
    REWRITE (rewritten in place): 4179   PURE_DEL: 26519   PURE_ADD: 83899
    Data churn: 302 of 130586 churned statements are data initializers (elements: 24449 chg / 2296 del / 2380 add)
    Working-code churn: 130284 statements | working-code REWORK: 8.9%

  SLOC change metrics:
    CHG_SLOC: 24939   DEL_SLOC: 56672   ADD_SLOC: 145939   CHURN: 227550

  COM change metrics (comment churn):
    CHG_COM:  608   DEL_COM:  2199   ADD_COM:  3540   CHURN: 6347

  Totals:    1453858 SLOC | 775593 LLOC | 2075642 LOC | 81119912 bytes
  Data composition: 2873 data statements | 170366 SLOC inside them (11.7% of SLOC) | 1200824 elements
  Text and data files: 3583 files, 195953 U_LOC (Markdown, config, data - not source code;
                       counted separately, kept out of the SLOC/LLOC totals)
  Text and data churn (U_LOC, reported separately): 6552 CHG | 6435 DEL | 17004 ADD | 29991 CRN
  Generated files: 3 (generator marker (automatically generated) 1, minified stylesheet 2) - 2 LLOC; churn INCLUDED in totals above
  Generated churn (subtotal): 1 CHG | 26 DEL | 0 ADD | 27 CRN  ->  authored churn: 130559 of 130586 CRN_LLOC
  Comments:  25620 J_COM (//)  179665 C_COM (/* */)  15726 EOL_COM (# -- ; ' !)  221011 total

The same run writes a CSV with one row per file. Six rows, chosen to show the range, with the columns the sections below refer to:

FileCHGDELADDMOVCRNREWORKREP_CHURN
libavcodec/aarch64/hevcdsp_qpel_neon.S11204,23624,2670.3%1.00
fftools/ffmpeg.c351,4217151,5272.3%0.98
libavutil/hwcontext_vulkan.c15059634261,08813.8%0.86
fftools/ffmpeg_mux_init.c116315588731,01911.4%0.89
libavcodec/aarch64/h264cmc_neon.S205000205100%0.00
libavutil/hdr_dynamic_metadata.c0019201920%1.00
CodeDelta desktop app, churn results for FFmpeg 6.0 to 7.0: tiles for CHG/DEL/ADD/MOV/CRN in SLOC and LLOC, comment churn, totals, U_LOC, TRUE_CHURN, generated churn, data share, file counts, and the change-shape row with REPLACEMENT CHURN 0.91 and REWORK 9.0%
The same run in the desktop app. Every figure in the results block above is a tile: the change counts in statements and in lines, comment churn, the three totals, the text-and-data tier, TRUE_CHURN, generated churn, data share, the file counts, and the change-shape row — REPLACEMENT CHURN 0.91, REWORK 9.0%, and the rewrite / pure-deletion / pure-addition split. Click the picture to enlarge it. The ⓘ on each tile gives its formula. Colours follow one convention throughout the product: red for changed, blue for deleted, green for added, orange for churn.

Two things to notice before the definitions. The header accounts for everything the run did not measure: 3 files of unrecognised type, named by extension, and a coverage figure printed even though it is 100%. And the results block reports the same change five ways, in statements, in lines, in comments, in text-and-data files, and with generated files subtracted, so that a reader can see which count a figure comes from.

03Lines: LOC, SLOC and LLOC

Elsewhere, “lines of code” is used for all three of the following without distinction, which is why line counts between tools rarely agree.

Here: LOC is every line in a file, blank and comment lines included. SLOC is the lines left after blanks and comments are removed: the lines a compiler reads. LLOC, logical lines, is the count of statements: in languages that end a statement with a semicolon, one statement is one logical line however many physical lines it spans, and however many statements share a physical line. In languages with no statement terminator (Python, Ruby, shell), and in markup, LLOC equals SLOC, and the tool says so.

What it tells you. LLOC is the count that survives a reformat. Re-indent a file, wrap its long lines, split one statement over three lines, and LOC and SLOC move while LLOC does not. That property is what makes the churn metrics below usable: they are counted in statements so that layout changes are not counted as work. What it does not tell you: anything about difficulty, quality or value. A statement is a statement.

In the run. The totals line reads 2,075,642 LOC, 1,453,858 SLOC and 775,593 LLOC for FFmpeg 7.0: three lines of the file for every two the compiler reads, and roughly two compiler lines per statement, which is what C with its braces and comments produces.

One figure. The LLOC paper [2] dissects one 35-line file: 35 LOC, 25 SLOC, 6 statements, of which 5 are working code and 1 is a data table.

04Code churn: CHG, DEL, ADD and CRN

Elsewhere, churn is the number of lines a commit touched, as git reports it: lines removed plus lines inserted. Git has no category for a line that was edited; it reports an edit as one removal and one insertion. Churn is also, in other departments, the rate at which staff or customers leave, which is why the word needs the prefix.

Here, code churn is counted in statements and has three parts. CHG is statements that existed before and were edited in place. DEL is statements removed. ADD is statements added. CRN, the churn total, is their sum:

CRN = CHG + DEL + ADD

Nothing else is inside it. Moved statements (Section 07) are reported beside it and never added to it. A file whose only difference is whitespace has zero churn.

What it tells you. How much of the program's logic changed between two versions, in a unit that does not inflate when code is reformatted or re-indented. The three parts tell you the shape of the change: mostly CHG is repair, mostly ADD is growth, mostly DEL is removal. What it does not tell you: whether the change was good. Churn is volume and shape, not quality.

In the run. CHG_LLOC 11,810, DEL_LLOC 30,698, ADD_LLOC 88,078, CHURN 130,586: the release is two-thirds growth by statement count. The same change in SLOC is 227,550, and in comment lines 6,347. The first CSV row above is a hand-written assembly file that arrived almost entirely as additions: 4,236 added, 11 changed.

One figure. One year of Chromium, 43.5 million lines, was measured in a single run [3]; the churn paper [4] gives the method and the counting rules per language.

05REWORK: the share of change made in place

Elsewhere: the deployment measure above, or hours re-spent, or lines recently modified. All are about work, not code.

Here, REWORK is the fraction of churn that was spent editing statements that already existed:

REWORK = CHG / CRN

A REWORK of 17% means that of every hundred statements of churn, seventeen were existing statements edited in place and eighty-three were statements added or deleted. The tool prints it alongside as “1 statement in 6 edited in place”.

In the run. REWORK: 9.0% (1 statement in 11 edited in place). The line below it splits the rest: REWRITE 4,179 statements that were removed and re-added in rewritten form, PURE_DEL 26,519 and PURE_ADD 83,899. Per file the range is the whole scale: h264cmc_neon.S is 205 statements changed in place and nothing else, REWORK 100%; libavutil/hdr_dynamic_metadata.c is 192 additions and nothing else, REWORK 0%.

What it tells you. How much of a project's change is repair of what is there, as opposed to adding new and removing old. Hand-maintained, established projects sit in a narrow band: curl 16.7%, PostgreSQL 17.1%, Express 16.7% to 18.3% depending on the window [5]. The baselines paper [6] measured 136 codebases over sixteen years and found that the band holds for mid-size established projects across growth and maintenance alike, that very large projects run lower (WebKit 7.1% over 59 million statements of churn), and that intensively agent-built codebases run far lower still: crewAI 2.7%, the GitHub CLI 0.9%. What it does not tell you: whether low is bad. A young project growing fast has low REWORK because there is little to repair. The figure is evidence only against the baseline for a project of that size and phase, which is what the baselines paper provides.

06REP_CHURN: the share of change made by replacement

Elsewhere: the term is CodeDelta's own [5], so it has no other meaning to disentangle; it is included here because it is the mirror of REWORK and the two are read together.

Here, REP_CHURN is the fraction of churn that was adding and deleting rather than editing, bounded between 0 and 1:

REP_CHURN = (ADD + DEL) / CRN   =   1 − REWORK

In the run. The TOTAL row of the CSV carries REP_CHURN 0.91 for the release as a whole, the complement of the 9.0% REWORK. In the six rows above it runs from 0.00 for the assembly file that was edited in place to 1.00 for the file that was only added to.

What it tells you. Near 1, the code was thrown away and regenerated rather than repaired; near 0, existing code was carefully edited. The REP_CHURN paper's finding is that this ratio separates hand-maintained from agent-built development more cleanly than any volume measure: an assistant asked to fix a function tends to emit a new function, which arrives as a deletion and an addition, not as an edit. What it does not tell you: who or what wrote the code. It is a process signature, not an authorship test, and the paper says so.

One figure. Express, one release window: REP_CHURN 0.833. crewAI, an agent-built framework: 0.973 [5].

07TRUE_CHURN: churn with generated files taken out

Elsewhere: no established meaning; tools that count lines count every file the same.

Here, a file is classed as generated when it is mechanically derived from work done elsewhere: a lockfile, a minified bundle, a generator's output. The classification is by well-known file names, well-known suffixes, and the marker lines generators stamp into their output, and every classification names the rule that made it. Generated churn stays inside CRN, so totals never change; TRUE_CHURN is the total with it taken out:

TRUE_CHURN = CRN − churn in generated files

What it tells you. How much of the change a person wrote. What it does not tell you: it does not judge the generated files; a regenerated lockfile is real change to the repository, which is why it stays in the total and is reported as a subtotal.

In the run. Generated files: 3 (generator marker 1, minified stylesheet 2), and their churn: 27 statements, all but one of them in libavcodec/cos_tablegen.c, a deleted generator output. So TRUE_CHURN: 130,559 against CHURN 130,586. FFmpeg commits almost nothing generated; the metric earns its keep on repositories that do.

One figure. In the public npm command-line tool, release 10.9.0 to 11.0.0, 80.1% of all measured churn came from a single generated file, package-lock.json, which no developer edits [7].

08Moved code: MOV

Elsewhere: git has an option to colour moved lines in a diff, but its counts do not know about moves; a block cut from one place and pasted in another is reported as that many deletions and that many insertions.

Here, a statement is moved when its exact text, at least twelve characters once whitespace is stripped and unique on both sides, is deleted at one position and added at another in the same file. It is counted once, as MOV, reported beside churn, and not counted as churn at all. The length rule stops short common lines such as a closing brace from pairing.

What it tells you. How much of a change was rearrangement rather than writing. What it does not tell you: a statement that moved and was also edited is a changed statement, not a moved one; and a move between files is reported but still counted as a deletion and an addition, pending a decision to count it otherwise.

In the run. The TOTAL row carries MOV_LLOC 834 across 148 files, none of it inside CHURN; fftools/ffmpeg_mux_init.c alone has 73 moved statements, and a further 11 that moved and were also edited, which the CSV reports as CHM_LLOC and counts as changed. Counted the way git counts, those 73 would have added 146 statements of churn to a file whose real churn is 1,019.

One figure. The moved-code paper, measured on the same release pair with the engine of 4 September, reported 744 statements moved across 128 files. In two of those files, 71 and 47 statements moved; counted as delete plus add, those two files alone would have carried 236 statements of churn that never happened [8].

09Data churn

Elsewhere: no established meaning in source measurement.

Here, a data statement is an initialiser: a table, an array literal, a lookup, data wearing code's syntax. Data churn is the share of churned statements that are data initialisers, with the count of elements changed inside them; the remainder is working-code churn, and REWORK is reported for the working code separately.

What it tells you. Whether a large churn figure is engineering or a refreshed table. What it does not tell you: it is measured for the C-family languages, where initialisers have a recognisable shape, and the report says which languages it covered.

In the run. Data churn: 302 of 130,586 churned statements are data initializers (elements: 24,449 chg / 2,296 del / 2,380 add), and working-code REWORK: 8.9% against 9.0% overall. Three hundred statements carrying twenty-nine thousand element changes is what a table looks like to a statement counter; libavformat/movenc.c has 7 such statements holding 258 changed elements. The composition line says 11.7% of FFmpeg's source lines sit inside data statements.

One figure. In NVIDIA's open GPU kernel modules, single C statements of up to 3.0 MB were measured, and 28.4% of that tree's source lines are data, not code. The metric's first validation, on Erlang/OTP, found that its 2026 repair regime, REWORK 22.6%, survived the data filter unchanged: genuine engineering, not table refresh [9].

10Text and data files: U_LOC

Here, files that are not source in any language the engine parses, such as Markdown, configuration and data files, are counted in their own tier, U_LOC, and their churn is reported separately. They are never inside the SLOC or LLOC totals and never classed as generated. What it tells you: the report always says how many files it did not measure as code and why, so a total is never silently missing something. Anything left out of a measurement is visible.

In the run. Text and data files: 3,583 files, 195,953 U_LOC, with their own churn line: 29,991 U_LOC churned, reported separately and absent from the 130,586. FFmpeg's doc/*.texi manuals and its doc/APIchanges log are in this tier: the CSV lists them as “Unclassified text” with their own churn figures, and none of it reaches the source totals.

11What any of them really tell you

Each metric above is a count with a formula, and each has a sentence saying what it does not show. Read together, they describe a change by volume (CRN), shape (CHG, DEL, ADD), character (REWORK and REP_CHURN), authorship of the volume (TRUE_CHURN), rearrangement (MOV) and substance (data churn). What none of them does is stand alone. A REWORK of 3% is unremarkable for a project in its first year and remarkable for a twenty-year-old database; a churn of ten thousand statements is a quiet month for a browser and a rewrite for a library. The number becomes information against a baseline measured the same way, on projects of the same size and age, which is what the corpus behind the baselines paper exists to supply.

The other requirement is reproducibility. Every figure in this paper names the versions it was measured between and the tool version that measured them, and each of the studies cited publishes the hashes. A metric whose number cannot be reproduced is a word again.

·References

  1. DORA, DORA metrics guide: change failure rate and deployment rework rate, the deployment sense of rework. dora.dev/guides/dora-metrics/
  2. CodeDelta (2026). Why LLOC is what really counts. Position paper. paper-lloc.html
  3. CodeDelta (2026). CodeDelta scanned Chromium: a year of the world's browser codebase, one command. Measurement report. paper-chromium.html
  4. CodeDelta (2026). Automated Source Code Churn Measurement: Logical and Physical Line Differencing in Large Codebases. Technical Paper A. paper-churn.html
  5. CodeDelta (2026). REP_CHURN: Replacement-Dominant Churn as a Process Signature of AI-Assisted Development. Technical Paper C. paper-repchurn.html
  6. CodeDelta (2026). REWORK Baselines by Project Type: a 136-Codebase, 16-Year Corpus. Technical Paper D. paper-baselines.html
  7. CodeDelta (2026). True churn: separating what developers wrote from what tooling produced. Measurement paper. paper-true-churn.html
  8. CodeDelta (2026). Moved code is not churn. Measurement paper. moved-code-is-not-churn.html
  9. CodeDelta (2026). Data wearing code's syntax: how one measurement anomaly became a new metric. Measurement report. paper-data-metrics.html