devinside.

CONTEMPORARY LABORATORY · ORIGINAL EVIDENCE

A Small C# Function and the Next Change: Contract and Maintainability Tests

This notebook preserves the later investigation, its original measured results, exact versions and execution dates. It is separate from the historical subject scope of the reconstructed article.

Return to the historical explanation → · Current reconstruction sources

Contemporary laboratory record. Original provenance and measurement limits remain intact.

A short function can contain a large agreement. It can decide which input is valid, which arithmetic is acceptable, when a threshold applies and what happens at the edge of a rule. The fact that those choices fit on one screen does not make them obvious. It can make them easier to overlook.

This matters when reviewing code produced with AI assistance, but the problem is not exclusive to generated code. A human can write a compact expression that hides the same assumptions. A review that concentrates on style can approve either implementation without establishing what the function is supposed to promise.

The laboratory in this article uses a small synthetic calculation to make that problem concrete. It is a teaching fixture with integer accounting units, not a production checkout system or financial recommendation. The first rule gives a discount to all accepted lines and applies a strict shipping threshold. The next rule changes the threshold to inclusive and allows some lines to be ineligible for the discount.

We implement the old contract, a more explicitly separated calculation, and one deliberately incorrect refactor. Independent arithmetic and boundary fixtures check what each implementation does. A small timing experiment measures execution cost, but it does not pretend to measure developer productivity or maintainability. The distinction between those questions is part of the lesson.

Name six choices before accepting the function

01 / A concrete example

A short function contains several decisions

The fixture defines the input, discount base, threshold base and equality rule before choosing implementation style.

The fixture defines the input, discount base, threshold base and equality rule before choosing implementation style.

Interpretation and evidence
  • Write the rule before the code.
  • Name the amount each rule uses.
  • Make the changed boundary explicit.
Actual contract fixtures and measured output where numeric; no developer productivity study.

The first version accepts nonnegative quantities and unit values. Its inputs are integer minor units. It computes a gross subtotal, rounds one aggregate discount to an integer using an explicit midpoint rule, and adds a shipping amount unless the gross subtotal is strictly greater than a threshold. All lines in that original contract are discount-eligible.

That description already contains several decisions. Are negative quantities a valid way to represent returns, or invalid input? Is the discount rounded for each line or once after aggregation? Does the threshold use the original gross amount or the discounted amount? Does equality qualify? What happens if multiplication or accumulation exceeds the numeric representation? Can the implementation modify or reorder the caller's input?

None of these questions is answered by a method name such as CalculateTotal. A generated implementation can choose plausible answers and still be wrong for the intended product. A reviewer cannot resolve the ambiguity by deciding that one version looks more idiomatic.

The fixture fixes the answers deliberately. Negative quantities and unit values are rejected. Rounding happens once on the eligible subtotal. The shipping threshold uses the gross amount before discount. Version one uses a strict comparison; version two uses an inclusive comparison. Checked integer arithmetic rejects overflow. The input is an array of immutable line records and is not modified by the calculation.

These rules are not recommendations for every commercial system. They are a sufficiently precise contract for the experiment. A real application might need taxes, multiple currencies, refund semantics or another rounding policy. Adding those concerns would require more decisions, not simply more lines of code.

Keep a readable baseline instead of inventing a straw man

The initial implementation is not intentionally broken. It validates the line values, computes the subtotal, requires every line to be eligible, calculates the aggregate discount and applies the strict threshold. For its supported input domain, it can be correct and simple.

public static long Legacy(Line[] lines, Policy policy)
{
    long sum = ValidateAndSubtotal(lines);
    if (lines.Any(x => !x.DiscountEligible))
        throw new ArgumentException("Legacy v1 accepts eligible lines only");

    long net = checked(sum - Discount(sum, policy.DiscountPercent));
    return checked(net +
        (sum > policy.FreeShippingAt ? 0 : policy.ShippingMinor));
}

The name Legacy in the source means original contract, not bad code. A useful review should resist the temptation to present an older implementation as foolish merely because a later requirement exists. The original version has a narrower responsibility, and that narrower responsibility is part of the comparison.

