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

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

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

先等价迁移,再行为改造:AI 协作时代最稳的工程工作流

把复杂改造拆成“对齐基线”和“设计优化”两波,控制变量、提升可回滚性与排障效率。

2026年3月5日 · 2 分钟 · 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]

图算法专题学习路径:从 BFS 到图计算模型

这是一页“图算法专题导航”。目标不是把文章堆在一起,而是给你一条从基础遍历到分布式图计算的可执行学习路径。 目录现状(已完成专题化) 图算法系列已迁移到: content/zh/dev/algorithm/graph/ 并采用两位数字前缀(00/10/20...)做阅读顺序标识,方便: 文件系统内按顺序浏览 后续增量插入新文章(可保留编号间隔) 批量维护时快速定位阶段 推荐阅读顺序(按能力建设) 第 0 阶段:遍历基本功(先打地基) BFS / DFS 工程入门:k-hop 查询、子图抽取与路径可达性 最短路径实战:BFS、Dijkstra、A* 的工程化选型 目标: 能稳定写出迭代版图遍历; 能解释什么时候用 BFS、什么时候用 Dijkstra/A*; 习惯加 early stop、visited、预算限制。 第 1 阶段:可达性与连通结构(图查询核心) k-hop 与可达性查询:BFS 限制、Reachability 索引与 2-hop Labeling Connected Components 与 SCC:Tarjan / Kosaraju 目标: 把“能不能到达”从一次搜索升级成系统能力; 理解无向连通与有向强连通是两类不同问题; 建立“在线 BFS + 离线索引”的组合思维。 第 2 阶段:图分析指标(从可达走向洞察) 图中心性:Degree / Betweenness / Closeness PageRank / Personalized PageRank:节点重要性与增量更新 目标: 能解释“重要性”的不同定义与适用边界; 能把中心性与 PageRank 用在推荐/风控/影响力分析; 理解“指标正确”和“平台能跑”是两回事。 第 3 阶段:结构挖掘与匹配(应用层能力) 子图匹配:VF2、Ullmann 与剪枝 社区发现:Louvain 与 Label Propagation 目标: ...

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

动态图与增量计算:增量最短路径、增量 PageRank、连通性维护 ACERS 解析

副标题 / 摘要 动态图场景里,真正的痛点不是“会不会算法”,而是“更新来了能不能顶住”。本文按 ACERS 模板讲透三件工程必修:增量最短路径、增量 PageRank、连通性维护,以及三条现实策略:局部重算、延迟更新、近似结果。 预计阅读时长:14~18 分钟 标签:动态图、增量计算、最短路径、PageRank、连通性维护 SEO 关键词:动态图, 增量最短路径, 增量 PageRank, 连通性维护, 局部重算, 延迟更新, 近似结果 元描述:动态图工程指南:在高频更新场景下如何用增量算法与工程策略控制时延和成本。 目标读者 做图数据库、关系图、推荐图在线服务的工程师 从离线图计算转向实时增量计算的开发者 想把“全量重算”改造成“可上线更新流水线”的技术负责人 背景 / 动机 静态图算法在论文里很优雅,但真实系统里图是不断变化的: 用户关系新增/删除 交易边持续流入 内容图和知识图谱持续更新 工程上 80% 的痛点就在这里: 全量重算太慢,赶不上更新速率 在线强一致代价太高,P99 失控 业务只要“可用近似”,却在做“昂贵精确” 所以核心问题变成: 不是怎么把答案算出来,而是怎么在更新流下持续算得动。 核心概念 概念 含义 工程关注点 增量最短路径 边/点更新后只修复受影响区域 影响域检测、局部重算 增量 PageRank 图更新后迭代残差局部传播 残差阈值、批量窗口 连通性维护 动态维护是否连通/分量变化 插入快、删除难 局部重算 只对受影响子图重新计算 降低 CPU/内存 延迟更新 把更新合并成批次统一处理 吞吐优先、可控延迟 近似结果 用误差边界换计算成本 SLA 与精度平衡 A — Algorithm(题目与算法) 题目还原(工程化) 给定一个持续更新的图 G_t=(V_t,E_t) 和操作流: ...

2026年2月9日 · 5 分钟 · map[name:Jeanphilo]

图中心性三件套:Degree、Betweenness、Closeness 工程 ACERS 解析

副标题 / 摘要 中心性不是论文概念,而是图系统里的“节点重要性排序器”。本文按 ACERS 结构讲透 Degree / Betweenness / Closeness,并给出一条务实结论:线上大多数系统只稳定支持 Degree + 近似 Betweenness。 预计阅读时长:12~16 分钟 标签:图论、中心性、Degree、Betweenness、Closeness SEO 关键词:图中心性, Degree Centrality, Betweenness, Closeness, 近似 Betweenness 元描述:图中心性工程指南:三大指标定义、复杂度、近似算法与落地策略,附可运行代码。 目标读者 做关系图分析、知识图谱、图数据库查询优化的工程师 需要把“节点重要性”从概念变成上线指标的开发者 想知道为何 Betweenness 工程上昂贵、如何做近似替代的同学 背景 / 动机 你在图系统里迟早会遇到这类问题: 哪些节点是“社交大 V”或“交易枢纽”? 哪些节点是关键桥梁,断开就会让图显著分裂? 哪些节点整体上离其他节点更近,适合作为入口/缓存热点? 对应到中心性指标: Degree Centrality:连接数多不多(本地重要性) Betweenness Centrality:是否位于大量最短路径中(桥梁重要性) Closeness Centrality:到全图平均距离是否更短(全局接近性) 现实里最大的坑不是“不会定义”,而是“算不动”: Degree 非常便宜,几乎所有系统都能实时支持 Betweenness 精确计算很贵,通常只能离线或近似 Closeness 需要大量最短路,图一大就难在线 核心概念 1) Degree Centrality 无向图中节点 v 的度中心性常写为: C_D(v) = deg(v) / (n - 1) 含义:节点局部连接活跃度。 ...

