代码职责未拆分、时区处理错误、测试非确定性、依赖管理越界是四大典型技术债。应拆分函数职责、统一使用utc时间、冻结测试时间与随机值、锁死依赖版本并避免私有模块导入。

函数里塞了太多 if 分支,改一个逻辑要翻十页
这不是代码“多”,是职责没拆开。比如一个叫 process_order 的函数里,同时处理微信支付、支付宝回调、虚拟卡发码、库存扣减、短信通知……还混着不同状态(待支付/已退款/已发货)的判断。每次加个新渠道,就得在几十行 if/elif 里插一行,稍不留神就漏掉某个状态下的异常分支。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 把每个支付渠道抽成独立函数,统一接收
order和event,返回True或抛出明确异常(如PaymentFailedError) - 用字典映射代替长
if链:PAYMENT_HANDLERS = {"wechat": handle_wechat, "alipay": handle_alipay} - 别让一个函数既做校验、又做持久化、还调第三方——校验放
validate_order(),落库走save_to_db(),发消息交给notify_service.send()
datetime.now() 直接写进数据库字段,默认时区错得离谱
本地开发看着没问题,一上服务器就发现订单创建时间比实际晚 8 小时,或者凌晨跑的定时任务总在下午触发。根本原因是 datetime.now() 返回的是系统本地时区时间,而 PostgreSQL/MySQL 默认按 UTC 存储 timestamp without time zone,Python 写进去时没带时区信息,数据库就当它是 UTC 处理了。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 永远用
datetime.now(timezone.utc)替代datetime.now() - ORM 层(如 SQLAlchemy)配置
timezone=True,确保字段类型是timestamp with time zone - 读数据后别直接
.strftime("%Y-%m-%d"),先.astimezone(local_tz)转成本地时区再格式化 - 测试时在 CI 环境显式设
TZ=UTC,避免依赖宿主机时区
测试只 mock 了 HTTP 请求,没 mock 时间和随机值
单元测试偶尔失败,报错像 AssertionError: '2024-05-12' != '2024-05-13',或者生成的 token 每次都不一样导致断言失败。问题不在逻辑,而在测试本身依赖了真实时间或随机数生成器。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
freezegun.freeze_time("2024-01-01")锁死时间,所有datetime.now()、time.time()都返回固定值 - 对
random.choice()、uuid.uuid4()这类非确定性调用,用unittest.mock.patch替换为固定返回值 - 别在测试 setup 里调
datetime.utcnow()算“当前时间”再传给被测函数——这会让测试随时间漂移 - CI 流水线里禁用系统时间同步(
systemd-timesyncd),防止测试中途时间跳变
依赖版本锁死了,但 setup.py 里还写 requests>=2.20
pip install -r requirements.txt 在本地能跑,CI 构建却失败,报错 ModuleNotFoundError: No module named 'urllib3.util.retry'。查下来是 requests 2.31 升级后内部重构了 retry 模块路径,而你项目里某处硬导入了 urllib3.util.retry.Retry —— 这属于越界使用了 requests 的私有依赖。
实操建议:
立即学习“Python免费学习笔记(深入)”;
-
requirements.txt必须用==锁死主版本(如requests==2.31.0),不能只写>= - 禁止直接 import 第三方库的子模块(如
from urllib3.util.retry import Retry),一律走 requests 官方接口(requests.adapters.Retry) - 用
pipdeptree --reverse --packages requests查谁在偷偷依赖 urllib3,再看是否真需要 - 升级前跑
pip install --dry-run -r requirements.txt,提前暴露冲突
技术债最麻烦的不是它存在,而是它藏在“能跑就行”的缝隙里——比如那个每次部署都得手动改两行的 settings.py,或者注释写着“临时兼容老接口”的函数,三年没动过,也没人记得它到底兼容了谁。