Its supported policy domain also matters. The selected tests use nonnegative thresholds and shipping amounts. The baseline does not implement the expanded policy validation of the composed version. We do not claim the two accept every possible malformed policy identically. Their equivalence claim is limited to the declared valid version-one cases.

The baseline's use of Any is another visible choice. The array can be enumerated again without consuming a one-shot stream. Replacing the array parameter with an arbitrary lazy enumerable would introduce a new contract question about repeatability, side effects and cost. The laboratory does not quietly make that API change.

A review begins to improve when it can say which properties are stable and which are new. That is more informative than calling one implementation modern and another legacy without naming the difference in responsibility.

Separate mechanisms where the next rule needs separation

02 / Watch the mechanism

Two subtotals have different jobs

The discount base can change without changing the amount that qualifies for shipping.
Enlarge diagram

Static view. Play to see how the mechanism unfolds.

The discount base can change without changing the amount that qualifies for shipping.

Interpretation and evidence
  • Separate meaning, not just methods.
  • Keep validation visible.
  • An extra pass has a measurable cost.
Actual contract fixtures and measured output where numeric; no developer productivity study.

The composed implementation keeps gross subtotal and discount-eligible subtotal distinct. The discount is calculated on the eligible amount, while shipping eligibility still uses the gross amount. A policy flag expresses the intended strict or inclusive threshold.

public static long Composed(Line[] lines, Policy policy)
{
    long gross = ValidateAndSubtotal(lines);
    long eligible = 0;
    foreach (var line in lines)
        if (line.DiscountEligible)
            eligible = checked(eligible +
                checked(line.Quantity * line.UnitMinor));

    long net = checked(gross - Discount(eligible, policy.DiscountPercent));
    bool free = policy.InclusiveThreshold
        ? gross >= policy.FreeShippingAt
        : gross > policy.FreeShippingAt;

    if (policy.ShippingMinor < 0 || policy.FreeShippingAt < 0)
        throw new ArgumentOutOfRangeException(nameof(policy));

    return checked(net + (free ? 0 : policy.ShippingMinor));
}

This code is longer. Its value is not the number of methods or variables. Its value is that two quantities with different meanings no longer share one ambiguous name. A reviewer can see that the discount base and the shipping base are separate decisions.

That separation has a cost. The implementation traverses the array to validate and sum all lines, then traverses it again to calculate the eligible subtotal. A different implementation could combine those passes while preserving the two named results. The article does not claim that this first separated version is the fastest possible design.

The point is to make the next change reviewable. When a requirement says that one line is ineligible for discount, the calculation has a place to express that rule without changing which amount qualifies for free shipping. Whether this organization actually reduces human review time remains a question for a separate study, not a fact established by the source layout.

A helpful abstraction exposes a distinction the domain needs. An unhelpful abstraction merely moves the same ambiguity into more files. In this fixture, gross and eligible are named because they can legitimately differ, not because every short function needs an architecture.

The independent oracle is not another copy of the implementation

04 / A concrete example

Independent expectations use another representation

The arithmetic oracle uses a different implementation path and is anchored by literal boundary results.

The arithmetic oracle uses a different implementation path and is anchored by literal boundary results.

Interpretation and evidence
  • Do not duplicate the same expression.
  • State the oracle domain.
  • A correct oracle cannot choose business policy.
Actual contract fixtures and measured output where numeric; no developer productivity study.

A test that compares the composed implementation only with the baseline can preserve an existing defect. We therefore use an independent expected calculation based on BigInteger arithmetic for the nonnegative integer domain. It accumulates gross and eligible amounts without overflow in the intermediate representation and calculates the rounded discount through an integer formula.

For this particular nonnegative, integer-percent fixture, rounding to the nearest integer with midpoint away from zero can be represented as (eligible * percent + 50) / 100. The production-style implementation instead uses decimal arithmetic and an explicit MidpointRounding.AwayFromZero argument. Agreement between the two paths gives a stronger check than copying the same decimal expression into the test.

