推荐阅读
- 先读工程化流程(规范/测试/CI)
- 再看自动化工具链与质量门禁
- 最后看可观测性与运维闭环
把复杂改造拆成“对齐基线”和“设计优化”两波,控制变量、提升可回滚性与排障效率。
标题 先定不变量与契约,再写实现: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) 问题:失败是“静默返回”,调用方必须自己猜“这次到底算成功还是失败”。 ...
副标题 / 摘要 你不需要“每个 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 快速给出候选实现。 草稿分支只做提议,不做长期维护。 ...
副标题 / 摘要 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 回收对象。 ...
副标题 / 摘要 深层嵌套的错误处理难以维护。本文用“早返回 + 分解函数”重构嵌套代码。 目标读者 参与代码评审的工程师 需要重构遗留代码的团队 关注可维护性的开发者 背景 / 动机 嵌套 if 会让控制流难以理解。 重构的目标是降低认知负担并明确失败路径。 核心概念 早返回:失败立刻返回 错误码表意:减少嵌套层级 小函数拆分:让逻辑更清晰 实践指南 / 步骤 把失败路径提前返回 给错误码赋予清晰语义 把每一步拆成小函数 用测试覆盖边界 可运行示例 #include <stdbool.h> int op1(); int op2(); int op3(); int run() { int err = op1(); if (err) return err; err = op2(); if (err) return err; err = op3(); if (err) return err; return 0; } 解释与原理 早返回让失败路径清晰,避免“右倾树式嵌套”。 拆分函数还能让每个步骤可独立测试。 常见问题与注意事项 早返回会不会隐藏逻辑? 不会,它让逻辑更直接。 是否需要统一错误码? 需要,否则调试困难。 如何保证重构安全? 用测试验证行为一致。 最佳实践与建议 先写测试再重构 用枚举/常量替代魔法数字 让错误码具备可读语义 小结 / 结论 重构嵌套错误处理的关键是“减少层级、明确失败路径”。 早返回能显著提升可读性。 ...
副标题 / 摘要 每周复盘能让学习产生复利。本文给出结构化模板与实践建议。 目标读者 想持续提升的工程师 负责团队成长的技术负责人 需要建立知识沉淀的人 背景 / 动机 学习如果没有复盘,很容易遗忘或难以迁移。 结构化复盘能让知识更快变成能力。 核心概念 输入:本周学习内容与来源 输出:可应用的实践点 迁移:在真实项目中的应用计划 实践指南 / 步骤 记录本周 3 个学习点 写出 1 个可落地实践 列出 1 个未解决问题 设定下周行动项 可运行示例 # 简化复盘模板结构 review = { "learned": ["cache stampede", "retry backoff"], "apply": "add timeout + circuit breaker", "question": "how to measure p99 reliably?", "next": "add histogram metrics", } print(review) 解释与原理 复盘的关键在“可行动”。 只记录知识点不够,还需要明确应用路径。 常见问题与注意事项 复盘会不会太耗时? 10 分钟足够,关键是持续。 如何避免流于形式? 强调“本周可落地动作”。 团队复盘如何做? 每周短分享 + 文档沉淀。 最佳实践与建议 固定时间复盘(周五下午) 复盘内容可共享 把问题变成行动项 小结 / 结论 每周复盘能让学习沉淀为能力。 持续的小复盘比偶尔的大总结更有效。 ...
副标题 / 摘要 阅读能帮助建立长期能力。本文提供 5 本工程师常读书籍与适用场景。 目标读者 想提升工程素养的开发者 关注长期成长的工程师 需要建立阅读清单的团队 背景 / 动机 系统化阅读能形成长期能力储备。 选择合适的书能显著提升投入产出比。 核心概念 基础能力:代码质量与工程实践 系统思维:架构与性能 软技能:沟通与团队协作 实践指南 / 步骤 选 1 本基础书(如代码质量) 选 1 本架构书(如分布式系统) 选 1 本流程书(如交付与 DevOps) 选 1 本成长书(如职业发展) 每周固定阅读时间 可运行示例 books = [ "Clean Code", "Designing Data-Intensive Applications", "The Pragmatic Programmer", "Accelerate", "Site Reliability Engineering", ] print(books) 解释与原理 书单应覆盖“技术 + 软技能 + 体系化思维”。 单点阅读很难形成完整能力图谱。 常见问题与注意事项 读书是否比实战重要? 不,比重应合理。 如何避免半途而废? 固定时间与读书伙伴。 书是否过时? 原则类书籍通常不过时。 最佳实践与建议 每月固定一本书 读完写一页总结 在团队内做读书分享 小结 / 结论 阅读是长期投资。 选择覆盖基础、架构、工程与软技能的书籍更有效。 ...
副标题 / 摘要 软件开发既需要艺术性的创造,也需要工程化的稳定。本文给出三者的平衡视角。 目标读者 关注工程文化的技术负责人 想提升软件质量的工程师 学习软件工程方法的读者 背景 / 动机 有人把软件当作艺术,强调创造;有人强调工程,追求可控。 理解三者的关系有助于建立正确的团队文化。 核心概念 艺术:创造性解决问题 技艺:经验与手感的积累 工程:标准化、可复制与可管理 实践指南 / 步骤 在原型阶段鼓励艺术性探索 在交付阶段强调工程化流程 通过代码评审传承技艺 用标准化工具降低风险 可运行示例 # “艺术” vs “工程”的简单比喻 def art(): return "creative prototype" def engineering(): return "stable delivery" if __name__ == "__main__": print(art()) print(engineering()) 解释与原理 探索阶段更需要创造性,而规模化交付需要工程化。 技艺是两者之间的桥梁,靠经验与复盘积累。 常见问题与注意事项 工程化会扼杀创新吗? 不会,合理流程反而释放创新空间。 艺术性是否等于不受约束? 不是,仍需要目标与反馈。 技艺如何沉淀? 通过评审、复盘与实践。 最佳实践与建议 用阶段性流程平衡创新与稳定 建立可复用的工程模板 重视知识传承与复盘 小结 / 结论 软件开发既是艺术、也是技艺、更是工程。 在不同阶段选择合适的侧重点是关键。 参考与延伸阅读 The Pragmatic Programmer Clean Code 元信息 阅读时长:6~8 分钟 标签:工程文化、方法论 SEO 关键词:软件开发本质, 艺术与工程 元描述:讨论软件开发的艺术性与工程性。 行动号召(CTA) 回顾你最近的一个项目,标注哪些部分更偏“艺术”,哪些是“工程”。
副标题 / 摘要 可读性强的代码不一定短,但必须降低认知负担。本文给出可执行的判断标准。 目标读者 参与代码评审的工程师 关注可维护性的团队 初中级开发者 背景 / 动机 代码的主要读者是人而不是机器。 可读性差会带来维护成本和错误风险。 核心概念 结构清晰:层次分明、职责单一 命名准确:表达意图而非实现细节 认知负担:阅读时需要记住的临时信息 实践指南 / 步骤 函数短小且单一职责 命名体现意图而不是过程 减少嵌套,提前返回 用测试与注释解释复杂逻辑 可运行示例 # 不好的命名 def f(x): return x * 1.08 # 更好的命名 def apply_tax(price): return price * 1.08 if __name__ == "__main__": print(apply_tax(100)) 解释与原理 读代码的时间通常远大于写代码。 清晰命名与结构能降低理解成本,减少错误。 常见问题与注意事项 注释能替代好命名吗? 不能,注释是补充而不是替代。 缩短代码一定更好? 不一定,过度压缩会降低可读性。 如何量化可读性? 用评审与维护时间做间接衡量。 最佳实践与建议 在评审中强调命名与结构 复杂逻辑写成小函数 用一致的代码风格 小结 / 结论 可读性强的代码能降低认知负担,减少维护成本。 结构、命名与测试是三大关键。 参考与延伸阅读 Clean Code Code Complete 元信息 阅读时长:6~8 分钟 标签:可读性、代码质量 SEO 关键词:可读性, 命名 元描述:定义什么是可读性强的代码。 行动号召(CTA) 挑一段难读的代码,重命名并拆分后再做一次评审。
副标题 / 摘要 “不要重复造轮子”并非总是正确。本文讨论 NIH、狗粮文化的边界与现实价值。 目标读者 负责技术选型的工程师 关注工程文化的团队 需要评估自研与采购的人 背景 / 动机 过度依赖外部方案会失去核心能力,过度自研会浪费资源。 找到平衡是工程管理的关键。 核心概念 重复造轮子:重新实现已有方案 NIH(Not Invented Here):排斥外部方案 Dogfooding:使用自家产品改进质量 实践指南 / 步骤 评估业务是否形成核心竞争力 估算自研成本与维护周期 确认外部方案的风险与依赖 对核心能力进行内部狗粮验证 可运行示例 # 简化决策表 def decide(core, cost_high, risk_high): if core and risk_high: return "build" if cost_high: return "buy" return "adopt" if __name__ == "__main__": print(decide(True, False, True)) 解释与原理 重复造轮子在核心能力领域可能是必要投入。 NIH 是“失衡”的表现,需要通过数据与试点评估取舍。 常见问题与注意事项 自研一定更好? 不一定,长期维护成本可能更高。 狗粮文化会不会浪费时间? 合理范围内能显著提升产品质量。 如何判断“核心能力”? 是否直接影响竞争力与差异化。 最佳实践与建议 用决策矩阵评估自研 vs 采购 对核心模块做内部狗粮验证 避免因偏好而拒绝外部方案 小结 / 结论 重复造轮子并非全错,关键在于是否服务核心能力。 理性评估与狗粮实践能降低决策风险。 ...