CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
软件设计与架构入门/课时/整洁代码
RawBlame
课时目录12 节

软件设计与架构入门

  1. 01整洁代码
  2. 02设计原则
  3. 03面向对象
  4. 04编程范式
  5. 05设计模式
  6. 06架构原则
  7. 07架构风格
  8. 08架构模式
  9. 09领域驱动设计
  10. 10企业模式
  11. 11综合架构练习
  12. 12路线图对照复习

整洁代码

命名、小函数与可读性。

30 分钟

整洁代码:命名、函数与注释纪律

这一课你将能做到

  1. 给变量、函数、类起出"读一遍就懂"的名字,不再依赖注释解释
  2. 把 80 行的"面条函数"拆成一组 10 行以内、单一职责的小函数
  3. 用判题平台真实代码识别"坏味道":魔法数、布尔参数、嵌套过深、重复代码

为什么要学

代码被阅读的次数远多于被编写的次数。在判题平台里,cf_submission 表对应一次代码提交,cf_practice_record 对应一次刷题记录,这些领域对象会在 Service、Controller、定时任务、消息消费者里被反复读取。如果函数叫 process()、变量叫 data、参数叫 flag,下一个接手的人(也许就是三个月后的你)就要花时间还原上下文。

整洁代码不是洁癖,是降低协作成本。面试官让你手写一段代码,命名和结构是第一印象分。

先建立图像

烂代码                     整洁代码
─────────────             ─────────────
process(d, f)             judgeSubmission(submission, skipCache)
if (s == 1) {...}         if (submission.isAccepted()) {...}
// 判断用户是否Vip          user.isVip()   ← 注释消失
80 行函数                  5 个 10 行函数

跟着做

步骤 1:坏味道现场

下面是"提交判题"业务的反面教材,先感受痛:

public void process(long id, String s, boolean f) {
    // 查询提交
    Submission sub = submissionMapper.selectById(id);
    if (sub == null) return;
    // 判断状态
    if (sub.getStatus() == 0) {
        // 调用判题
        String r = judgeClient.call(sub.getCode(), sub.getLessonId());
        if ("AC".equals(r)) {
            sub.setStatus(1);
        } else if ("WA".equals(r)) {
            sub.setStatus(2);
        } else {
            sub.setStatus(3);
        }
        submissionMapper.updateById(sub);
        // 是否发通知
        if (f) {
            notifyService.send(sub.getUserId(), "判题完成");
        }
    }
}

问题:数字状态码 0/1/2/3、单字母参数、布尔开关、嵌套判断、注释解释代码本应该说清的事。

步骤 2:用枚举替代魔法数

public enum SubmissionStatus {
    PENDING, ACCEPTED, WRONG_ANSWER, RUNTIME_ERROR;

    public static SubmissionStatus fromJudgeResult(String result) {
        return switch (result) {
            case "AC" -> ACCEPTED;
            case "WA" -> WRONG_ANSWER;
            default  -> RUNTIME_ERROR;
        };
    }
}

状态从"需要查表"变成"自解释"。

步骤 3:小函数 + 有意义的命名

public void judgeSubmission(long submissionId) {
    Submission submission = submissionMapper.selectById(submissionId);
    if (submission == null || !submission.isPending()) {
        return;
    }
    SubmissionStatus result = runJudge(submission);
    submission.markAs(result);
    submissionMapper.updateById(submission);
}

private SubmissionStatus runJudge(Submission submission) {
    String judgeResult = judgeClient.call(submission.getCode(), submission.getLessonId());
    return SubmissionStatus.fromJudgeResult(judgeResult);
}

每个函数做一件事,函数名即文档。原本"是否发通知"这个布尔参数被拆出去——调用方决定要不要通知,而不是塞进 judgeSubmission 里:

judgeService.judgeSubmission(id);
notifyService.notifyUserJudged(id);   // 调用方显式表达意图

步骤 4:注释纪律

只在"为什么"上写注释,不要在"做什么"上写注释。

// 坏:注释重复代码
// 设置状态为已接受
submission.setStatus(SubmissionStatus.ACCEPTED);

