跳到正文

容易混淆的问题 ​

这些概念名字相近,误用不会立刻报错,只会在某次 review 或线上事故里暴露。下面按检查链、测试、正则、重构分组,每组先给对照表,再补判断依据。

格式化与静态检查 ​

格式化器只重写 token 之间的空白与标点,输出确定、可逆;linter 读控制流和数据流,输出是「可能有问题」,允许误报。前者全自动,后者只有一部分能自动修。

判断方法只有一条:这条建议能不能用「改完一定等价」来证明。能,就是格式化或安全修复;不能,就只能报告,由人决定。

对照区别判断方法
格式化 vs lint排版 vs 语义模式问「改完是否等价」
--check只返回状态码,不改文件CI 用
--write直接写回文件本地用
noqa单行或单文件豁免补上具体规则编号与理由
ignore全局关闭一条规则只用于确认不适用的规则
bash
ruff format --check .   # CI:不合规就红
ruff format .           # 本地:直接改
ruff check .            # 只报告
ruff check --fix .      # 修可证明等价的部分

--check 与 --fix 必须读同一份配置。配置漂移时,本地全绿、CI 全红,排查方向会被引到错误的地方。

提交钩子与 CI ​

钩子跑在本地,跟着工作副本走,能被 --no-verify 跳过,也会因为新机器没装依赖而静默失效。CI 跑在干净容器里,每次从零开始,是唯一无法绕过的关卡。

所以钩子负责把反馈提前,CI 负责裁决。把整套测试塞进钩子,结果是人开始习惯性跳过它。

对照运行位置能否绕过适合
pre-commit本地能,且可卸载秒级:格式化、lint
pre-push本地能分钟级:测试
CI服务端不能全量验证与构建

测试 ​

pytest -k 匹配的是用例名,表达式支持 and、or、not;go test -run 匹配的是测试函数名,用的是正则,不加锚点时按子串匹配。

bash
pytest -k "login and not slow"
go test -run 'TestLogin|TestSignup'

覆盖率是「执行过的行」占比,和断言质量无关。100% 覆盖的测试可能一条断言都没写,把断言删掉覆盖率也不会掉。

对照匹配或衡量对象常见误用
pytest -k用例名字符串表达式当成正则写
go test -run测试函数名的正则以为必须从开头匹配
行覆盖执行过的行当成正确性证明
分支覆盖条件的两侧以为等价于行覆盖

正则 ​

re.match 只从字符串开头尝试,re.search 会扫描中间,re.fullmatch 要求整串被消耗完。多数校验 bug 来自用了 match 却以为在整串匹配,于是末尾多出来的内容被放过。

贪婪与懒惰决定量词停在哪里:.* 吃到最后一个可行位置,.*? 停在第一个。回溯决定代价:嵌套量词会让失败尝试指数增长。

对照行为
re.match只匹配开头,不要求整串
re.fullmatch整串必须匹配
.*贪婪,尽量多吃
.*?懒惰,尽早停下
$1VS Code、sed 的替换模板
\1Python、Vim、PCRE 的反向引用
grep字符层,注释与字符串也会命中
semgrepAST 层,跳过注释、跨行也能匹配

替换引用写错不会报错,只会得到一段看似合理的新文本。改完先 git diff 看一遍,再跑测试。

重构与重写 ​

重构保持外部行为不变,靠测试证明;重写允许改变行为,需要新的验收标准。前者可以随时停下,每一步都能发布;后者必须一次做到可用状态,中途停下就是半成品。

对照外部行为测试提交粒度
重构不变改前改后都绿小步、可回滚
重写可能变需要新基线整体切换
自动修复不变修完仍需验证单独提交

判断自己在做哪一种,看有没有一条能同时跑在旧代码和新代码上的测试。没有,就是在重写,风险要按重写来估。