The oracle is still based on a chosen contract. It does not prove that the contract is the right commercial policy. It also should not be extended to negative values without revisiting the rounding formula. Its independence concerns implementation mechanics, not an independent discovery of business intent.

The fixture also contains literal expected outcomes at the boundary. With a gross value of 1000, a 10 percent discount and 50 units of shipping, version one's strict threshold produces 950. Version two's inclusive threshold produces 900. Those numbers can be checked without executing either implementation. They expose exactly what changed at equality.

Microsoft's decimal rounding API supports an explicit rounding strategy, which the code uses rather than relying on an unstated default. That source establishes the API's meaning; our fixture establishes the selected calculation's result. Decimal.Round documentation.

The next requirement reveals a hidden dependency

03 / A concrete example

One boundary case kills the wrong refactor

The literal equality case detects moving the shipping decision from gross amount to discounted amount.

The literal equality case detects moving the shipping decision from gross amount to discounted amount.

Interpretation and evidence
  • The expected result is independent.
  • Keep both policy versions.
  • One killed mutant is not exhaustive coverage.
Actual contract fixtures and measured output where numeric; no developer productivity study.

The second contract contains two deliberate changes. Equality at the shipping threshold now qualifies, and a line can be ineligible for discount. The two changes are related to the same calculation but affect different quantities.

Consider an order consisting of an eligible line worth 600 units and an ineligible line worth 400. The gross amount is 1000, so it qualifies under the inclusive shipping rule. The discount applies only to 600. A refactor that uses the discounted total for shipping can charge shipping even though the gross threshold was reached.

That incorrect implementation is included as WrongBoundary. It is a deliberate mutant, not an accidental production bug presented as a customer incident. It computes the net amount, then compares that net amount with the shipping threshold. The literal equality fixture rejects it.

This is a small but useful test of the test. A collection of happy-path cases far above the threshold could let the wrong implementation pass. Placing the example exactly at equality makes the hidden dependency observable. The failure is not about formatting or naming; it is about which amount controls a consequence.

The test does not establish that every possible mutant would be caught. It establishes that one plausible, consequential mistake is detected by an independent expected result. A serious test suite can add other mutation targets when the contract identifies other failure risks, rather than treating a large test count as sufficient evidence by itself.

The work item should describe this boundary before an implementation is requested. “Add mixed discount eligibility and inclusive shipping” is still incomplete if it does not say which subtotal drives shipping, where rounding occurs and what behavior must remain unchanged.

Enumerated cases and one metamorphic property

The executable check covers eleven selected unit values, four quantities and five discount percentages. Each valid combination is evaluated under the original implementation, the composed version-one policy and the composed version-two policy, using the independent oracle. The values include zero, small rounding-sensitive amounts and points around the shipping threshold.

The fixture then tests mixed eligibility and reverses the order of those lines. For this contract, permuting the lines must not change the result. That is a metamorphic property: it relates two valid inputs without relying solely on a separately calculated numeric output for every permutation.

There are also explicit rejection checks for a negative quantity and multiplication overflow, plus the literal threshold examples and the deliberately wrong boundary. The accepted run reports 669 assertions and one detected mutant. The report does not rename those assertions as 669 independent business requirements or a complete proof of correctness.

Checked arithmetic matters at the point where the operation occurs. Placing a checked block around a call does not automatically rewrite unchecked arithmetic inside the called method. The actual multiplication and accumulation in this fixture are explicitly checked. C# checked and unchecked reference.

The test also has limitations worth retaining. It does not exhaust every long value, every invalid policy or every possible line collection. It does not model concurrent mutation, because its input records and selected use are immutable. A broader public API would need additional input-contract decisions before claiming those behaviors.

That honesty makes the result more useful. Another engineer can see what to reuse, what to challenge and where a new requirement changes the acceptance boundary. A vague statement that all tests pass would hide those distinctions.

Measure execution cost without calling it maintainability

05 / A concrete example