// 好:解释业务原因
// 判题平台要求 WA 后允许 5 分钟内免费重交,所以这里不清空 attemptCount
submission.setStatus(SubmissionStatus.WRONG_ANSWER);

讲清楚要点

  1. 命名要揭示意图:cfUser 比 u 好;isVip() 比 getType() == 2 好;daysSinceRegister 比 d 好。
  2. 函数要短、要单一职责:一个函数只做一件事,缩进不超过两层,长度建议 ≤ 20 行。
  3. 参数越少越好:超过 3 个参数就该考虑封装成对象(如 JudgeContext);布尔参数是拆函数的信号。
  4. 用代码表达,不用注释补救:注释应该解释"为什么",而不是翻译代码在做什么。
  5. 消除魔法值:状态、类型、上限都该有名字——枚举、常量、record 都行。

你可能会懵的地方

  • 小函数会不会性能差? JIT 会内联短函数,几乎无开销。可读性收益 >> 微秒级损耗。真有性能瓶颈再上 JMH 测,不要预先优化。
  • 命名太长怎么办? findUnjudgedPendingSubmissionsByLessonId 是合理的,因为调用方一眼就懂。比 find(id, true, 0) 强得多。
  • 注释到底写不写? 业务规则、外部约束、历史坑、算法来源写;getter/setter、显而易见的方法签名不写。
  • 重构老代码从哪入手? 先重命名,再抽函数,再消魔法数。每一步都可独立提交,不要一次大爆炸式重写。
  • Java 17 有哪些"写出整洁代码"的新武器? record 替代 DTO、sealed 替代继承枚举滥用、switch 表达式消除 if-else 链、Optional 表达可空返回。

错法对照

错法问题正解
int status = 1;魔法数,1 是什么?SubmissionStatus.ACCEPTED
void handle(boolean flag)布尔参数搞两种逻辑拆成 handleWithNotify() / handleSilently()
// 校验用户 + if (u.getType() == 2)注释解释代码if (user.isVip())
函数 100 行嵌套 5 层难读难测早 return、抽小函数
Map<String, Object> data啥都能塞,啥都不知道定义 record SubmissionDto(...)

卡点急救

  1. 命名卡壳:先写一段中文描述这个变量干什么,再翻译成英文,去掉无用前缀(the/a/data/info)。
  2. 函数太长不知道从哪拆:找注释段落和空行——它们就是天然的拆分点,把每段抽成函数。
  3. 不敢动老代码:先补一个最小单元测试保护现有行为,再小步重构;没测试就只改命名不动逻辑。

面试常问

  • 整洁代码的核心标准是什么? 答:可读性优先。具体表现为:命名表意、函数短小单一职责、无副作用、最少注释、一致的风格。判断标准:新同事 30 秒能看懂这段代码干什么。
  • 怎么平衡"函数要短"和"调用层级过深"? 答:函数短不等于散。相关的几个小函数应该集中在同一个类或同一个文件中,用"自顶向下"的阶梯式抽象:public 入口在最上,越私有越往下。读代码像读目录,不需要跳来跳去。
  • Java 里怎么用类型系统消除魔法值? 答:枚举表达有限状态、record 表达值对象、sealed 类表达受限继承。状态码、类型字段、错误码都不应该裸用 int/String。

自测三问

  1. 为什么说"布尔参数是坏味道"?应该怎么处理?
  2. getUserInfo()、fetchCfUser()、loadVipUserDetail() 三个命名哪个最整洁?为什么?
  3. 看到一段 100 行的方法,你的拆分顺序是什么?

小结一句话

整洁代码 = 用命名和结构让代码自己说话,让注释只解释"为什么"。

延伸

  • 《Clean Code》Robert C. Martin,第 2/3/4 章(命名、函数、注释)
  • 《重构:改善既有代码的设计》Martin Fowler,第 6 章(第一组重构)
  • Google Java Style Guide
  • 下一课:设计原则——SOLID 与 DRY、YAGNI 怎么落到判题平台代码上。
加载本节练习…
下一课 →设计原则