设计模式之美
王争
前Google工程师,《数据结构与算法之美》专栏作者
立即订阅
17839 人已学习
课程目录
已更新 24 讲 / 共 100 讲
0/6登录后,你可以任选6讲全文学习。
开篇词 (1讲)
开篇词 | 一对一的设计与编码集训,让你告别没有成长的烂代码!
免费
设计模式学习导读 (3讲)
01 | 为什么说每个程序员都要尽早地学习并掌握设计模式相关知识?
02 | 从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?
03 | 面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
设计原则与思想:面向对象 (11讲)
04 | 理论一:当谈论面向对象的时候,我们到底在谈论什么?
05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?
06 | 理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?
08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
10 | 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
13 | 实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?
14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
设计原则与思想:设计原则 (7讲)
15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?
16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?
17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?
18 | 理论四:接口隔离原则有哪三种应用?原则中的“接口”该如何理解?
19 | 理论五:控制反转、依赖反转、依赖注入,这三者有何区别和联系?
20 | 理论六:我为何说KISS、YAGNI原则看似简单,却经常被用错?
21 | 理论七:重复的代码就一定违背DRY吗?如何提高代码的复用性?
不定期加餐 (2讲)
加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
加餐二 | 设计模式、重构、编程规范等相关书籍推荐
设计模式之美
登录|注册

21 | 理论七:重复的代码就一定违背DRY吗?如何提高代码的复用性?

王争 2019-12-20
在上一节课中,我们讲了 KISS 原则和 YAGNI 原则,KISS 原则可以说是人尽皆知。今天,我们再学习一个你肯定听过的原则,那就是 DRY 原则。它的英文描述为:Don’t Repeat Yourself。中文直译为:不要重复自己。将它应用在编程中,可以理解为:不要写重复的代码。
你可能会觉得,这条原则非常简单、非常容易应用。只要两段代码长得一样,那就是违反 DRY 原则了。真的是这样吗?答案是否定的。这是很多人对这条原则存在的误解。实际上,重复的代码不一定违反 DRY 原则,而且有些看似不重复的代码也有可能违反 DRY 原则。
听到这里,你可能会有很多疑问。没关系,今天我会结合具体的代码实例,来把这个问题讲清楚,纠正你对这个原则的错误认知。除此之外,DRY 原则与代码的复用性也有一些联系,所以,今天,我还会讲一讲,如何写出可复用性好的代码。
话不多说,让我们正式开始今天的学习吧!

DRY 原则(Don’t Repeat Yourself)

