Skip to content

Validation

Two generated reports back the faithfulness claim. Both live in validation/bw_faithfulness/derived/ and are included on this page verbatim, by transclusion, so the site cannot drift from the artifacts it describes.

What reruns offline, and what does not

The Buse-Weimer lane is self-contained. Its raw inputs are tracked in validation/bw_faithfulness/derived/arbitration_inputs/: the 100 rated Java snippets, oracle.csv (the raw 121-row annotator matrix), and scores.csv (the per-snippet means derived from it). extract.py, train.py, arbitrate.py and report.py read those pins as their primary source, so with no network and no cache/ directory they regenerate every number quoted below. A missing tracked input is a hard error and exit 1; there is no skip-and-pass path anywhere in the lane. When the original archive happens to be present in the gitignored cache/, the scripts cross-check every pin against it byte for byte, and any difference is a hard error rather than a silent re-pin. pinned.py is the input contract; fetch.py is not on this lane's path at all.

Those inputs can be tracked because an author of the dataset gave written permission to redistribute the snippets and annotator scores and to publish data derived from them. What was asked, what was answered, and how the message can be checked are in PERMISSIONS.md.

The cross-corpus lane is not self-contained and never will be. The Scalabrino et al. (2018) and Dorn (2012) corpora carry no permission of any kind, so not one byte of either is tracked, now or later. They are fetched at run time into the gitignored cache/, measured there, and only aggregates are published. Reproducing validation/breadth/ therefore needs a network fetch (fetch.py --all), even though the Buse-Weimer lane never does. Nothing on this page should be read as "the whole project regenerates from the git tree alone".

The arbitration loop

Ambiguities in the published Buse-Weimer feature definitions (how wide is a tab? do error fragments count?) are not resolved by taste. Each candidate interpretation ships as a PROVISIONAL ruling. A pre-registered experiment on the paper's original dataset arbitrates it, with the decision criteria written down before any result was seen. The winner is adopted by superseding the old ruling under a new immutable ID, the spec MAJOR version is bumped, and the faithfulness reproduction is re-run under the adopted rulings. Both reports below are the output of that loop.

To rerun it:

pip install -e ".[retrain]" -c constraints/ci.txt -c constraints/retrain.txt
python validation/bw_faithfulness/extract.py     # -> derived/features.csv
python validation/bw_faithfulness/train.py       # -> derived/train_results.json
python validation/bw_faithfulness/report.py      # -> derived/bw_faithfulness_report.{json,md}
python validation/bw_faithfulness/arbitrate.py   # -> derived/arbitration_report.{json,md}

BW 2010 faithfulness reproduction

Reproduction of the Buse-Weimer readability study with codecaliper's public snippet-granularity extractor (ARCHITECTURE.md §8.3).

Citing the original work

An author of the dataset asked that the work be cited as both papers; this project honours that requested citation form:

  • Raymond P. L. Buse and Westley Weimer, 'Learning a Metric for Code Readability', IEEE Transactions on Software Engineering 36(4):546-558, 2010, DOI 10.1109/TSE.2009.70
  • Raymond P. L. Buse and Westley Weimer, 'A Metric for Software Readability', ISSTA 2008:121-130, DOI 10.1145/1390630.1390647

Headline numbers

  • 10-fold accuracy: 0.820 (bootstrap 95% CI [0.770, 0.870]) vs the paper's ~0.80; CI OVERLAPS the paper's figure
  • AUC: 0.828
  • fold accuracies: 0.90, 0.70, 0.80, 0.70, 0.90, 0.80, 0.80, 0.80, 1.00, 0.80
  • convergence warnings during fitting: 0
  • sign agreement vs Fig. 9: 21/24 agree, 3 disagree, 1 excluded (direction unclear in the paper)

