DEV Community

Dakota Huang
Dakota Huang

Posted on

Freeze the Deadline Split Before You Extract It

Freeze the Deadline Split Before You Extract It

A cleanup should not change timeout math by accident. Pin the current split with characterization tests first. Extract one pure function only after those tests stay green.

This note is a proposed workflow for a messy handler. It is not a report from a private production system. Every figure below is an input in the fixture, not a measured result.

The conclusion in one line

Structure moves only after observed outputs are frozen. Behavior changes belong in a later, separate commit. That order is the whole method in this note.

Why this handler is risky

One method currently owns three separate jobs here. It reads a remaining deadline budget from the caller. It then splits that budget across the planned hops.

It also writes a log line for the plan. Those three jobs fail in different, separate ways. A simple rename can hide a math edit.

A test that only checks logs can miss a bad share. Separate those risks before you move any code. That split is the point of the pin.

What stays out of scope

Retry policy stays untouched in this cleanup pass. HTTP client code stays untouched in this pass too. Logging text stays inside the existing handler method.

A third downstream hop looks unfinished in the fixture. Leave that unfinished gap alone for this commit. File it as a follow-up, not as part of the extract.

Clock reads also stay outside this particular change. Pass remaining time in as an integer argument. Do not call a wall clock from the test.

The proposed fixture

The listing below is proposed teaching code only. It is not a decompiled private production module. Run it only inside a scratch project you control.

# deadline_handler.py
# Proposed fixture. Not a production trace.

class DeadlineHandler:
    def __init__(self, logger):
        self.logger = logger

    def plan(self, remaining_ms, hops, reserve_ms):
        usable = remaining_ms - reserve_ms
        if usable < 0:
            usable = 0
        if hops <= 0:
            first, second = 0, 0
        else:
            share = usable // hops
            first = share
            second = share if hops > 1 else 0
        self.logger(first, second, usable)
        return first, second
Enter fullscreen mode Exit fullscreen mode

Read the return shape before you trust it. The method always returns exactly two integer slots. Extra hops do not gain a third slot.

Any remainder from floor division is dropped here. That is current behavior, not a target design. A later patch may change that division rule.

Rows worth freezing

Use the table below as the behavior contract. Each row is an input triple and a return pair. Do not add rows that the function cannot emit.

remaining_ms hops reserve_ms first second why it matters
1000 2 100 450 450 even floor split of the usable pool
100 2 80 10 10 reserve leaves a small usable pool
40 2 50 0 0 reserve larger than remaining
50 1 10 40 0 single hop keeps the second slot at zero
30 3 10 6 6 third hop is not returned
0 2 0 0 0 already expired budget
-5 2 0 0 0 negative remaining clamps to zero
80 0 10 0 0 zero hops allocate nothing

Eight rows are enough for this narrow seam. More rows do not make the extract safer. They only help if they cover a new branch.

Check the three-hop row by hand before you commit the table. Usable time in that row is twenty milliseconds. Integer division by three yields six, and six again.

The dropped remainder in that row is two milliseconds. The missing third return slot is also real. Both facts belong in the pin, not in a silent fix.

Characterization tests

These tests describe what the code does today. They do not claim the split is fair. They fail if a refactor changes a return.

# tests/test_deadline_split.py
# Proposed tests. Run them before you trust a result.

import deadline_handler

ROWS = [
    (1000, 2, 100, 450, 450),
    (100, 2, 80, 10, 10),
    (40, 2, 50, 0, 0),
    (50, 1, 10, 40, 0),
    (30, 3, 10, 6, 6),
    (0, 2, 0, 0, 0),
    (-5, 2, 0, 0, 0),
    (80, 0, 10, 0, 0),
]

def test_plan_rows_stay_put():
    seen = []
    handler = deadline_handler.DeadlineHandler(
        logger=lambda *args: seen.append(args)
    )
    for remaining, hops, reserve, first, second in ROWS:
        assert handler.plan(remaining, hops, reserve) == (first, second)
    assert len(seen) == len(ROWS)
Enter fullscreen mode Exit fullscreen mode

The log assertion is intentionally narrow on purpose. It checks the call count, not message wording. Log wording can change without touching the math.

Keep that separation explicit inside the test file. A wording assertion would freeze the wrong seam. This patch is about budget integers only here.

How to read a red test

A red row means the returned pair moved. It does not mean the new return is better. Restore the old arithmetic before you edit an expected pair.

# Proposed failure shape. Not a captured CI log.
E   AssertionError: assert (7, 7) == (6, 6)
Enter fullscreen mode Exit fullscreen mode

That shape appears if floor division becomes rounding. Seven is not the frozen share for the three-hop row. Revert the changed division operator in the helper.