DRY 原则的定义非常简单,我就不再过度解读。今天,我们主要讲三种典型的代码重复情况,它们分别是:实现逻辑重复、功能语义重复和代码执行重复。这三种代码重复,有的看似违反 DRY,实际上并不违反;有的看似不违反,实际上却违反了。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《设计模式之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(33)

  • 辣么大
    1、注释或者文档违反DRY
    2、数据对象违反DRY

    对于1,例如一个方法。写了好多的注释解释代码的执行逻辑,后续修改的这个方法的时候可能,忘记修改注释,造成对代码理解的困难。实际应用应该使用KISS原则,将方法写的见名知意,尽量容易阅读。注释不必过多。

    对于2、例如类
    class User
      String id
      Date registerDate
      int age
      int registedDays
    其中 age可以由身份证号码算出来,而且每年都会递增。注册会员多少天了,也可以算出来。所以是不是可以考虑,数据只存储id和注册时间。其余两个字段可以算出来。

    补充:
    DRY不是只代码重复,而是“知识”的重复,意思是指业务逻辑。例如由于沟通不足,两个程序员用两种不同的方法实现同样功能的校验。
    DRY is about the duplication of knowledge, of intent. It’s about expressing the same thing in two different places, possibly in two totally different ways.

    当代码的某些地方必须更改时,你是否发现自己在多个位置以多种不同格式进行了更改? 你是否需要更改代码和文档,或更改包含其的数据库架构和结构,或者…? 如果是这样,则您的代码不是DRY。

    when some single facet of the code has to change, do you find yourself making that change in multiple places, and in multiple different formats? Do you have to change code and documentation, or a database schema and a structure that holds it, or…? If so, your code isn’t DRY.

    参考:
    The Pragmatic Programmer: your journey to mastery, 20th Anniversary Edition (2nd Edition)
    2019-12-20
    1
    4
  • 啦啦啦
    产品经理有时候设计产品功能的时候也会重复
    2019-12-20
    4
  • blacknhole
    1,提个小问题:

    “实现逻辑重复”一节的代码是不是有点问题啊?

    if (!(c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.') {}似乎应该改为if (!((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.')) {}。

    2,说个小体会:

    “可复用性、可扩展性、可维护性……”与“复用性、扩展性、维护性……”,加不加“可”,其实没有本质差别。我以为,在通常的语境中(或者几乎任何情况下),两者都是可以通用的。

    比如,可复用性高,说明能够复用,与当前是否已经复用无关。复用性高,是指当前已经大量复用,说明在这之前可复用性高。已经大量复用时,依然可以更多地复用,也即:复用性高,意味着可复用性依然高。

    通常的语境中,也即通常提到“复用性”时,人们几乎只关注能不能复用,而不是已经复用了多少。所以,可以认为,可复用性高等同于复用性高。
    2019-12-20
    1
  • 李小四
    设计模式_20

    # 作业:
    想到的只有文档和注释的重复了,比如两个不同功能的文档,同时描写一个细节时,可能“负责”的产品经理会各自清清楚楚地写一遍。然后:
    - 看的人就会懵,(描述相同时)写了两个地方,看一下是不是还有别的地方有描述;(描述不同时),应该以哪个为准。
    - 改的人也会懵,很容易忘记修改更新,更何况文档不更新程序又不会报错。。。


    #感想:
    回到“少干活 和 少犯错”的宗旨,重复的代码不仅写的时候会多些一遍,改的时候也要多看很多地方,多想很多差异性,多改很多地方,这样就违背了“少干活”;改的时候,容易忘记一些地方,维护多种逻辑实现的同一个逻辑,也容易疏忽而出错,这样就违背了“少出错”。

    说句题外话,文中提到“Rule of Three”时,原来外国人也用“三”表示多个,而且表示的还是2个。。。
    2019-12-20
    1
  • DullBird
    课堂讨论没有想到其他的了。
    理解一下DRY,总结就是抽取统一“逻辑”,还有相似逻辑的简化统一,
    为的就是同一“逻辑”,维护一块地方就行了。
    2019-12-20
    1
  • 黄林晴
    “Rule of Three”中的“Three”并不是真的就指确切的“三”,这里就是指“二”。😂
    这句话看了好几遍

    作者回复: 😂

    2019-12-20
    4
    1
  • magict4
    > 重复执行最明显的一个地方,就是在 login() 函数中,email 的校验逻辑被执行了两次。一次是在调用 checkIfUserExisted() 函数的时候,另一次是调用 getUserByEmail() 函数的时候。这个问题解决起来比较简单,我们只需要将校验逻辑从 UserRepo 中移除,统一放到 UserService 中就可以了。

    这样处理会有一个问题:如果别的 xxxService 也需要用到 UserRepo,而且没有对 email 跟 password 进行校验,直接调用了 UserRepo.checkIfUserExisted() ,会产生异常。

    一种方法是约定,所有关于 User 的操作都只能通过 UserService 进行,不能直接调用 UserRepo。

    另一种方法是“强制” xxxService 进行校验。我们可以把 UserRepo.checkIfUserExisted 的方法签名改成

    UserRepo.checkIfUserExisted(Email email, Password password)

    并且把 validation 的逻辑封装在 Email 跟 Password 类的构造函数中。这样 xxxService 必须先把 email 跟 password 从 String 类型转成对应的 Email/Password 类,才能调用 UserRepo,validation 的逻辑会在转换中被强制执行。
    2019-12-20
    1
  • 小伟
    从服务提供者的角度看,提供者不收敛也违反了DRY。
    举例,userService和userManager都提供了getUser方法,无论这两个getUser的签名多么不一样,都违反DRY。因为在service层有了两个user服务提供者,这样对于userService的上层调用者来说会困惑,而且调用链路很长且日志相似的话,很难不靠debug来追踪问题
    2019-12-20
  • 哈喽沃德
    啥时能出设计模式的教程,我的大刀早已饥渴难耐了
    2019-12-20
  • whistleman
    打卡~
    Don't repeat yourself.
    逻辑重复、功能语义重复、代码执行重复
    2019-12-20
  • 我来也
    对于老师说的<代码执行重复>下面的<查询数据库>重复,我有话要说.

    在大部分的场景下,这种方法确实可以减少数据库的I/O.
    但是,如果在对安全性要求较高时,可能会选择牺牲一些这方面的性能了.

    避免出现像CPU的旁路攻击,把不该取的信息给取出来了,有泄漏的风险.
    2019-12-20
  • Dimple
    同一个项目,多个人维护,如果内部没有沟通好,会经常会出现重复性的无用功,就像老师文中说的检测IP地址合法性这个,所以,需要一些好的经验来命名模块、类、函数等,尽量做到知名知意。

    代码经验也是避免重复路径之一,这也是为什么这么多人需要提升模块设计吧。
    2019-12-20
  • Jxin
    关于本章,提个问题:

    1.函数名 检验用户名或密码。这样违反单一职责,因为语义上做了两件事。但如果函数名定义为 检验注册入参,那么对“用户名”和“密码”的检验都属于入参检验,是否就满足单一职责了。我一直认为是满足的,望栏主给个定论或者看法。

    关于本章,给个补充:

    1.除了可复用性,其实还可以加一个易复用性。

    2.可复用性讲究的是抽象接口对多个场景兼容的能力,而易复用性讲究的是对特定一类场景,在使用该接口时的成本要低,意思要明确(可以认为是接口隔离的一种体现)。

    3.可复用性追求的最终目标是平台化。易复用性则是中台化。


    以上非官方论述,仅个人的认知定义,望栏主给予一些自己的看法。谢谢。
    2019-12-20
  • 陈华应
    理解思想和原则比直奔模式更重要,知其然,知其所以然!
    业务上的重复!
    2019-12-20
  • 台风骆骆
    脱离出代码,从系统设计来看,技术与业务分离也是一种符合DRY原则的做法,即把一些与业务无关的脱离出来形成一个框架,而不是每来一种业务就再写一个相应的逻辑出来,如mapreduce框架/storm框架这些。
    2019-12-20
  • plain
    设计每个模块、类、函数,都要像设计外部api一样去思考,隐藏可变的细节、暴露不变的接口。
    2019-12-20
  • mike
    大佬,感觉更新好慢啊,真正讲到具体的设计模式还得要1个月呢。。。
    2019-12-20
    2
  • 马哲富
    重复可以从三个方面去理解:代码逻辑重复,功能语义重复,代码执行重复;其中代码逻辑重复,但是功能语义不重复并不违反DRY原则;代码逻辑不重复,但是功能语义重复,违反了DRY原则;执行代码重复违反DRY原则。
        提高代码可复用性的七个方法:
        1、减少代码耦合;
        2、满足单一职责原则;
        3、模块化,从代码层面来讲包括方法、类,其中需要运用抽象思维,和单一职责原则;
        4、业务代码和业务代码分离,业务代码一般不通用,非业务代码通用些;
        5、通用代码下沉,上层代码可以调用下层代码,下层代码不调用上层代码;
        6、合理运用面向对象语言特性:继承、多态、抽象、封装;其中抽象能力很重要;
        7、合理运用各种设计模式,例如模板模式
    2019-12-20
  • 堵车
    写业务的时候,经常会有get/set的赋值操作。把一个对象的值部分/全部拷贝给另一个对象,对象的字段有些异样,有些不一样。这种代码怎么写比较优雅?如果是A对象值拷贝给B对象,这种get/set操作归属于A域还是B域啊?
    2019-12-20
    1
  • 编程界的小学生
    dry相对来讲应该是最简单的一种原则,但是真正做到的却很少。尤其是一开始想着后期重构,结果发现越写越乱,难以维护。
    2019-12-20
收起评论
33
返回
顶部