Execution cost and changeability are separate questions

The composed implementation was slower in this local comparison. It also supports a wider rule domain, which a v1 timing cannot evaluate.

The composed implementation was slower in this local comparison. It also supports a wider rule domain, which a v1 timing cannot evaluate.

Interpretation and evidence
  • Preserve the slower result.
  • Do not call runtime speed maintainability.
  • Keep functional coverage separate.
Actual contract fixtures and measured output where numeric; no developer productivity study.

The local timing experiment compares only the two implementations under the same version-one rule. It uses 16 prebuilt eligible lines, so both implementations are expected to return the same result. There are seven samples per implementation, with their order reversed on alternate samples. Each sample executes 100,000 calls after a small warm-up.

The run used SDK 10.0.400 and runtime 10.0.15 on the shared Windows development host. All checks and the timing run completed successfully. The code validates the accumulated result of every measured sample. No network, database, user input or model generation is involved.

Implementation Median sample-average Range of sample averages Managed allocation per call
legacy 82.206 ns 82.069–499.184 ns 0 B
composed 289.658 ns 88.467–545.550 ns 0 B

The separated implementation is slower in this narrow comparison. That observation belongs in the article. Naming more concepts does not automatically make a loop faster, and a design intended to expose a policy boundary can perform additional work. The measurement does not justify hiding the clearer implementation's cost.

It also does not establish that the baseline is the better implementation for the changed requirement. The original code rejects mixed eligibility and has a fixed strict threshold. A timing comparison under the old supported domain cannot decide whether the new behavior is valuable. Performance and functional coverage are different axes of the decision.

Both measured paths reported no managed allocation inside the successful call loop. The prebuilt array and records still occupy memory, and the process and harness allocate outside that interval. This is a local allocation observation, not a claim that the program uses no memory or that the surrounding service is allocation-free.

Most importantly, the times are not a measurement of maintainability. We did not recruit developers, assign review tasks, measure their effort or study defect rates after later changes. The code and fixtures make a change inspectable; they do not produce a numerical multiplier for human productivity.

A review packet can be concrete without pretending to be a study

The useful deliverable is a small packet that a reviewer can navigate. It contains the original rule, the changed rule, the independent expected outcomes, the implementation differences, the negative case and the observed execution cost. Each item answers a different review question.

A reviewer can first check the intended change without looking at the implementation: equality changes, mixed eligibility is introduced, gross amount still controls shipping, rounding remains aggregate and input mutation remains forbidden. They can then inspect whether the code expresses those decisions and whether the fixtures would detect a violation.

Only after that should they interpret the timing result. If this calculation is a demonstrated hot path, the extra traversal may justify a follow-up implementation that returns gross and eligible subtotals together. That optimization would have to preserve the same oracle cases and refusal behavior. If the function is rarely called relative to remote work, the local nanosecond difference may not matter to the product.

The packet makes both conclusions possible. It does not force a preference for short code, layered code, LINQ or loops. It gives the reviewer evidence about the promise and a way to test a proposed change. Style can still matter for the team, but it is no longer standing in for correctness.

The same approach works when an AI assistant proposes the implementation. Give it the acceptance boundary, then review the result against that boundary. A confident explanation of the code is not evidence that equality, overflow and mixed eligibility were handled as intended.

The failure mode of an incomplete prompt is an incomplete contract

An instruction such as “make this pricing function cleaner” gives a model broad freedom to alter structure without establishing which behavior is sacred. It may combine calculations, change rounding placement or choose a different comparison because the rewritten code looks more natural.

A better work item can be shorter than a long prompt full of stylistic advice. State the valid input domain, immutable outputs, changed rules, boundary examples and conditions for refusal. Point to the existing tests and require additional cases for the new behavior. That is useful whether the implementer is a colleague, an assistant or the original author returning months later.

The experiment does not prove that such a work item always produces better code from a particular model. No model comparison was performed. The claim is narrower: the work item exposes decisions that the tests and implementation can subsequently check. It reduces the amount of meaning a reviewer has to infer from syntax.

