DEV Community

Dakota Huang
Dakota Huang

Posted on

Freeze One Function's I/O Table, Then Change One Branch

Freeze one function before you rewrite a messy module. Hidden branches are where refactors silently break callers. An I/O decision table is the oracle for that function.

A green unit suite is not local enough. Unit suites drift and skip rare error paths. A finite JSONL table does not drift that way.

Why this order beats a full rewrite

Brownfield retry code hides policy in nested conditionals. Callers depend on sleep values and error types. One missed branch ships a new outage mode.

Coding models will rewrite the entire file on request. They drop rare conditions without a frozen oracle. Record observable I/O before any model sees the file.

What belongs in the table

I/O means every value a caller can observe. Return payloads count as observable I/O. Raised exception types also count as I/O.

Message text prefixes count when callers parse them. Numeric delays count when callers persist them. Unordered log lines do not belong here.

Disk writes stay outside this particular freeze. Network calls stay outside this freeze too. Those seams need a different recording method.

Artifact: JSONL I/O rows

The artifact is a replayable JSONL oracle. Each row stores args, result, and error. Replay fails when any frozen field moves.

The function below is a labeled example. It is not production traffic or telemetry. Treat decide_retry as your stand-in target.

1. Isolate the function under test

Copy the function into a thin module. Do not rename branches or magic numbers. You are recording behavior, not improving it.

# example_retry.py — messy stand-in, not a real service
def decide_retry(exc_name, attempt, status, retry_after):
    if exc_name in ("Timeout", "ConnectionError"):
        if attempt >= 5:
            return {"action": "fail", "sleep_s": 0, "reason": "max"}
        backoff = min(2 ** attempt, 32)
        return {"action": "retry", "sleep_s": backoff, "reason": "net"}
    if status == 429:
        wait = retry_after if retry_after is not None else 10
        if wait > 60:
            return {"action": "fail", "sleep_s": 0, "reason": "hot"}
        return {"action": "retry", "sleep_s": wait, "reason": "throttle"}
    if status and 500 <= status < 600:
        if attempt >= 3:
            return {"action": "fail", "sleep_s": 0, "reason": "5xx"}
        return {"action": "retry", "sleep_s": 1, "reason": "5xx"}
    if exc_name == "AuthError":
        raise PermissionError("no retry on auth")
    return {"action": "fail", "sleep_s": 0, "reason": "other"}
Enter fullscreen mode Exit fullscreen mode

That nested function already is a decision table. The source just conceals the rows. Your fixtures must make those rows explicit.

2. Build the fixture matrix

List corners that callers actually hit. Cover max attempts and a missing retry_after header. Cover auth failures and unknown HTTP status codes.

# fixtures.py
CASES = [
    {"id": "t1", "exc_name": "Timeout", "attempt": 0, "status": None, "retry_after": None},
    {"id": "t2", "exc_name": "Timeout", "attempt": 5, "status": None, "retry_after": None},
    {"id": "t3", "exc_name": "ConnectionError", "attempt": 4, "status": None, "retry_after": None},
    {"id": "t4", "exc_name": None, "attempt": 0, "status": 429, "retry_after": 12},
    {"id": "t5", "exc_name": None, "attempt": 0, "status": 429, "retry_after": 90},
    {"id": "t6", "exc_name": None, "attempt": 0, "status": 429, "retry_after": None},
    {"id": "t7", "exc_name": None, "attempt": 2, "status": 503, "retry_after": None},
    {"id": "t8", "exc_name": None, "attempt": 3, "status": 503, "retry_after": None},
    {"id": "t9", "exc_name": "AuthError", "attempt": 0, "status": 401, "retry_after": None},
    {"id": "t10", "exc_name": None, "attempt": 0, "status": 404, "retry_after": None},
]
Enter fullscreen mode Exit fullscreen mode

Ten rows form a minimum useful oracle table. Add a row when a caller surprises you. Never delete a row to make a patch pass.

3. Record the oracle once

Run every fixture through the current function. Capture the result or the exception object. Write one sorted JSON object per line.

# record_io.py
import json
from example_retry import decide_retry
from fixtures import CASES

def capture(case):
    payload = {k: case[k] for k in ("exc_name", "attempt", "status", "retry_after")}
    try:
        result = decide_retry(**payload)
        return {"id": case["id"], "ok": True, "result": result, "error": None}
    except Exception as exc:
        return {
            "id": case["id"],
            "ok": False,
            "result": None,
            "error": {"type": type(exc).__name__, "msg": str(exc)},
        }

if __name__ == "__main__":
    with open("retry_io.jsonl", "w", encoding="utf-8") as handle:
        for case in CASES:
            row = capture(case)
            handle.write(json.dumps(row, sort_keys=True) + "\n")
Enter fullscreen mode Exit fullscreen mode

Run the recorder before any source edit exists.

python record_io.py
test -f retry_io.jsonl
Enter fullscreen mode Exit fullscreen mode

That committed pair is the observable function contract. Source code may change only after this commit. Keep fixtures.py and retry_io.jsonl in the same change.

Sample recorded row for t1 looks like this.

{"error": null, "id": "t1", "ok": true, "result": {"action": "retry", "reason": "net", "sleep_s": 1}}
Enter fullscreen mode Exit fullscreen mode

