-
Notifications
You must be signed in to change notification settings - Fork 0
0041 one sample file per public class
Status: accepted · Date: 2026-08-19
samples/Lodestar.Sample/ groups its demonstrations by lot, and the lot numbers say nothing about
what is inside. Lot5Metrics.cs is 591 lines, Lot3Embeddings.cs is 417, and between them they
carry dozens of examples under names that help nobody find one.
Someone meeting MutualInformation for the first time cannot guess that its example lives in
Lot5Metrics.cs. Neither a file-name search nor an IDE's go to file reaches it — only a full-text
search does, and only if they think to try. The opacity compounds with every lot: issues #262, #263
and #264 each added a demonstration into a file whose name mentions none of them.
The samples are the first thing a reader reaches for, because they are the only place that shows a type being used from outside its assembly. Being the hardest thing to navigate is the wrong property for that.
One file per public class, named <ClassName>Sample.cs, holding a Run() the package's
aggregator calls. Finding the example for a type becomes the same gesture as finding the type.
Three rules fix the edges:
-
An enum gets no file of its own. It is demonstrated through the class whose parameter it is —
TextElementbyLevenshtein,ZeroDivisionbyPrecision— and a file exercising an enum alone would have to invent a use for it. The reference pages already document enums separately; the samples are about calls, and an enum is not one. - An internal type gets no sample, and this is not a convention. A sample shows what a consumer of the package can write, and a consumer cannot name an internal type. If one deserved an example, the finding would be that it should be public.
-
A per-package aggregator survives,
<Package>Samples.cs, calling each class'sRun()in a readable order.Program.cskeeps calling one method per package rather than growing to 140 lines, and the order in the aggregator is where a reader learns which types belong together — which is the one thing the lot numbering did well.
Nested public types were weighed and are not a case: measured across src/, there is no indented
public type declaration. If one appears it arrives with its own decision.
-
140 files, and therefore not one pull request. The convention lands with
Lodestar.Textas its worked example, then one lot per package —Fuzzy(4 types),Embeddings(31),Metrics(67). Each is reviewable on its own, and none of them collides with the whole ofsamples/for a week. -
The packaging gate is what proves nothing was lost. ADR 0009 requires every public type to be
reachable from the sample by a member reference; splitting the files moves where those references
live.
PackagingGate.csfails if one goes missing, which is why the split is safe to do mechanically and unsafe to do without running it. -
A new public type now has an obvious home, which is the point: a contributor adding
FooBarwritesFooBarSample.csrather than choosing a lot, and a reviewer notices its absence by the file not being there. -
The
Lot*files shrink rather than disappear. The last package to leave them decides whether anything is left worth keeping; asking that question now, with four packages still inside, would be guessing.
Keeping the lots and adding an index — a table in samples/README.md mapping each type to the
lot that demonstrates it — was the cheap option, and it was seriously in play: no refactor, no
packaging-gate risk, one file to maintain.
It lost because it is a second copy of a fact the file system can hold directly. An index goes stale
the first time someone adds a type without updating it, and nothing fails when it does — the same
silence this repository already fights in bench/bench-map.json and the exception-parity gate, both
of which needed a guard to stay honest. Naming the file after the class needs no guard: the file is
either there or it is not, and the packaging gate already asks that question for the type.
- 0001-target-framework
- 0002-unicode-comparison-unit
- 0003-provenance-and-licensing
- 0004-levenshtein-myers-backlog
- 0005-hamming-jellyfish-divergence
- 0006-ratcliff-autojunk
- 0007-metaphone-scope
- 0008-italian-enza-nltk-divergence
- 0009-sample-consumes-a-local-feed
- 0010-stop-word-list-provenance
- 0011-persistence-format
- 0012-per-package-versioning
- 0013-sentencepiece-parity-scope
- 0014-precompiled-normalizer
- 0015-sonar-rules-in-the-build
- 0016-metrics-package-placement
- 0017-bpe-parity-scope
- 0018-multiclass-roc-auc-parallelism-is-opt-in
- 0019-the-net-analysers-run-in-the-build-too
- 0020-normalize-is-a-projection-not-a-parameter
- 0021-multioutput-is-a-method-not-an-enum
- 0022-added-token-matching-flags
- 0023-byte-level-decode-substitutes
- 0024-weighted-median-averages-within-scikit-learns-epsilon
- 0025-quickselect-replaces-a-full-sort-for-the-median
- 0026-r2-and-explainedvariance-split-their-undefined-cases-differently
- 0027-r2-and-explainedvariance-vectorize-only-a-single-output
- 0028-log1p-is-kahans-identity-not-math-log-1-plus-x
- 0029-balanced-accuracy-adjusted-is-left-to-ieee-754-at-the-edge
- 0030-cohen-kappa-keeps-scikit-learns-expected-matrix-orientation
- 0031-nosamplecorrect-mirrors-numpys-float64-upcast
- 0032-fbeta-substitutes-tp-predicted-and-support-algebraically
- 0033-compensated-sum-is-neumaiers-variant
- 0034-dropout-is-refused-for-want-of-a-user
- 0035-a-null-pre-split-is-removed-with-invert-not-isolated
- 0036-a-member-may-ship-without-an-oracle-if-it-says-so
- 0037-the-guards-run-before-the-commit
- 0038-the-gate-confronts-an-exception-tag-with-the-page-that-documents-it
- 0039-mutual-information-returns-zero-on-an-empty-input
- 0040-a-curve-is-a-sealed-class-per-curve
- 0041-one-sample-file-per-public-class
- 0042-phonetic-encoders-refuse-a-null-word
- 0043-the-equality-table-is-sized-to-the-pattern
- 0044-compression-belongs-to-the-caller
- 0045-a-console-call-carries-its-reason-on-the-line
- 0046-check-adr-immutable-runs-in-ci-only
- 0047-one-gate-per-kernel-not-one-per-alphabet
- 0048-the-gate-depends-on-the-kernel-and-the-alphabet
- 0049-two-gates-per-kernel-tested-where-the-width-is-known
- 0050-the-sentencepiece-bpe-lineage-stays-a-bpe-model
- benchmark_latest
- decisions
- equivalence
- matplotlib
- migration
- nightly_run
- numpy
- pandas
- performance
- pytorch
- seaborn
- sklearn
- statsmodels