首页/开发/代码质量与评审速查

代码质量与评审速查

代码评审方法、静态检查与规范、重构手法、SOLID 原则与设计模式、技术债与质量度量:代码质量速查。

共 316 条更新于 2026-10-12所属领域:开发
共 316 条

代码评审64 条

评审清单、评审节奏与常见反例

Google 代码评审标准:以改进代码为目标

Google 工程实践文档给出的评审决策标准:只要改动让代码库整体变好就应放行,不要求完美;当改动让代码明确劣化时才阻止。

小 CL 原则:一次评审一个自包含改动

Google 评审文档建议把 CL 控制在小规模(约 100 行为参考),每个 CL 是单一自包含改动,便于评审、合并与回滚。

评审评论怎么写:问题 + 原因 + 建议

Google 评论写作指南:评论要说明问题的原因与影响,并给出方向性建议或提问,而不是只丢一句结论。

评审者指南:按层次看改动

Google 评审者指南:先看设计意图再看细节,关注点按'设计 → 功能 → 复杂度 → 测试 → 命名 → 风格'分层推进,风格细节交给工具。

LGTM 的含义与边界

LGTM(Looks Good To Me)表示评审者未发现阻塞问题;它不担保零缺陷,作者仍对质量负责,合并前 CI 与测试条件仍需满足。

评审速度:一个工作日内响应

Google 评审速度准则:评审者应在约一个工作日内对改动给出回复;忙不过来时明确告知作者或转交他人,避免改动卡在队列里。

评审检查项:测试

测试检查:新增行为是否有对应测试、测试能否失败、断言是否具体,以及改动对既有行为的回归是否有测试保护。

CL 作者指南:写清意图再请评审

Google 作者指南要点:把改动写成单一自包含的 CL、在描述里写清意图与验证方式、提交前先自审,然后邀请评审并快速响应意见。

三种评审动作:评论、批准、请求修改

GitHub 评审模型的三种提交方式:Comment(仅评论)、Approve(批准)、Request changes(请求修改,默认阻止合并)。

评审检查项:正确性

正确性检查聚焦行为:意图是否达成、边界与异常路径、空值与溢出、幂等与顺序假设,以及对外部依赖行为的理解是否正确。

nit 前缀:标注小问题的惯例

Google 评审惯例:用 Nit: 前缀标注不影响正确性的小问题,让作者能区分'必须改'与'可改可不改',主次一目了然。

评审检查项:错误处理

错误处理检查:失败路径是否被显式处理、错误是否被吞(空 catch)、错误信息能否支撑排查、资源在异常路径上是否仍释放。

十分钟快速评审清单(速查卡)

时间有限时的评审兜底清单:从意图、正确性、测试、安全到兼容性逐项快扫,找不出问题也要能说明看过什么。

评审清单的正确用法

清单把评审注意力固定在易漏项上:用于自查与交叉检查,不替代理解代码;团队应按技术栈裁剪成短清单并定期回顾。

评审检查项:安全

安全评审速查:输入校验与注入、认证授权、敏感数据落点、访问控制与 SSRF 等高风险模式是否被引入改动。

作者如何处理评审意见

Google 作者指南要点:对每条意见给出回应(修改或解释),不要已读不回;不同意时先澄清事实再讨论,避免情绪化对抗。

提交前自审:先读一遍自己的 diff

请评审前的自审步骤:通读完整 diff、删除调试残留与无关文件、确认注释与描述准确、跑一遍本地校验与测试。

评审检查项:命名与可读性

命名检查:名称是否表达意图与领域含义、是否与代码库既有术语一致、名称长度与作用域是否匹配、控制流是否一眼可读。

PR 描述要素:背景、改动、验证

评审友好的 PR 描述模板:说明背景与动机、改动范围与关键决策、验证方式与结果、以及风险与回滚方式。

拆分大 CL 的常用切法

大改动拆分的实战切法:横向按文件或模块、纵向按依赖顺序、先重构后功能;每个新 CL 合入后仓库保持可构建可测试。

评审分歧的处理

评审意见不一致时的处置顺序:先澄清事实与目标,再讨论方案;两轮内未收敛则转当面沟通,由作者综合意见决策并留记录。

GitHub 评审流程要点

GitHub PR 评审的实操要点:行级评论与 suggestion 建议块、批量提交评审、评审请求(reviewers)的用法与阅读顺序。

评审检查项:并发与共享状态

并发检查:共享可变状态是否有保护、锁的范围与获取顺序、阻塞操作的超时与取消、回调在锁内执行的死锁风险。

什么时候用请求修改(Request changes)

请求修改适用于必须修复后才能合入的问题:正确性缺陷、安全风险、兼容性破坏、测试缺失;小建议与风格问题用普通评论即可。

评审检查项:接口与兼容性

接口变更评审:公有 API 的增删改是否破坏兼容、默认值与序列化格式变化、版本策略与弃用流程是否完整。

GitLab MR 审批规则要点

GitLab MR 的审批机制:Approval Rules 可要求指定人数或团队批准,可与 Code Owners 联动;任一评审者请求修改即阻止合并。

一个改动只做一件事

单一职责同样适用于变更:一个 PR 只解决一个问题;混合多个主题的改动难评审、难回滚、难二分定位。

风格问题交给工具,而不是评审

格式与风格应由格式化器与 lint 自动处理:评审讨论风格消耗注意力且有主观性;团队约定工具管风格,人管设计与正确性。

评审检查项:性能

性能评审:关注热路径上的分配与 IO、循环内查询(N+1)、缓存与失效策略、以及复杂度的量级变化,而非常数级微调。

Conventional Comments:给评论打标签

Conventional Comments 约定用标签起始评论:praise、nitpick、suggestion、issue、todo、question、thought,让作者识别类型与紧急度。

评审中要求补测试的时机

遇到行为变化无测试、修复无复现用例、关键分支无覆盖三种情况应要求补测试;纯注释、纯格式与无行为变化的机械重构可豁免。

评审发现误提交的密钥怎么办

评审中看到提交进代码的真实密钥或令牌:按已泄露处理——先吊销轮换,再清理代码与历史;仅从当前文件删掉不等于安全。

评审检查项:依赖变更

依赖变更评审:新增依赖的必要性与维护状态、版本锁定与可复现构建、许可证兼容、供应链与漏洞影响。

评审检查项:文档与注释

文档检查:行为变化是否更新文档、注释是否解释为什么、README 与示例是否仍可直接运行、变更日志是否同步。

分支保护与强制评审

分支保护把评审要求固化为规则:主干必须通过 CI、达到批准数、解决讨论后才能合并,防止绕过评审的直接推送。

评审检查项:日志与可观测性

日志评审:关键路径是否有可诊断日志、级别是否恰当、是否避免敏感信息、监控指标与告警是否需要同步更新。

评审检查项:数据迁移与回滚

数据迁移评审:脚本是否可重入、大表变更的锁与时长、新旧代码与数据格式的兼容窗口、回滚方案是否演练过。

合并前检查(pre-merge checklist)

点合并前快速核对:CI 全绿、批准齐全、讨论已解决、目标分支正确、无冲突与调试残留;赶时间时这些项更不能跳过。

评审积压的处理路径

改动长时间无人评审时的处理:先在评论区点名并给出期望时间,无法及时处理则请对方指定替代评审人,仍无响应再走团队约定渠道。

评审检查项:配置与功能开关

配置与开关评审:默认值是否安全、开关是否有清理计划、环境差异是否显式、失败时的降级行为是否明确。

CODEOWNERS:把评审责任绑定到目录

CODEOWNERS 文件为路径指定默认评审人:PR 修改到对应路径时自动请求 owner 评审,与分支保护配合可要求必须批准。

非阻塞评论:标出不修也能合

用 non-blocking 或 nit 明确标注非阻塞意见:改动可以带着这些小建议合并,作者后续处理,减少为细节反复往返。

评论语气:对事不对人

评审评论针对代码与问题,不针对作者:用事实、影响与提问推进讨论,避免情绪化表达,保护团队的心理安全感。

评审者读 diff 的策略

读 diff 的两种模式:按文件线性通读,或按意图切片(核心逻辑、测试、配套);大 diff 先看统计信息建立全貌再逐块读。

评审检查项:大文件与二进制

大文件与二进制评审:构建产物、数据集、媒体文件是否应入库;二进制不可评审,且会让仓库历史体积不可逆增长。

Draft PR:未完成改动的共享方式

把未完成的改动以草稿(Draft)状态推送:可跑 CI、可收集早期反馈,同时明确表示暂不可合并,避免评审者误判。

反模式:橡皮图章式评审

