A messy repository fails a rewrite when behavior is unfrozen.
Snapshot one process seam before any helper extract lands.
Then allow a one-file change budget, nothing larger.
Why whole-tree rewrites fail
Brownfield trees mix side effects, caches, and silent branches.
Existing tests are missing, stale, or tied to internals.
A broad model rewrite then breaks callers without warning.
The tempting move is a clean module in one pass.
That pass is expensive once production traffic hits it.
You need a freeze that does not require a clean design.
What counts as a process seam
A process seam is an input and output you already own.
CLI stdout, HTTP JSON, or a batch file all qualify.
Pick the edge that callers already treat as a contract.
Do not start inside a helper with undocumented callers.
Do not start with a rename that spans twelve packages.
Start where a subprocess or request can still be replayed.
Locale, clocks, and map key order are not the seam.
They are noise sources that make snapshots lie later.
Pin or canonicalize them before the first freeze run.
Worked example: a tangled invoice CLI
The following listing is a labeled sketch, not production telemetry.
It models a small Python CLI with mixed concerns together.
Treat SKUs and amounts as fixtures, not measured traffic.
# invoice_cli.py — messy starting point (example)
import json, sys
TAX = 0.0875
_cache = {}
def run(argv):
cmd = argv[1] if len(argv) > 1 else "help"
if cmd == "quote":
sku = argv[2]
qty = int(argv[3])
if sku in _cache:
price = _cache[sku]
else:
price = 19.99 if sku.startswith("A") else 29.99
_cache[sku] = price
sub = price * qty
if qty >= 10:
sub *= 0.9
total = round(sub * (1 + TAX), 2)
print(json.dumps({"sku": sku, "qty": qty, "total": total}))
return 0
if cmd == "help":
print("usage: quote SKU QTY")
return 0
print("unknown", file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(run(sys.argv))
Pricing, cache, tax, and CLI parsing share one function.
That mix is typical of a repository that grew without seams.
The refactor target is this process, not a new architecture.
Artifact: a characterization harness
Build the oracle outside the code you plan to touch.
Replay fixed argv rows and canonicalize stdout plus codes.
Store that result as a snapshot later diffs must match.
# characterize.py — example harness
import json, subprocess, sys, hashlib, pathlib
ROOT = pathlib.Path(__file__).resolve().parent
CASES = [
["quote", "A100", "1"],
["quote", "A100", "10"],
["quote", "B200", "2"],
["quote", "A100", "1"], # cache path
["help"],
["nope"],
]
def run_case(args):
proc = subprocess.run(
[sys.executable, str(ROOT / "invoice_cli.py"), *args],
capture_output=True, text=True,
)
stdout = proc.stdout
try:
stdout = json.dumps(json.loads(proc.stdout), sort_keys=True)
except Exception:
stdout = proc.stdout.replace("\r\n", "\n")
return {
"args": args,
"code": proc.returncode,
"stdout": stdout,
"stderr": proc.stderr.replace("\r\n", "\n"),
}
def main():
mode = sys.argv[1] if len(sys.argv) > 1 else "check"
snap = ROOT / "seam.snapshot.json"
rows = [run_case(c) for c in CASES]
blob = json.dumps(rows, indent=2, sort_keys=True) + "\n"
if mode == "freeze":
snap.write_text(blob)
print("froze", hashlib.sha256(blob.encode()).hexdigest()[:12])
return
if not snap.exists():
raise SystemExit("no snapshot; run freeze first")
if snap.read_text() != blob:
raise SystemExit("seam drift")
print("seam ok")
if __name__ == "__main__":
main()
# example targets
.PHONY: freeze check
freeze:
TZ=UTC PYTHONHASHSEED=0 python characterize.py freeze
check:
TZ=UTC PYTHONHASHSEED=0 python characterize.py check
Run freeze once on the known-good tree, then commit it.
Keep seam.snapshot.json beside the messy CLI it describes.
Every later edit must keep make check fully green.
Choose rows with intent
Cover the happy path, a discount path, and a cache hit.
Cover help text and one unknown-command failure path.
Skip rows that need live network, clocks, or user input.
If a branch has no row, you must not delete that branch.
Add the row first, freeze again, then consider deletion.
Absence of a snapshot row is not proof of dead code.
Duplicate a case that hits a module-level cache on purpose.
The second A100 quote exists to lock cache behavior.
Without it, an extract can drop the cache unnoticed.
Numbered workflow
1. Inventory the observable edge
List commands, flags, and payloads the process already accepts.
Keep only rows you can replay on a clean checkout.
Write them as data, not as comments in a test file.
2. Pin nondeterminism
Set TZ=UTC and PYTHONHASHSEED=0 for the harness process.
Reject timestamps in stdout, or replace them with a stub.
Sort JSON object keys before you write the snapshot file.
TZ=UTC PYTHONHASHSEED=0 python characterize.py freeze
3. Freeze the seam
Run the harness against the current tree, not the desired tree.
Commit the snapshot as an oracle, not as pretty documentation.
Do not fix surprising totals while you freeze behavior.
4. Budget one file
Name the single file allowed to change in this pass.
If the edit needs a second file, the budget is blown.
Split the work and ship the first file alone.
5. Change one concern
Extract tax, cache, or parsing, never all three at once.
Keep names that any in-process caller already imports.
Prefer a local helper over a new package layout now.
# invoice_cli.py — after one concern extract (example)
import json, sys
TAX = 0.0875
_cache = {}
def price_for(sku):
if sku in _cache:
return _cache[sku]
price = 19.99 if sku.startswith("A") else 29.99
_cache[sku] = price
return price
def run(argv):
cmd = argv[1] if len(argv) > 1 else "help"
if cmd == "quote":
sku = argv[2]
qty = int(argv[3])
sub = price_for(sku) * qty
if qty >= 10:
sub *= 0.9
total = round(sub * (1 + TAX), 2)
print(json.dumps({"sku": sku, "qty": qty, "total": total}))
return 0
if cmd == "help":
print("usage: quote SKU QTY")
return 0
print("unknown", file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(run(sys.argv))
Only the pricing lookup moved in this pass.
Tax, discounts, and CLI parsing stayed in run().
That is the point of a one-file, one-concern budget.
Run make check before any second extract starts.
A red check means revert, not a snapshot refresh.
6. Classify drift before you fix it
When make check fails, do not refresh the snapshot immediately.
Classify the first mismatch as encoding, key order, or behavior.
Only a behavior mismatch should block the extract.
The other two classes are harness bugs to fix.
Do not treat every red check as a product defect.
Read the first differing row before you touch either tree.
# classify_drift.py — example classifier, not a shipped metric
import json, pathlib, sys
def load(path):
return json.loads(pathlib.Path(path).read_text())
def classify(old, new):
if len(old) != len(new):
return "length-mismatch"
for left, right in zip(old, new):
if left == right:
continue
if left["args"] != right["args"]:
return "harness-row-mismatch"
same_code = left["code"] == right["code"]
if left["stdout"].strip() == right["stdout"].strip() and same_code:
return "encoding"
try:
ja = json.loads(left["stdout"])
jb = json.loads(right["stdout"])
if ja == jb and same_code:
return "key-order"
except Exception:
pass
return "behavior"
return "unknown"
if __name__ == "__main__":
print(classify(load(sys.argv[1]), load(sys.argv[2])))
Encoding drift means newline or JSON spacing changed by accident.
Key-order drift means canonicalization is incomplete or skipped.
Behavior drift means the extract changed a total, code, or message.
Revert the extract when the drift is real behavior.
Repair the harness when encoding or key order drifted.
Never mix a snapshot refresh with a helper extract commit.
Those are different reviews with different failure costs.
Decision table: smallest safe change
| Observed problem | Allowed change | Stop if |
|---|---|---|
| Long function, one concern mixed | Extract one helper in the same file | A second file is required |
| Duplicated literal | Name a constant in that file | Callers outside the file need it |
| Hidden branch | Add a comment or a local name | Behavior of the seam would change |
| Dead branch with no snapshot row | Add a characterization row first | You delete code without a row |
| Cross-file rename urge | Reject in this pass | The budget is one file |
Use the table during review, not after the merge lands.
The stop column is the gate for every pass.
A larger cleanup waits for a new freeze cycle later.
Wire the check into CI
Add one job that clones the repo and runs the pinned check.
Fail the job on any seam drift, including stderr text changes.
Do not parallelize against a shared cache file on disk.
CI is the second machine that does not share your editor buffer.
If it cannot clone and check, the oracle is not portable.
Fix path assumptions before you extract another helper.
Where a free coding model fits
A model is useful after the snapshot exists, not before it.
Ask it to extract one helper inside the budgeted file only.
Reject any patch that edits a second path or the snapshot.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode provides free model access and a free server option.
Those two facts are the only product claims used here.
A free server can run make freeze and make check remotely.
That keeps the oracle off the laptop that drafts the diff.
The model proposes the one-file extract for review.
The free server runs the seam check against that patch.
Do not prompt for a greenfield rewrite of the invoice CLI.
Do not ask for a new framework, types, or package split.
Prompt for the smallest diff that keeps characterize.py quiet.
Limitations
This method does not prove internal correctness of helpers.
It only locks the chosen process seam for listed rows.
Unlisted flags, encodings, and race paths remain free to break.
Floating time, random IDs, and unordered maps will false-fail.
Canonicalize or pin those sources before you freeze anything.
Subprocess checks are slow relative to in-process unit tests.
A seam that talks to a live database is a weak oracle.
You would be characterizing the database, not the messy module.
Stub I/O at the process edge or skip this method entirely.
Who should not use this
Do not use this on a greenfield service with a real suite.
Do not use this when the CLI contract must change today.
Do not use this as cover for deleting error handling you dislike.
Security-sensitive parsers need adversarial tests instead of snapshots.
Numeric ledgers need property tests, not three SKU fixture rows.
If you cannot name one seam, you are not ready to edit.
Close
Freeze one process seam before you edit the tree.
Budget one file for the extract, then stop.
Change one concern behind that seam, then ship.
The messy repository can wait for another freeze cycle.
The snapshot cannot wait if you plan to extract.
Ship the small diff only while make check stays green.
Draft that one-file diff with a free remote model.
Keep this harness as the only reviewer.
Top comments (0)