基于 David Farley《Modern Software Engineering》(2021) 的工程理论落地。 核心信念:软件工程 = 把科学方法用于复杂、不确定的系统。 两大支柱:为学习优化(Optimize for Learning) + 为管理复杂度优化(Optimize for Managing Complexity)。 两块试金石:可测试性(Testability) 与 可部署性(Deployability) —— 难测、难部署,几乎等于设计有问题。
本规范由真实项目反例库(脱敏)+ 业界标杆(具名)反复证伪迭代而来,演进记录见 CHANGELOG。
- 元原则:上下文优先
- 0. 总原则
- 第一部分:代码开发(为学习优化)
- 第二部分:代码设计(为管理复杂度优化)
- 第三部分:两块试金石(可测试性 / 可部署性)
- 第四部分:Code Review 规范
- 第五部分:健康度量(DORA + SLO 错误预算)
- 第六部分:落地路线(分阶段)
- 第七部分:架构治理即代码(Fitness Functions)
- 第八部分:棕地 / 遗留系统适配
- 第九部分:跨服务与共享数据
- 第十部分:安全基线
- 一句话速记
锚定的业界标杆:Farley《Modern Software Engineering》· ArchUnit · Pact · strong_migrations · OpenTelemetry · OpenFeature · Testcontainers · trunk-based development · Cockburn(ports & adapters)· Google SRE · OWASP ASVS · 12-factor。
本规范的所有规则都服务于两个目标:为学习优化、为管理复杂度优化。当某条规则明显阻碍这两个目标时,团队有权在达成共识并记录理由后打破它,并在事后复盘。
规范本身也是一个可迭代、可反馈的制品——用规范倡导的方式(小步、反馈、实验、数据说话)去演进这份规范。 规则是默认值(default),不是教条(dogma);僵化执行规范,本身就违背了规范的精神。
- 小步快走优于一次做对。 第一版一定不完美,目标是快速拿到反馈再迭代。
- 可测试性 + 可部署性是设计质量的反向探针。 写代码时先问:"这东西怎么测?怎么独立部署/回滚?"
- 决策靠证据,不靠权威或猜测。 有争议就做实验(spike / A-B / 度量),让现实说话。
- 复杂度是头号敌人。 任何让系统更难理解、更难改动的东西,都要被质疑。
- 质量与速度不是权衡,而是同源。 好工程让你又快又稳(参考 DORA 四指标)。
- 任务拆成可独立验证的增量单元(默认目标:1~2 天内合并),每个增量本身可工作、可验证。
- 优先走通最薄的端到端切片(walking skeleton),再逐步加厚,而不是先把某一层做完美。
⚠️ walking skeleton ≠ 无设计堆砌。每个迭代仍要保留轻量级架构前瞻(架构守护测试 / 定期走查),避免把骨架走成死胡同导致大返工。
- 默认禁止长命大分支(long-lived branch),频繁合回主干(trunk-based)。
- 例外原则:底层重构、复杂 DB 迁移、合规/审计受限的场景(金融、医疗等)允许长跑分支,但必须经团队共识 + 配套更频繁的中间集成验证(每天合主干或反向合入),且记录理由。"独立可验证的增量"优先于"死磕分支时长"。
- 大改造用特性开关(feature flag) 隐藏未完成功能,边开发边集成。
- 大型重构用分支抽象(branch by abstraction):在旧实现旁先引入一层抽象,流量逐步迁到新实现,全程主干保持可发布——绿地版的"绞杀者",让大改造也不需要长命分支。
- 配套:push 前本地先跑构建 + 单测 + 集成(pre-integration),不把红的提交带进主干;主干 build 红了优先于一切修复。参考实践:trunk-based development。
- 本地一条命令即可跑完快速测试套件(单元 + 关键集成),秒级~分钟级反馈。
- 每次提交触发 CI;CI 红了,先修 CI,不叠新代码。
- 例外(solo / 单部署目标项目):无团队 PR 协作流的小项目,可用强制本地 pre-commit
check(typecheck + lint + 测试)替代 PR CI——前提是这条命令是提交的硬门禁,而非"记得跑一下"。
- 例外(solo / 单部署目标项目):无团队 PR 协作流的小项目,可用强制本地 pre-commit
- 有测试就必须有强制门禁,否则测试会腐烂:测试存在但 CI / pre-commit 不跑 = 等于没有;反过来,CI 必须真正 gate 测试——只 build 不跑测试的 CI 是"剧场",给人虚假安全感。
- 工作区保持近零:未提交改动是风险(无可复现 / 易丢失 / 无审计)。大量长期未提交(几十上百个文件堆在工作区)是头号工程卫生告警——频繁小步提交,而不是攒一大坨。
- 错误不许静默吞掉:每个 catch 要么上报日志/监控(sink),要么重新抛出,要么显式注释理由;最好用静态门禁(lint 规则 / 扫描脚本)强制,而非只靠人肉 review。
- 反馈链路按速度分层:本地编译/lint → 单元测试 → 集成测试 → 验收测试 → 生产可观测性。
- 提交粒度小且语义清晰,让 review 者能在 5~15 分钟读懂一个 PR。
- 面对不确定的技术/性能问题,先做限时 spike 验证假设,而不是凭直觉直接上。
- 性能、可用性类结论必须有度量数据支撑,不接受"我觉得这样更快"。
- 有 bug 先写一个能复现的失败测试,再修(让现实可观测、可回归)。
- 难写测试时,停下来改设计——这是耦合过高/职责不清的信号,不是"测试太难写"。
- 测试聚焦行为与契约,不绑定实现细节(避免改实现就大面积红)。
- 测试金字塔:大量快单元测试 + 适量集成 + 少量端到端,不倒挂。
- 把比例当可追踪指标(如 ~70–80% 单元 / 15–25% 集成 / 5–15% e2e),监控漂移。引擎/纯逻辑可单测重,但别让关键用户旅程的 e2e 趋近 0——单测全绿、关键流程却没人测,照样能上线即崩。
- 集成测试对真实依赖、用即弃容器,而非 mock 或共享测试库:对 DB / 消息队列 / 缓存这类外部依赖,在每个测试起一个一次性容器跑真东西——避免两类陷阱:① 共享测试库被并发污染、配置漂移(本规范多次踩到的"测试间互相串台");② in-memory/mock 与生产行为不一致,给假信心。要求本地与 CI 同款、用后自动清理。参考实现:Testcontainers。
五个手段:模块化 / 内聚 / 关注点分离 / 抽象与信息隐藏 / 管理耦合。
- 每个模块有清晰边界和单一对外契约,内部实现可自由替换。
- 模块大小以"一个人能完整理解"为度;过大就拆。
- 判据是责任爆炸半径,不是行数(v0.3 修正 v0.2):行数只是触发"看一眼"的信号(~600–800 行扫一眼)。真正决定要不要拆的是:① 它 import 了多少模块、改它会波及多大范围;② 能否被独立理解;③ 它会不会长进相邻业务域。
- 据此区分:bounded-large(可留) —— 被固定接口封死的 handler、只做"取数→表单→回写"的薄配置 UI,即使上千行也低风险;tangled-large(必拆) —— import 十几个模块、内嵌字段映射/权限/状态机的 god service。
- 真实反例:一个 381 行、接口封死的字段 handler,比一个 200 行、import 15 模块的 god service 安全得多。
- 判断复杂度时排除生成物 / vendor / 压缩文件:一个几十万字符的"超大文件"多半是 minified / 自动生成,不是 god 文件,别误判。并且这类产物、备份(
.bak)、构建输出都不该进 VCS——用.gitignore排除,保持仓库只装源码。
- 一起变化的东西放在一起,不一起变化的分开(把相关逻辑聚拢)。
- 一个类/函数只讲一件事;出现"和"字描述职责(做 A 和 B)就该拆。
- 业务逻辑与基础设施分离(领域核心不直接 import 框架/DB/HTTP 细节)。
- I/O、配置、第三方 SDK 放到边缘(端口-适配器 / 六边形架构思路)。
- port 按业务意图命名,不按技术命名(
RateRepository而非MySQLClient);适配器通过依赖注入传进核心,而不是核心里硬调外部。 - 每个外部依赖都有内存替身,核心逻辑的验收测试应能在不接真实 DB / UI 的情况下通过——这既是好设计的证据,也是可测试性的来源(参考 Cockburn 的 ports & adapters)。
- port 按业务意图命名,不按技术命名(
- 接口暴露意图("做什么"),隐藏机制("怎么做")。
- 抽象基于真实存在的稳定概念,不为"将来可能"提前抽象(避免投机性泛化)。
- 反对过早抽象:先有两三处真实重复,再抽(rule of three)。
- 分层适用:对业务逻辑重复严格遵循 rule of three(业务会分叉,过早统一反成耦合);对技术基础设施级重复(公共安全库、统一认证、加密、日志)可适当放宽、更早统一,避免重复实现带来的安全漏洞与数据不一致。
- 默认松耦合;依赖指向稳定方向,易变细节依赖稳定抽象(依赖倒置)。
- 警惕以"复用"之名制造耦合——错误的复用比重复更贵。
- 跨服务/跨模块用契约通信,避免共享可变状态、共享数据库表带来的隐式耦合。
- 这段代码能在不依赖真实外部系统的情况下被测试(可注入/可替身)。
- 不需要 sleep、不依赖运行顺序、不依赖真实时钟/随机数(可控)。
- 测试确定性:同样输入永远同样结果,无 flaky。
- 安全/权限边界专测:越权访问、数据隔离、租户/部门边界必须有专门的边界测试,不能只测 happy path——这类 bug 一旦上线就是数据泄露。
- 每个技术栈都要有指定的测试框架 + 最低覆盖底线:"这个栈难测"(小程序 / 原生 App / 桌面壳)通常是工具链还没搭,不是不能测——先把 test harness 搭起来,别让"难测"成为零测试的借口。
- 变更能独立部署,不需要"一堆东西一起上"。
- 有回滚/前滚路径;数据库变更向后兼容(可与旧版本共存)。
- 迁移契约:迁移必须有序 + 幂等 + N-1 向后兼容(新 schema 能被上一版代码安全运行);runner 用唯一表记录已执行 + 并发锁 + 单迁移事务。回滚优先forward-only(写一条修正迁移),而非 down()。
- CD 必须 gate:部署管线要跑与本地同款的检查门禁(typecheck/lint/test/架构守卫),并在构建前校验必需 env 存在且合法——CD 跳过测试 = 把没跑过测试的代码直接送上线。
- 依赖可复现:lock 文件锁精确版本并入库,manifest 里才用范围。禁止
dev-master/SNAPSHOT/latest这类浮动依赖进生产——否则构建不可复现,出事无法定位、无法回到"上次好的状态"。 - 密钥/凭证绝不入源码:DB 密码、云厂商 AK/SK、第三方 secret 一律走 env / vault,不写进代码或配置文件并提交。用扫描器(secrets scanner)守门,且定期审计扫描器的 allowlist / 忽略规则——扫描器存在但有豁免漏洞 = 虚假安全(真实踩坑:仓库装了 secrets 扫描却仍泄露明文密钥)。一旦泄露立即轮换。
- 遵循 12-factor 的部署铁律(节选 · 参考 12factor.net):
- 配置放环境变量,不是把含密钥的
.env提交进库;后端服务(DB/缓存/队列)当可替换的附加资源(靠 URL/凭证连接,可随时换实例)。 - build / release / run 三段分离:同一构建产物 + 发布期注入配置,跨环境复用同一 image(呼应"可复现")。
- 进程无状态 + 可随时丢弃(状态进外部存储),提供 readiness/liveness + 优雅关闭以支持零停机。
- dev/prod 尽量一致(parity),减少"本地好好的、上线就炸";日志当事件流写 stdout 由外部聚合(呼应可观测性)。
- 配置放环境变量,不是把含密钥的
- 通过 feature flag 实现"部署 ≠ 发布",支持灰度。
- 渐进式发布:按百分比/人群逐步放量(canary),缩小爆炸半径;flag 兼作kill switch,故障时一键降级失败子系统。
- 目标投放走 evaluation context(用户分群 / 地域 / 灰度比例),而不是在代码里硬编码 rollout 逻辑。
- flag 是技术债,必须有 owner + 到期日,并安排清理:长期不删的 stale flag 会让代码分支爆炸、行为不可预测。开 flag 时就登记"何时删"。
- 用厂商中立的抽象(如 OpenFeature 规范)隔离 flag SDK,别把业务代码绑死某个 flag 平台。
- 例外(单部署目标项目):无多环境灰度需求的桌面/单机/单 LAN 部署,feature flag 非必须;env 门控 + 可回滚部署即可满足"部署 ≠ 发布"的实质。
- 上线后有可观测性(日志/指标/告警)能确认变更效果。生产是最慢但最真实的反馈层,要主动建设:
- 三类信号都要有:trace(跨服务请求链路)/ metric(量化指标)/ log(离散事件),而不是只有日志。
- 跨服务传播 trace context(HTTP header / 消息 / RPC 都带上),否则分布式调用无法串成一条链(与第九部分跨服务呼应)。
- 统一语义约定与资源标签(service name / version / env / deploy id),让跨服务关联、过滤、告警成为可能。
- 优先自动埋点(auto-instrumentation)再补手写;采样按环境分级(生产省成本、预发高保真)。
- 参考实现:OpenTelemetry(厂商中立的 trace/metric/log 标准)。
- PR 足够小,单一目的;描述写清:为什么改、改了什么、怎么验证。
- CI 全绿;自己已先 review 过一遍 diff。
- 关联需求/issue;关键业务边界、外部 API errcode、时间窗、隐式契约已内联注释说明。
- 不夹带无关重构(重构单独 PR)。
A. 学习/反馈维度
- 是否有对应测试?测试是否覆盖行为与边界,而非仅 happy path?
- 增量是否合理、可独立合并?是否引入长命分支隐患?
B. 复杂度维度
- 职责是否单一、内聚?有没有"上帝类/巨函数"?
- 业务逻辑是否被框架/IO 细节污染?抽象是否恰当(不过度、不投机)?
- 引入的耦合是否必要?有没有"为复用而强耦合"?
C. 试金石维度
- 难测吗?如果难测 → 标记为设计问题,而非"补个测试就行"。
- 能独立部署/回滚吗?DB 变更向后兼容吗?
D. 可读性与证据
- 半年后的人能读懂吗?命名是否表达意图?
- 性能/安全类断言有没有数据或测试支撑,而非口头声称?
- 评论对事不对人;给出理由和替代方案,不只说"我不喜欢"。
- 区分阻塞项(must-fix) 与 建议项(nit/suggestion),标注清楚。
- 有分歧且无定论 → 不靠职级压人,用最小实验或度量裁决。
- Review 是双向学习,不是守门检查。
| 指标 | 含义 | 方向 |
|---|---|---|
| 部署频率 | 多久能上一次线 | 越高越好 |
| 变更前置时间 | 提交到上线的时间 | 越短越好 |
| 变更失败率 | 上线导致故障的比例 | 越低越好 |
| 故障恢复时间 (MTTR) | 出事到恢复的时间 | 越短越好 |
反模式提醒:不要用代码行数、提交数、"看起来很忙"来衡量生产力;不要把估算当承诺。
DORA 四指标仅用于团队自我改进,严禁与个人绩效考核/排名直接挂钩。
一旦用于考核,指标立刻被博弈:为冲"部署频率"拆碎无意义提交、为压"前置时间"跳过测试、为降"变更失败率"瞒报故障。 (Goodhart 定律:当度量变成目标,它就不再是好的度量。)
正确用法:把四指标当作团队级的趋势信号和实验反馈——"我们最近这项变差了,根因是什么、怎么改",而不是"谁的数字更好看"。
DORA 衡量交付效能,SLO 衡量"用户体验到的可靠性",两者互补。参考:Google SRE book。
- SLI 从用户体验倒推:测用户真正在乎的(延迟 / 成功率 / 可用性),而不是最好测的。用百分位(p50/p95/p99)看尾部,别只看平均。
- 错误预算当发布闸门:不追求 100%(那会扼杀创新)。设定允许的 SLO 失误额度;预算用完 → 冻结发布,直到窗口重置——让"上新 vs 稳态"的张力有客观裁决,而非吵架。
- 按错误预算燃烧率(burn rate)告警,而非单条指标抖动;告警对症状(用户受影响)不对原因,避免告警疲劳。
- 内部 SLO 比对外承诺更严,留缓冲;SLO 数量精简,只留真会用来排优先级的(没人用的 SLO 只制造噪音)。
本规范要求较高的工程素养与基础设施(秒级测试、稳定快速的 CI、特性开关 + 监控)。 若基础设施不匹配就硬推,只会带来挫败感。 应把规范当作"目标态蓝图",分阶段引入,逐步投资配套平台。
第一阶段 · 先尝到反馈与协作的甜头
- 落地:1.2 反馈循环、1.4 TDD/测试驱动设计、第四部分 Review 文化。
- 目标:让团队感受到"快速反馈 + 建设性审查"的好处,建立信任。
第二阶段 · 解耦与可独立部署
- 落地:第二部分(模块化/解耦)、第三部分 3.2 可部署性。
- 配套:建设特性开关管理、独立部署/回滚能力、基础监控。
第三阶段 · 全面主干开发 + 数据驱动
- 落地:1.1 小增量 + trunk-based、第五部分 DORA 度量。
- 前提:CI 稳定且分钟级、可观测性完善。
适用性提示:本规范最适合已认同持续交付/敏捷工程、且有一定自动化测试与 CI/CD 基础的中大型团队;传统模式转型团队请按上面三阶段渐进,不要一步到位。
来自真实元数据驱动平台的实战检验:架构约束不该只活在文档和 review 者脑子里——能写成可执行检查的,就别靠人肉守。 这是 Farley"可测试性是设计探针"思想的延伸:架构不变量也应该可测试、可在提交时自动验证。
- 架构不变量用可执行守卫(fitness functions)在提交/CI 时强制,而非只靠 review。可被写成测试的常见不变量:
- 层边界(禁止跨层 import,如业务层直接 import 元数据 schema、引擎层反向依赖业务层)
- 包/模块边界(
a不许 importb.internal)、循环依赖检测、命名约定(controller 以 Controller 结尾等)、内部 API 访问控制(仅指定模块可用@Internal) - 关键 dispatcher / 入口文件的体积与形态上限(防止它悄悄长成 god 文件)
- 实现为 pre-commit / CI 的脚本或 lint 规则;例外走 allowlist + 到期日 + ADR,不许无限期豁免。
- 参考实现:ArchUnit(Java,把架构规则写成单测)是这一思路的成熟范例,各语言有等价物——核心是"架构是活的契约,不是 aspirational 文档"。
- SSOT 用扫描器守,不靠自觉:每个权威配置源(数据库表 / seed / env)配一个静态检测,扫描"平行真相"——同域的
*LIST / *MAP / *RULES / *CACHE常量;命中即告警或拦截,豁免需在 PR 里给迁移计划 / 兼容窗口 / 删除期限。 - 风险路由 review 深度(细化第四部分):按
复杂度 × 风险 × 不确定性 × 副作用打分,分级 review——- 琐碎改动:免 review;普通单模块:1 人轻审;跨模块:2 人 + 计划审;架构/数据/权限:全流程 + 对抗/新上下文评审。
- 把分数和理由写进 PR。统一 review 强度是浪费:该重的不够重,不该重的拖慢反馈。
- 高风险变更随附可执行验收脚本(schema 迁移 / 引擎切换 / 跨层 cutover / 影响生产行为):脚本验证变更后的可观测量(payload 字节等价、延迟 SLO、预期行数、错误率),结果作为结构化 marker 记入 commit,使"这次切换安全吗"可被静态审计。
⚠️ 防腐警示:一次性 verify 脚本会腐烂成 stale gate(baseline 过期后误报或失效)。必须登记索引 + 随平台演进维护或显式退役,别让它变成没人维护的僵尸门禁。
- 缺陷复发 → 沉淀成守卫:同一类缺陷重复出现 N 次(如 2–3 次),就把它固化为一条 fitness function / lint 规则 / 回归测试。让规范与防线随真实失败自我演进,而不是反复踩同一个坑。
适用性:fitness functions 在分层架构 / 元数据驱动 / 多人协作或 AI 参与的系统收益最大;单文件小脚本项目过度套用反而是负担(回到元原则:上下文优先)。遗留 / 老栈系统别从这里开始——先走第八部分的最小安全网,有了可复现+可回滚+特征化测试,再逐步引入 fitness functions。
来自真实老栈系统(无 CI、生产直改、无自动化测试、千行级 god 文件)的实战检验:前七部分大量假设"现代栈"。对遗留系统直接要求"先上 CI + 80% 单测 + feature flag"= 规范自己警告的"硬推带来挫败感"。 遗留系统不套绿地严格度,按 "风险下降 / 投入" 排序铺最小安全网,且绝不靠重写。
- 先建可复现 + 可回滚(最高 ROI,先于一切):VCS 是部署的唯一真相源——改完即提交,让生产 == 某个已知 commit。这一步立刻换来回滚 + 审计 + diff 能力。
- 反模式:生产上堆积大量未提交热改、用
*.bak/ 备份目录手工存档。手工备份制造"哪个才是线上"的歧义;VCS 就是备份。
- 反模式:生产上堆积大量未提交热改、用
- 改遗留代码前先打特征化测试(characterization test):对一段没法单测的庞大代码,在 HTTP / API 边界对其现有行为打快照,锁住"改之前是什么样",再动手。这是遗留系统版的"可测试性"——不要求先有完美单测,只要求改动前先钉住现状。
- god 文件用绞杀者模式(strangler)增量替换:新逻辑抽到新类 / 新模块,流量逐步路由过去,旧文件慢慢缩小。禁止大爆炸重写(细化第二部分"迁移不是重写":遗留场景尤其如此)。
- no-CI 热补丁流的最小门禁:做不到全套 CI 时,至少——语法/静态检查(如
php -l/ linter)+ 改动端点的 smoke 验证 + 先提交后部署。比"什么都不查直接存盘上线"高一个数量级。 - 锁依赖版本:禁止
dev-master/latest这类浮动依赖,否则构建/行为不可复现,出事无法定位。 - 可追溯性可廉价保留:即使没有 CI 和测试,老栈也能做到——conventional commit + 关联工单号 + 关键业务边界 inline 注释。低成本、高回报。
排序原则:遗留系统的头号风险往往不是"没单测",而是"无可复现 / 无回滚 / 无审计"。先解决后者,再谈测试与解耦。每一步都要能独立见效,不要求"先大改造再受益"。
来自真实多语言后端系统的实战检验:两个不同语言的服务直接读写同一个数据库,等于它们之间存在一份谁都没写下来的契约。 没有显式契约时,一方改了 schema,另一方在运行时才炸——而且因为分属不同仓库 / 不同 CI,这种变更天然无法原子完成。
- 共享数据库是隐式契约,要让它显式:当 ≥2 个服务(尤其不同语言)读写同一批表时,维护一份表所有权登记(哪张表 / 哪些列由谁拥有 DDL 与写权),并把 schema 当作版本化契约对待。改"共享表"需所有消费方签字。
- 跨服务 schema 变更必须向后兼容、分步走:分仓 + 分 CI 意味着变更无法在多个服务间原子完成。用 expand-contract / 并行变更——先加新列(两边都能跑)→ 各服务迁移到新列 → 最后删旧列;禁止破坏性重命名一把梭(一个仓先提,另一个仓会一直炸到它也提)。
- 具体危险操作 → 安全替代(参考 strong_migrations 的清单):删列前先在代码层标记忽略并先部署上线;改列类型 / 加约束分两步(先加不校验 → 再单独校验存量);并发建索引避免锁表;backfill 分批且放在迁移之外;上线前用生产规模数据演练锁与超时。
- 服务间 API / 消息用消费者驱动契约测试(不只是共享 DB 的列假设):消费方在自己的测试里生成"契约"(contract by example),提供方在 CI 里独立验证自己满足这些契约,无需把所有服务一起部署。契约存中央 broker,发布前用 can-i-deploy 类门禁确认"我升级不会弄坏任何已知消费方"。参考实现:Pact(consumer-driven contract testing)。
- 在共享数据上做契约测试:消费方对共享表的假设(字段类型 / 精度 / 枚举取值 / 时区语义,如不同语言的时间戳处理差异)必须有测试守住,而不是各猜各的。
- 必须一起变的东西,要有跨边界的变更编排:若它们分散在多个独立仓库,迁移脚本应集中在单一真相目录,CI 强制"所有语言后端都通过 schema 兼容检查才放行"。分仓不该意味着失去协同。
- 一个服务只用一套数据访问策略:同一实体同时挂两套 ORM / 两份映射(如 JPA + 另一个 ORM)= schema 映射有两份真相,迟早漂移——回到 SSOT 原则。
适用性:这一部分针对共库 / 多服务 / 多语言 / polyrepo 场景。单服务单库项目用不上,别套(元原则:上下文优先)。最干净的长期解法往往是服务各拥有自己的数据,跨服务走 API / 事件而非直连对方的表——但在共库既成事实下,先用上面的护栏止血。
安全不是"上线前扫一遍",而是需求阶段就写进去、review/测试阶段逐条验。参考:OWASP ASVS(Application Security Verification Standard)——它同时是安全需求清单和验证 checklist。
- 用 ASVS 当安全需求 + review/测试 checklist,并按风险分级:L1(基础,人人适用)/ L2(多数业务应用)/ L3(高敏:支付、医疗、PII)。按数据敏感度选级别,而不是凭感觉(呼应第七部分的风险路由)。
- 覆盖核心类目:认证、会话管理、访问控制(越权)、输入校验与输出编码、加密、错误处理与日志、数据保护。每类至少有对应的测试或 review 项。
- 高频硬规则(出现即 must-fix):
- 参数化查询 / 预编译语句防注入,禁止字符串拼 SQL/命令;
- 按上下文输出编码(HTML / JS / SQL / OS 各不同),防 XSS/注入;
- 访问控制在服务端校验,不信前端(呼应第三部分"安全/权限边界专测");
- 密钥不入源码(见第三部分)。
- 安全需求可追溯:把 ASVS 需求编号挂到测试用例 / review 项上(ASVS 提供 CSV/JSON 可编程映射),让"是否验过"可审计;第三方组件 / 采购也挂同一套要求。
适用性:L1 是所有联网应用的底线;面向公网、处理用户数据的系统应到 L2;金融/医疗/强合规到 L3。内网小工具按 L1 即可(上下文优先)。
小步迭代拿反馈,科学实验做决策,死磕复杂度; 写之前先问"怎么测、怎么部署",PR 小而清,Review 对事不对人、有分歧就用证据。