Protocol

  • labels: label = 1 iff snippet mean score >= 3.14 (the paper's Figure 5 bimodal cutoff; scores below 3.14 are 'less readable') (paper Fig. 5 cutoff 3.14; 59 high / 41 low)
  • classifier: LogisticRegression(max_iter=1000); CV: StratifiedKFold(n_splits=10, shuffle=True, random_state=0)
  • AUC: roc_auc_score over cross_val_predict(method='decision_function') on the same folds
  • Deviation from the paper: (1) The paper evaluated a battery of classifiers (Bayesian classifier, logistic regression, multilayer perceptron, ...) under 10-fold cross-validation; its ~0.80 accuracy summarizes that setting. This reproduction runs logistic regression ONLY (LogisticRegression(max_iter=1000), raw unscaled features). (2) The paper repeats the entire 10-fold validation 10 times over fresh random partitionings and averages across runs; this reproduction uses ONE fixed-seed partitioning (random_state=0) for byte-reproducibility (ARCHITECTURE.md section 8.3).

Per-feature Spearman vs Fig. 9 directionality

Spearman rho of each feature against the snippet mean score; expected sign is the Fig. 9 direction of correlation with HIGH readability. A disagreement localizes a candidate divergence to a specific BW-/TOK- ruling. It is an empirical-arbiter input (ARCHITECTURE.md §8.3), not a failure to hide.

feature Spearman rho expected sign (Fig. 9) rel. power agree
avg_line_length -0.544 - 0.96 yes
max_line_length -0.412 - 0.78 yes
avg_identifiers -0.660 - 1.00 yes
max_identifiers -0.494 - 0.64 yes
avg_identifier_length -0.535 unclear 0.00 excluded (unclear)
max_identifier_length -0.239 - 0.40 yes
avg_indentation -0.230 - 0.55 yes
max_indentation -0.254 - 0.50 yes
avg_keywords -0.331 - 0.55 yes
max_keywords -0.206 - 0.13 yes
avg_numbers -0.167 - 0.23 yes
max_numbers -0.129 - 0.16 yes
avg_comments +0.291 + 0.33 yes
avg_periods -0.442 - 0.78 yes
avg_commas -0.442 - 0.45 yes
avg_spaces +0.039 - 0.21 NO
avg_parentheses -0.619 - 0.93 yes
avg_arithmetic_ops -0.230 + 0.07 NO
avg_comparison_ops -0.335 - 0.21 yes
avg_assignments -0.067 - 0.26 yes
avg_branches -0.213 - 0.20 yes
avg_loops -0.131 - 0.19 yes
avg_blank_lines +0.262 + 0.53 yes
max_char_occurrences +0.090 - 0.37 NO
max_identifier_occurrences -0.285 - 0.41 yes

Extraction quality (reported, not tuned away)

  • parse_ok 29/100 snippets, 10 scaffolded (CORE-JAVA-0001), 0 with an EMPTY token vector. On parse errors BW token-family features are computed over the full lexical stream, ERROR subtrees included (BW-ALL-0007, bw-lexical-fallback diagnostic), which is why this count is zero; metrics remain error-opaque per CORE-ALL-0002. This is measured extractor behaviour on bare snippets, an arbitration outcome (see arbitration_report.md), never tuned away.

Dataset

  • 100 snippets; 121 annotator rows in the archive vs 120 in the paper, reported as-is, never reconciled silently.
  • Permission (a grant, not a licence): an author of the dataset (W. Weimer) granted redistribution and derived-data publication by email 2026-07-11 (PERMISSIONS.md, dataset.toml). Under that grant the repository tracks the raw inputs this lane consumes: the 100 snippets, the per-annotator score matrix (oracle.csv) and the per-snippet mean ratings (scores.csv), all under derived/arbitration_inputs/. Every number reported here therefore regenerates from pinned tracked inputs with no network and no cache/. Re-downloading the archive is an optional byte-for-byte cross-check, never a prerequisite. This grant covers the Buse-Weimer corpus alone; the other readability corpora this project measures carry no permission and are never redistributed.

Anti-circularity

Anti-circularity note (stated in the paper too): ambiguity rulings are arbitrated on the same 100-snippet dataset whose reproduction accuracy we report. The reproduction is evidence of faithful operationalization of the published feature definitions, not an independent validation of BW's construct (ARCHITECTURE.md §8.3).

Provenance

  • codecaliper 0.2.1, spec 1.2.1
  • grammar: tree-sitter-java 0.23.5 (ABI 14, validated=True)
  • BW feature-order sha256: 8adae3992539a4eb2cf8d3b2386558612c77ad8e2e850f5bab940bbf430ad56d

Reading the two reports together: the faithfulness report's headline AUC (0.828, the final instrument run) and the arbitration report's adopted-cell AUC (0.827) are the same configuration. They differ by one ranked pair out of 2419 (about 0.0004), a matter of feature-serialization precision, as the arbitration report's reconciliation note details. 0.828 is the instrument's number.

BW arbitration experiment (A1 tab width / A2 arithmetic ops / A3 lexical fallback)

Empirical-arbiter loop, ARCHITECTURE.md §8.3. Every cell runs the exact train.py protocol; the baseline cell reproduced the committed pre-fallback training record (derived/arbitration_inputs/) exactly before the rest of the matrix was trusted.

Citing the original work

The experiment is scored against the Buse-Weimer dataset. An author of the dataset asked that the work be cited as both papers; this project honours that requested citation form:

  • Raymond P. L. Buse and Westley Weimer, 'Learning a Metric for Code Readability', IEEE Transactions on Software Engineering 36(4):546-558, 2010, DOI 10.1109/TSE.2009.70
  • Raymond P. L. Buse and Westley Weimer, 'A Metric for Software Readability', ISSTA 2008:121-130, DOI 10.1145/1390630.1390647

Pre-registered decision rule (verbatim)

Primary criterion: the number of sign agreements over the 24 clear-signed Fig. 9 features (fig9_signs.toml; avg_identifier_length is 'unclear' and excluded). Tie-break: AUC. A winner must hold under BOTH extraction modes to be adopted for the instrument. The lexical fallback is adopted iff it does not reduce sign agreements or AUC materially (its independent justification is construct fidelity + coverage 29/100 -> 100/100). Operationalization: within each mode the 16 (tab, ops) cells are ranked by (n_sign_agree desc, AUC desc); a candidate beats the current setting (tab=1, V0_current) iff it strictly increases n_sign_agree in at least one mode, never decreases n_sign_agree in either mode, and never decreases AUC by more than 0.01 in either mode; ties keep the current ruling. Among clearing candidates: highest summed n_sign_agree, then summed AUC, then fewer changed dimensions, smaller tab, earlier variant. 'Materially' for the fallback: at the adopted (tab, ops), fallback_on must not have fewer sign agreements than fallback_off and its AUC must not be lower by more than 0.01. No candidate clearing the bars => recorded null result, current rulings stand.

Extraction modes

  • fallback_off: derived/features_fallback_off.csv (pre-BW-ALL-0007 extraction, error-opaque token stream); empty-token vectors 8/100.
  • fallback_on: derived/arbitration_inputs/features_fallback_on_tab1.csv (extract.py with BW-ALL-0007 implemented: full lexical stream on parse errors; tab=1, spec 0.1.0); empty-token vectors 0/100 (parse_ok 29/100, BW-ALL-0007 gives the other snippets a full lexical stream).

Ops variants (Java operator sets)

  • V0_current: % * + - /
  • V1_minimal: * + - /
  • V2_incdec: % * + ++ - -- /
  • V3_compound: % %= * *= + += - -= / /=

Matrix (n_sign_agree / AUC / accuracy)

fallback_off

tab \ ops V0_current V1_minimal V2_incdec V3_compound
tab=1 20/24, auc 0.7834, acc 0.720 20/24, auc 0.7830, acc 0.720 20/24, auc 0.7830, acc 0.720 20/24, auc 0.7830, acc 0.720
tab=2 21/24, auc 0.7854, acc 0.720 21/24, auc 0.7859, acc 0.720 21/24, auc 0.7859, acc 0.720 21/24, auc 0.7859, acc 0.720
tab=4 21/24, auc 0.7929, acc 0.770 21/24, auc 0.7929, acc 0.770 21/24, auc 0.7929, acc 0.770 21/24, auc 0.7929, acc 0.770
tab=8 21/24, auc 0.7983, acc 0.770 21/24, auc 0.7974, acc 0.770 21/24, auc 0.7987, acc 0.770 21/24, auc 0.7979, acc 0.770

fallback_on

tab \ ops V0_current V1_minimal V2_incdec V3_compound
tab=1 20/24, auc 0.8297, acc 0.790 20/24, auc 0.8297, acc 0.790 20/24, auc 0.8305, acc 0.790 20/24, auc 0.8297, acc 0.790
tab=2 21/24, auc 0.8260, acc 0.800 21/24, auc 0.8260, acc 0.800 21/24, auc 0.8251, acc 0.800 21/24, auc 0.8260, acc 0.800
tab=4 21/24, auc 0.8210, acc 0.800 21/24, auc 0.8214, acc 0.800 21/24, auc 0.8210, acc 0.800 21/24, auc 0.8210, acc 0.800
tab=8 21/24, auc 0.8272, acc 0.820 21/24, auc 0.8280, acc 0.820 21/24, auc 0.8272, acc 0.820 21/24, auc 0.8276, acc 0.820

Sign disagreements per cell

cell disagreements
fallback_off/tab=1/V0_current avg_indentation, avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=1/V1_minimal avg_indentation, avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=1/V2_incdec avg_indentation, avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=1/V3_compound avg_indentation, avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=2/V0_current avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=2/V1_minimal avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=2/V2_incdec avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=2/V3_compound avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=4/V0_current avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=4/V1_minimal avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=4/V2_incdec avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=4/V3_compound avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=8/V0_current avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=8/V1_minimal avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=8/V2_incdec avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_off/tab=8/V3_compound avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=1/V0_current avg_indentation, avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=1/V1_minimal avg_indentation, avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=1/V2_incdec avg_indentation, avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=1/V3_compound avg_indentation, avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=2/V0_current avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=2/V1_minimal avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=2/V2_incdec avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=2/V3_compound avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=4/V0_current avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=4/V1_minimal avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=4/V2_incdec avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=4/V3_compound avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=8/V0_current avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=8/V1_minimal avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=8/V2_incdec avg_spaces, avg_arithmetic_ops, max_char_occurrences
fallback_on/tab=8/V3_compound avg_spaces, avg_arithmetic_ops, max_char_occurrences

Decision (per-dimension, with disclosed deviation)

DISCLOSED DEVIATION from the pre-registered operationalization (the decision rule itself is unchanged): the joint (tab, ops) preference chain put summed AUC before parsimony, allowing a dimension with zero primary-criterion evidence to change on ~4e-4 summed-AUC noise, contradicting the rule's own 'ties keep the current ruling / a spec change requires positive evidence' clause. Dimensions were therefore evaluated marginally; the joint chain's outcome is recorded here for transparency.

  • Pre-registered joint chain would pick: tab=8, V2_incdec (ops component carried by AUC noise only).
  • A1 clearing tab widths (ops at V0_current): [2, 4, 8].
  • A2 clearing ops variants (tab at adopted): none; max AUC spread across variants within any (mode, tab) row: 0.001240.
  • A3 at the adopted setting: sign agreements on/off = 21/21, AUC on/off = 0.8272/0.7983.

Recommendation

  • tab_width = 8
  • ops_variant = V0_current
  • lexical fallback (BW-ALL-0007): adopt

A1 ADOPTED: tab=8. Every tab>=2 fixes the avg_indentation sign disagreement in BOTH extraction modes (+1 sign agreement, the primary criterion); among clearing tab widths [2, 4, 8], tab=8 is the (n_sign_agree, AUC) top cell in both modes (the pre-registered tie-break). It also moves max_indentation's Spearman rho from ~0 to clearly negative, matching Fig. 9. A2 NULL RESULT: no ops variant changes ANY Fig. 9 sign in any of the 32 cells (avg_arithmetic_ops disagrees with the paper's near-zero-power positive bar everywhere), and the AUC spread across variants within any (mode, tab) row is <= 0.00124018189334, within noise. The current ruling stands (BW-ALL-0006, Java arithmetic_ops as-is). A3: at the adopted setting, fallback_on has 21/24 sign agreements vs 21/24 and AUC 0.827201322861 vs 0.798263745349: no material reduction, so BW-ALL-0007 is ADOPTED. Its independent justification is construct fidelity (the original BW instrument was grammar-less) and coverage (empty-token vectors 8 -> 0, full token streams 29/100 -> 100/100).

