Archived
为什么先测后改:_batch_zip_worker(347 行) 与 _podcast_zip_worker(295 行) 近乎 复制——连 _wait_if_paused 那 16 行都逐字相同(注释只差一行)。重复的代价是实打实 的:上一次修的 zip_valid「变量可能未绑定」缺陷就是**同型出现在这两处**。 但暂停/恢复是一条并发状态机,此前**零测试覆盖**,直接改不符合先测量后改动。 所以本 commit 分两步落地。 第一步(新增 tests/test_zip_workers.py,9 个用例)——行为级契约 断言只用「调用 worker 后世界里发生了什么」:任务终态、ZIP 内条目、账本守恒 (delivered / refunded)、暂停/恢复/取消后的余额。不引用任何内部函数名, 这样第二步重构时测试不必跟着改,才具备等价性验证能力。 - batch:正常完成 / 暂停→恢复 / 暂停→取消 / 全失败退费 / 循环中途取消 / 发布前一刻取消 - podcast:正常完成 / 暂停→恢复 / 全失败退费 - 脚手架一律用 reserve_and_open 真预扣(ledger_open 只记账不扣款, 会让「退款」凭空给余额加次数,守恒断言就失去意义) 写测试过程中暴露两处代码与注释不符,如实钉住而非粉饰: 1. **循环内取消并不会「交付已打包部分」**。zip_valid = True 位于 with zipfile 块**之后**,而 while 循环内的任何取消都以异常**穿过**该块 (zipfile.__exit__ 仍写出中央目录,但那行赋值不执行)→ zip_valid 恒为 False → delivered = ok_count if zip_valid else 0 恒为 0。源码注释所称的 「部分成功后取消照常交付(成果守恒)」只在发布前最后一个检查点可达, 已分别用两个用例把两条路径都钉住。 2. podcast 的 _wait_if_paused 与 batch 版本除 docstring 外完全一致。 第二步——抽取(只抽逐字相同、零语义差异的部分) - _taskcore._wait_if_paused(task, pause_ev, cancel_ev, done):done 写成参数 而非捕获闭包,使函数脱离两个 worker 各自的局部状态 - _taskcore._task_work_dir(task_id):任务专属目录约定(ZIP 与中间文件都落 tasks/<task_id>/,对根目录的缓存清扫免疫) - 两个 worker 各自的 16 行副本与 2 行 mkdir 删除,调用点改为显式传参 复杂度实测量(同口径,git HEAD 对比): 两个 worker 合计 行数 642 → 604(-38) 决策点 152 → 140(-12) 新增共享实现 _wait_if_paused 27 行/7 决策点 · _task_work_dir 11 行/1 **净行数基本持平(总量 -38 +38)**——重复代码抽取的收益从来不是行数变少, 而是**唯一真源**:以后改暂停语义只需改一处,不会再出现「同型缺陷出现在两地」。 验证:pytest 184 passed(175 → 184,+9)· ruff 0 · pyright 0 errors/0 warnings