代码之丑
郑晔
开源项目 Moco 作者
19833 人已学习
新⼈⾸单¥59
登录后,你可以任选2讲全文学习
课程目录
已完结/共 24 讲
代码之丑
15
15
1.0x
00:00/09:18
登录|注册

开篇词 | 这一次,我们从“丑”代码出发

讲述:郑晔大小:8.50M时长:09:18
你好,我是郑晔!我又回来了!
我在“极客时间”里已经写了两个专栏,分别是《10x 程序员工作法》和《软件设计之美》,从工作原则和设计原则两个方面对软件开发的各种知识进行了探讨,帮助你搭建了一个开启程序员精进之路的框架。
不过,无论懂得多少道理,程序员依然要回归到写代码的本职工作上。所以,这次我准备和你从代码的坏味道出发,一起探讨如何写代码。

千里之堤毁于蚁穴

为什么要讲这个话题,就让我们先从一次代码评审讲起。在一次代码评审中,我注意到了这样一段代码:
public void approve(final long bookId) {
...
book.setReviewStatus(ReviewStatus.APPROVED);
...
}
这是在一个服务类里面写的,它的主要逻辑就是从仓库中找出一个作品,然后,将它的状态设置为审核通过,再将它存回去。前后的代码都属于常规的代码,但是,设置作品评审状态的代码引起了我的注意,于是有了下面这段对话。
我:这个地方为什么要这么写?
同事:我要将作品的审核状态设置为审核通过。
我:这个我知道,但为什么要在这里写 setter 呢?
同事:你的意思是?
我:这个审核的状态是作品的一个内部状态,为什么服务需要知道它呢?也就是说,这里通过 setter,将一个类的内部行为暴露了出来,这是一种破坏封装的做法。
同事被我说动了,于是这段代码变成了下面这个样子:
public void approve(final long bookId) {
...
book.approve();
...
}
之所以我注意到这段代码,完全是因为这里用到了 setter。在我看来,setter 就是一个坏味道,每次一看到 setter,我就会警觉起来。
setter 的出现,是对于封装的破坏,它把一个类内部的实现细节暴露了出来。我在《软件设计之美》中讲过,面向对象的封装,关键点是行为,而使用 setter 多半只是做了数据的聚合,缺少了行为的设计,这段代码改写后的 approve 函数,就是这里缺少的行为。
再扩展一步,setter 通常还意味着变化,而我在《软件设计之美》中讲函数式编程时也说过,一个好的设计应该尽可能追求不变性。所以,setter 也是一个提示符,告诉我们,这个地方的设计可能有问题。
你看,一个小小的 setter,背后却隐藏着这么多的问题。而所有这些问题,都会让代码在未来的日子变得更加不可维护,这就是软件团队陷入泥潭的开始。
我也一直和我团队的同学说,“写代码”有两个维度:正确性和可维护性,不要只关注正确性。能把代码写对,是每个程序员的必备技能,但能够把代码写得更具可维护性,这是一个程序员从业余迈向职业的第一步

将坏味道重构为整洁代码