橡皮图章指不看或草草扫一眼就批准:评审信号失真,缺陷照常流入主干;常见成因是流程强制批准与评审量过载。

评审时如何对待 flaky 测试

不稳定的测试会侵蚀信任:评审新增或修改测试时,检查时间依赖、随机数据、共享状态与外部依赖等常见不稳定来源。

无责文化下的代码评审

把评审定位为共同改进代码的机制而非问责工具:根因指向系统与流程,评审意见谈代码不谈人,才有人愿意暴露问题。

评审遗留项怎么追踪

评审中产生的非阻塞遗留项要有归属:当场能改的直接改,不能改的建任务或写 TODO 并关联跟踪编号,避免欠账消失。

评审优先级:先核心后外围

评审时间有限时按影响面排序:核心逻辑与数据路径优先,其次接口与兼容性、测试质量,最后命名与文档、风格细节。

单次评审的体量控制(研究视角)

评审研究(SmartBear 的评审著作)提示:单次审查超过约 400 行后缺陷发现率下降;控制单次评审量、分批进行更有效。

结对评审与即时评审

即时评审(结对、旁站)在写代码的同时完成反馈:适合危急修复、复杂逻辑与新人上手;结论应回写到 PR,保证可追溯。

反模式:过度把关与微观管理

过度把关指评审者用个人偏好阻塞改动、要求完美才放行:抑制贡献意愿、拉长交付周期,与持续改进的评审标准相悖。

多轮评审:每轮都要收敛

评审多轮是常态,但要保证每轮收敛:作者处理完阻塞项再请求复核,评审者聚焦增量变化,避免每轮重新打开已决问题。

评审作为知识传递

评审是低成本团队学习机制:评论解释理由让新人学到规则,评审他人代码接触陌生模块;用好它可以降低单点依赖。

生成代码的评审方法

生成代码(ORM 迁移、protobuf、模板产物)重点不看逐行实现,而看输入与生成规则:源头正确、差异符合预期、生成物可重现。

评审度量怎么用

评审度量用于发现流程瓶颈:首次响应时长、评审完成时长、轮次与 CL 规模分布;用于改进流程,而不是考核个人。

合并后观察:评审的延伸

合并不是终点:高风险改动应在发布后观察关键指标与告警,确认行为符合预期;发现问题快速回滚,并把教训反馈到评审清单。

文档类改动的评审

文档评审重点:事实准确性(命令、参数、版本)、示例可运行、结构可扫读、链接无失效;用照着做能否成功来检验。

Gerrit 评审模型:Change 与 Code-Review 标签

Gerrit 以 Change 为评审单位:每个提交带 Change-Id,评审人给出 Code-Review 分值(-2 到 +2),+2 并通过 Verified 后可合入。

作者走查(walkthrough)

作者向评审者当面讲解改动的走查方式:适合大型或高复杂度改动,先讲背景与设计,再进入代码,常常一次抵数轮评论。

外部贡献者评审要点

开源项目评审外部 PR:先对齐贡献指南与范围,再评审实现;对首次贡献者友好表达,明确列出必须修改项与后续步骤。

评审的双向学习

评审是双向的:作者从评论中学到团队标准与设计权衡;评审者从他人实现里看到新写法与盲区;把它当学习机制,评审质量会更好。

规范与静态检查65 条

Lint、格式化、命名与注释规范

PEP 8:Python 风格基线

Python 官方风格指南:4 空格缩进、行宽上限 79 字符(注释与文档 72)、snake_case 函数变量名、CamelCase 类名等基础约定。

ESLint:JavaScript 检查入门

ESLint 是 JavaScript/TypeScript 的可插拔检查器:规则分级 off/warn/error,--fix 可自动修复部分问题,从 recommended 集合起步。

Black:不妥协的格式化器

Black 以统一为目标的 Python 格式化器:默认行宽 88,几乎不接受风格选项,用自动化结束风格争论,配置写入 pyproject.toml。

Ruff:Rust 实现的快速 lint 与格式化

Ruff 用 Rust 实现,聚合数百条规则(flake8、isort、pyupgrade 等兼容规则)并提供格式化;单工具替代多工具链,速度优势明显。

Prettier:固化格式,消灭风格争论

Prettier 是少量选项的格式化器:默认 printWidth 80、2 空格缩进、分号开启、双引号、trailingComma all(v3+);支持 JS/TS/CSS/MD 等多种语言。

mypy:Python 静态类型检查

mypy 读取类型注解做静态检查,--strict 开启一组严格选项;支持增量缓存与 stub 包,是渐进式类型化的常用工具。

typescript-eslint:类型感知规则

TypeScript 的 ESLint 插件体系:部分规则需要类型信息(parserOptions.project),预设如 recommended-type-checked、strict-type-checked,检查能力更强但更慢。

gofmt:Go 官方格式化

Go 自带格式化器:tab 缩进、无风格选项,gofmt -l 列出未格式化文件、gofmt -w 就地修改;goimports 额外管理 import 增删与分组。

golangci-lint:Go 检查聚合器

golangci-lint 聚合多种 Go 检查:默认组含 errcheck、govet、ineffassign、staticcheck、unused 等(版本间略有差异),配置文件 .golangci.yml。

SonarQube:持续代码质量平台

SonarQube 是持续检查平台:规则库、质量配置、质量门与新代码聚焦构成核心模型;通过 Scanner 与 CI 集成,多语言项目统一门禁。

CI 中的 lint 门禁(示例)

把静态检查放进 CI:PR 触发、固定工具版本、失败即门禁;CI 是静态防线的兜底,本地钩子可跳过,CI 不可跳过。

no-floating-promises:别让 Promise 悬空

未处理的 Promise 会导致错误被吞、执行顺序错乱:typescript-eslint 的 no-floating-promises 规则要求 await、显式 void 或统一错误处理。

flake8:组合式 Python 检查

flake8 组合 pycodestyle(风格)、pyflakes(未使用导入等)与 mccabe(复杂度)三类检查;经典告警如 E501 行太长、F401 未使用导入。

pyproject.toml:统一 Python 工具配置

pyproject.toml 按 PEP 518/621 承载构建配置与项目元数据,同时集中 Black、Ruff、mypy 等工具配置,替代分散的旧配置文件。

Google Java Style:100 列与 2 空格

Google Java 风格指南:行宽上限 100、缩进 2 空格、K&R 括号风格、常量 UPPER_SNAKE_CASE;由 google-java-format 等工具落地执行。

go vet:官方静态检查

go vet 是 Go 官方静态检查:go test 会默认运行一组 vet 检查(printf、atomic、bool、errorsas 等),也可单独执行 go vet ./...。

clang-format:C/C++ 格式化

clang-format 按 .clang-format 配置格式化 C/C++ 等语言:内置 LLVM、Google、Chromium、Mozilla、WebKit、Microsoft 等基座风格,可局部覆盖选项。

ShellCheck:Shell 脚本检查

ShellCheck 检查 Shell 脚本的经典陷阱:SC2086(变量未加引号)、SC2046(未加引号的命令替换)等,是脚本评审的默认工具。

pre-commit:提交前钩子框架

pre-commit 把检查挂在 git 提交前:配置文件声明仓库与钩子清单,支持多语言工具,pre-commit run --all-files 可整体执行。

Secret 扫描:提交前拦截凭据

把密钥泄漏拦在提交与推送阶段:gitleaks 扫描工作区与 git 历史,检测 AWS 密钥、私钥、Token 等模式;发现泄漏须轮换凭据并清理历史,仅删文件不算处置。

PEP 257:docstring 规范

Python 文档字符串约定:三重双引号;首行摘要句 + 空行 + 细节段落;描述行为与参数含义,供帮助系统与 IDE 引用。

Pylint:检查与评分

Pylint 输出分类消息(C 约定 / R 重构 / W 警告 / E 错误 / F 致命)与 0-10 评分,支持按模块禁用与 --fail-under 门禁。

Bandit:Python 安全静态检查

Bandit 扫描 Python 代码中的安全模式:测试编号如 B101(assert 用于校验)、B301(pickle)、B602(subprocess shell=True)。

ESLint Flat Config(v9 默认)

ESLint 9 起默认使用 Flat Config(eslint.config.js):配置为数组式导出,手动组合文件范围与规则,旧 .eslintrc 进入兼容维护状态。

Checkstyle:Java 规范检查器

Checkstyle 检查 Java 代码规范:内置 google_checks.xml 与 sun_checks.xml 预设,覆盖 Javadoc、命名、import 顺序、行宽等;可集成进构建。

clang-tidy:C++ 检查与现代化

clang-tidy 提供多族检查:bugprone(易错模式)、modernize(现代化改写)、performance、readability、clang-analyzer(静态分析),支持 --fix 自动修复部分问题。

依赖的许可与漏洞检查

依赖治理两类检查:许可合规(生成许可证清单、识别传染性许可)与已知漏洞(npm audit、pip-audit、govulncheck 等),结果纳入 CI 并设定处置时限。

Python 类型注解关键 PEP

类型注解四件套:PEP 484(typing 基础)、PEP 585(内置泛型 list[int])、PEP 604(X | Y 联合类型)、PEP 695(类型参数语法,3.12+)。

ESLint complexity:圈复杂度检查

ESLint 内置 complexity 规则限制函数圈复杂度:默认阈值 20,可按团队基线在配置中收紧(如 15),用于标记难理解的函数。

Prettier 与 ESLint 的分工协议

格式与语义分开:Prettier 管格式,ESLint 管逻辑与通用实践;用 eslint-config-prettier 关闭冲突的格式类规则,避免互相覆盖。

husky + lint-staged:前端提交守卫

husky 管理 git 钩子,lint-staged 只对暂存文件跑检查:两者组合是前端仓库的常见提交守卫,控制提交耗时。

isort:导入排序与分组

isort 按约定排序 import:标准库、第三方、本地包分组;配合 profile=black 避免与 Black 的格式冲突,也可用 Ruff 的 I 规则替代。

Pyright:类型检查模式与工具链

Pyright 提供 basic/standard/strict 三档类型检查模式,命令行可进 CI;VS Code 的 Pylance 语言服务基于它,配置可共用。

eslint-disable 的合理边界

禁用注释是技术债:优先行级禁用(eslint-disable-next-line)并写明原因,避免文件级整片禁用;必要时统计禁用数量并定期审计。

SpotBugs:Java 字节码缺陷检测

SpotBugs 是 FindBugs 的后继:在字节码层面检测潜在缺陷,典型模式有 EI_EXPOSE_REP(暴露内部可变引用)、NP_NULL_ON_SOME_PATH(可能空指针)等。

Spotless:Java 统一格式化

Spotless 为 Java 等语言提供格式化与清理(Gradle/Maven 插件):常用 googleJavaFormat、removeUnusedImports、trimTrailingWhitespace 等步骤。

staticcheck:Go 深度检查套件

staticcheck 提供 SA 系列(真实缺陷)与简化类检查:能发现无效比较、错误的 time 用法等编译器不报的问题,与格式检查分开对待。

EditorConfig:跨编辑器基础统一

EditorConfig(.editorconfig)统一文件级基础约定:缩进风格与宽度、行尾、字符集、行尾空白、文件末尾换行,主流编辑器原生或插件支持。

存量代码的 lint 治理策略

存量治理公式:老代码宽容、新代码严格——用基线文件冻结存量问题,CI 只对新增违规失败,逐迭代清理小片存量。

commitlint:提交信息规范检查

commitlint 校验提交信息是否符合约定格式(如 Conventional Commits):type、scope、subject 结构固定,配合 husky 在 commit-msg 钩子拦截,CI 兜底校验。

Clippy:Rust 官方 lint 工具

Clippy 提供 700 余条 lint 并按正确性、性能、风格等分组:correctness 默认 deny,其余多为 warn,可用 -D/-A/-W 调整级别或按 lint 名精确豁免。

PMD:Java 源码规则集

PMD 对 Java 源码做规则化静态分析:规则集如 bestpractices 覆盖未使用字段(UnusedPrivateField)、空 catch(EmptyCatchBlock)、生产代码 System.out 等。

Error Prone:Java 编译期检查

Error Prone 是 Google 的 javac 插件:编译期直接报出常见错误,如 ReturnValueIgnored(忽略有意义返回值)、DeadException(创建异常未抛出)、EqualsIncompatibleType。

Hadolint:Dockerfile 检查

Hadolint 检查 Dockerfile 规范:DL 系列规则(如 DL3008 apt 安装固定版本),支持通过 --failure-threshold 控制失败级别。

SonarLint:IDE 内的实时检查

SonarLint 把 Sonar 规则带进 IDE:编码时实时提示,支持连接模式同步服务器质量配置,问题在提交前就被发现。

linter 噪声治理

让每条告警都值得看:误报通过提报或参数调整解决,苛刻规则降级或调阈值,版本升级引入的告警逐类评估,而不是整体无视。

CodeQL:语义级代码扫描(示例)

CodeQL 把代码编译为可查询数据库,用 QL 查询做数据流与污点分析:可发现注入、反序列化等路径问题,在 CI 以 SARIF 报告呈现。

revive:可配置的 Go 规范检查

revive 是 golint 停更后的社区替代:规则可按 revive.toml 配置,命名、注释要求等都能按团队约定裁剪,适合作为规范检查层。

Cppcheck:C/C++ 缺陷类静态分析

Cppcheck 面向 C/C++ 的静态分析:--enable 选择检查类别(warning/style/performance/portability),--error-exitcode 让发现问题直接失败构建。

yamllint:YAML 语法与风格检查

yamllint 检查 YAML 的缩进、行宽、truthy 书写等:CI 配置与 Kubernetes manifest 是主要检查对象,规则可在 .yamllint 中定制。

Quality Profile:规则集的管理

质量配置(Quality Profile)定义某语言下启用的规则与参数:官方内置 Sonar way 等配置,团队应复制后自定义,并把变更当成标准来管理。

rustfmt:Rust 代码格式化

rustfmt 按官方风格重排 Rust 代码:通过 rustfmt.toml 调整少数稳定选项,多模块项目用 skip_children 或按文件就地忽略,cargo fmt 负责批量应用。

Roslyn Analyzers:.NET 静态分析

Roslyn 分析器在编译期对 C#/VB 报告诊断:内置 CA 系列规则加第三方分析器,通过 .editorconfig 或规则集设置严重级别,支持仅对新增代码生效。

PHPStan:PHP 静态分析(可选级别)

PHPStan 用级别 0-10 控制分析严格度,逐级提升并逐级修问题;支持基线文件冻结存量报错,配 phpstan.neon 指定扫描路径与级别。

markdownlint:Markdown 规范检查

markdownlint 检查 Markdown 结构与风格:MD013(行宽)、MD041(首行须为一级标题)、文件末尾换行等,适合文档仓库批量执行。

Semgrep:轻量模式匹配扫描

Semgrep 用近似语法的模式(pattern)在代码中搜索反模式:自带 registry 规则集,也可写项目自定义规则,适合把团队事故经验沉淀成可执行检查。

架构约束检查(依赖方向)

用工具固化分层依赖:dependency-cruiser、ArchUnit、import-linter 等可声明允许的依赖方向并断言违规,把架构图变成 CI 里可执行的检查。

SQLFluff:SQL 检查与格式化

SQLFluff 是可配置的 SQL 检查与格式化工具:必须指定方言(--dialect,如 postgres/mysql),规则以 L 系列编号,支持自动修复。

PHP_CodeSniffer:PHP 编码规范检查

PHP_CodeSniffer 检查 PHP 是否符合编码规范:phpcs 检查、phpcbf 自动修复,PHPCompatibility 等标准可叠加,用 phpcs.xml 声明标准与排除路径。

Monorepo 的分层 lint 策略

多包仓库按边界拆检查:每包独立配置与忽略清单,共享预设避免漂移;CI 只对变更包及其依赖方执行,兼顾时间与覆盖。

生成代码的检查排除

生成代码不参与 lint 与覆盖率统计:按目录排除(如 protobuf 生成物)、加文件头标记(如 @generated),或使用各工具的内置排除机制,避免噪声淹没真实问题。

检查工具的版本一致性

工具版本漂移是 CI 噪声来源:把版本写进项目清单(package.json、requirements、工具链文件)并让 CI、本地钩子、编辑器引用同一版本;升级在独立提交中评审。

检查豁免的注释规范

豁免必须可审计:禁用指令按行、注明规则名与原因、附上跟踪链接(issue/工单),禁止无说明的整文件关闭;定期复查豁免列表,防止长期累积。

检查结果在审查中的呈现

让告警贴着改动出现:CI 以文件与行号注释(annotation)、SARIF 或评论机器人输出到 diff 页,再由审查者判断是否阻断,减少上下文切换。

编辑器实时检查与自动修复

把检查前移到编辑器:保存时自动修复(format on save / fix all)与行内提示,让格式类问题不进提交;编辑器读取项目配置,与 CI 保持同一套规则。

重构手法62 条

常见坏味道与安全重构步骤

安全重构的纪律清单

安全重构靠流程而不靠自觉:小步改动、每步保持可编译可运行、提交粒度小、重构与功能分离、随时可回滚;任何一步失控立即退回上一步。

重构:定义与适用边界

重构是在不改变外部行为的前提下改善代码内部结构(Martin Fowler 定义);它服务于可理解性与可修改性,不是为清闲而改,也不等同于重写。

手法:Extract Function(提炼函数)

把一段可命名的逻辑取出为独立函数:用意图命名替代「这段是干什么的」注释,缩小函数体、暴露复用点;片段内局部变量越少,提炼越顺。

坏味道:重复代码(Duplicated Code)

同一逻辑在多处重复时,修改需要同步多处;先用 Extract Function 合并片段,跨类重复用 Pull Up Method 或 Extract Class;重复的测试代码同样值得处理。

坏味道:过长函数(Long Function)

函数越长越难理解与测试;处理路径是提取与分解:注释分段、条件分支、循环体都可以提炼为命名函数,长度只是信号,可理解性才是目标。

行为不变:重构的验收标准

行为不变指对外可观察行为保持一致:接口输出、错误码、日志格式、副作用、性能数量级都属于行为;内部实现、私有结构、算法步骤可以改。

坏味道:神秘命名(Mysterious Name)

命名没表达意图时先换名:函数、变量、类、模块的名字应说明它做什么或是什么;命名卡壳常意味着职责划分有问题,改名的过程往往揭示设计。

坏味道:过长参数列表(Long Parameter List)

参数过多表示函数依赖面过宽:把成组参数换成对象(Introduce Parameter Object)、把查询替换为对象方法(Replace Parameter with Query)、移除多余参数。

坏味道:霰弹式修改(Shotgun Surgery)

一个变化需要同时改很多处(分散在各处的相似修改)时,用 Move Function、Move Field、Inline Class 把相关代码聚拢;目标是单一修改点。

手法:Replace Conditional with Polymorphism(以多态取代条件)

按类型分支的行为分散在多个条件语句里时:为类型建立类层次,把分支下沉到各自实现;新增类型无需修改调用方,分支逻辑与类型绑定。

坏味道:依恋情结(Feature Envy)

函数频繁访问另一个对象的内部数据:说明职责放错了地方,用 Move Function 搬过去;数据与行为同处一个模块,边界才清晰。

坏味道:基本类型偏执(Primitive Obsession)

用 int / String 表示领域概念(金额、电话、币种):类型即约束,替换为 Replace Primitive with Object / Replace Type Code with Class 后,校验与格式逻辑有归属,调用处更难传错。

手法:Inline Function(内联函数)

把简单到多余的函数收回调用处:函数体与名字一样清楚、或只剩一层转发时,内联能消除不必要的间接层;内联也是重组前清场的手段。

遗留代码:接缝与包装(Legacy Seams)

给无测试的遗留代码找接缝:在接缝处引入包装层(Wrapper、Adapter)与特征测试(characterization test),先锁定行为再逐步重构;不能全量重写时,测试是关键投资。

坏味道:数据泥团(Data Clumps)

多个字段或参数总是一起出现(开始/结束时间、经纬度成对出现):用 Extract Class 或 Introduce Parameter Object 合并为类型,并顺手消除重复校验逻辑。

手法:Change Function Declaration(调整函数声明)

为函数改名或改参数:改名表达新意图,加参数补充信息,删参数简化调用;迁移期可保留旧签名作为过渡(并标记废弃),调用点分批切换。

手法:Replace Nested Conditional with Guard Clauses(卫语句)

嵌套 if 的深层分支改为前置守卫:先处理异常与边界并提前返回,主流程保持在同一层缩进;深层嵌套常意味着条件被反向表达。

童子军规则(Boy Scout Rule)

每次经过顺手改善一点:提交时让代码比检出时略干净(改好一个命名、删一段死代码、补一个测试);小额定投比集中整顿更可持续,也天然控制风险。

坏味道:重复的 switch(Repeated Switches)

同一组 switch 在多处按类型分支时,新增类型要改所有分支;用 Replace Conditional with Polymorphism 把分支收敛到类型体系,或先用 Extract Function 集中。

坏味道:发散式变化(Divergent Change)

一个类因不同原因被反复修改(数据库字段变了改它、税费规则变了也改它):按变化原因拆分,用 Split Phase、Extract Class 让每种变化只影响一个模块。

手法:Encapsulate Variable(封装变量)

把裸字段变为访问器:先建 getter/setter 再逐步收紧可见性,为后续校验、惰性初始化、变更通知留出插入点;全局变量尤其值得封装。

手法:Decompose Conditional(分解条件表达式)

把复杂条件拆成命名函数:条件判断与各分支各自成函数,主干只剩意图表达;与 Extract Function 组合使用,分支逻辑可分别测试。

完整示例:一次条件逻辑重构的前后对照

把坏味道与手法串成一次完整重构:从「长函数 + 重复 switch + 深嵌套」到「卫语句 + 多态 + 命名函数」;每一步保持测试绿灯,提交历史可逐步回溯。

坏味道:夸夸其谈通用性(Speculative Generality)

为想象中的扩展预留的抽象(只有一个子类的基类、无人调用的参数、从未使用的钩子):先删或内联,等真实需求出现再加;YAGNI 是它的处方。

手法:Introduce Parameter Object(引入参数对象)

把总是成组出现的参数替换为一个对象:调用更短、默认值与校验有归属,后续行为也能逐步搬进该对象;适用于 Data Clumps 坏味道。

手法:Replace Primitive with Object(以对象取代基本类型)

为有约束的原始值建立值对象:金额带币种、电话号码带格式、温度带单位;校验、格式化和运算都有归属,类型错误在编译期或构造期即暴露。

手法:Extract Class(提炼类)

一个类承担了两类职责时,把其中一类搬进新类:字段、方法、以及它们之间的依赖一起迁移;提炼后原类以组合引用新类,职责边界显式化。

配套实践:优先不可变(const/final 先行)

把能不变的变量都声明为不可变:缩小可变状态范围,审查时一眼分清「会变的地方」;与 Split Variable、Replace Temp with Query 协同使用,副作用面持续收窄。

坏味道:过长的消息链(Message Chains)

a.getB().getC().getD() 这类链条让调用方耦合于整条对象图:用 Hide Delegate 封装转发,或对调用方引入更贴近意图的方法;链条每多一环,改动波及面更大。

手法:Combine Functions into Class / Transform

多个函数围绕同一份数据工作:用 Combine Functions into Class 把数据与行为合并为类;处理「先输入再输出」的管道用 Combine Functions into Transform 聚为一次转换。

手法:Extract Variable(提炼变量)

把复杂表达式的一部分取出为命名变量:让条件与运算表达意图,调试时也便于观察中间结果;配合引入解释性变量进一步可读。

手法:Introduce Special Case(引入特例)

为「特殊值」(未知客户、缺失配置)建立可复用对象:把判空与兜底逻辑收进特例对象,消除调用方的重复条件判断;空对象模式是它的常见形态。

手法:Move Function(搬移函数)

把函数搬到更合适的位置:引用某个模块更多的函数应住在那个模块里;搬移前先看调用方分布与依赖方向,避免制造反向依赖。

手法:Remove Dead Code(移除死代码)

无人调用、不可达、被开关永久关闭的代码删除:版本库保留历史,不需要靠注释与 if(false) 存档;删前用引用检索与运行时统计确认,删后靠测试回归。

重构改动的评审要点

评审重构 PR 时看三点:行为是否等价(测试证据)、动机是否单一(一次只解决一类问题)、是否引入新坏味道(如更长的参数表);重构与功能混提时请作者拆分。

手法:Split Phase(拆分阶段)

把「解析输入」与「执行逻辑」分成两个阶段,中间用明确的数据结构承接:第二阶段只面向中间结构,逻辑可单独测试,也便于复用不同输入前端。

手法:Encapsulate Collection(封装集合)

返回集合时给出只读视图,修改走专用方法:调用方无法绕过校验与通知,集合的所有变更点收敛到类的内部;对数组、字典同样适用。

手法:Replace Temp with Query(以查询取代临时变量)

把临时变量替换为查询函数:变量被就地复用会掩盖逻辑、阻碍提炼;函数化后可从任意调用点复用,也便于单测该表达式。

手法:Replace Type Code with Class / Subclasses

用枚举、值类或子类取代魔法数字与字符串编码:取值集合受类型系统约束,比较与分支更安全;行为差异明显时再用子类分解编码行为。

手法:Replace Loop with Pipeline(管道取代循环)

用 map/filter/reduce 等管道操作表达集合处理:过滤、转换、聚合各自成段,意图比手工循环更直接;管道链过长时切分为命名阶段。

手法:Replace Derived Variable with Query(派生值改为查询)

删除可由其他数据推导的存储字段:改为计算查询后不存在同步问题;性能敏感且计算昂贵时,用显式缓存并管理失效,而不是隐式冗余。

坏味道:中间人(Middle Man)

类的大部分方法只是转发给另一个对象:用 Remove Middle Man、Inline Class 削减多余委托;封装与转发之间要找平衡点,而不是谁转发谁就坏。

手法:Move Field(搬移字段)

字段应和它主要服务的函数待在一起:把字段搬到使用更频繁的类,减少访问器转发;移动前用封装确认读写点,移动后运行测试验证。

手法:Hide Delegate(隐藏委托关系)

让服务方替你转发对下游对象的调用:调用方不再了解对象图内部,委托链变化被隔离在服务方;与 Remove Middle Man 是一对相反方向的权衡。

手法:Remove Flag Argument(去标志位参数)

布尔参数表示两种行为的函数拆成两个命名函数:调用点自解释、内部分支消失;标志参数是典型的可读性侵蚀点,拆分成本通常很低。

手法:Split Variable(拆分变量)

同一变量被赋予不同含义的值时,拆成多个各自命名的变量:变量名准确、范围缩小,副作用减少;循环变量与收集变量常是重灾区。

重构与性能的次序原则

先重构求清晰,再针对已知热点做性能优化:性能问题用度量定位(profiler),而不是猜测;重构通常不会显著改变性能数量级,担心时用基准测试对比。

坏味道:被拒绝的馈赠(Refused Bequest)

子类只用到父类很少的能力(或抛异常拒绝继承的方法):优先用组合替换继承(Replace Superclass with Delegate),或把不适用的部分下推(Push Down Method / Field)。

手法:Inline Class(内联类)

类不再有单独价值时把它内联回使用方:职责极少、只剩数据袋、或过度提炼的产物;与 Extract Class 互为逆操作,两者都是让边界与当前需求匹配。

手法:Slide Statements(移动语句)

把相关语句聚到一起:变量声明贴近使用点、相关操作集中,为后续提炼创造条件;移动前确认语句之间没有隐藏依赖与副作用顺序。

手法:Split Loop(拆分循环)

一个循环同时做多件事时拆成多个单职责循环:可读性与可提炼性提升,现代编译器与运行时常常能抵消遍历开销;数据量大时先度量再决定。

手法:Combine Functions into Class(合并为类)

函数组共享同一批参数或同一份数据时,将它们收进类:数据成为字段、函数成为方法,调用方从此只面对对象;适合把散落的计算集中成领域行为。

手法:Replace Superclass with Delegate(以委托替换继承)

子类关系只为复用父类实现而建立时,改为持有父类实例并转发需要的能力:类型语义不再被伪造,继承带来的多余接口消失;组合优先的具体应用。

手法:Remove Middle Man(移除中间人)

转发过多时让调用方直接与真实对象交互:中间层只剩透传就失去价值,删掉它减少调用链;先确认中间人没有校验、缓存、权限等隐式职责。

手法:Change Reference to Value / Change Value to Reference(值引用互换)

按相等性语义选择对象模型:值对象按内容相等(钱、坐标),引用对象共享实例(客户、账户);模型选错的典型症状是相等判断与更新语义别扭。

手法:Parameterize Function(函数参数化)

结构相同只是取值不同的多个函数合并为一个带参数的函数:消除重复、统一入口;参数含义太宽泛时拆回多个函数,避免布尔参数可读性问题。

手法:Introduce Assertion(引入断言)

用断言显式化代码所依赖的前提:断言是团队对调用契约的记录,帮助快速定位违反点;生产环境注意断言开关(如 Java -ea)与替代方案(抛异常)。

手法:Replace Return Value with ... / 拆出修改与查询

函数同时承担「回答一个问题」与「改变状态」时,按查询与命令分离:要么返回结果并保持无副作用,要么只做动作不回值;副作用与返回值混杂最常见的坑是重复执行。

手法:Preserve Whole Object(保持对象完整)

从对象取出多个字段只为传参时,改为直接传对象:减少字段级耦合,被调函数可自行取用需要的数据;边界场景注意传递依赖范围是否变大。

手法:Replace Subclass with Delegate(以委托替换子类)

变体组合爆炸(类型 × 场景)时继承难以扩展:改为对象持有策略/委托字段,运行时组合行为;多态从编译期继承转移到运行期装配。

手法:Remove Assignments to Parameters(移除对参数的赋值)

参数是调用方契约的一部分:在函数内改写参数会掩盖真实输入、制造调试困惑;改用局部变量承接中间值,保持参数只读。

手法:Remove Subclass / Replace Subclass with Delegate(撤销子类)

子类的差异只剩数据或极少量分支时,撤销子类后用字段、枚举或委托表达:类数量减少、结构更直白;先确认没有以类型身份参与多态分派的逻辑。

设计原则与模式62 条

SOLID 原则与常用设计模式

单一职责原则 SRP(Single Responsibility Principle)

一个模块应该只有一个被修改的原因(Robert C. Martin 表述):把「一个类一个职责」落到「一个变化原因」,需求变化只影响相关模块;判断用变化轴而非代码行数。

SOLID 速览:五项原则与记忆锚点

SOLID 是五个面向对象设计原则的缩写(Martin 归纳):围绕「职责、扩展、替换、接口、依赖」五个视角,目标是让代码对变化友好;原则是权衡工具,不是评分表。

开闭原则 OCP(Open-Closed Principle)

软件实体应对外扩展开放、对修改关闭(Bertrand Meyer 提出):新增行为靠扩展(新实现、新分支集中于注册点),而不是改动被反复触碰的旧逻辑。

依赖倒置原则 DIP(Dependency Inversion Principle)

高层策略与低层细节都依赖抽象,抽象不依赖细节(Martin 表述):在架构边界放接口,业务核心不 import 数据库与框架类型,方向感由依赖关系体现。

里氏替换原则 LSP(Liskov Substitution Principle)

子类型必须能替换其基类型而不破坏程序正确性(Liskov 与 Wing 的表述):子类不收紧前置条件、不放松后置条件,调用方对基类成立的推理对子类同样成立。

DRY:知识重复的边界