2026年2月9日 · 6 分钟 · map[name:Jeanphilo]

最短路径三件套:BFS、Dijkstra、A* 工程实战 ACERS 解析

副标题 / 摘要 最短路径不是一道题,而是一组“按图条件选算法”的工程能力。本文按 ACERS 结构拆解 BFS(无权)/ Dijkstra(非负权)/ A(启发式)*,并给出你在关系图、推荐链路、路径解释里真正会用到的优化模板。 预计阅读时长:14~18 分钟 标签:图论、最短路径、BFS、Dijkstra、A* SEO 关键词:最短路径, BFS, Dijkstra, A*, 双向搜索, 多源 BFS 元描述:最短路径三件套工程指南:算法边界、复杂度、可运行代码、优化策略与实战场景。 目标读者 正在补图算法基础,希望形成可复用工程模板的学习者 做社交关系链路、推荐路径、图查询解释的后端/算法工程师 对 BFS、Dijkstra、A* 都“知道名字”,但选型和优化还不稳定的开发者 背景 / 动机 最短路径问题常见于: 社交网络里的最短关系链路(几跳可达) 推荐系统里的最小代价路径(多目标折中) 可解释系统里的“为什么推荐给你”路径展示 工程里最容易犯的错误是“只会一个算法硬套全部场景”: 用 BFS 跑加权图,结果错但不报错 用 Dijkstra 跑负权边,得到不可靠结果 用 A* 但启发函数不合格,性能退化成 Dijkstra 本质上,最短路径应先做 图条件分类,再做算法选型。 核心概念 算法 适用图 最优性条件 典型复杂度 关键词 BFS 无权图 / 等权图 按层首次到达即最短边数 O(V+E) queue, level Dijkstra 非负权图 堆顶弹出的节点距离已最优 O((V+E)logV) relaxation, min-heap A* 非负权图 + 启发式 h(n) 可采纳(不高估) 最坏同 Dijkstra,平均更快 f=g+h 关键公式: ...

2026年2月9日 · 10 分钟 · 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]

什么时候适用函数式语言:场景与边界

副标题 / 摘要 函数式语言擅长处理并发与复杂逻辑,但并非所有场景都适用。本文给出清晰的适用边界。 目标读者 进行语言选型的团队 想理解函数式价值的工程师 关注可维护性的技术负责人 背景 / 动机 函数式编程提高可推理性,但学习成本与性能特性也带来代价。 明确适用场景有助于合理选型。 核心概念 纯函数:无副作用,便于测试 不可变性:并发友好 表达力:复杂逻辑更清晰 实践指南 / 步骤 并发场景优先考虑函数式 业务规则复杂时优先使用纯函数 I/O 密集系统需评估生态与性能 团队学习成本纳入评估 可运行示例 # 函数式风格更适合复杂规则组合 def apply_rules(x, rules): for r in rules: x = r(x) return x if __name__ == "__main__": print(apply_rules(10, [lambda x: x + 1, lambda x: x * 2])) 解释与原理 函数式适合“规则多、并发高、可推理性强”的场景。 但在高性能或生态依赖强的场景要谨慎评估。 常见问题与注意事项 函数式一定更安全? 更易推理,但仍需正确设计。 是否适合所有团队? 不一定,学习成本较高。 性能会不会更差? 取决于实现与数据结构。 最佳实践与建议 先在模块中试点 评估生态与性能指标 在团队内建立函数式规范 小结 / 结论 函数式语言适合复杂逻辑与并发场景,但不适合所有系统。 正确的选型需要结合业务与团队能力。 参考与延伸阅读 Functional Programming in Scala Haskell in Industry 元信息 阅读时长:6~8 分钟 标签:函数式语言、选型 SEO 关键词:函数式语言 适用场景 元描述:说明函数式语言的适用场景与限制。 行动号召(CTA) 挑一个规则复杂的模块,试着用函数式风格重写对比。

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

Java 栈的内存泄漏:为什么 pop 之后仍然占用

副标题 / 摘要 Java 有 GC 也会出现内存泄漏。本文用经典栈实现解释为什么对象引用没清理会导致泄漏。 目标读者 使用 Java 的开发者 关注内存问题的工程师 需要理解引用机制的人 背景 / 动机 GC 只能回收“不可达对象”。 如果引用没清理,哪怕对象不再需要,也不会被回收。 核心概念 对象可达性:决定是否可回收 引用残留:对象仍被数组引用 逻辑泄漏:对象不再使用却无法回收 实践指南 / 步骤 识别不再使用的引用 在 pop 后显式置空 使用工具分析堆快照 写回归测试验证 可运行示例 import java.util.EmptyStackException; import java.util.Arrays; public class Stack { private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) throw new EmptyStackException(); Object result = elements[--size]; elements[size] = null; // 防止内存泄漏 return result; } private void ensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } } 解释与原理 数组中残留的引用使对象仍然“可达”。 显式置空可以让 GC 回收对象。 ...

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