极客时间-《代码之丑》学习记录

一、课程总结

13类典型坏味道

1. 命名问题:缺乏业务含义

  • 命名不应过于宽泛(如 processChapter),而应描述意图而非实现细节 (如 startTranslation)。
  • 避免使用技术术语命名(如 bookList),应使用业务语言 (如 books)。
  • 方法名应为动词或动宾结构(如 completeTranslation 而非 completedTranslate)。

2. 重复代码

  • 复制粘贴是技术债的温床。应遵循 DRY(Don't Repeat Yourself)原则
  • 即使是结构相似的异常处理、日志上报等,也应提取为公共逻辑。

3. 长函数与大类

  • 函数应尽量短小(建议不超过20--40行),一个函数只做一件事
  • 类过大往往违反单一职责原则,应通过拆分职责、分组字段等方式"瘦身"。

4. 长参数列表

  • 参数过多会降低可读性,推荐封装为参数对象(如使用 Builder 模式)。
  • 区分变动频率不同的参数,静态参数可内化,动态参数才需传入。

5. 滥用控制结构(如 if/else)

  • 避免深层嵌套,提倡"早退"(early return)策略。
  • 对于复杂分支,可考虑多态、策略模式或状态模式替代条件判断。

6. 缺乏封装

  • "火车残骸"式调用(如 book.getAuthor().getName())违反迪米特法则。
  • 应通过隐藏委托关系 (如在 Book 中提供 getAuthorName())提升封装性。

7. 可变数据与 setter 滥用

  • 暴露 setter 破坏封装,导致状态不可控。
  • 推荐用行为方法 替代(如 book.approve() 而非 book.setReviewStatus(...))。

8. 变量声明与赋值分离

  • 变量应一次性初始化完成,避免先声明为 null 再赋值。
  • 可通过提取辅助函数(如 toEpubStatus(response))保持主逻辑清晰。

9. 不一致的代码风格

  • 同一系统中应保持命名、结构、抽象层次的一致性。
  • 例如:函数中混合业务动作与底层细节(如 HTTP 构造)属于"层次混乱",应提取子函数统一抽象层级。

10. 落后的代码风格

  • 积极采用现代语言特性(如 Java 8+ 的 Optional、Stream、Lambda)。
  • Lambda 表达式应简洁,复杂逻辑需提取为命名函数。

11. 依赖混乱

  • 业务代码不应直接依赖外部接口的具体实现(如 HttpClient、Redis)。
  • 应引入防腐层(Anti-Corruption Layer) 隔离外部变化。

12. 代码评审与持续改进

  • 频繁、小粒度的代码评审能及早暴露设计与规范问题。
  • 鼓励结对编程作为极致的实时反馈机制。

13. 应对新需求的破坏性

  • 新功能应尽量扩展而非修改现有核心类和接口。
  • 谨慎对待实体和 API 的变更,它们是系统稳定性的关键。

二、结课测试

1.下面关于控制语句的讨论中,正确的是:

plain 复制代码
A.if、for 这些控制语句都是源自结构化编程的产物,如今它们在一些场景下,有了更好的替代方案
B.在大多数情况下,else 语句是可以被优化掉的
C.switch 语句本身不是问题,但重复的 switch 往往意味着缺乏一个模型
D.多层的缩进会增加代码的理解难度

答案:ABCD

2.下面的函数 / 变量 / 类命名中,哪些一眼就能看出来是有问题的?

plain 复制代码
A void processChapter(long chapterId) {
...
}

B List bookList = service.getBooks();

C void approveChapter(long chapterId) {
...
}

D class ChapterServiceImpl {
...
}

答案:ABD

3.下面关于 getter/setter 讨论中,不正确的是:

plain 复制代码
A.写代码的时候,写完字段之后,应该立刻生成 getter/setter
B.getter/setter 本质上等于暴露了类实现的细节,破坏了封装
C.setter 提供修改数据的接口,应该尽可能规避
D.即便需要修改数据,setter 也不是一个好的命名,应该用更有业务含义的命名

答案:A

4.如果新需求到来,我们应该怎么做?

plain 复制代码
A.新增业务,就要新增接口
B.在一堆判断条件后面,增加一个判断条件就好了
C.需要内部变化的,直接去修改实体
D.从业务层面谨慎地评估要做的修改

答案:D

5.下面关于重复代码的讨论中,正确的是:

plain 复制代码
A.只有复制粘贴才会产生重复的代码
B.重复代码违反了 DRY 原则
C.不仅名词的重复是重复,动词的重复也是重复
D.if..else 代码块中语句高度类似也是一种重复

答案:BCD

6.下面关于重构的讨论中,正确的是:

plain 复制代码
A.重构是一种模式匹配,识别坏味道,采用对应的重构手法
B.重构就是重写一段代码
C.保证重构正确性最好的方式是测试
D.重构是小步地调整

答案:ACD

7.下面关于 static 函数讨论中,正确的是:

plain 复制代码
A.static 方法用起来很方便,可以多使用
B.static 函数自身不容易测试,所以,应该少用
C.static 函数 / 变量本质上也是一种全局函数 / 变量,所以应该少用
D.绝对不能使用 static 函数

答案:C

8.下面关于大类的讨论中,不正确的是:

plain 复制代码
A.函数长点,可以看到一个业务的全貌,这是好事
B.函数小,数量就多,会影响整体的性能,所以,不能把函数写得太小
C.100 行以上的才算长函数
D.可以通过提取方法消除长函数

答案:ABCD

题目解析

选项 A,大类难以理解,本身就是坏味道

选项 B,类多了,可以采用包或命名空间进行封装

选项 C,实体类到表的映射有很多框架可以做,多个类也可以映射到一张表上

选项 D,类的字段要根据职责来

9.下面关于火车残骸代码的讨论中,正确的是:

plain 复制代码
A.像 book.getAuthor().getName() 这种连续多个函数的调用,属于火车残骸代码
B.火车残骸代码是违反迪米特法则的
C.所有连续多个函数调用的,都属于火车残骸
D.通过隐藏委托关系消除火车残骸的代码

答案:ABD

10.下面关于长函数的讨论中,正确的是:

plain 复制代码
A.函数长点,可以看到一个业务的全貌,这是好事
B.函数小,数量就多,会影响整体的性能,所以,不能把函数写得太小
C.100 行以上的才算长函数
D.可以通过提取方法消除长函数

答案:D