先写骨架,再补细节:用契约拆解算法题与中型程序

围绕“公开接口先行、helper 以契约占位、实现围绕不变量展开”这条主线,系统讲解如何从算法题过渡到中型程序设计,并用 LRUCache 与下单流程示例说明它和 DDD 的分工关系。

2026年3月20日 · 8 分钟 · map[name:Jeanphilo]

先定不变量与契约,再写实现:Evans/Fowler 实战法

标题 先定不变量与契约,再写实现:Evans/Fowler 实战法 副标题 / 摘要 很多人理解“先定不变量与契约”时,会觉得只是“多写几行校验”。这篇文章给出更精确的答案:它的本质是固定责任归属,让调用方可以依赖行为语义,而不是猜测实现细节。 目标读者 正在做业务系统设计、代码评审的工程师 觉得“代码能跑,但改需求总出坑”的团队 想把 DDD/契约思想落到日常开发的人 背景 / 动机 常见开发顺序是“先把功能跑通,再补规则”。短期看速度快,长期会出现三个问题: 业务规则散落在多个 service/controller 里 调用方只能通过读实现猜行为 改一个需求会牵动大量分支判断 Evans/Fowler 这一脉的核心不是“写得更学术”,而是先明确系统必须成立的事实,再让实现为这些事实服务。 核心概念 不变量(Invariant):无论任何路径,始终为真的业务规则。 例如:已支付订单不能再次支付。 契约(Contract):对外可依赖的行为承诺,至少包含前置条件、后置条件、失败语义。 例如:cancel(order) 只接受可取消状态,成功后状态必须是 CANCELLED,否则抛明确异常。 接口 vs 契约:接口是签名,契约是语义保证。 同一个函数签名,可以有强契约,也可以完全没有契约。 契约分层(建议团队统一术语) 前面的 cancel(order) 示例主要覆盖了行为契约与失败契约。 在真实项目里,建议把契约至少拆成下面 6 类,一起设计: 数据契约:输入/输出的数据形状、类型、取值范围、单位、精度、是否可空。 例:金额必须 > 0,币种必须是 ISO 4217,时间必须是 UTC。 状态契约:状态机允许哪些迁移,不允许哪些迁移。 例:订单只能 CREATED -> PAID -> SHIPPED,不能 SHIPPED -> CREATED。 不变式契约:跨方法、跨状态始终成立的事实。 例:订单总额 = 明细金额之和 + 运费 - 优惠;库存不可为负。 行为契约:调用成功时,调用方可以依赖什么结果与语义。 例:reserve_stock() 成功后,一定返回预留记录 ID,且库存已被占用。 失败契约:违约/异常时返回什么错误、错误是否可重试、是否有副作用残留。 例:重复请求返回 409;超时返回 503 且标记 retryable=true。 副作用契约:方法会修改哪些外部状态(DB、缓存、消息、文件),顺序如何,失败如何补偿。 例:先写 DB 再写 outbox;缓存删除失败不影响主事务提交。 实践指南 / 步骤 先写目的,不写实现 明确本次功能要改变什么业务结果。 列不变量清单 逐条写出“绝对不能被破坏”的规则。 定义契约 为核心行为定义前置条件、后置条件、失败语义,并补齐数据/状态/副作用契约。 再落实现 数据库、框架、缓存、消息等实现细节后置。 用测试锁契约 测试验证的是契约,不是某一版实现细节。 可运行示例 示例 1:无契约(可运行,但语义模糊) class Order: def __init__(self, status): self.status = status def cancel(order: Order) -> Order: if order.status != "CREATED": return order order.status = "CANCELLED" return order if __name__ == "__main__": order = Order("PAID") after = cancel(order) print(after.status) 问题:失败是“静默返回”,调用方必须自己猜“这次到底算成功还是失败”。 ...

2026年2月11日 · 4 分钟 · map[name:Jeanphilo]

AI 辅助编程不黑盒:责任主线工作流实战