Keep the expected pair at six and six. Do not update the snapshot merely to hide the drift. A characterization file is a lock, not a wish list.

Update it only when a reviewed behavior change lands. Pair that change with a new review note. Do not hide it inside an extract diff.

Numbered workflow

Follow the numbered steps in the order below. Do not skip any of the review gates. A green extract with a red pin is not a pass.

  1. Copy the handler into a scratch package you can delete.
  2. Add the eight rows as characterization tests next.
  3. Run the suite on unchanged code and keep the output.
  4. Reject any expected value the function does not emit.
  5. Next, extract split_budget into one pure helper function.
  6. Keep the logging call inside the plan method.
  7. Re-run the same tests without editing expected pairs.
  8. Stop when the diff is only the move.
python -m pytest tests/test_deadline_split.py -q
python -m pytest tests/test_deadline_split.py -q --tb=short
git diff --stat -- deadline_handler.py tests/test_deadline_split.py
Enter fullscreen mode Exit fullscreen mode

A local green pass is still necessary here. It is not sufficient if your laptop hides a dependency. A clean checkout should repeat the same command.

Save the command text beside the code diff. Reviewers should see both of those artifacts together. A verbal claim that tests passed is weaker than the log.

Where a free model and a free server fit

Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode free model access can draft extra candidate rows from the fixture. Treat those generated drafts as untrusted review proposals.

Accept a row only when the current function emits that pair. The free server option can rerun the same pytest file in a clean checkout. Use that rerun to reduce hidden machine-local drift.

Do not treat that option as a sized machine. Do not treat it as a quota or a permanent offer. Those extra product details are not claimed here.

Confirm current availability with the operator before you depend on it. A model may suggest a fair third slot for the three-hop row. Refuse that suggestion inside this same cleanup commit.

A fairer split counts as a behavior change. The characterization pin exists to block that mix. Ask the model for missing input rows, not for new rules.

You still own every expected pair in the table. A draft row is not evidence by itself. Run the current function before you accept it.

The smallest safe change

Move only the arithmetic out of the handler. Leave the handler method as the public caller. Do not rename public methods in the same patch.

# Proposed extract. Returns should match the table.

def split_budget(remaining_ms, hops, reserve_ms):
    usable = remaining_ms - reserve_ms
    if usable < 0:
        usable = 0
    if hops <= 0:
        return 0, 0, usable
    share = usable // hops
    first = share
    second = share if hops > 1 else 0
    return first, second, usable


class DeadlineHandler:
    def __init__(self, logger):
        self.logger = logger

    def plan(self, remaining_ms, hops, reserve_ms):
        first, second, usable = split_budget(
            remaining_ms, hops, reserve_ms
        )
        self.logger(first, second, usable)
        return first, second
Enter fullscreen mode Exit fullscreen mode

Notice that the return of plan stays unchanged. The usable value stays an internal log input. Callers that read the pair should see the same integers.

Compare the two function bodies line by line. The arithmetic operators should match the old body. A new helper name is not permission to clean up the math.

Go and stop table

Use this gate before you open a review. A single stop row should block the merge. Record the blocking signal in the review note.

Signal Go Stop
Unchanged code passes all eight rows Extract the function Fix the test setup first
A draft row disagrees with a live return Drop that draft row Do not edit code to match the draft
Diff edits retry or client code Revert those hunks Split them into another change
Three-hop output looks unfair Record a follow-up issue Do not add a third return slot now
Log text is rewritten Revert the wording edit Keep this patch structural
Expected pairs change in the same diff Reject the combined diff Separate the behavior change

What the tests do not prove

Green tests do not prove the split is correct for users. They only prove the extract preserved today's outputs. Floor division still drops the unused integer remainder.

A negative remaining input still clamps to zero. Those choices can be wrong for your product. Correctness work needs a new table and a new review.

No timing or latency result is included here. This workflow does not benchmark models or servers. If you need latency data, design a separate harness with published inputs.

Who should skip this approach

Skip it when the patch must change behavior now. A frozen behavior pin will fight that goal. Skip it when log lines may contain secrets.

Secret redaction needs its own separate freeze first. Skip it when nobody can review generated rows. An unread generated draft is not a contract.

Skip it in a hotfix window with no time to rerun the suite. Skip it if the handler cannot run without live network calls. This fixture assumes injected time and an injected logger.

Review checklist

Check these four items before you approve the patch. The same tests pass on the old code. The same tests pass after the pure extract.

The diff does not touch retries or clients. The disclosure and the follow-up note are both visible. Then ask for a human review of the draft.

A clean command log is the review artifact. A model summary is not a substitute for that log. If a free MonkeyCode server is already available, replay the suite there.

Keep the same rows and the same command. Do not add a fairness fix during that replay. The replay only checks that the pin still holds.

Top comments (0)