或许你也认同代码要有可维护性,也看了很多书,比如《程序设计实践》《代码整洁之道》等等,这些无一不是经典中的经典,甚至连怎么改代码,都有《重构》等着我们。没错,这些书我都读过,也觉得从中受益匪浅。
不过,回到真实的工作中,我发现了一个无情的事实:程序员们大多会认同这些书上的观点,但每个人对于这些观点的理解却是千差万别的。
比如书上说:“命名是要有意义的”,但什么样的命名才算是有意义的呢?有的人只理解到不用 xyz 命名,虽然他起出了自认为“有意义”的名字,但这些名字依然是难以理解的。事实上,大部分程序员在真实世界中面对的代码,就是这样难懂的代码。
这是因为,很多人虽然知道正面的代码是什么样子,却不知道反面的代码是什么样子。这些反面代码,Martin Fowler 在《重构》这本书中给起了一个好名字,代码的坏味道(Bad Smell)。
在我写代码的这 20 多年里,一直对代码的坏味道非常看重,因为它是写出好代码的起点。有对代码坏味道的嗅觉,能够识别出坏味道,接下来,你才有机会去“重构(Refactoring)”,把代码一点点打磨成一个整洁的代码(Clean Code)。Linux 内核开发者 Linus Torvalds 在行业里有个爱骂人的坏名声,原因之一就是他对于坏味道的不容忍。
所以,我也推荐那些想要提高自己编程水平的人读《重构》,如果时间比较少,就去读第三章“代码的坏味道”。
不过,《重构》中的“代码的坏味道”意图虽好,但却需要一个人对于整洁代码有着深厚的理解,才能识别出这些坏味道。否则,即使你知道有哪些坏味道,但真正有坏味道的代码出现在你面前时,你仍然无法认得它。
比如,你可以看看 Info、Data、Manager 是不是代码库经常使用的词汇,而它们往往是命名没有经过仔细思考的地方。在很多人眼中,这些代码是没有问题的。正因如此,才有很多坏味道的代码才堂而皇之地留在你的眼皮底下。
所以,我才想做一个讲坏味道的专栏,把最常见的坏味道直接用代码形式展现出来。在这个专栏里,我给你的都是即学即用的“坏味道”,我不仅会告诉你典型的坏味道是什么,而且也能让你在实际的编程过程中发现它们。比如前面那个例子里面的 setter,只要它一出现,你就需要立即警觉起来。
这里我也整理了一份“坏味道自查表”,把一些明显的“坏味道”信号列了出来,你可以和自己的代码做对比。
除了为你列出来哪些代码有坏味道之外,我还会给你讲支撑这些“坏味道”之所以为“坏味道”的原因,比如说:长方法和大类之所以为坏味道,因为它们都违背了单一职责的原则。
有坏味道的代码需要经过重构才能长成新的样子,在这个专栏里,我也会提到一些重构的手法,比如,改名(Rename)、提取方法(Extract Method)等等。在今天,拜许多能力强大的 IDE 所赐,重构已经变得越来越自动化,《重构》里的很多手法已经成为了 IDE 中的一个选项。
我还想给你一个安全提示,即便 IDE 功能再强大,也不要忘了重构的重要根基:测试。即便像 Java 这样,IDE 功能已经非常强大了,依然会有一些像反射之类的场景可能会从自动化重构的鼻子底下溜走。所以,重构一段代码之前,最好能够给它写下测试,确保改动前后的代码,功能上是一致的。
如果你订阅过我的《10x 程序员工作法》和《软件设计之美》,你就会发现,三个专栏一脉相承,这些背后的道理恰恰就是我在那两个专栏中已经提到过的内容。所以,三个专栏一并服用,效果会更佳。

写在最后

最后,还是要做一个自我介绍。我叫郑晔,一个写代码超过二十年的程序员,做过与软件开发的各种工作:编代码、带团队、做咨询、写开源。正如前面所说,我已经在极客时间平台上写了两个专栏,分享我在软件开发中的各种思考。这次,我会带你进入到我的基本功里,帮你一起写好代码。
十年前,我在 InfoQ 写过一个专栏《代码之丑》,把一些真实世界的代码展示了出来,让大家看到丑陋代码是什么样子的。
不少读者都表示,那个专栏让他们受益匪浅。不过,那个系列只是我日常工作的随手之作,没有更好地整理。这个专栏就是脱胎于 InfoQ 上的《代码之丑》,我对相关内容进行了更系统地整理,保证即便看过那个《代码之丑》专栏,你依然能够在这里有所收获。
这是一条通往代码精进之路,我愿意与你一起前行,成为你在这条路上的向导。如果你想摆脱平庸的小白程序员状态,成为一个更优秀的程序员,那么,请加入我的专栏,让我们一起修炼,日益精进写代码的手艺!
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

这篇文章以“从‘丑’代码出发”为题,由资深程序员郑晔撰写。文章从一次代码评审中发现的坏味道入手,讨论了代码中的坏味道和重构为整洁代码的重要性。作者强调了对代码坏味道的嗅觉和重构的必要性,并推荐了《重构》一书。他还提到了自己的专栏将重点讲解常见的坏味道,并提供了坏味道自查表和重构手法。最后,作者介绍了自己的经历和专栏的目的,希望与读者一起精进写代码的技艺。整体而言,文章强调了对代码质量的重视和提高编程水平的重要性,对读者有着很好的启发和指导作用。

