Skip to content

refactor(recorder): wydziel SelectDriver z god-klasy Recorder (faza 4)#66

Merged
mpasternak merged 1 commit into
mainfrom
cleanup/faza-4-recorder
Jul 22, 2026
Merged

refactor(recorder): wydziel SelectDriver z god-klasy Recorder (faza 4)#66
mpasternak merged 1 commit into
mainfrom
cleanup/faza-4-recorder

Conversation

@mpasternak

Copy link
Copy Markdown
Member

recorder.py 1329 → 394. Plik nie miał ani jednego naruszenia complexity — problem był wyłącznie rozmiarem: 23 z ~37 metod klasy to napędzanie <select> (~72% ciała klasy), przy zależności ściśle jednokierunkowej.

moduł linie
recorder.py 394 rdzeń + 2 delegatory + re-eksporty
select/__init__.py 56 fasada
select/driver.py 560 choreografia — jedyna część stanowa
select/errors.py 318 SelectDriveError, 2 powody, 9 konstruktorów
select/probe.py 263 8 bezstanowych pytań do strony
select/_js.py 206 dziesięć stałych ze skryptami
overlay/geometry.py +21 _center_ofcenter_of, zrywa cykl importów

Warstwy ściśle jednokierunkowe: _js ← errors ← probe ← driver ← recorder.py

Zostaje jeden plik kodu ponad 600 linii w całym repo: render/_run.py (941) — jawny dług Fazy 3.

Kształt: wąskie zależności, nie wsteczna referencja

SelectDriver(*, page, frame, approach: Approach, animated: bool, open_hold_ms: float)

animated: bool zamiast overlay, bo jedyne pytanie brzmiało is None — obiekt nigdy nie był dotykany. approach jako Protocol, nie goły Callable, żeby dwa znaczące argumenty nazwane (ripple, click_sound) były w sygnaturze.

Odrzucone: wsteczna referencja do Recorder (god-class z jednym przeskokiem, znów ukrywa prawdziwą zależność) i mixin (plik się skraca, klasa nie).

Dwie niezależne blokady na tę samą pułapkę

Test podmienia rec._approach na instancji, żeby próbkować geometrię listy po obu stronach każdego przesunięcia kursora. Driver budowany jest per wywołanie, więc self._approach jest już wtedy szpiegiem — i metoda związana też by zadziałała. Ale lambda jest zamkiem, który przeżyje wyniesienie budowy do __init__.

Zweryfikowane mutacją: wyniesienie + metoda związana → test pada (assert 0 == 2); wyniesienie + lambda → przechodzi.

Dowód ciszy: mutacja zamrożonego limitu

Konstruktory błędów są synchroniczne i biorą described: str — to zrywa cykl probe↔errors. Limity docierają jako limit_ms=, nigdy importem.

Mutacja zamrażająca OPTION_WAIT_MS w module błędów: driver czekał 400 ms, komunikat mówił „(limit 5000 ms)", a test nadal przechodził. Dokładnie to ciche kłamstwo, przed którym chroni jawne przekazywanie limitu.

Czego analiza nie przewidziała

Płaskie moduły siostrzane są niekompatybilne ze strażnikiem szwów — guarduje on pakiet. Zbudowanie na guidebot_recorder.recorder zlewa compile/run.py i render/_run.py do jednego klucza; na samym recorder.py czyni sześć podmian atrybutu klasy naruszeniami, bo strażnik nie ma modelu podmiany na klasie. Stąd select/ jako pakiet z fasadą, zgodnie z precedensem mux/render/compile.

Ponadto: dziewięć konstruktorów błędów zamiast siedmiu (dwa miejsca podnosiły wyjątek w miejscu), SelectReveal i PointResult też wymagają re-eksportu, istniał trzeci szew (require_option), oraz cztery nieaktualne odwołania w niepowiązanych plikach.

Testy

1496 unit + 51 integration passed, 1 skipped — zweryfikowane niezależnie.

Czas testów jednostkowych spadł o 163 s — żaden patch timeoutu nie został źle przecelowany (zerwany kosztowałby +28 s).

Kolejność approach → hook reveal → click → confirm potwierdzona osobnym przebiegiem 50 testów integracyjnych i guide.

