A god function is a side-effect sequence, not a blob. Pin that call sequence before you extract anything else. A bigger rewrite still hides untested order bugs.
Return values lie in messy scripts. Side effects do the real work. Call order is the contract most reviews miss.
Why order beats a stdout snapshot
A snapshot of printed text is not enough. Two helpers can swap and still print identical lines. The file, the log, and the network still change.
Order bugs survive green unit tests. They also survive AI rewrites that preserve names. The sequence is the smallest honest oracle.
This method does not claim production metrics. It is a labeled, reproducible workflow. Run it on one fixture before any extract.
Synthetic god function, labeled example
The module below is a proposal, not production code. It mixes parse, I/O, and formatting in one place. Treat it as a stand-in for a scary repo.
# proposal: messy_report.py — synthetic, unexecuted in this article
from __future__ import annotations
import json
from pathlib import Path
def load_rows(path: Path) -> list[dict]:
text = path.read_text(encoding="utf-8")
rows = []
for line in text.splitlines():
if not line.strip() or line.startswith("#"):
continue
name, count = line.split(",", 1)
rows.append({"name": name.strip(), "count": int(count)})
return rows
def write_json(path: Path, payload: dict) -> None:
path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
def process_report(src: Path, dest: Path, min_count: int) -> dict:
rows = load_rows(src)
kept = [r for r in rows if r["count"] >= min_count]
payload = {"kept": kept, "dropped": len(rows) - len(kept)}
write_json(dest, payload)
return payload
The extract target is not the whole function. It is one collaborator with a stable call slot. load_rows and write_json are the only safe seams here.
1. Wrap every collaborator you might extract
Do not start with a rewrite. Wrap the functions the god function already calls. Record name, args, and a monotonic index.
# proposal: trace_wrap.py — synthetic harness
from __future__ import annotations
import json
from functools import wraps
from pathlib import Path
from typing import Any, Callable
TRACE: list[dict[str, Any]] = []
def reset_trace() -> None:
TRACE.clear()
def wrap(name: str, fn: Callable) -> Callable:
@wraps(fn)
def inner(*args: Any, **kwargs: Any) -> Any:
TRACE.append(
{
"i": len(TRACE),
"name": name,
"args": _safe_args(args),
"kwargs": sorted(kwargs.keys()),
}
)
return fn(*args, **kwargs)
return inner
def _safe_args(args: tuple[Any, ...]) -> list[str]:
out: list[str] = []
for item in args:
if isinstance(item, Path):
out.append(f"path:{item.name}")
elif isinstance(item, (str, int, float, bool)) or item is None:
out.append(repr(item))
elif isinstance(item, dict):
out.append("dict:keys=" + ",".join(sorted(map(str, item.keys()))))
else:
out.append(type(item).__name__)
return out
def dump_trace(path: Path) -> None:
path.write_text(json.dumps(TRACE, indent=2) + "\n", encoding="utf-8")
Keep payloads out of the log when they are huge. Store keys, types, and path names only. Secrets never belong in a trace file.
2. Run one golden input and dump JSON
Pick one production-shaped fixture. Run the god function once through the wrappers. Write the ordered log to disk.
# proposal: record_once.py — synthetic, unexecuted
from pathlib import Path
import messy_report as mr
from trace_wrap import dump_trace, reset_trace, wrap
reset_trace()
mr.load_rows = wrap("load_rows", mr.load_rows)
mr.write_json = wrap("write_json", mr.write_json)
src = Path("fixtures/report.csv")
dest = Path("tmp/report.json")
mr.process_report(src, dest, min_count=3)
dump_trace(Path("tmp/side_effects.json"))
mkdir -p fixtures tmp
printf 'alpha,4\n# skip\nbeta,1\ngamma,9\n' > fixtures/report.csv
python record_once.py
cat tmp/side_effects.json
The expected sequence is two calls, not a novel. load_rows must fire before write_json. The dest name must match the fixture path.
A sample trace looks like this. It is a fixture, not a benchmark.
[
{"i": 0, "name": "load_rows", "args": ["path:report.csv"], "kwargs": []},
{"i": 1, "name": "write_json", "args": ["path:report.json", "dict:keys=dropped,kept"], "kwargs": []}
]
3. Pin the sequence, not the prose
Load the JSON in pytest. Assert names and selected args in order. Leave values you do not understand unpinned.
# proposal: test_side_effect_order.py — synthetic
import json
from pathlib import Path
import messy_report as mr
from trace_wrap import TRACE, reset_trace, wrap
GOLDEN = json.loads(Path("tmp/side_effects.json").read_text(encoding="utf-8"))
def test_process_report_keeps_collaborator_order(tmp_path: Path) -> None:
src = tmp_path / "report.csv"
dest = tmp_path / "report.json"
src.write_text("alpha,4\n# skip\nbeta,1\ngamma,9\n", encoding="utf-8")
reset_trace()
mr.load_rows = wrap("load_rows", mr.load_rows)
mr.write_json = wrap("write_json", mr.write_json)
mr.process_report(src, dest, min_count=3)
names = [row["name"] for row in TRACE]
assert names == [row["name"] for row in GOLDEN]
assert TRACE[0]["args"][0].endswith("report.csv")
assert "dropped,kept" in TRACE[1]["args"][1]
Run the pin before any extract. Then run it after the extract. Same command, same names, same slots.
python -m pytest test_side_effect_order.py -q
If the order flips, fail the branch. Do not argue about style. The sequence is the gate.
4. Draft pins from the trace, not from vibes
A free coding model can turn JSON into pytest. It cannot own the extract. Keep the model on the trace file only.
Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode offers free model access and a free server option. That pair can host the harness and draft pins from side_effects.json.
Paste the JSON. Ask for assertions on name and argument slots. Reject any patch that rewrites process_report in the same step.
# proposal prompt, unexecuted
Turn tmp/side_effects.json into one pytest.
Pin call names and argument slots only.
Do not refactor messy_report.py.
Do not invent extra collaborators.
Treat model output as a draft. Re-run pytest on the real fixture. Delete any assertion the fixture cannot fail.
5. Extract one collaborator after the pin is green
Move only write_json or only load_rows. Keep the god function as the orchestrator. Re-run the same ordered pin.
# proposal: one extract, not a redesign
from report_io import write_json
def process_report(src: Path, dest: Path, min_count: int) -> dict:
rows = load_rows(src)
kept = [r for r in rows if r["count"] >= min_count]
payload = {"kept": kept, "dropped": len(rows) - len(kept)}
write_json(dest, payload)
return payload
Patch the wrap target after the move. Point it at the new module name. The trace names must stay stable.
import report_io
report_io.write_json = wrap("write_json", report_io.write_json)
Stop after one seam. A second extract needs a second green pin. Mixed extracts hide which move broke order.
Decision table for the next seam
| Trace observation | Safe next change | Unsafe next change |
|---|---|---|
| Two calls, stable names | Extract the second helper | Inline both helpers |
load_rows args include a path name |
Move the reader only | Change CSV splitting |
write_json keys stay dropped,kept
|
Move the writer only | Rename payload keys |
| A new third call appears | Update the golden trace first | Keep extracting |
| Call order flips under the same fixture | Revert the extract | “Fix forward” in the same PR |
Use the table in review. Do not debate taste. The trace row is the evidence.
What this pin does not prove
It does not prove numeric accuracy. It does not prove encoding, locale, or clock behavior. It does not prove concurrent runs.
It also does not prove the model understood the domain. The model only saw a JSON list. Domain bugs can keep the same order.
Huge traces are a smell. Wrap fewer collaborators. Pin the seam you plan to move.
Who should skip this workflow
Skip it when the module has no I/O. Pure functions need value pins, not call order. Skip it when the process is not repeatable.
Skip it when fixtures contain secrets. Redact before any model draft. Skip it when the team cannot run pytest locally or on a server.
Skip it for public API redesigns. Order pins protect behavior inside one repo. They do not replace a compatibility suite.
Close
Record one fixture. Pin the collaborator sequence. Extract one helper only after that pin stays green. Attach side_effects.json to the PR so review can see the order, not the vibes.
Top comments (0)