Module 5 · Architecture and maintainable code

Lesson 28 — Refactoring without breaking anything

Test-backed refactoring: changing the inside without moving the contract.

Published
In this lesson
  1. Exercise 1 — The net audit
  2. Exercise 2 — The guided extraction
  3. Exercise 3 — The bug in passing (the forbidden experiment)
  4. Exercise 4 — Feigning the contract
  5. Exercise 5 — The job extraction (29's prep)
  6. Submit

A real refactor on TicketFlow with a complete net. No solutions.md before submitting.

Exercise 1 — The net audit

  1. Before touching anything: what does your suite cover for reservar()? List by level (unit/integration/contract/E2E) which test exists TODAY and which gap you see (the locked transaction? the outbox after commit? the 409's problem+json?).
  2. Write the 2 missing characterization tests so you can refactor with a net (if you think none is missing: write one that fails if anyone moves the outbox before the commit).

Exercise 2 — The guided extraction

  1. Run the reservation service's refactor in 4 commits (validar_asientos / cobrar / ReservacionService / deprecating the old function). Suite green after each commit — paste the 4 hashes and the git log --oneline output.
  2. In each step note: how many tests were touched? The goal is that the characterization tests are NOT touched in any of them.
  3. Step 4 (deprecation): the old function delegates to the service with a DeprecationWarning. Who still calls it, and over how long does it die?

Exercise 3 — The bug in passing (the forbidden experiment)

  1. DIDACTIC PURPOSE: do it ONCE to watch it break. On the refactor branch, fix in passing the total rounding bug (08) and add a new field to the response. Run the suite: how many red tests? Which contract ones?
  2. Now do it right: git revert the mixed commit, the bug in a separate commit with the regression test first, the new field with its test and version note (14). The whole suite green in both commits separately.

Exercise 4 — Feigning the contract

  1. List YOUR frontend's 5 untouchables (status, problem types, JSON fields, IDs, parsed messages). Write the "contract guard" test: a battery that fails if any of them changes (you can reuse assert_problem from 26).
  2. Break ONE on purpose (rename seat_refs → seats in the response): which test catches it, and after how many seconds did you know?

Exercise 5 — The job extraction (29's prep)

  1. Extract expirar_reservas(clock) -> list[str] (expired refs) into the service, leaving the management command as a pure trigger. Test with FakeClock: 3 reservations, one expired, two valid → returns 1 ref and marks them EXPIRED with the right expires_at.
  2. Write the trigger's test: the command calls the service once and returns the count. The logic is NOT in the command — verify with a mock that if the service raises, the command propagates.

Submit

Paste the refactor's hashes, the bug-in-passing experiment (red), the contract guard and the job extraction. Next: Lesson 29 — Queues and background jobs (module 6).