There is a corresponding obligation on the reviewer. If the stated rule is ambiguous, returning a beautifully tested interpretation is not the same as resolving the ambiguity. The tests may be internally consistent while protecting a choice nobody authorized. Identifying that uncertainty is part of technical work, not a failure to provide enough code.

The most valuable review comment may therefore be a question about the contract rather than a requested refactor. Which amount qualifies? When is rounding applied? Is a missing line valid? Those questions can prevent an entire class of polished but incorrect implementations.

Evolve the implementation without rewriting its evidence

A future optimization should preserve the accepted source and reports. If the code changes, record the new source revision, runtime and output. Do not overwrite the old timing table and leave the surrounding explanation pretending it describes the same experiment.

The same principle applies to a policy change. Version one and version two intentionally differ at equality and eligibility. A test failure after selecting version two can be expected if it asserts the old behavior. The right response is not to delete the boundary case; it is to retain both expectations with explicit policy identities.

This distinction becomes more important in a service with persisted decisions. A result produced under an earlier policy may need to remain explainable after the application deploys another policy. The small function does not implement that persistence, but its explicit policy input shows where the versioned decision belongs.

Download the runnable contract laboratory and raw measurement records. The package contains the original, composed and deliberately incorrect implementations, the independent BigInteger oracle, literal fixtures and accepted sample output. Run check before measure from the pinned project directory.

A small function is an excellent place to practice this discipline because the whole mechanism is visible. The review can follow every arithmetic operation and every boundary decision. If the contract is still unclear there, adding more infrastructure will not make it clear.

The result is not a rule that longer code is better, or that performance should be sacrificed for abstraction. It is a way to make tradeoffs explicit. Correctness comes from a stated promise and independent evidence. Execution cost comes from a defined measurement. Maintainability remains a claim about future work that deserves its own evidence rather than a flattering inference from today's code.

The review should state which behavior was deliberately left out

The laboratory's API accepts an array, not an arbitrary stream of lines. That choice keeps enumeration repeatable and lets the fixture focus on policy and arithmetic. It also means that a claim about the behavior of deferred queries, remote database results or iterators with side effects would be unsupported. Those inputs would require a different interface or a documented materialization step.

Likewise, the function does not handle a null array as a business case. Its contract assumes an actual array, which may be empty. A public boundary can reject null before calling it, or the component can choose a dedicated exception. The absence of that policy in the fixture should remain visible rather than being described as comprehensive input validation.

The distinction between an empty array and an array of zero-quantity lines is interesting even in this small domain. Under the selected rule, neither produces a gross subtotal, but the shipping rule can still add a fee when the threshold is not met. That behavior may be commercially surprising. It is nevertheless what the stated fixture contract means. A real product owner might instead reject an empty order or suppress shipping, which would be another explicit rule change.

This is why an independent oracle is necessary but not sufficient. It protects the agreement that was specified; it cannot decide whether that agreement matches the intended customer experience. An engineer who notices an odd boundary should raise the question instead of assuming that a green test makes the policy unquestionably correct.

The mutation is also deliberately modest. It changes only the shipping base from gross to net. Detecting it shows that the equality fixture is sensitive to a consequential distinction. It does not imply that the suite detects every rounding, overflow, eligibility or ordering defect. A future mutation exercise should be driven by the particular risks in the next change, with positive cases that still demonstrate legitimate behavior.

For a review, I would place these limitations beside the proposed patch. The author can then explain what is preserved, what changes and what remains outside the component. That is an actionable discussion. “The code looks clean” is not an adequate replacement for it, and neither is an impressive assertion count without a map to the contract.

The experiment gives us a small, complete example of this separation. The arithmetic expectations are inspectable, the negative case fails for an understandable reason, and the timing has a named boundary. The remaining uncertainty is not hidden. It is the next question a real team would have to answer before embedding the component in a larger system.

Source code and execution evidence

Download the complete lab, source record and raw execution results. This package supports the specific execution claims in this article. It is not a production certification.