ruff check czysto · preview blank-lines czysto · C901 bez zmian

🤖 Generated with Claude Code

https://claude.ai/code/session_0134rSCftVxsMVL8sngceT7A

recorder.py 1329 → 394. Plik NIE miał ani jednego naruszenia complexity —
problem był wyłącznie rozmiarem: 23 z ~37 metod klasy to napędzanie <select>,
czyli ~72% ciała klasy, przy zależności ściśle jednokierunkowej.

  recorder.py            394  rdzeń + 2 delegatory + re-eksporty
  select/__init__.py      56  fasada
  select/driver.py       560  choreografia, jedyna część stanowa
  select/errors.py       318  SelectDriveError, 2 powody, 9 konstruktorów
  select/probe.py        263  8 bezstanowych pytań do strony
  select/_js.py          206  dziesięć stałych ze skryptami
  overlay/geometry.py    +21  _center_of → center_of (zrywa cykl importów)

Warstwy ściśle jednokierunkowe: _js ← errors ← probe ← driver ← recorder.py

KSZTAŁT: wąskie zależności, nie wsteczna referencja.
SelectDriver(*, page, frame, approach, animated: bool, open_hold_ms)
`animated` zamiast `overlay`, bo jedyne pytanie brzmiało `is None`.
`approach` jako Protocol, nie goły Callable, żeby dwa znaczące argumenty
nazwane (ripple, click_sound) były w sygnaturze.

DWIE BLOKADY NA TĘ SAMĄ PUŁAPKĘ. Test podmienia rec._approach NA INSTANCJI,
żeby próbkować geometrię listy po obu stronach każdego przesunięcia kursora.
Driver budowany JEST PER WYWOŁANIE, więc self._approach jest już szpiegiem —
i wtedy metoda związana też działa. Ale lambda jest zamkiem, który przeżyje
wyniesienie budowy do __init__. Zweryfikowane mutacją: wyniesienie + metoda
związana → test pada (assert 0 == 2); wyniesienie + lambda → przechodzi.
Obie blokady udokumentowane.

Konstruktory błędów są SYNCHRONICZNE i biorą `described: str` — to zrywa cykl
probe↔errors (require_option podnosi błędy, więc errors nie może wołać
describe). Limity czasowe docierają jako limit_ms=, nigdy importem.

DOWÓD: mutacja zamrażająca OPTION_WAIT_MS w module błędów — driver czekał
400 ms, a komunikat mówił "(limit 5000 ms)" i test NADAL PRZECHODZIŁ.
Dokładnie to ciche kłamstwo, przed którym chroni przekazywanie limitu jawnie.

CZEGO ANALIZA NIE PRZEWIDZIAŁA
- Płaskie moduły siostrzane są niekompatybilne ze strażnikiem szwów: guarduje
  PAKIET. Zbudowanie go na guidebot_recorder.recorder zlewa compile/run.py
  i render/_run.py do jednego klucza; na samym recorder.py — czyni sześć
  podmian atrybutu KLASY naruszeniami, bo strażnik nie ma modelu podmiany
  na klasie. Stąd select/ jako pakiet z fasadą, zgodnie z precedensem
  mux/render/compile.
- Dziewięć konstruktorów błędów, nie siedem: dwa miejsca podnosiły
  SelectDriveError w miejscu.
- SelectReveal i PointResult też wymagają re-eksportu (guide/capture.py).
- Istniał trzeci szew: require_option, podmieniany na instancji.
- Cztery nieaktualne odwołania w niepowiązanych plikach (selects.py,
  visibility.py, selects.js, validate.py).

Suite: 1496 unit + 51 integration passed, 1 skipped. Czas testów jednostkowych
SPADŁ o 163 s — żaden patch timeoutu nie został źle przecelowany (zerwany
kosztowałby +28 s). Kolejność approach → hook reveal → click → confirm
potwierdzona osobnym przebiegiem 50 testów integracyjnych i guide.

Zostaje JEDEN plik kodu ponad 600: render/_run.py (941) — dług fazy 3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0134rSCftVxsMVL8sngceT7A
@mpasternak
mpasternak merged commit d28a96f into main Jul 22, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant