Write down the contract before changing the function
Choose one repeated branch or other specific source of complexity whose inputs and effects you can inspect. Keep a copy of the original and run its existing tests first. If a test already fails, record the failure and establish the intended result with the owner or specification before treating the old output as correct. Fixing a defect and simplifying the structure need separate checks. The debugging guide covers the defect-fixing step.
Our example calculates a delivery fee. It accepts subtotal: Decimal, member: bool, country: str, and weekend: bool, in that order. Successful calls return Decimal. The base-fee rules are:
| Country and membership | Free at subtotal | Base fee below threshold |
|---|---|---|
| US member | Decimal("50") | Decimal("5") |
| US non-member | Decimal("100") | Decimal("8") |
| Canada, either membership value | Decimal("120") | Decimal("12") |
| Great Britain, either membership value | Decimal("150") | Decimal("15") |
At the threshold, the base fee is zero. A weekend adds Decimal("4") afterward, including when delivery is otherwise free. Any other country raises ValueError("unsupported country"). These details matter more than the number of lines in the function: moving the surcharge inside a paid-fee branch, for example, would change the fee for a US member with a subtotal of exactly 50 on a weekend from 4 to 0.
Save the original as original.py
This is the synthetic function submitted in the August 19, 2026 ChatGPT test. It has no external calls or mutable shared state, which makes value-by-value comparison a useful first check.
from decimal import Decimal
def delivery_fee(subtotal: Decimal, member: bool, country: str, weekend: bool) -> Decimal:
if country not in {"US", "CA", "GB"}:
raise ValueError("unsupported country")
if country == "US":
if member:
if subtotal >= Decimal("50"):
fee = Decimal("0")
else:
fee = Decimal("5")
else:
if subtotal >= Decimal("100"):
fee = Decimal("0")
else:
fee = Decimal("8")
elif country == "CA":
if subtotal >= Decimal("120"):
fee = Decimal("0")
else:
fee = Decimal("12")
else:
if subtotal >= Decimal("150"):
fee = Decimal("0")
else:
fee = Decimal("15")
if weekend:
fee += Decimal("4")
return fee
Ask for one drop-in replacement
Paste only synthetic or approved code into ChatGPT. Remove secrets and private data before sharing any excerpt; if repository policy does not permit sharing the code, stop or use an approved environment. The upload-safety guide covers that decision. Give ChatGPT the contract above and require a rule table before the rewrite. If the table omits a threshold, exception, return type, or weekend interaction, correct it before accepting code. This prompt is an adaptation of the retained test prompt; the exact submitted prompt is included in the practice kit.
I want to refactor this Python function without changing behavior.
Keep the exact four-argument signature and Decimal return type. Preserve
the four threshold/fee rows, the >= boundary, ValueError("unsupported
country"), and the weekend surcharge even when the base fee is zero.
Do not add validation, dependencies, logging, or features.
First restate the rules as a compact table. Then give one complete drop-in
replacement function, explain the repetition removed, and list the inputs
I should compare locally. Do not claim equivalence from the prompt alone.
[PASTE THE CONTENTS OF original.py]
The retained response restated the four rows before returning a candidate. The screenshot records that table; it is useful for checking the rules, but the complete response transcript was not retained.

Inspect the proposed structure
The retained candidate chooses a threshold and paid fee for the country, then makes one shared subtotal comparison. Save it as refactored.py. Compare the two versions before running tests: the public arguments, exception, and weekend addition are still present.
from decimal import Decimal
def delivery_fee(subtotal: Decimal, member: bool, country: str, weekend: bool) -> Decimal:
if country not in {"US", "CA", "GB"}:
raise ValueError("unsupported country")
if country == "US":
threshold, paid_fee = (
(Decimal("50"), Decimal("5"))
if member
else (Decimal("100"), Decimal("8"))
)
elif country == "CA":
threshold, paid_fee = Decimal("120"), Decimal("12")
else:
threshold, paid_fee = Decimal("150"), Decimal("15")
fee = Decimal("0") if subtotal >= threshold else paid_fee
if weekend:
fee += Decimal("4")
return fee
The repeated subtotal comparison now appears once, after the country-specific threshold and fee are chosen. The original has nine if statements; this version has four and uses two inline conditional expressions. The optional show_structure.py script in the practice kit reports those counts separately from the behavior tests. A lower count does not prove readability or equivalence. Decide whether threshold and paid_fee make the pricing rules easier to follow in your code review.
Run the comparison and independent contract checks
Put this file beside both implementations as test_equivalence.py. The grid compares the implementations for 3 countries × 2 membership values × 2 weekend values × 9 selected subtotals. Fixed-answer cases also check that the original and candidate match the written fee rules; otherwise, two implementations could agree on the same wrong answer. The third test checks the signature and exact exception. Syntax counts are excluded from this pass/fail gate because an equally correct rewrite may use a different structure.
import inspect
import unittest
from decimal import Decimal
import original
import refactored
class RefactorEquivalenceTests(unittest.TestCase):
def test_selected_input_grid(self):
subtotals = [
Decimal("0"), Decimal("49.99"), Decimal("50"),
Decimal("99.99"), Decimal("100"), Decimal("119.99"),
Decimal("120"), Decimal("149.99"), Decimal("150"),
]
checked = 0
for country in ("US", "CA", "GB"):
for member in (False, True):
for weekend in (False, True):
for subtotal in subtotals:
with self.subTest(country=country, member=member,
weekend=weekend, subtotal=subtotal):
expected = original.delivery_fee(subtotal, member, country, weekend)
actual = refactored.delivery_fee(subtotal, member, country, weekend)
self.assertEqual(actual, expected)
self.assertIs(type(actual), Decimal)
checked += 1
self.assertEqual(checked, 108)
def test_known_contract_outputs(self):
cases = [
(Decimal("49.99"), True, "US", False, Decimal("5")),
(Decimal("50"), True, "US", False, Decimal("0")),
(Decimal("50"), True, "US", True, Decimal("4")),
(Decimal("99.99"), False, "US", False, Decimal("8")),
(Decimal("100"), False, "US", False, Decimal("0")),
(Decimal("119.99"), False, "CA", False, Decimal("12")),
(Decimal("120"), False, "CA", False, Decimal("0")),
(Decimal("149.99"), True, "GB", False, Decimal("15")),
(Decimal("150"), True, "GB", False, Decimal("0")),
(Decimal("150"), True, "GB", True, Decimal("4")),
]
for module in (original, refactored):
for subtotal, member, country, weekend, expected in cases:
with self.subTest(module=module.__name__, country=country,
subtotal=subtotal, member=member,
weekend=weekend):
self.assertEqual(
module.delivery_fee(subtotal, member, country, weekend),
expected,
)
def test_signature_and_exception(self):
self.assertEqual(
inspect.signature(refactored.delivery_fee),
inspect.signature(original.delivery_fee),
)
for module in (original, refactored):
with self.subTest(module=module.__name__):
with self.assertRaises(ValueError) as caught:
module.delivery_fee(Decimal("50"), True, "FR", False)
self.assertIs(type(caught.exception), ValueError)
self.assertEqual(str(caught.exception), "unsupported country")
if __name__ == "__main__":
unittest.main(verbosity=2)
From that directory, run python3 -m unittest -v test_equivalence.py. On Python 3.13.2, all three behavior test methods passed in the September 27, 2026 local rerun. Download the complete practice kit if you prefer to run the same files without copying the code blocks.
One especially useful check is the interaction at the free-delivery boundary: US member, subtotal Decimal("50"), weekend True must return Decimal("4"). The grid also checks values immediately below each listed threshold and at each threshold; it does not sample every possible Decimal. Passing it does not establish what should happen for unusual values such as NaN, nor does it validate a caller or production workload. Add cases for inputs your actual callers can supply.
Reject a candidate when a check fails
When a comparison fails, inspect the difference before changing the test. The practice kit includes a deliberately faulty rewrite that adds the weekend charge only when the base fee is nonzero. We wrote this defect to demonstrate rejection; it was not part of the recorded ChatGPT response. Run python3 demonstrate_rejected_candidate.py to reproduce this result:
input: subtotal=50, member=True, country=US, weekend=True
expected: 4; candidate: 0; decision: REJECT
First confirm that 4 comes from the written rule, not just from the old code. Then find the changed condition in the candidate and give ChatGPT the smallest failing input, the expected and actual results, and the relevant diff. Keep the contract and test fixed while it proposes a correction. For this case, a focused follow-up would be:
The refactor changed a documented result. For subtotal=Decimal("50"),
member=True, country="US", weekend=True, the required fee is Decimal("4")
but the candidate returns Decimal("0"). The weekend charge applies even
when the base fee is zero.
Identify the condition that changed this result. Return the smallest
correction to the candidate function. Do not change the original function,
the contract, or the test to make the failure disappear. Name the other
boundary and exception checks I should rerun.
[PASTE THE CANDIDATE FUNCTION AND RELEVANT DIFF]
Review the new diff yourself, rerun the failing case, then rerun the full local suite. If the revision changes another fee or exception, reject it too. The same loop applies when a candidate changes an error message or duplicates an external call: record the observable mismatch before asking for another rewrite.
Carry the method into a real repository
A pasted function cannot show ChatGPT every caller, configuration value, or integration boundary in your project. Before accepting a real patch, use this narrower sequence:
- Save a recoverable version in Git and run the relevant tests before editing. Record existing failures separately.
- Search references to the function and note positional arguments, defaults, imports, return-value consumers, and any code that depends on its exceptions or side effects. If ordinary ChatGPT cannot see those files, give it only approved excerpts and identify the rest as unknown.
- Name the repeated logic to simplify and the files allowed to change. Ask ChatGPT for one bounded diff, its assumptions, and the affected tests. Keep a dependency upgrade or behavior fix out of this pass. For a mechanical rename or extraction supported by your editor, its language-aware refactoring command is often the simpler choice.
- Inspect the entire diff, run the focused tests and the tests for affected callers, then run the project's applicable type check, lint, build, and integration checks. Exercise representative valid and invalid inputs. Report a check as unverified if you could not run it.
This repository sequence follows the same contract-first principle as the small example, but it is guidance for readers' projects, not a claim that the August ChatGPT run exercised a multi-file application. See the VS Code safe-refactoring guide for a worked caller-compatibility workflow.
Check returned values, calls, and state
The delivery-fee function has no external effects. In a second controlled example in the practice kit, both notification functions return receipt-A17, but one sends the same message twice. This faulty candidate was also written for the lesson. Run python3 demonstrate_side_effect.py to see the return values and unittest.mock call records:
original: return=receipt-A17, calls=[call.send('A17', 'receipt-A17')]
candidate: return=receipt-A17, calls=[call.send('A17', 'receipt-A17'), call.send('A17', 'receipt-A17')]
decision: REJECT candidate because it sends twice
A return-value comparison would miss that duplicate. Assert the call count and arguments, then test the failure path that matters to your own service. The mock checks what your code asked the mailer to do; it cannot prove that a real delivery service behaves the same way. Other functions need different observable checks:
| If the function… | Check before and after | Reject the candidate when… |
|---|---|---|
| Updates a list, cache, or database | Compare the returned value and the final state. Record the order and number of writes, including on an exception path. | The same value is returned after an extra write, a reordered update, or a partial update on failure. |
| Calls an API, sends a message, or runs async work | Replace the external dependency in tests and inspect arguments, call count, order, error propagation, and cancellation where relevant. Python's mock library supports call assertions. | A request is duplicated, moved before validation, silently swallowed on failure, or left running after cancellation. |
| Rounds money or compares dates | Keep the numeric type, rounding rule, timezone assumptions, and just-below/at/above boundary cases in independent assertions. | The new code switches to float, moves a rounding step, or changes which side owns a boundary. |
| Processes large inputs | Run representative data sizes and measure the resource or latency limit that matters to the caller, alongside correctness tests. | A shorter implementation loads all records into memory or adds unacceptable repeated work. |
The table describes checks to adapt to readers' own code, not additional outcomes measured in the August 19 ChatGPT run. Keep the old implementation available until the replacement passes the relevant behavior and caller checks. If the baseline is uncertain or an effect cannot be observed in tests, state that limit in the review instead of calling the refactor safe.