2020-12-2871人觉得很赞给文章提建议

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《代码之丑》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(29)

  • 最新
  • 精选
  • Changing
    看到这个Setter,有个疑惑。在现在的项目中,service层经常出现各种setter,基本是把所有逻辑都放到service层了。之前网上查询了一些资料,把这种称之为“失血模型”。代码这样写有哪些坏处?在既有的项目中,如果要改变的话,需要从哪里做起呢?

    作者回复: 后面会有一讲专门讨论这个问题,简言之,就是缺了行为,暴露了细节,解决办法就是,引入行为,封装细节。

    2020-12-28
    3
    19
  • escray
    前两个专栏都还没有学完的老读者又来了。 两个专栏其实已经听了不止一遍,一直想再认真学习一遍,却没能做到。 结果现在已经脱离了编程一线,转型项目经理了,不过还是希望终有一天可以继续写代码。 写 setter 的原因很有可能是因为那些教程或者代码生成工具,set、get 不亦乐乎。 info、Data、Manager 我之前也常用。 去 InfoQ 看了一下十年前的专栏,摘抄一下要点: 1. 让判断条件做真正的选择 2. 判断条件里面不允许多个条件的组合 3. switch 陷阱 4. 重复,是最为常见的坏味道 5. 大心脏是不受喜欢的(函数应该只做一件事) 6. 代码的声明和使用应尽量接近 7. 工欲善其事,必先利其器(Effective 你的语言) 8. 有时候,没有选择是幸福的(两个字符串类) 9. 不允许出现多层缩进 10. 条件编译里面不允许包含多条执行语句 11. 封装全局变量(封装出个行为来) 12. 尽量编写无状态方法 13. 当容器开始嵌套容器,请考虑封装 14. 使用工厂方法处理多个构造函数

    作者回复: 多谢你对于我老专栏的总结,欢迎回来!

    2021-01-05
    12
  • 邵俊达
    期待已久,老师前两个专栏都学完了并且都学了不只一遍,收获良多。继续跟老师学习,打磨写代码的手艺。

    作者回复: 我们一起加油!

    2020-12-28
    6
  • 李威
    老师,接手一个买回来的商业产品,做二次开发,里面有大量重复代码,此时需要新增一些功能,给的工期短,简单粗暴的办法是继续复制粘贴,再修改修改就可以上线使用,实际上,我们现在就是这么干的。我想请问的是,针对这种烂代码,要去修改它,是应该坚持边加功能边重构,还是一顿复制粘贴用起来先,后续再重构,还是怎么处理比较好。

    作者回复: 先确认你们自己的策略是什么,如果是短时间用用,就抛弃,然后自己重写,还是就打算以这个系统为核心一直维护下去,不同的策略方案是不同的。 如果要抛弃,就是另起炉灶,按照好的标准要求新代码;如果是维护,每做一块,就重构一块,并且向负责人说明情况,做好投入技术债的准备。

    2021-02-03
    4
  • 每天晒白牙
    看看我的代码写的多丑

    作者回复: 看运气了,哈哈。

    2020-12-28
    4
  • G小调
    public void approve(final long bookId) { ... book.approve(); ...} book.approve() 是对setter做了封装吗

    作者回复: 严格地说,是把对数据的操作封装了。

    2020-12-28
    3
    3
  • Seed2009
    我前同事说老师才给他们做过一周的技术指导,对您的技术很膜拜。

    作者回复: 都不记得我在哪个公司留下过足迹了。😁

    2020-12-29
    2
  • Hobo
    老师,如果我需要封装一个方法能够对类的一个字段进行多种状态的更改应该怎么封装比较好?

    作者回复: 为什么是一个方法而不是多个方法呢?用一个方法的结果就是一个setter的变体。

    2020-12-31
    2
    1
  • Ankhetsin
    一个几百行的长SQL算不算方法过长呢?一个bean有上百个字段光设置属性的值并转json不电网络库的代码肯定不止40行了。这个怎么解决?

    作者回复: 写出几百行的 SQL,一般都是些存储过程,而存储过程早在很多年前就已经被列到不推荐的做法里了。如果是普通的语句,除非是做某些特殊的统计,一般不建议这么做。 一个 Bean 为什么要有上百个字段呢?以我的经验看,这通常是没有想清楚就把所有的东西都塞进来了。所以,应该做的是,做职责分解,把不同的内容放到不同的接口去。

    2020-12-30
    1
  • Jxin
    老粉前来打卡。以上书籍都拜读过。就看郑老师如何讲出花来了~

    作者回复: 我也努力啊!

    2020-12-28
    1
收起评论
大纲
固定大纲
千里之堤毁于蚁穴
将坏味道重构为整洁代码
写在最后
显示
设置
留言
29
收藏
54
沉浸
阅读
分享
手机端
快捷键
回顶部