Do not pretty-print later with a new key order. Sorted keys keep equality checks boring. Boring equality is the point of the freeze.

4. Replay after every subsequent edit

Replay loads the oracle and recaptures each row. Any mismatch prints both JSON objects. A mismatch is a failed refactor, not noise.

# replay_io.py
import json
from record_io import capture
from fixtures import CASES

def load_oracle(path="retry_io.jsonl"):
    with open(path, encoding="utf-8") as handle:
        return [json.loads(line) for line in handle if line.strip()]

if __name__ == "__main__":
    oracle = {row["id"]: row for row in load_oracle()}
    failures = []
    for case in CASES:
        got = capture(case)
        expected = oracle[case["id"]]
        if got != expected:
            failures.append((case["id"], expected, got))
    if failures:
        for item in failures:
            print("MISMATCH", item[0])
            print(" expected", json.dumps(item[1], sort_keys=True))
            print(" got     ", json.dumps(item[2], sort_keys=True))
        raise SystemExit(1)
    print(f"ok {len(CASES)} rows")
Enter fullscreen mode Exit fullscreen mode
python replay_io.py
Enter fullscreen mode Exit fullscreen mode

Green replay means the observable contract held. Red replay means a branch moved under you. Stop there and restore the last green commit.

5. Change one branch only

Pick a single internal path to clarify now. Leave every other conditional untouched in this round. Extract a helper only for that chosen path.

Labeled example: fold the 5xx block into one helper.

def _server_error(attempt):
    if attempt >= 3:
        return {"action": "fail", "sleep_s": 0, "reason": "5xx"}
    return {"action": "retry", "sleep_s": 1, "reason": "5xx"}
Enter fullscreen mode Exit fullscreen mode

Then replace only the original 5xx block. Call the helper from that remaining 5xx branch. Run python replay_io.py before any second fold.

If replay fails, revert that helper immediately. Do not "fix" rows to match new output. The oracle wins until you deliberately version it.

Expected baseline outcomes for the sample table follow. Timeout at attempt 0 retries with sleep_s=1. Timeout at attempt 5 fails with reason=max. 429 with retry_after=90 fails hot. AuthError raises PermissionError. Unknown 404 fails with reason=other.

Those expected fields are now test assertions. They are not comments in the messy function. Comments will not fail a bad extract.

Numbered workflow

Use this sequence on the next messy file.

  1. Pick one function with nested branches.
  2. List observable fields that callers already consume.
  3. Write a fixture matrix for those fields.
  4. Record JSONL and commit the oracle pair.
  5. Confirm replay is green on the baseline.
  6. Edit one internal branch or helper only.
  7. Replay again before starting a second edit.
  8. Add a fixture when a new corner appears.

Step six is the only design step here. The remaining steps only gather frozen replay evidence. Evidence first keeps the resulting diff bisectable.

Decision matrix for the next keystroke

Observed signal Allowed action
JSONL file is missing Record rows. Do not edit code.
Replay fails on baseline Fix the harness, not production code.
One branch is unclear Change that branch. Replay once.
Patch rewrites the whole file Reject it. Demand a smaller diff.
A new caller shows a miss Add a fixture row, then record.

This matrix exists to shrink the allowed diff. Small diffs bisect faster than broad cleanups. Cleanup is allowed only after replay stays green.

Where a free model belongs

A coding model helps only after the table exists. The coding model cannot replace the JSONL oracle. Ask for a rewrite only against frozen rows.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

MonkeyCode offers free model access and a free server option. Use that pair to propose one function-local patch. Supply the function, the JSONL table, and one rule.

The instruction rule stays simple and mechanical. Keep every recorded row byte-identical after the patch. Do not request a repository-wide cleanup from the model.

Run python replay_io.py on whatever comes back. Keep the patch only when replay stays green. Discard the patch when any frozen row drifts.

Limitations

This JSONL table does not prove general correctness. It only proves the listed fixture rows today. Unlisted branches remain free to rot silently.

Nondeterministic code will thrash the oracle JSONL file. Clocks, RNG, and unordered sets break JSON equality. Stub those seams before you record oracle rows.

Concurrency bugs will not appear in this JSONL. Latency regressions will not appear in replay either. File and network effects need another freeze method.

Large input spaces stay badly under-sampled here. Ten fixture rows can miss an eleventh branch. Promote failing production calls into new fixture rows.

Who should skip this approach

Skip this workflow during a live production hotfix. You need a runner and a committed oracle. Hotfixes without JSONL replay remain unverified guesses.

Skip this method on cryptographic or authz cores. JSON row equality is not a security review. Those changes need threat analysis beyond JSONL rows.

Skip this when the function cannot run isolated. A 400-line method closing over request state is blocked. Split a test seam before you record anything.

Skip model output that you cannot replay locally. Careful-looking model patches still drop rare branches. The JSONL file owns the contract, not the model.

Close

Pick one messy function on the next working day. Record ten oracle rows and commit them. Change one branch, then replay the JSONL table.

Green rows mean the refactor was evidence-backed work. Red rows mean you found the hidden branch. Either result is cheaper than a silent rewrite.

Use free model access only after replay is green.

Top comments (0)