副标题 / 摘要 你不需要“每个 commit 都手打重写”,但必须对核心 commit 具备独立实现能力。本文给出一套可落地的 AI 协作流程:让 AI 负责胶水和草稿,你负责领域规则、状态变化与边界裁决。 目标读者 正在大量使用 AI 写代码,但担心自己变成“黑盒工程师”的开发者 想同时提升交付效率和技术判断力的工程师 在做 DDD、业务系统或中后台项目的开发者 背景 / 动机 AI 代码生成越来越强,这不是坏事。真正的风险在于:当你只会“接收实现”,却不能解释“为何这样实现”,系统一复杂就失去控制权。 问题不是“要不要用 AI”,而是“哪些决策必须留在人手里”。 核心概念 责任主线(main):只保留你愿意为其设计与后果负责的 commit。 AI 草稿(ai/draft-*):一次性候选实现分支,用于对照、压力测试与发现盲区。 git worktree:在不二次 clone 的前提下,为不同分支创建多个物理目录(一个仓库,多处并行)。 核心 commit:包含领域规则、不变量、状态机、关键边界与失败路径的提交。 胶水 commit:CRUD、DTO、映射、样板接口、注释等可标准化提交。 硬核评价标准:去掉 AI 和网络,你仍能写出该 commit 的核心伪代码,并解释每一步为什么这么做。 实践指南 / 步骤 先分层,再决定谁写 把需求拆成 Domain / Application / Infrastructure。 Domain 核心规则优先自己实现;Infrastructure 优先交给 AI。 先写判断标准,再看 AI 方案 先写不变量、边界条件、错误路径、伪代码或测试,再生成 AI 草稿。 先有你的“尺子”,再拿 AI 代码来量。 为每轮任务创建短生命周期草稿分支 从当前 main 派生 ai/draft-<topic>,让 AI 快速给出候选实现。 草稿分支只做提议,不做长期维护。 ...

2026年2月7日 · 3 分钟 · map[name:Jeanphilo]

反腐败层(ACL):如何隔离外部系统的复杂性

副标题 / 摘要 反腐败层用于隔离外部系统的模型与语义污染。本文解释其工程价值与实现策略。 目标读者 需要系统集成的后端工程师 使用 DDD 的团队 负责跨系统数据一致性的架构师 背景 / 动机 外部系统的字段命名、流程与规则可能与你的领域模型不一致。 如果直接耦合,会让核心领域被污染。 核心概念 反腐败层(ACL):隔离外部模型的适配层 适配与映射:在边界处转换语义 领域模型保护:核心逻辑不被外部侵蚀 实践指南 / 步骤 定义领域模型的核心语义 在边界层做字段与概念映射 把外部协议封装在 ACL 中 为 ACL 设计测试样例 可运行示例 # 外部系统返回的字段命名不同 external_payload = {"user_id": "u-1", "plan": "VIP"} def to_domain(payload): return { "id": payload["user_id"], "membership": "premium" if payload["plan"] == "VIP" else "standard", } if __name__ == "__main__": print(to_domain(external_payload)) 解释与原理 ACL 把外部系统的变化隔离在边界层,避免影响核心业务代码。 它是“保护领域模型”的关键设施。 常见问题与注意事项 ACL 会增加复杂度吗? 会,但能降低长期维护成本。 何时需要 ACL? 当外部系统不受你控制、变化频繁时。 ACL 是否等同于 DTO? 不完全,ACL 是语义转换而非简单结构映射。 最佳实践与建议 ACL 层保持薄而清晰 把外部依赖集中管理 对外部字段变化建立监控 小结 / 结论 反腐败层是系统集成的“防污染墙”。 它让你的领域模型保持清洁与稳定。 ...

2026年1月24日 · 1 分钟 · map[name:Jeanphilo]

以业务对象为核心的 Python 架构实践

这篇文章从一个简单的工单系统出发,展示如何在 Python 项目中以业务对象为中心设计接口、仓储与服务,而不是让 ORM、框架和表结构牵着鼻子走。

2025年12月1日 · 3 分钟 · map[name:Jeanphilo]

从表结构到领域模型:用聚合仓储设计权限系统

以一个权限组管理模块为例,展示如何用领域模型 + 聚合仓储的方式设计后端,而不是让业务直接围着数据库表转。

2025年12月1日 · 4 分钟 · map[name:Jeanphilo]