Skip to content

epic: атомарный collector.py — разбить перегруженные функции и области обработки ошибок #40

Description

@axisrow

Проблема

За время работы над #38 (PR #39) одна и та же правка прошла четыре итерации ревью, и три из них нашли новый дефект в том же куске кода:

  1. except DownloadTimeoutError глотал и «скачалось больше одного CSV», и настоящие сбои ретрая → введён отдельный DownloadNoNewPathError;
  2. при перезаписи файла на месте старый распарсенный dataset соединялся с новым raw-файлом → parquet и manifest расходились с CSV → добавлено сохранение копии до ретрая;
  3. область очистки временной копии начиналась после её создания → при падении copy2 копия оставалась в output_root → try расширен на создание файла.

Каждый раз код был локально верным, а дефект был на границе области действия: проверка или защита стояла рядом с настоящим местом риска, а не на нём. Это не серия случайных промахов, а свойство формы кода.

Измерения

src/wordstat/collector.py — 1420 строк, 31 функция.

Функция Строк try Ветвлений Вложенность
_collect_one 324 4 26 8
collect_many 157 4 7 7
_assert_contiguous_dynamics_rows 102 1 10 5
_download_current_view 99 0 4 4
_select_view 47 1 4 7

Вложенные try в _collect_one (строки 403–726):

for view in enumerate(pending_views)        :564-706
  try (AuthenticationRequiredError, Exception)   :565-706  142 строки
    try (Exception)                              :583-669   87 строк
      try ... finally                            :600-633   34 строки
        try (DownloadNoNewPathError)             :609-630   22 строки

Четыре уровня обработки ошибок в одной функции. Ни одна проверка на такой глубине не читается целиком — именно поэтому три ревью подряд находили новое.

Цель

Разбить крупные функции collector.py на атомарные единицы с явными результатами и одним владельцем жизненного цикла у каждого ресурса. Поведение сохранить полностью: 180 тестов должны остаться зелёными без правок, кроме случаев, где тест обращается к приватному имени, которое переехало.

Подзадачи

1. Ретрай пустого экспорта (первоочередное, блокирует PR #39)
Вынести в отдельный helper, возвращающий явный результат: success(source, dataset) / no_new_path / проброс прочих ошибок. Жизненный цикл временной копии — один внешний try/finally, охватывающий создание, копирование, использование и удаление. Ни одной ветки, где source и dataset происходят из разных файлов.

2. _collect_one (324 → цель ≤ 80 строк)
Выделить: подготовку вида, скачивание с ретраем (п. 1), запись датасета и финализацию raw, обработку сбоя одного вида. Каждый вложенный try должен стать try внутри своей маленькой функции.

3. collect_many (157 строк, 4 try)
Отделить жизненный цикл общего временного каталога загрузок от обхода фраз и от обработки ошибок отдельной фразы.

4. _assert_contiguous_dynamics_rows (102 строки, 10 ветвлений)
Разделить на проверку вхождения в окно и проверку непрерывности; они сейчас переплетены.

5. _download_current_view (99 строк)
Отделить снапшот-диффинг от поллинга и от проверки «файл не убежал за пределы каталога».

Правила

  • Чистый рефакторинг: никаких изменений поведения, никаких новых возможностей.
  • После каждой подзадачи — все тесты зелёные, ruff check . чист.
  • Каждая подзадача — отдельный PR, чтобы ревью было обозримым.
  • Все инварианты из комментариев (порядок «скачать → распарсить → записать parquet → убрать raw», зона безопасности finalize_raw, fail-closed для dynamics) сохранить и не растерять по дороге — они документируют реальные баги.
  • Мутационная проверка на каждый PR, включая границы областей try/finally, а не только ветвления.

Порядок

Подзадача 1 делается первой и вливается в PR #39 — он сейчас на ней и заблокирован. Остальные — после.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    epicТребует декомпозиции

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions