CODE STYLE · 代码质量 / 规范先行
代码规范速查
命名、注释、函数、重构、提交与 Review 六张表,把「代码怎么写才体面」讲清楚,条条可对照执行,适合贴在手边随查随用。
69条规范
11大主题
∞持续更新
📖 速查表
点击展开各小节
🏷️ 命名规范
| 场景 | 规范 | 示例 |
|---|---|---|
| Java 类 / 接口 | 大驼峰 UpperCamelCase,用名词,见名知义 | UserService、OrderController |
| Java 方法 / 变量 | 小驼峰 lowerCamelCase;方法用动词或动词短语开头 | getUserById()、orderCount |
| Java 常量 | 全大写 + 下划线分隔,集中定义,禁止散落的魔法值 | MAX_RETRY_COUNT、DEFAULT_TIMEOUT |
| 包名 | 全小写,倒置域名开头,层级清晰,不使用下划线与大写 | com.codechuan.service.impl |
| JS / TS | 变量与函数 camelCase;类、组件、类型用 PascalCase;私有约定 _ 前缀 | fetchUserData、LoginForm |
| CSS 类名 | kebab-case 中划线小写,避免下划线与驼峰混用 | .nav-bar、.card-header |
| 数据库表 / 字段 | 蛇形命名 snake_case,表名单复数风格全库统一,时间字段带后缀 _at | user_order、created_at |
| 布尔命名 | 用 isXxx / hasXxx / canXxx 前缀,保持正向语义,避免双重否定 | isEnabled、hasPermission isNotError ✗ 难以理解 |
| 禁用项 | 拼音与英文混用、无意义缩写、魔法值直接出现在逻辑里 | getUserDataByYh ✗ if (status == 3) ✗ 应定义 ORDER_PAID = 3 红线 |
💬 注释规范
| 主题 | 说明 | 示例 |
|---|---|---|
| Javadoc 要素 | 公共类与方法注明用途、参数、返回值、抛出异常;对外契约必须完整 | @param、@return、@throws 公共 API 必写 |
| 解释为什么 | 注释的价值在动机与背景,而非复述代码在做什么 | 「延迟 200ms 等待下游最终一致」✓ 「睡 200 毫秒」✗ 无信息量 |
| 该写注释 | 复杂算法、魔法值含义、绕坑说明(兼容性、踩坑记录)、公开接口契约 | 「JDK 8 的 Arrays.asList 返回定长列表,勿增删」 |
| 不该写注释 | 废话注释、与代码脱节的过期注释、被注释掉的死代码 | // 获取用户 + getUser() 应删 死代码交给 Git 历史 可检索 |
| TODO / FIXME | 统一格式便于 IDE 全局检索与清理;TODO 表示待实现,FIXME 表示待修复缺陷 | // TODO(zhang): 接入网关限流 |
| 及时同步 | 代码改动必须同步更新注释,过期注释比没有注释更有害 | Review 时注释与实现不一致应打回 一致性 |
🧩 函数设计
| 原则 | 说明 | 示例 |
|---|---|---|
| 单一职责 | 一个函数只做一件事,能用一句不带「与 / 或」的话描述清楚 | sendEmail() 不要顺便写审计日志 |
| 函数长度 | 一屏可读完,经验值不超过 20 行(不含空行注释);超长先按逻辑块提取子函数 | 80 行函数 → 拆为「校验 / 计算 / 落库」三段 20 行经验值 |
| 参数个数 | 不超过 3 个;过多用对象封装或 Builder,同类型连续参数尤其易传错 | createUser(name, age, email, phone) ✗ → createUser(UserCreateCmd cmd) ✓ |
| 避免布尔参数 | boolean 参数在调用处不可读,拆成两个语义明确的函数 | render(true) ✗ → renderHtml() / renderJson() |
| 卫语句提前返回 | 先处理空值与边界并尽早 return,消除多层嵌套 | if (user == null) return; 优于 if-else 层层包裹 扁平化 |
| 返回值一致 | 同一函数不要既返回对象又返回 null;集合返回空集合,单个对象可用 Optional | 无数据返回 Collections.emptyList() 而非 null |
| 无副作用 | 查询类函数不偷偷改状态、不发请求、不写库,副作用显式命名 | getOrder() 内部不应更新库存 隐蔽 Bug 源 |
🚩 重构信号(坏味道)
| 坏味道 | 特征 | 重构手法 |
|---|---|---|
| 长函数 | 超一屏、靠注释分隔的多段逻辑堆在同一个方法里 | 提取方法(Extract Method) |
| 重复代码 | 相似逻辑散落多处,改一处漏三处 | 提取公共函数 / 父类 / 组件 DRY |
| 过深嵌套 | if / for 超过 2~3 层,可读性骤降 | 卫语句提前返回、continue、提取方法 |
| 霰弹式修改 | 一个小需求要同时改 N 个文件 | 聚合内聚逻辑,按业务域重新划分模块 |
| 依恋情结 | 函数大量访问别的类的数据,本地数据反而不关心 | 搬移函数(Move Method)到数据所在类 |
| 发散式变化 | 一个类因多种不同原因被反复修改,职责过多 | 按单一职责拆分类 SRP |
📝 提交规范(Conventional Commits)
| 类型 / 原则 | 说明 | 示例 |
|---|---|---|
| 统一格式 | <type>(<scope>): <subject>;subject 用祈使句、一行说清、结尾不加句号 | feat(order): 支持优惠券叠加 |
| feat / fix | 新功能 / 缺陷修复,最常用两类;破坏性变更需加 ! 或脚注 | feat: add export api fix: npe when cart empty |
| docs / style / refactor | 文档变更 / 格式调整(不影响逻辑)/ 重构(不加功能也不修缺陷) | refactor: extract payment strategy |
| perf / test / chore | 性能优化 / 测试相关 / 构建脚本与依赖等杂项 | perf: batch load user roles chore: bump spring-boot to 3.2 |
| scope 用法 | 标注影响模块,便于生成变更日志与定位责任人,团队内约定固定词表 | fix(user): 头像上传超时 可追溯 |
| 原子提交 | 一次提交只做一件事,message 讲清「为什么改」;拒绝混合大提交 | fix bug & update docs & misc ✗ 禁止 |
单行 subject 说不清时,空一行写 body 讲动机与影响面;破坏性变更必须写在脚注里。
feat(order): 支持优惠券叠加
购物车可叠加多张券,按发放时间顺序核销,与满减互斥;
互斥校验见 OrderPromotionService#checkConflict。
BREAKING CHANGE: /api/coupons/apply 入参 couponId 改为 couponIds 数组
🔍 Code Review 清单
| 检查项 | 要点 | 提示 |
|---|---|---|
| 正确性 | 逻辑是否达成需求,边界条件是否处理 | 空值、空列表、越界、并发场景逐一过 |
| 可读性 | 命名达意、结构清晰,新人能否不问人就看懂 | 复杂逻辑需注释说明动机 最高性价比 |
| 异常与边界 | 参数校验是否完整,异常是否被吞掉 | catch 后至少记录日志,禁止静默失败 |
| 并发安全 | 共享可变状态、锁粒度、容器选型 | HashMap 并发写、双重检查锁是惯犯 |
| 测试覆盖 | 关键路径有无单测,回归场景是否补齐 | 修复 Bug 必须附带复现用例 防复发 |
| 性能 | 循环内 IO、N+1 查询、重复计算、大对象 | 批量替代逐条、必要时加缓存并评估失效策略 |
| Review 礼仪 | 对事不对人;PR 保持小步(建议 ≤ 400 行);24 小时内响应 | 「这里建议改成 X,因为 Y」✓ 「你写得太烂」✗ 对事不对人 |
🔖 版本与分支命名
| 场景 / 规则 | 说明 | 示例 |
|---|---|---|
| SemVer 语义化版本 | 版本号遵循 主版本.次版本.修订号 三段式,变更类型决定递增哪一段 | 2.4.1(主.次.修订) |
| 主版本 MAJOR | 出现不兼容的 API 变更时递增,归零次版本与修订号 | 1.9.9 → 2.0.0 |
| 次版本 MINOR | 向下兼容的新功能、新接口时递增,修订号归零 | 2.0.0 → 2.1.0 |
| 修订号 PATCH | 向下兼容的缺陷修复时递增 | 2.1.0 → 2.1.1 |
| 先行版本标识 | 正式发布前的预览版本加后缀,稳定后去掉后缀发布 | 3.0.0-alpha、3.0.0-beta.2、3.0.0-rc.1 |
| 分支命名 | 小写 + 中划线,按用途加类型前缀;常驻分支仅 main | feature/user-export、fix/login-timeout、hotfix/order-refund |
| tag 命名 | 发布时在 main 打 tag,格式 v + 三段版本号,与发布一一对应 | git tag v1.2.0 可追溯 |
📋 日志与异常规范
| 主题 | 说明 | 示例 |
|---|---|---|
| DEBUG | 开发调试细节,生产环境关闭,不进告警 | 打印入参出参、中间状态 |
| INFO | 关键流程节点,能凭日志还原操作轨迹 | 「订单 1001 支付成功」主流量 |
| WARN | 可自动恢复或存在潜在风险的场景 | 重试后成功、降级触发、慢 SQL 预警 |
| ERROR | 需要人工介入的故障,带堆栈与业务上下文 | 下游持续超时、落库失败 需告警 |
| 占位符 {} | 用占位符替代字符串拼接,级别未开启时零开销 | log.info("user {}", id) ✓ log.info("user " + id) ✗ |
| 异常三不 | 不吞异常;不打印后又重抛(双份日志);不用异常做流程控制 | catch 后要么记录要么上抛,二选一 红线 |
| 全局兜底 | @RestControllerAdvice + @ExceptionHandler 统一兜底未捕获异常 | 返回统一错误结构,避免堆栈直接暴露给前端 |
| 业务异常体系 | 定义 BusinessException + 错误码枚举,与系统异常分离 | throw new BusinessException(ErrorCode.ORDER_NOT_FOUND) |
⚙️ 配置与常量
| 主题 | 说明 | 示例 |
|---|---|---|
| 配置外置 | 配置进 application.yml / 环境变量 / 配置中心,禁止硬编码在代码里 | 数据库地址、第三方密钥、功能开关 |
| 多环境隔离 | application-{profile}.yml 按环境拆分,敏感配置不入 Git | application-dev.yml / application-prod.yml |
| 魔法值治理 | 逻辑中禁止裸字面量,提取为有名字的常量或配置 | if (status == 3) ✗ → STATUS_PAID ✓ |
| 枚举替代常量类 | 状态 / 类型用枚举内聚语义与行为,携带 code 与描述,便于遍历与校验 | OrderStatus.PAID.getCode() 推荐 |
| 工具类静态导入 | 高频工具方法用静态导入,调用处更简洁;仅对高频惯用工具启用 | import static java.util.Objects.requireNonNull; |
| 敏感配置 | 密码、密钥不写死、不入库,用环境变量或配置中心加密下发 | ${DB_PASSWORD} 从环境注入 红线 |
🧼 坏味道 → 整改示范
一段「长函数 + 魔法值 + 吞异常」三连的典型坏代码,对照整改后的样子;整改按「补测试 → 一处小改 → 跑测试 → 提交」节奏推进,每步只改一处
// ❌ 整改前:超长方法 + 魔法值 + 吞异常(三连典型)
public void handle(List<Order> orders) {
for (Order o : orders) {
if (o.getStatus() == 3 && o.getAmount().doubleValue() > 0) { // 3 是什么状态?
try {
BigDecimal fee = o.getAmount().multiply(new BigDecimal("0.03")); // 费率哪来的?
o.setFee(fee);
orderMapper.update(o);
} catch (Exception e) {
// 吞异常:失败无声消失,出了 Bug 无从排查
}
}
}
}
// ✅ 整改后:提取方法 + 魔法值具名 + 异常上抛
private static final BigDecimal FEE_RATE = new BigDecimal("0.03"); // 更进一步:收敛为费率配置
private static final int ORDER_PAID = OrderStatus.PAID.getCode(); // 或直接用枚举比较
public void settlePaidOrders(List<Order> orders) { // 方法名说清做什么
orders.stream().filter(this::isPaid).forEach(this::settleFee);
}
private boolean isPaid(Order order) { // 具名布尔方法替代魔法值判断
return order.getStatus() == ORDER_PAID;
}
private void settleFee(Order order) { // 单一职责,一屏读完
order.setFee(order.getAmount().multiply(FEE_RATE));
orderMapper.update(order); // 异常交全局处理器记录并告警,绝不静默
}
🚷 命名红线
| 红线 | 反例 ✗ | 正例 ✓ |
|---|---|---|
| 布尔加 is / has / can 前缀 | flag、vip、名词化的 deletion | isVip、hasChildren、isDeleted,读起来就是一句判断 |
| 集合命名用复数 | List<Order> list、orderArr、data | List<Order> pendingOrders,复数自带「这是一组」的信息 |
| 方法用动词开头 | order()、userState() 分不清查询还是改状态 | createOrder()、getUserState(),读方法名即知行为 |
| 禁拼音与拼音缩写 | getYhmById()、djzt(订单状态?) | getUsernameById()、orderStatus;拼音让新人完全无法上手 |
| 常量全大写 + 下划线 | final int maxRetry = 3;、逻辑里裸写 status == 3 | MAX_RETRY_COUNT、ORDER_STATUS_PAID,全库可检索 |
| 类名用名词,拒绝万金油后缀 | OrderProcessor、DataManager 什么都往里塞 | OrderFeeCalculator、RefundOrderHandler,按职责具名,一个类一个变更理由 |