P20第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

Python 服务复盘

Python 服务复盘 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

课程收尾不学新东西,学「审自己的代码」:用五个维度审查 P19 的服务,找到并修复一个有实际影响的问题,量化修复前后的差异,沉淀一张可复用的复盘清单。

问题场景

P19 跑通了,但你心里清楚:有些函数是「能跑就行」糊出来的。复盘和刷代码风格检查不一样——目标不是逼洁癖,是找到那些正在真实消耗你的问题:每次改动都要多读三个文件、某个接口越用越慢、某个报错永远查不出原因。

核心解释

五个维度按「出问题概率 × 排查成本」排序,逐个过:

  1. 模块边界:router 文件里有没有 SQL?db.py 里有没有 HTTP 概念?业务逻辑在不在错误的一层?判断法:随便指一个函数,看它的 import 里混了几种不相干的职责。
  2. 数据模型:边界处是否都有 Pydantic 模型把关?内部传递还有没有裸 dict?StudyRecord 定义了却没人用,等于没定义。
  3. 异步使用:每个 async def 里搜 time.sleep、requests、同步 sqlite 长查询——P10 的病在真实代码里最容易复发;反过来,纯 CPU 任务也别伪装成 async。
  4. 异常:搜裸 except: 和 except Exception: pass;错误信息能不能定位到「哪个文件、什么输入、为什么」?P04 的分界线(可恢复的记下来继续,不可恢复的带上下文抛出)是否守住了。
  5. 测试:pytest 覆盖了哪些失败路径?最近一次手改手测的场景,为什么不固化成测试?

发现问题的目的只有一个:修掉一个,验证它值得修。例子是本站和你的服务里都真实的模式——统计接口的 N+1 查询:

# 修前:每门课一次查询(N+1)
def per_course_slow(conn):
    codes = [r["course_code"] for r in conn.execute("SELECT DISTINCT course_code FROM study_record")]
    return [{"course": c,
             "minutes": conn.execute("SELECT SUM(minutes) FROM study_record WHERE course_code=?", (c,)).fetchone()[0]}
            for c in codes]

# 修后:一次 GROUP BY
def per_course_fast(conn):
    rows = conn.execute(
        "SELECT course_code, SUM(minutes) AS minutes FROM study_record GROUP BY course_code"
    ).fetchall()
    return [dict(r) for r in rows]

修复的完整闭环是量化:修前用 1000 条记录压一下 time.perf_counter,修后再压一次,两个数字写进提交说明。「感觉快了」不算修复完成,0.42s → 0.01s 才算。

示例拆解

把五维审查跑成一遍可重复的机械操作:

grep -n "except:" *.py                    # 维度4:裸 except
grep -n "time.sleep\|requests\." *.py     # 维度3:async 里的阻塞
pytest -q                                 # 维度5:绿不绿、几秒
python -c "
import time, sqlite3
from stats import per_course_slow, per_course_fast
conn = sqlite3.connect('bench.db'); setup_1000_rows(conn)
t0 = time.perf_counter(); per_course_slow(conn); t1 = time.perf_counter()
per_course_fast(conn); t2 = time.perf_counter()
print(f'N+1: {t1-t0:.3f}s  GROUP BY: {t2-t1:.3f}s')"
# N+1: 0.412s  GROUP BY: 0.009s —— 数字进提交说明

复盘的最后一步是沉淀:把这次发现的问题写进一张 checklist(模块边界 3 问、异步 3 问、异常 3 问……),下个 Python 项目开工前先过一遍。审查能力不来自记性,来自清单。

动手任务

  1. 用五维清单完整审一遍 P19 的代码,把发现的问题按「有实际影响 / 纯洁癖」分成两列。
  2. 从第一列挑一个修复并量化:修前修后各测一次(性能用计时、健壮性用测试用例数),数字写进提交信息。
  3. 给修复补一个 pytest 用例,证明问题不会回来(例如 N+1 修复后断言 sqlite3 的查询计数只发了一次 SQL)。

常见错误

  • 复盘变成重写:觉得哪都看不顺眼推倒重来,改动面越大风险越大;一次只修一个有量化收益的问题。
  • 修「纯洁癖」优先:变量改名、换引号风格这类零影响项排在功能性问题前面;先修消耗,再谈品味。
  • 只修不测:问题修了但没有测试锁住,三个月后同样写法又会回来;每个修复配一个用例。
  • 凭感觉说变快了:没有修前基线的性能声称不可信;先测再改,改完再测。

检查与复盘

自查清单:

  • 五个维度各能指出自己代码里「过关」和「不过关」的具体位置
  • 本次修复有修前/修后两个可复现的数字
  • 复盘清单已落盘成文件,下个项目可以直接复用

参考资料

检查题共 1 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1故障定位较难

bug 现场:学习记录 API 上线后用户反馈两件事——「偶尔保存的学习记录丢了」「列表页偶尔特别慢甚至超时」。相关代码如下,诊断与修法都正确的是?

# 1) 保存接口(sqlite3 同步驱动)
@app.post('/records')
async def save_record(rec: Record):
    conn = sqlite3.connect('app.db')
    conn.execute('INSERT INTO records(...) VALUES (...)', (...))
    # 没有 commit,直接 return

# 2) 列表接口
@app.get('/records')
async def list_records():
    rows = db.execute('SELECT * FROM records ORDER BY id').fetchall()
    return rows

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...