DRY(Don't Repeat Yourself)针对的是知识重复:同一规则在多处表述,改动需同步;外观相似的代码若语义不同、会独立变化,硬合并反而制造错误耦合。

接口隔离原则 ISP(Interface Segregation Principle)

客户不应被迫依赖它们不使用的接口(Martin 表述):大而全的接口拆成面向调用者的细接口,实现类不必为空方法占位,调用方依赖面收窄。

YAGNI:不要为假想需求设计

YAGNI(You Aren't Gonna Need It,极限编程实践)主张:只在真实需要时实现功能;未验证的扩展点、为将来预留的配置面都是要支付维护成本的负债。

KISS:简单优先的设计取向

KISS(Keep It Simple, Stupid,出自 Kelly Johnson 的设计军规)在软件里指:满足需求的前提下选择可理解、少机制的方案;复杂度是成本,不是资历证明。

组合优于继承(Favor Composition over Inheritance)

优先用组合与委托获得复用:继承把子类绑死在父类实现与接口上,组合可以运行期替换、只暴露需要的能力,是 GoF 在《设计模式》中给出的核心取向。

单例模式 Singleton(创建型)

保证一个类仅有一个实例并提供全局访问点:适合进程内共享的配置、连接池等;但全局状态会带来隐藏依赖与测试困难,多数场景用依赖注入持有单实例更可控。

策略模式 Strategy(行为型)

把可互换算法封装为独立实现,运行期选择:消除调用方的条件分支,易于新增变体与单测;上下文只持接口,不感知具体算法。

建造者 Builder(创建型)

把复杂对象的构造过程与表示分离:逐步设置参数、最后 build() 产出不可变对象;适合参数多、有可选组合、需要校验的构造场景。

SOLID 落地:用策略模式实现开闭

把「新增类型要改调用方」改造为「新增类型只加实现」:以折扣计算为例,策略接口 + 工厂/注册表把分支集中到组装点,业务主流程保持不变。

高内聚与低耦合:怎么度量、怎么改

高内聚指模块内部元素围绕同一目的,低耦合指模块间依赖尽量少而稳;两者通常同向变化;工具上可用扇入扇出、不稳定度等指标做观察,最终仍以变更成本为准。

防御式编程与快速失败(Fail Fast)

防御式编程检查输入与状态,快速失败在错误发生点立即终止:构建期能拦的不留到运行期,能抛异常的不静默返回;与契约式设计互补——进入系统时严格校验,内部信任契约。

工厂方法 Factory Method(创建型)

让子类/实现方决定实例化哪个具体类:调用方依赖创建接口而非具体类型,新增产品时扩展工厂实现即可;简单场景用静态工厂函数与之等效。

装饰器模式 Decorator(结构型)

在不改变接口的前提下为对象叠加行为:层层包装(如流处理、压缩、加密、缓存)可自由组合;相比继承,组合方式在运行期决定。

观察者模式 Observer(行为型)

一对多依赖:主题状态变化时通知所有订阅者(推送或拉取);事件总线、监听器、响应式流都是它的形态;注意退订、异常隔离与通知顺序。

SOLID 落地:核心域与基础设施分离

业务核心只依赖接口(端口),数据库、消息、第三方 SDK 作为适配器实现端口:技术选型可替换、核心可脱离环境测试;依赖注入在组装层完成,业务代码无 new 基础设施。

迪米特法则(Law of Demeter)

对象只与「直接朋友」通信:优先调用自身、参数、自建对象的方法,避免 a.getB().getC().do();降低对对象图结构的耦合,但不必教条化。

关注点分离(Separation of Concerns)

把不同关注点(表现、业务、持久化、横切能力)分开组织:Dijkstra 提出的设计视角,MVC、分层、AOP 都是它的具体形态;关注点边界与团队边界常一致。

适配器模式 Adapter(结构型)

把不兼容接口转成调用方期望的接口:包装第三方 SDK、遗留模块、异构协议;适配器只做转换与错误映射,不含业务规则,保持薄与可测。

代理模式 Proxy(结构型)

为其他对象提供同一接口的替身,控制访问:用于延迟加载、访问控制、日志审计、远程调用(RPC 存根);与装饰器的差别在意图——代理控制,装饰增强。

模式误用与过度设计识别

设计模式是词汇表而非目标:没有对应变化点就套模式,会引入额外类型与间接层;识别信号——为模式而生的接口只有单一实现、类名里写着模式名却说不清意图。

SOLID 落地:用契约视角核对子类

给子类做契约审查:列举基类的可见承诺(入参范围、返回值范围、异常、副作用),逐条对照子类实现;拿不准的用基类引用编写集成测试,统一套跑所有子类。

SOLID 自检清单(评审时用)

评审中用五个问题快速扫描:这个类为什么会被改?新增同类能力要改哪里?子类替上去行为还成立吗?有没有人被迫实现不用到的方法?依赖方向指向抽象了吗?

接口稳定性与兼容性设计

对外接口是承诺:向后兼容优先(新增而非删除、可选参数、默认值),破坏性变更走版本与废弃周期;参考 Postel 法则(发送保守、接收宽容)设计边界输入。

分层与依赖方向(同心圆模型)

分层架构约束依赖方向:外层可依赖内层,内层不感知外层(Clean Architecture 同心圆:实体 → 用例 → 接口适配器 → 框架与驱动);依赖穿过边界时由内层定义接口。

责任链模式 Chain of Responsibility(行为型)

让多个处理者依次尝试处理请求,各自决定是否继续传递:适合可组合的校验、审批、过滤管线;链的装配顺序与终止条件要显式,避免请求无人处理。

SRP 实操:如何找到「变化原因」

找变化原因看三类线索:需求来源(不同干系人)、发布节奏(改它的工单类型)、数据依赖(不同上游系统);把同一原因聚集进一个模块,不同原因之间画边界。

贫血模型与充血模型(Anemic vs Rich Domain)

贫血模型把数据与行为分离,业务逻辑集中在服务层;充血模型让领域对象自带行为,服务只做编排。选择取决于团队共识与复杂度:简单 CRUD 贫血够用,复杂规则充血更稳。

封装与信息隐藏(Information Hiding)

模块通过接口承诺能力,把实现选择藏起来(Parnas 1972):隐藏的是「可能变化的设计决策」,不是单纯的私有字段;封装让替换实现不惊动调用方。

设计原则评审清单(落地版)

把原则变成评审问题:这段代码的依赖方向如何?新增同类能力要改几处?有没有为假想需求预留的机制?模块内是否只讲同一层的事?回答时不求一致,要求显式。

抽象工厂 Abstract Factory(创建型)

创建一族相关产品(同一风格的按钮 + 输入框 + 滚动条):产品族内保证一致,风格切换只换工厂实现;产品种类增加时接口需同步扩展,注意这一耦合。

外观模式 Facade(结构型)

为复杂子系统提供简化入口:屏蔽内部协作细节,调用方只面向稳定门面;门面本身不新增业务,保持薄,子系统能力仍可绕过门面单独使用。

静态工厂方法(Static Factory Method 惯用法)

用命名静态方法代替构造器:方法名可表达意图(of、valueOf、parse、create)、可缓存实例、可返回子类型;是 GoF 工厂方法在语言层面的常用变体。

命令模式 Command(行为型)

把请求封装为对象:可排队、可记录、可撤销、可组合(宏命令);编辑器的撤销栈与任务队列是常见应用,命令对象只封装意图与数据。

模板方法 Template Method(行为型)

在基类定义算法骨架,把可变步骤交给子类实现:复用流程、约束扩展点;注意钩子方法的默认行为要安全,别让子类必须实现过多步骤。

OCP 实践:插件与注册机制

把扩展点做成显式注册:启动时扫描/登记实现(插件式),运行时按标识选择;新增能力以新增模块 + 注册条目完成,不触碰既有调用路径。

不变量与前后置条件(Design by Contract 视角)

把约束写成可检查的三元组:前置条件(调用方义务)、后置条件(实现方承诺)、不变量(对象恒真约束);写进测试与断言后,重构与扩展时错误更早暴露。

抽象层次一致性(同一层说同一类事)

函数与模块内部应保持在同一抽象层次:高层编排与底层位运算混排会打断阅读节奏;提炼(Extract Function)是最常用的层级校正手段,配合命名让层次显式。

组合模式 Composite(结构型)

让「单个对象」与「组合对象」拥有一致的接口:树形结构(文件系统、组织架构、UI 容器)中调用方无需区分叶子与分支;注意约束可组合操作。

状态模式 State(行为型)

对象行为随内部状态改变:把每个状态封装为类,状态迁移显式化;相比状态字段 + switch,状态类让「迁移表」可读、单状态逻辑可测。

行为型模式选型速查(Strategy/State/Observer/Command 等)

行为型模式都在分配职责与交互:算法替换用 Strategy、状态迁移用 State、一对多通知用 Observer、请求对象化用 Command、算法骨架用 Template Method;先描述问题再选模式。

单例滥用与测试困境

单例把依赖藏进全局访问点:调用方看不出依赖、测试无法替换、并行测试互相污染;替代方案是显式注入共享实例(容器作用域单例),让依赖出现在签名里。

SRP 与类体积:判断指标与误区

类过大往往对应多职责:可参考方法数、依赖数、被修改频率与广度;但体积只是信号,真正的判据是变化原因数量与方向的差异。

ISP 实操:从调用方绘制角色接口

画接口前先画调用方:列出每个使用场景真正需要的方法集合,合并同类使用场景,得到角色接口;实现类按需实现多个角色,接口之间可再组合。

耦合类型与解耦手段对照

耦合有强弱梯度:内容耦合(直接改对方内部)到消息耦合(仅传数据);日常解耦从「不碰对方内部、只依赖稳定接口、限制依赖方向」三步走。

原型模式 Prototype(创建型)

通过克隆已有实例创建新对象:配置模板、成本高昂的初始化对象适合此模式;注意浅克隆与深克隆的区分,成员含引用类型时要显式深拷贝策略。

桥接模式 Bridge(结构型)

把抽象(做什么)与实现(怎么做)分离为两条独立的继承体系:维度一(如形状)与维度二(如渲染引擎)各自扩展,组合而非相乘。

结构型模式选型速查(Adapter/Bridge/Decorator/Facade/Proxy)

五个结构型模式常在评审中混淆:Adapter 换接口、Bridge 拆维度、Decorator 加行为、Facade 简化面、Proxy 控访问;按意图选择而不是按结构选择。

备忘录模式 Memento(行为型)

在不破坏封装的前提下捕获并恢复对象内部状态:撤销、快照、回滚场景;快照应不可变且控制体积,频繁快照要评估内存与序列化成本。

空对象模式 Null Object(行为型惯用法)

用实现同一接口的「空行为对象」替代空引用:调用方无需层层判空,默认行为集中定义;与《重构》的 Introduce Special Case 一脉相承。

创建型模式选型速查(Singleton/Factory/Builder/Prototype)

创建型模式解决「怎么造对象」:唯一实例用 Singleton、按参数选型用 Factory、多参数装配用 Builder、克隆模板用 Prototype;无必要不要引入工厂层。

迭代器模式 Iterator(行为型)

提供顺序访问聚合元素的统一方式而不暴露内部结构:语言内建(for-each、生成器)已成默认形态;自定义迭代器要处理并发修改与失效语义。

访问者模式 Visitor(行为型)

把作用于对象结构的操作从元素类中分离:元素接受访问者,操作以访问者形式新增,元素类无需改动;元素类型频繁新增时此模式成本较高。

LSP 与协变逆变:类型层面的替换规则

参数类型可以放宽(逆变:子类接受更宽输入),返回类型可以收窄(协变:子类返回更具体类型);语言若不允许表达更宽参数,常用重载或 Object 参数配合运行时校验。

享元模式 Flyweight(结构型)

共享大量细粒度对象的不可变部分(内部状态),把可变上下文(外部状态)留在调用方:降低内存与构造开销;经典场景是字符排版、地图瓦片、缓存池。

工厂方法 vs 抽象工厂:何时再升一层

工厂方法创建单一产品;抽象工厂创建相互关联的一族产品:当出现「同一主题的多个产品必须配套」时再升抽象工厂,否则单产品工厂足够。

中介者模式 Mediator(行为型)

把多对多的对象互引用收敛为一对多的中央协调:同事对象只与中介者通信,交互规则集中;中介者负担过重时再拆分领域服务。

解释器模式 Interpreter(行为型)

为小型语言/规则定义语法树并解释执行:表达式、规则引擎、查询条件的经典结构;复杂语法优先用解析器生成器(ANTLR 等)而非手写解释器。

技术债与度量63 条

复杂度、重复率与技术债管理

技术债隐喻:来源与含义

技术债(Technical Debt)由 Ward Cunningham 在 1992 年提出:用「借款」比喻为了当下交付而选择的次优实现,未来需要以额外的修改成本偿还利息;隐喻用于沟通取舍,不是骂人的标签。

Fowler 技术债四象限

Fowler 用两轴划分债务:有意/无意 × 审慎/鲁莽;「审慎且有意」是主动取舍,需要记录与偿还计划;「鲁莽且有意」是明知故犯;无知型债务来自知识缺口。

圈复杂度(Cyclomatic Complexity)

McCabe 1976 年提出:以控制流图判定点计数衡量分支规模,公式 M = E − N + 2P(边 − 节点 + 2×连通分量),实践中约等于判定点数量 + 1;它是分支测试数量的下界参考。

债务利息:如何描述与量化

利息是债务的持续成本:修改耗时变长、回归缺陷增多、新人上手变慢、发布风险升高;用可观察证据描述利息(工单时长、缺陷分布、发布频率),比形容词更有说服力。

认知复杂度(Cognitive Complexity)

SonarSource 提出的可读性导向复杂度:嵌套越深增分越多,线性顺序分支少加分,switch 整块计一次,直接回应「圈复杂度对可读性建模不足」的问题。

行覆盖与分支覆盖:含义与局限

覆盖度回答「哪些代码被执行过」:行覆盖粗、分支覆盖细;覆盖高不代表测试好(断言可能缺失),覆盖低也未必危险(生成代码、简单转发);它适合发现未测区域而非质量评分。

技术债分类:代码、设计、测试、环境等

债务不止在代码:设计债(结构劣化)、测试债(缺自动化)、文档债、构建与依赖债、环境债、数据债都常见;分类的目的是找准偿还动作,不同债务的偿还手法完全不同。

偿还策略:重构、重写与冻结

偿还债务按场景选策略:小步重构(多数情况)、模块级替换(边界清晰时)、冻结维护(只修不扩)、最终重写(最后手段);决定前评估利息与偿还成本的比例。

SonarQube 质量指标:可靠性/安全性/可维护性

SonarQube 把问题归入可靠性、安全性、可维护性三个维度:严重级别从 Blocker 到 Info,配套质量门(Quality Gate)与「新代码」口径;评级(A–E)本质是问题密度映射。

热点识别:变更频率 × 复杂度

把改动最频繁的代码与复杂度指标叠加找热点:热点 = 高变更频率 × 高复杂度(或高缺陷密度);热点是偿还投资回报较高的区域,也是重构 backlog 的输入。

依赖债:版本陈旧与升级停滞

依赖债指被困在旧版本:安全补丁缺失、无法使用新特性、升级成本随跨度指数上升;治理是持续小步升级(每个迭代若干补丁版本),而不是攒到必须升级时冒险。

DORA 四指标:交付效能度量

DORA(DevOps 研究与评估)用四项指标刻画交付效能:部署频率、变更前置时间、变更失败率、故障恢复时间;指标用于团队效能与流程改进,不适合个人考核。

Goodhart 定律:指标一考核就失真

「当指标成为目标,它就不再是好指标」(Goodhart 定律,Strathern 的通俗转述常用于软件工程):把度量与奖惩绑定时,人们优化数字而非改善本质;度量应面向系统改进与讨论,避免用于个人考核。

技术债台账:登记、排序与偿还节奏

把债务当作可管理条目:登记症状与利息证据、给排序依据(利息 × 改动频率)、每个迭代固定配额偿还;台账公开在团队可访问处,随偿还更新状态。

测试债:脆弱与缺失的代价

测试债表现为:用例脆弱(一碰就红)、运行缓慢、覆盖虚高(断言薄弱)、环境依赖重;偿还方向是把测试分层、稳定数据夹具、缩短反馈,测试是重构的安全网,欠得越久重构越不敢做。

重复率:口径与误读

重复率工具(如 CPD、jscpd、SonarQube duplication)按令牌序列相似度找克隆:高重复率提示同步修改成本,但短小的结构性相似不应算作重复;关注「重复且经常改动」的片段。

质量门(Quality Gate):把标准变成门禁

质量门把容忍底线自动化:对「新代码」设定阈值(覆盖率、重复、阻断级问题),不达标即流水线失败;关键是阈值从团队基线出发、随能力逐步收紧,而非一步到位。

架构债:依赖缠绕与泥球迹象

架构债的表现:模块间依赖成网(循环依赖)、分层规则名存实亡、公共模块成为垃圾场、变更一触多动;检测靠依赖分析工具与变更耦合分析,治理按边界逐步收口。

顺手偿还:童子军规则的债务应用

每次改动顺手清理所在局部:命名、重复、死代码、缺小测;不追求一次偿清,靠持续小额投入降低利息;与债务台账配合——每次触达热点模块时清一格。

嵌套深度(Nesting Depth)

控制流嵌套层数直接影响可读性与出错率:超过三层左右通常难跟;治理手段是卫语句提前返回、提取函数、用查表替代分支;各语言风格指南常给出上限建议。

「利息支付」:为什么新功能越做越慢

债务的利息常以交付速度体现:同一需求在不同模块的实现耗时差异,就是利息的直接读数;把利息写进迭代回顾(哪个模块拖慢了这个迭代),偿还才有业务语言。

债务决策记录:把取舍写进 ADR

每个「有意负债」都留下决策记录:背景、选项、决定、代价与偿还条件(触发条件);将来复盘时能看清当初的约束,避免把有意选择误读为能力问题。

函数长度与文件长度:信号与陷阱

长度是弱信号:超长往往意味多职责,但机械切割带来碎片;更可靠的伴随信号包括认知复杂度、嵌套深度、被修改频率;长度阈值用于触发阅读与讨论,不是自动判罚。

度量组合诊断:复杂度 + 变更 + 缺陷三视图

单指标易误读,组合才接近真相:复杂度找「难」,变更历史找「动」,缺陷记录找「疼」;三者交集是重构与治理优先级最高的区域,再叠加覆盖率看安全网。

度量博弈:常见刷指标手法与对策

可预期的博弈行为:为覆盖率写空断言、为「关闭问题」加豁免、把大 PR 拆成多个小 PR 凑数量、让 flaky 测试跳过;对策是组合指标、抽查与只看趋势不做排名。

质量 SLO:把可靠性写成可检查目标

把质量目标写成 SLO(服务级目标):如可用性 99.9%、错误预算与告警阈值;SLO 让「质量」进入可度量、可协商的工程语言,与错误预算共同指导发布节奏。

债务预算:与功能同排的配额制

给偿还有固定预算:每个迭代(或每 5 个功能点)预留一定比例用于还债与工程改进;配额由团队持有,防止「永远优先功能」的默认排序。

变异测试(Mutation Testing):检验断言质量

变异测试自动制造小缺陷(改比较符、删调用)并检查测试是否失败:未被杀死的变异说明断言薄弱;Mutation Score 反映断言有效性,适合关键模块的测试体检。

缺陷密度与缺陷趋势

缺陷密度 = 缺陷数 / 规模(千行或功能点),更适合横向比较模块;趋势比绝对值重要:新版本缺陷率是否下降、回归缺陷占比是否收敛、修复时长分布如何;数据源是缺陷跟踪系统而非感觉。

废弃与清理:给「临时方案」设寿命

临时方案要带过期机制:兼容层、开关、旧接口登记清理条件与时间点(如「Q3 后无调用即删」),并纳入度量清单——未清理的临时物是隐性债务,会不断复利。

测试分层占比:金字塔与冰淇淋筒

分层占比影响反馈速度与稳定性:单元测试应占主体(快、稳),集成次之,端到端只覆盖关键路径;倒置(冰淇淋筒:大量端到端)是常见债务,表现为慢且 flaky。

向非技术干系人沟通技术债

用业务语言表达:交付速度、事故概率、合规风险、维护成本曲线;给选项与代价(继续借款 vs 分期偿还 vs 集中偿还),让对方在知情下选择,而不是技术团队单方面「清债」。

参数数量与依存度(fan-in / fan-out)

参数数量反映单点依赖面,扇入(被依赖数)与扇出(依赖数)刻画模块在依赖图中的位置:高扇入模块是稳定核心(改动要格外谨慎),高扇出模块较脆弱。

技术债比率(TDR):Sonar 口径与解读

技术债比率 = 修复全部已识别问题所需工作量 ÷ 重写成本估计(Sonar 口径):把问题按修复时间折算成本再比总量;比率用于宏观趋势与预算讨论,具体决策仍要落到热点与利息。

覆盖率 KPI 的反效果

把覆盖率当强制 KPI 的典型后果:大量无断言测试、为覆盖而写测试、测试脆化后全被忽略;更好的用法是补丁覆盖率(新代码)+ 未覆盖区域的针对性审查。

度量与团队文化:避免「警察化」

度量用于系统改进还是问责,决定团队行为:警察化(追责个人)导致数据隐藏与博弈;共建化(共同看趋势、选改进项)才可持续;公开数据前先约定使用边界。

错误预算(Error Budget)驱动发布

错误预算 = 100% − SLO:允许的失败额度;预算充足则加快发布,耗尽则收紧变更并要求可靠性投入;它把「稳定 vs 速度」的争论变成可观测数据的决策。

架构适应度函数(Fitness Function):把约束写成测试

适应度函数把架构期望写成自动化检查:依赖方向、模块边界、性能预算、接口兼容性等;它们像测试一样随架构决策演进,持续验证「结构没有走样」。

逃逸缺陷分析与测试改进

逃逸缺陷(Escaped Defect)= 上线后发现、测试阶段未拦住的缺陷:按根因归类(用例缺失/断言弱/环境差异/需求歧义),每类对应不同改进动作,是测试投资回报较高的分析。

告警疲劳与质量告警治理

质量与监控告警过多会导致整体忽略(告警疲劳):按可行动性分级(立即处理/排期/仅记录),周期性审查告警规则与阈值,删除长期无行动的噪声告警。

虚荣指标:好看但不驱动决策的数字

代码行数、提交次数、故事点总量属于虚荣指标:数字增长与价值无关,还会诱发刷量行为;能驱动决策的度量要回答「要改变什么、谁负责、怎么验证」。

覆盖率差值(Delta)门禁实践

比整体覆盖率更实用的是变更差值:只看本 PR 新增/修改行的覆盖情况(Patch Coverage),未达阈值即要求补测;存量覆盖率作为长期趋势跟踪,避免历史包袱压瘫门禁。

冻结式维护:只修不扩的适用与代价

对利息可控但偿还暂不急迫的模块:冻结新增(只修缺陷与安全),把利息计入预算观察;适用条件是改动频率低、替换或被替代路线图明确。

可维护性指数(Maintainability Index)

可维护性指数把 Halstead 体积、圈复杂度、代码行数合成 0–100 的分数(Visual Studio 等采用);指数是粗略导航,用于看趋势与找异常点,不可作为个人考核依据。

代码流失率(Code Churn):变更的稳定度

代码流失率 = 新增 + 修改 + 删除行随时间分布:高流失集中区往往设计不稳或需求反复;结合文件维度可看「改完又改」的震荡区,作为设计评审与需求复盘输入。

度量报告节奏:周报、迭代与版本

报表服务于决策节奏:周报看门禁与失败趋势(异常驱动)、迭代看偿还进度与利息变化、版本看质量趋势与技术债比率;报表要短、附变化解释与下一步动作,否则无人阅读。

基线设定与阈值校准

指标阈值从基线来:先用 2–4 周采集现状(P50/P90),再设「高于当前基线 10%」之类的可达成目标并定期重标;拍脑袋阈值要么轻易达标、要么永远红灯。

Conway 定律:组织结构映射到系统结构

康威定律(Melvin Conway,1968):系统结构会映射组织沟通结构;度量组织与代码边界的错位(哪个模块被多个团队交叉修改)常能解释架构摩擦,是「逆康威」调整的依据。

先行与本位指标:组合成改进闭环

先行指标(静态检查通过率、测试时长、评审时长)可被当期影响;本位指标(缺陷率、事故率、交付周期)反映结果滞后;用先行指标驱动行动、用本位指标验证效果。

季度质量复盘:框架与议程

季度复盘把度量转成决策:回顾四类输入(质量门趋势、事故与逃逸、债务台账、热点分析),产出下季度 1–3 个明确改进项与验证指标;少而具体的复盘比全景报告更有效。

评审效能度量:时长、轮次与规模

评审是质量的先行环节:观察首评响应时长、评审轮次、PR 规模分布与「评审覆盖率」(多少合并经评审);目标是缩短反馈回路,而不是增加评审环节的数量。

测试运行时长与套件维护成本

测试套件的运行时长决定反馈速度、维护成本决定可持续性:监控全量时长、单用例失败率(flaky 比例)、维护性改动占比;目标是把主流程控制在十分钟级并持续修剪。

债务偿还的估算与验收

偿还任务像功能一样估算与验收:用历史工单数据校准估算、定义「利息下降」的可验证指标(如改动耗时、缺陷数、门禁红项),并在完成后复测确认收益。

代码所有权与巴士系数

巴士系数指关键模块失去多少成员后会停摆:单点所有权(一人精通)风险高;用提交历史看模块的知识分布,通过结对、评审与轮岗降低集中度,而不是追求人人均等。

质量仪表盘:面向行动的设计

仪表盘的目标是驱动行动:按角色组织视图(开发者看门禁与热点、负责人看趋势与预算),每个图表配「若异常该做什么」;宁可少图,也要每个图有明确读者与动作。

日志与可观测性缺口度量

可观测性缺口也是质量债:错误无日志、日志无关联 ID、关键路径无指标;盘点关键接口的日志/指标覆盖率,把「排障时缺什么」记录为改进项,比事后抱怨更有抓手。

文档债:过期文档比没有文档更危险

文档债的典型是「文档与代码不一致」:误导新人与排障;治理方式是文档贴近代码(就近 README、代码注释、ADR),把关键文档纳入变更检查清单(改接口同步改文档)。

行业基线与标杆数据:怎么用才不误导

行业报告(DORA、Sonar 年度报告等)给的是分布与区间而非标准答案:用它们定位自身段位与改进方向,不要把外部数字直接设为本团队 KPI;样本行业与规模差异要说明。

缺陷老化(Defect Aging):多久未处理

缺陷老化看开放式缺陷的年龄分布:长期挂起的老缺陷消耗注意力、掩盖真实优先级;治理动作是定期清理(修复/降级/关闭并说明),让列表反映现实而不是历史沉积。

多项目/多仓库的指标对齐

多仓与多项目的度量要先对齐口径:语言与工具链差异、统计窗口、生成代码排除、门禁规则;对比只用于同语言同类型项目之间,跨技术栈先统一解释再谈对比。

返工率与变更失败率

返工率衡量「改完又改/上线回滚」的比例:与变更失败率、逃逸缺陷共同刻画交付质量;高返工通常指向需求不稳或评审不足,分析要落到具体环节而非责怪个人。

新人上手时间:可维护性的行为度量

新人从入职到独立交付首个变更的时间,是可维护性的直接读数:环境搭建、代码可懂性、文档与评审引导都体现在其中;每季度用真实入职案例复盘,找出阻力突出的环节。

开发者体验指标:摩擦点清单化

开发者体验度量聚焦日常摩擦:等待构建、查缺陷、环境修复、评审排队……;用「一周时间日志」或简短问卷收集频率与痛感,按「高频 × 高痛」排序治理。

没有匹配的条目。换个关键词试试,或直接全站搜索。
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