Scoping notes

  • Tab dimension: only avg_indentation/max_indentation were re-derived; avg_line_length, max_line_length and avg_spaces remain raw character counts (a tab is 1 character there), a possible future arbitration.
  • Ops dimension: avg_arithmetic_ops for V1-V3 was recomputed from a DIRECT parse of the raw snippet (lex(include_error_tokens=True) for fallback_on, plain lex() for fallback_off); the instrument itself may engage the CORE-JAVA-0001 scaffold at snippet granularity. V0_current cells keep the matrix's own column (the true instrument path); the 'approximation' block reports recomputed-V0 vs that column.
  • V3_compound counts compound assignments (+= -= *= /= %=) in avg_arithmetic_ops while avg_assignments (unchanged base column) still counts them too; adopting V3 in the instrument would additionally require a precedence decision in BW-ALL-0006.
  • Anti-circularity (README.md): these rulings are arbitrated on the same 100-snippet dataset whose reproduction accuracy is reported; the reproduction is evidence of faithful operationalization, not an independent validation of BW's construct.
  • The baseline cell (fallback_off, tab=1, V0_current) was asserted to reproduce the committed pre-fallback training record exactly (fold accuracies, CI, AUC, per-feature Spearman, sign table) before the rest of the matrix was computed.
  • Reconciliation with the final instrument run: the adopted cell's AUC (fallback_on/tab=8/V0, 0.827201322861) differs from the headline AUC of the final re-extraction (derived/train_results.json, 0.827614716825, first produced under spec 1.0.0 and byte-identical on every number when re-stamped under spec 1.1.0) by exactly one ranked pair (1/2419 = 0.000413): the matrix splices full-precision recomputed indentation values into the feature array, while the instrument's train.py consumes the canonical 12-significant-digit features.csv. The feature VALUES agree under 12-significant-digit quantization for all 100 snippets: the gap is serialization precision flipping one near-tied AUC pair, not a semantic difference. 0.828 (the final run) is the instrument's number.

