跳转至

Laws 法条图鉴

这些法条不会替你写业务代码,只是在你准备“越界”的那一刻吹响法槌。

PyCourt 当前内置 15 条 Law,分别从架构、类型、风格、时间与数据等维度,为你的项目提供一套“最低限度的文明约束”。

你不需要一次性全记住它们。把这页当成“法条菜单”就好:
当某类问题开始频繁出现时,再回来点开对应的 Law,慢慢加重审查即可。

如果你对某条 Law 有很多话想说——踩坑经历、重构故事、灰色边界讨论—— 欢迎按照参与共创指南提交一篇“法条解读”或“判例故事”, 未来我们会为每条 Law 挂出 1–2 篇社区精选解读,让新来的开发者看到的不只是“禁止条文”,还有“为什么值得这样禁止”。


架构与边界安全

关注“模块和模块之间,到底该怎么互相看”的一组法条。

BC001 边境管理

关键词:HTTP 边界 / 适配器 / DTO

禁止在 HTTP 层和适配器层直接使用原始类型(dictstrAny 等)或随手拼凑的容器类型,
以避免在系统边缘泄露内部模型,实现层不小心直接暴露给外部世界。

  • 它在看什么? 各种 api/routes/infra/adapters/** 下的函数签名与返回值。
  • 你为什么会被抓? HTTP handler 直接收发 dict[str, Any],把内部模型原样扔进响应。
  • 怎么和它和解? 给入口/出口一份清晰的 DTO / 契约类型,内部怎么折腾它管得不多。

DI001 依赖倒置

关键词:跨层 import / 组合根 / 反向依赖

禁止业务模块绕过核心抽象,直接跨层 import 具体实现,以避免模块之间纠缠成一团,彻底打穿依赖倒置。

  • 它在看什么?api / app 等边缘层偷偷伸进 infracore 的“奇怪导入”。
  • 你为什么会被抓? 在控制器里直接 import 仓储实现,而不是通过 Port / Service 接口。
  • 怎么和它和解? 把跨层 wiring 放进组合根(DI 容器、app/main.py 等),边缘层只面向抽象。

UW001 工作单元

关键词:事务边界 / 仓储 / UoW

禁止在仓储内部调用 commit() / rollback() 之类的事务控制,以避免工作单元的原子性被悄悄破坏。

  • 它在看什么? 各种 repositoryinfra/database 模块里对事务 API 的直接调用。
  • 你为什么会被抓? 某个仓储方法里顺手 session.commit(),把调用方彻底绑死在一个实现上。
  • 怎么和它和解? 让仓储只做数据访问,把事务控制留在 UoW 或服务层,保证一整个业务操作要么全部成功,要么全部回滚。

RE001 聚合导出

关键词:__init__.py / 包外观 / 隐形实现层

禁止把复杂逻辑、隐藏依赖塞进 __init__.py 里当作“导出技巧”,
以避免包外观退化成第二个实现模块,调试与审计都变得混乱。

  • 它在看什么? 各种包的 __init__.py,尤其是里面的 import 和逻辑。
  • 你为什么会被抓? 把真实逻辑写在 __init__.py 中,再导出给其他模块使用。
  • 怎么和它和解? __init__.py 只负责聚合导出已经存在的模块符号,不承担实现职责。

类型与契约安全

关注“值到底是什么?契约写没写清楚?”的一组法条。

AC001 鸭子类型

关键词:Any / dict / cast

禁止依赖 Any、未知 dict 和随手 cast
以避免将核心数据流变成类型系统无法保护的“鸭子类型迷雾”。

  • 它在看什么? 各种 dict[str, Any]、神秘的 cast(XXX, something)
  • 你为什么会被抓? 为了省事,所有 API 都接受 dict,内部到处是 ["foo"]["bar"]
  • 怎么和它和解? 用 Pydantic 模型 / dataclass / TypedDict 等给数据一个清晰身份。

OU001 裸对象类型

关键词:object / 模糊类型 / 灰色物质

禁止在核心代码中使用“裸 object”或同等模糊的类型签名,
以避免出现一堆“你说它是什么它就是什么”的灰色值,没人敢重构。

  • 它在看什么? 函数参数/返回值标注为 object / dict / str | object 之类的奇葩组合。
  • 你为什么会被抓? 把关键参数类型简单写成 object,希望“将来再细化”,结果就一直没下文。
  • 怎么和它和解? 至少给重要对象一个领域名:哪怕是 UserId 这样的 wrapper,也比 object 好得多。

PC001 参数分类

关键词:业务规则 / 技术常量 / RuleProvider

禁止把业务上需要调整的规则(阈值、权重、比例)当成“技术常量”写死在代码里,
以避免把“运营调参”变成“改代码上线”的低效流程。

  • 它在看什么? 各种看起来不像技术常量的数值/字符串常量,特别是在 core/constants/ 外面。
  • 你为什么会被抓? 在代码里直接写 TOP_K = 5SCORE_THRESHOLD = 0.7,而不是走配置或规则表。
  • 怎么和它和解? 把可调规则放进专门的 RuleProvider / 配置系统,技术常量只存放纯技术性的固定值。

TC001 内部盘查(TYPE_CHECKING)

关键词:if TYPE_CHECKING: / 仅类型导入 / 循环依赖

禁止滥用 if TYPE_CHECKING: 把真实的模块依赖藏在“只给类型检查看的”分支里,
以避免循环依赖被悄悄埋进类型注解中,导致结构腐化。

  • 它在看什么? 所有 if TYPE_CHECKING: 块以及里面的导入。
  • 你为什么会被抓? 通过“类型导入”偷偷实现跨层依赖,然后在运行时再想办法兼容。
  • 怎么和它和解? 类型注解也属于架构的一部分,避免借它绕过正常的依赖边界。

SK001 技能提供者

关键词:技能系统 / 资源目录 / 提供者模式

禁止业务代码直接对 assets/skills 等物理目录进行硬编码访问,
以避免核心逻辑与文件布局紧耦合,难以演进和替换。

  • 它在看什么? 所有直接访问技能资源目录的代码路径。
  • 你为什么会被抓? 在业务函数里 open("assets/skills/xxx.json") 之类的硬路径。
  • 怎么和它和解? 通过统一的 SkillId 注册表和 provider 访问技能,逻辑只认识 SkillId,不认识物理路径。

可读性与代码卫生

关注“别让未来的你(或你的同事)看不懂现在的你在干嘛”的一组法条。

DS001 文档字符串

关键词:docstring / 公共 API / 意图可读

禁止公共函数和类缺少清晰、非平凡的文档字符串,
以避免 API 意图和契约只能靠猜、靠读实现才能理解。

  • 它在看什么? 所有对外暴露的函数、类、方法有无合理 docstring。
  • 你为什么会被抓? 公共接口只有一个函数名,参数一长串,注释要么没有,要么写“do something”。
  • 怎么和它和解? 一两句话说明“做什么、不做什么、有何前置条件”,比写十行实现更有价值。

LL001 过度复杂

关键词:超长函数 / 深层循环 / 纠缠逻辑

禁止函数长度或循环复杂度超过约定阈值,
以避免出现“一看就想关电脑”的巨型过程。

  • 它在看什么? 超过合理长度的函数、多层嵌套 if/for、长得像脚本的业务代码。
  • 你为什么会被抓? 一个函数里什么都干:校验、查询、拼数据、发请求、写日志、处理异常……
  • 怎么和它和解? 把过程拆成几个有名字的步骤,让法官和同事都看得懂你的意图。

HC001 硬编码

关键词:魔法数字 / 业务字面量 / 常量仓

禁止在受控常量模块之外使用硬编码的业务字面量和魔法数字,
以避免重要阈值和业务规则散落在一堆 if/for 里,被遗忘在角落。

  • 它在看什么? 到处出现的 "vip", "normal", 0.8, 5000 之类奇怪数字/字符串。
  • 你为什么会被抓? 为了图方便,直接在条件里写 "gold"top_k = 10,过一阵子谁也记不清含义。
  • 怎么和它和解? 把这些东西搬进明确的常量模块或配置系统里,给它们一个名字和注释。

时间与数据一致性

关注“程序在时间轴上、数据流上是不是靠谱”的一组法条。

DT001 时间漂移

关键词:datetime.now() / 时区 / 可测试性

禁止在业务代码里直接使用 datetime.now() / utcnow()
以避免时间相关逻辑难以测试、线上线下行为不一致。

  • 它在看什么? 所有直接调用 datetime.*now() 的位置。
  • 你为什么会被抓? 在业务逻辑中到处读当前时间,却不给测试任何控制手段。
  • 怎么和它和解? 通过集中式的 TimeProvider / Clock 接口获取时间,让测试可以注入“现在”。

TP001 测试代码

关键词:测试纯度 / 重 I/O / 假单测

禁止在标称为“单元测试”的地方偷偷引入重 I/O、复杂依赖,
以避免出现又慢又脆、却测不出真正问题的“伪单测”。

  • 它在看什么? 测试代码中对数据库、外部服务、庞大依赖图的直接使用。
  • 你为什么会被抓? 一个 test 函数既连数据库,又跑网络,还依赖全局状态。
  • 怎么和它和解? 把重 I/O 留给集成/E2E 测试,单测专注于小而清晰的行为单元。

VT001 向量触发器

关键词:向量搜索 / 事件 / 能力边界

禁止触发路由发出当前配置下根本无法履行的向量请求或事件,
以避免“法官口头宣判了一堆向量行动”,但运行时没有任何实际能力支撑。

  • 它在看什么? 向量相关的触发点、事件路由与实际 provider 配置之间是否匹配。
  • 你为什么会被抓? 在代码里发出了某个向量事件,但没有任何 provider 能处理它。
  • 怎么和它和解? 确保向量触发配置与 provider 实现一致,不做“空头承诺”。

读完这页,你不需要立刻记住所有条文。
更实际的用法是:选你现在最头疼的那一类问题(比如硬编码、边界乱、时间漂移),先打开对应的 2–3 条法条,慢慢加到你的项目里。
等 PyCourt 在你的日常工作流里站稳脚跟之后,再考虑“全院审判”。