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

09 | 可变的数据:不要让你的代码“失控”

你好,我是郑晔。
最近几讲,我们讨论的坏味道挑战了很多人的编程习惯,明明很习惯的编码方式,如今却成了坏味道。这一讲,我们再来说一类这样的坏味道:可变的数据。
对于程序,最朴素的一种认知是“程序 = 数据结构 + 算法”,所以,数据几乎是软件开发最核心的一个组成部分。在一些人的认知中,所谓做软件,就是一系列的 CRUD 操作,也就是对数据进行增删改查。再具体一点,写代码就把各种数据拿来,然后改来改去。我们学习编程时,首先学会的,也是给变量赋值,写出类似 a = b + 1之类的代码。
改数据,几乎已经成了很多程序员写代码的标准做法。然而,这种做法也带来了很多的问题。这一讲,我们还是从一段问题代码开始。

满天飞的 Setter

还记得我们在开篇词里提到过的一个坏味道吗?我们复习一下:
public void approve(final long bookId) {
...
book.setReviewStatus(ReviewStatus.APPROVED);
...
}
这是一段对作品进行审核的代码,通过 bookId,找到对应的作品,接下来,将审核状态设置成了审核通过。
我当时之所以注意到这段代码,就是因为这里用了 setter。setter 往往是缺乏封装的一种做法。对于缺乏封装的坏味道,我们上节课已经用了一讲的篇幅在说,我提到,很多人在写代码时,写完字段就会利用 IDE 生成 getter,实际情况往往是,生成 getter 的同时,setter 也生成了出来。setter 同 getter 一样,反映的都是对细节的暴露。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

可变数据在软件开发中可能引发不可控的变化和并发问题,本文提出了解决方案。作者建议使用行为封装和不变类来限制数据变化,以避免可变数据带来的问题。文章强调了封装数据变化的重要性,介绍了函数式编程中的不变性概念,并指出越来越多的语言引入值类型以限制可变性。总之,文章呼吁限制可变数据,提倡使用不变类和值对象来解决可变数据带来的挑战。

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

全部留言(20)

  • 最新
  • 精选
  • crtmsvc
    https://immutables.github.io/ Java 用這個,好用 :)

    作者回复: 多谢分享

    2021-02-28
    13
  • 陈文昕
    对象如果需要构造的参数太多了怎么办? 我能想到的就是用builder模式来重构

    作者回复: 正解

    2021-01-19
    13
  • 刘大明
    前段时间,领导交代了一个任务。任务是将某某前端传进来的字段,存储到数据库里面。当时领导说这么简单的一个功能,就给你一天的时间解决。我足足花了两天时间。我后来复盘了一下,任务延期的原因就是可变的数据,失控的代码,需要修改的代码点里面逻辑,而且充斥着各种set,当我在我自以为某个地方set了该字段之后,发现在代码的后面又有一个地方set了,在往后面又有地方set,那种感觉,真的酸爽。 学习了这篇文章之后,我自己后面一定要避免这种问题,如果不改,说不定后面坑到的还是我自己。

    作者回复: 那种痛,经历过才懂。

    2021-01-19
    4
    9
  • 源泉
    这些算是基于充血模型的吗?可我做过的项目全是类似贫血模型

    作者回复: 你说得对,贫血模型本来就不是一个好的设计,只算把数据放到一起,谈不上封装。

    2021-02-25
    3
    6
  • sean77
    我们项目用mybatis-plus, 由mybatis-plus-generator生成model, 原则上自动生成的代码不应手动修改, 但是按此文讲的要去除setter方法, 那自动生成的model基本都要大改了, 这个悖论该怎么解决呢

    作者回复: 针对代码生成工具,如果它生成的是一个算法,比如编译器生成器,我可以去用,因为我只要关注接口,不需要关注细节。如果生成的是模型,我倾向于不用它,因为我要关注其中的细节。如果封装它,可能就失去了代码生成的意义。 我常常说到软件设计的品位,生成大量 setter 代码的工具,本身就是设计品位不佳的体现。基于一个品位不佳的工具谈改进,这可能不是我关注的方向。

    2021-08-25
    4
    5
  • Jxin
    1.现实情况下,@Setter这个我去不掉啊。毕竟很多框架(dozer,mapstruct)都是基于生成set方法操作数据的。去掉的话,兼容性会受影响,开发工作量和风险也会增加(需要手动去维护新增字段的初始化)。 2.基于idea,我声明的变量都会带上final。这一度倍受挑战,因为带上final代码自然就变长了,可读性算是变差了吧,毕竟有时因为多个final就得换行;包括jdk函数库在内,大部分好demo的代码部分都不会带上final,所以当小伙伴拿这些例子质疑时挺无奈。 3.为什么声明要带上final,因为我也认为缺省变量应该是不可变的。只是受限于java语法特性,所以采用这种曲线的方式。 4.提到final就谈谈不可变对象。一个对象一旦声明,内部的数值结构就不可变,任何对内部值的变更操作都会生成新的对象。这个其实我一直在想,如果语言层面提供一个备忘录模式或许会挺好的。使用者做变更时不用手动新建对象,回退时也可以基于his版本对象无需重新创建。并且我们也有回滚到变更前一个状态或者前多个状态的诉求,也会有打印出所有变更操作的诉求。

    作者回复: 用了 setter,习惯 setter,setter 才是去不掉的。 程序库中优秀的地方也许就是潜在的语言特性,这在《软件设计之美》讲程序库的时候提到过:程序库设计就是语言设计,语言设计就是程序库设计。

    2021-01-24
    6
    5
  • Peter
    class Book { public void approve() { this.reviewStatus = ReviewStatus.APPROVED; } } 有一个小小的疑问 就是采用这种方式this.reviewStatus = ReviewStatus.APPROVED; 有点像写死hardcode的感觉,万一这个reviewStatus可以存在其他状态,那不是又要增加一个设置函数了,反倒使用setter就不会有这种问题,不知道是不是我没有领悟这个方法的精髓,希望老师指点下

    作者回复: 这个 reviewStatus 就是可以保存不同的状态啊,使用 setter 的话,就等于你把实现细节暴露给其他人了,人家想怎么用就怎么用,而封装了之后,别人必须把自己的意图告诉你,比如这里,他必须说,我要审核通过。

    2021-07-08
    2
    4
  • 孙隽璐
    嵌入式C程序员表示眼馋这些其他语言直接提供的封装方法……

    作者回复: 其实,C 语言也是可以把代码写得很好的,我在《软件设计之美》中用过一个 Linux 文件系统的例子。只不过,花的精力比较多,很多人不愿意这么做而已。

    2021-01-27
    3
  • 桃子-夏勇杰
    我的理解使用不变性限制了大家一起改同一份数据的混乱,但同时又会引发多份副本数据的混乱,郑老师见过这样的情况么?

    作者回复: 难道数据不存回去,只在内存里吗?那就得考虑事务性内存了。😄

    2021-01-19
    3
  • adang
    在 Rust 中变量默认是不可变的,要想使其可变,需要在变量名前加 mut 关键字。这样不仅可以改变变量的值,而且也在告诉阅读代码的人,这个值在某个地方会被改变。 另外,它的所有权机制和对可变引用的规则限制,会减少很多可变的数据这种坏味道的发生。对 Rust 理解的不深,不知道这样的理解对不对。

    作者回复: 所有权机制,目的不是这个吧?

    2021-01-21
    2
    2
收起评论
显示
设置
留言
20
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部