Cross-corpus parse anatomy

The breadth lane measures three Java corpora with the shipped public API and reports what the parser met. It is the reason the BW lane is careful about error recovery: most snippets in the readability corpora are truncated fragments of larger units, not broken code.

corpus N clean parse at least one tab recovered of those, brace-imbalanced
Buse-Weimer (2010) 100 27 (27.0%) 32 73 69
Scalabrino et al. (2018) 200 190 (95.0%) 151 10 0
Dorn (2012), Java only 121 19 (15.7%) 108 102 76

These aggregates are spec- and grammar-dependent: they hold for the versions codecaliper env prints in this tree, and a change that moves them is a spec event. Reproducing them needs the archives, hence a network fetch. See validation/breadth/README.md.

Why this page says 73 and the Quickstart says 71

Both counts are of the same 100 Buse-Weimer snippets under the same grammar, and they differ because the two lanes measure at different granularities.

The breadth lane calls measure(src, language="java") and takes the default granularity="file". The CORE-JAVA-0001 snippet scaffold is gated on granularity == "snippet" (api._measure), so at file granularity it cannot fire: a snippet either parses as a bare program or it does not. 27 do, 73 are recovered, and that is the row above.

The faithfulness lane calls measure(src, language="java", granularity="snippet") (validation/bw_faithfulness/extract.py), where the scaffold does fire. It is adopted on 10 of the 100 snippets, and on 2 of those 10 the scaffolded parse is clean. So 29 snippets reach parse_ok: true and 71 keep parse_ok: false, which is the number the Quickstart breaks down. The two-snippet gap between the pages is the scaffold, and nothing else.