或许你听说过 DRY 原则,但我打赌,你理解的肯定有偏差;或许你从未听过,那太好了,本文会让你受益匪浅,对你的编码一定有指导作用,甚至对你的工作生活都有些许启发。

1 简介

DRY,Don’t Repeat Yourself

1.1 什么是 Repeat Yourself

Repeat Yourself:多个地方表达相同的含义。

Repeat Yourself 的坏处:

  • 变更其中一个,就必须记得变更其他,维护负担很重。
  • 就个人来讲,迟早会改漏。如果换个人维护,就更别指望了。

1.2 如何理解 DRY 原则

  • DRY 针对的是知识和意图的复制,强调多个地方表达的东西其实是相同的,只是表达方式不同。

Q:知识和意图 这两个词比较抽象,如果具体到编码,指代的什么呢?

理解误区:

  • 有些人将 DRY 固化为编码规范,这是狭隘的。
  • 至少,别把它理解为“不要复制粘贴代码”,它和你想的真的不一样。
  • 实际上,DRY 原则对工作、生活中的问题也有着指导作用,比如我写本文由于要作用于博客、ppt 等场景,创建了多个副本,设想一下,如果我要修改某个章节,是不是每处都要改?到时候我或许会抓狂。

2 DRY 原则描述了哪些重复现象

2.1 代码重复

“复制粘贴”代码只是代码重复的一种特例,很多情况下,都不是你想的那样。

# 定义账户类
class MyAccount(object):__slots__ = ['fee', 'balance']def __init__(self, fee, balance):self.fee = feeself.balance = balance# 定义打印函数
def printAccount(account):if account.fee < 0:print('fee:%10.2f' % -account.fee)else:print('fee:%10.2f' % account.fee)if account.balance < 0:print('balance:%10.2f' % -account.balance)else:print('balance:%10.2f' % account.balance)# 函数调用
myAccount = MyAccount(100, -300)
printAccount(myAccount)

以上代码没有复制粘贴,但仍有两处重复。

  • 第一处重复:负数处理。修改:

    def printAccount(account):print('fee: %10.2f' % formatMoney(account.fee))print('balance: %10.2f' % formatMoney(account.balance))def formatMoney(amount):return abs(amount)
    
  • 第二处重复:print 的格式。修改:

    def printAccount(account):myPrint('fee', account.fee)myPrint('balance', account.balance)def myPrint(label, amount):print('%s: %10.2f' %(label, formatMoney(amount)))def formatMoney(amount):return abs(amount)
    

Q:所有代码的重复都是知识的重复么?

2.2 文档重复

这里的文档是广义上的,还包括注释等。

比如方法的注释把方法中的逻辑分支都描述了一遍,函数的意图就被描述了两次(注释、代码各一次)。只要经过两次需求变更,代码和注释就会变得不一致。

private static boolean isAllTicketsStatusOk(FlightOrder order, BigOrder bigOrder) {/** 三种情况,都能申请邮递:* 1. 退票单 || 该机票已经退了* 2. 飞后寄 && 已乘机(只作用于新单,新单乘机后,自行递归原单进行处理)* 3. 立即寄 && 已出票、已改签、已乘机(由于打印行程单任务可能拖延时间,故加入已乘机)*/Predicate<FlightOrderTicket> isTicketStatusOk = item -> item.getStatus() == Returned|| bigOrder.getDistInfo().getDistMode() == DistMode.DistAfterRide && item.getStatus() == Ride// Changed 表达的是原单|| bigOrder.getDistInfo().getDistMode() == DistMode.DistImmediately && item.getStatus().in(Changed, TicketSuccess, Ride);boolean isAllTicketsStatusOk = order.getOrderTickets().stream().allMatch(isTicketStatusOk);LOG.debug("isAllTicketsStatusOk: {}", isAllTicketsStatusOk);return isAllTicketsStatusOk;
}

写注释是好习惯,这里绝对不是让你为了规避 DRY 原则,把注释全部删掉。

但是,注释掩盖不了糟糕的代码

如果是为了掩饰方法中糟糕或者晦涩难懂的代码,这时候应该重构代码。

推荐:

  • 方法名准确的描述方法要做什么,方法内每行代码都写的像注释一样清晰易懂,注释则可以移除。
  • 对于 if-else 分支多的场景,不要试图用注释把复杂的逻辑讲清楚,多借鉴一下设计模式来优化代码结构,比如策略模式模板方法模式等。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NiPZmWYw-1639407401231)(https://github.com/viper140/viper140.github.io/blob/master/_posts/2020/11/imgs/if-else.png)]

2.3 数据重复

其实就是常说的数据冗余。

class Line {Point x;Point y;double length;
}

x、y 两点即可确定连线的长度,length 字段明显重复了,应该改成方法:

double length() {}

即使不在同一个类,也可能构成重复。

举个例子,假设一个行程 route,包含多个航段 segment,route 有的距离,segment 上也有距离。

class Route {List<Segment> segments;int distance;
}class Segment {int distance;
}

route 上的距离是其下所有 segment 距离之和,定义成字段,就重复了,改成方法。

class Route {List<Segment> segments;int getDistance() {return this.segments.stream().map(item -> item.getDistance()).sum();}
}class Segment {int distance;
}

数据库实体类比较特殊,有时候要考虑性能因素,采取了冗余措施。

比如下面例子的 amount 字段。

@Table
class FlightOrder {BigDecimal amount;List<FlightOrderPassenger> passengers;
}@Table
class FlightOrderPassenger {BigDecimal amount;List<FlightOrderPassengerTicket> tickets;FlightOrder belongedOrder;
}@Table
class FlightOrderPassengerTicket {BigDecimal amount;FlightOrderPassenger belongedPassenger;
}

将更新冗余字段的逻辑封装在类内部集中处理

@Table
class FlightOrderPassengerTicket {BigDecimal amount;FlightOrderPassenger belongedPassenger;void setAmount(BigDecimal amount) {this.amount = amount;this.resetOrderAmout();}void addAmount (BigDecimal amount) {this.amount = this.amount + amount;this.resetOrderAmout();}private resetOrderAmout() {var passenger = this.belongedPassenger;passenger.amount = passenger.getTickets().stream().reduce(BigDecimal.ZERO, BigDecimal::add);var order = this.belongedPassenger.belongedOrder;order.amount = order.getPassengers().stream().reduce(BigDecimal.ZERO, BigDecimal::add);}
}

推荐:

  • 合理使用方法替换属性,来去除重复。
  • 不得不冗余的地方,将相关的逻辑尽可能控制在局部,来减少重复带来的风险。

2.4 表征重复

主要描述的和外部打交道的时候,不可避免的重复。代码必须持有外部系统已经蕴含的知识(表征)。包括 API、数据 schema,错误码等等。

2.4.1 API 的重复

对于 API,客户端代码、API 定义、服务端代码,两两之间存在重复。

推荐:

  • 使用 swagger 等 API 管理工具、框架。
  • 使用 lib 包,可以封装实体类,甚至更进一步,把远程调用的代码也封装进来。

以上两种方式,都消除了 API 定义、服务端代码之间的重复,不足是无法消灭客户端重复,但也可以非常便利的手动触发完成重复消除。

2.4.2 数据源的重复

实体类对数据表的定义和数据库实际的表结构存在重复。

推荐:

  • 借助 orm 框架,自动实现对象和关系型数据库的映射。这是一种方式,但需要谨慎对待,数据问题无小事。
  • 合理采用 json 字符串类型字段,那么它是什么就完全由客户端决定了,消除了数据表那边的定义。

2.5 开发人员重复

准确的说,是不同服务(开发人员)对同一需求都有自己的实现。它最大的问题是不在同一服务很难实现共用,尤其是前后端,存在语言壁垒。

最容易产生重复的就是校验规则,手机号、邮箱校验等,不同服务不同的开发人员都有自己的实现。

推荐:

  • 加强沟通。
  • 建立知识库。 (不得不说上面两句话是无比正确的废话)
  • 同语言的服务抽取通用 lib。
  • 同语言的服务,在项目构建工具的帮助下,在同一个仓库中组织起来,依赖公共组件服务。

但是,无法破除跨语言的壁垒

3 总结

  • DRY 原则描述的重复是 知识和意图 的重复。包含 代码重复文档重复数据重复表征重复开发人员重复
  • 这些类型的重复,很多都需要消灭,但不是全部:
    • 有些重复并不一定是的知识和意图 的重复,消灭反而讲不通。
    • 有些知识的重复,因为性能等方面的考量,予以保留,但应妥善对待。
    • 还有些重复没法根除。

4 最后的忠告

规则终究是规则,思想终究是思想。实践起来困难重重,忠告:

  • 不要打着 DRY 的旗号,提前抽象,请遵循 Rule of three 原则(三次原则)。
  • 不要过度追求 DRY,破坏了内聚性,这两者很难两头都握住,很遗憾的告诉你,没有规则可言,多向经验丰富的程序员讨教。

可能被你误解的 DRY 原则相关推荐

  1. 设计原则之DRY 原则

    设计原则之DRY 原则   DRY(Don't Repeat Yourself),翻译成中文就是,不要重复你自己.这个原则最早出现在经典著作<程序员修炼之道>里,定义是这样的:系统的每一个 ...

  2. 设计模式:KISS、YAGNI、DRY 原则,迪米特法则(LOD)

    如何理解"KISS 原则"? KISS 原则的英文描述有好几个版本,比如下面这几个. Keep It Simple and Stupid. Keep It Short and Si ...

  3. 【转】DRY原则的误区

    很多编程的人,喜欢鼓吹各种各样的"原则",比如KISS原则,DRY原则-- 总有人把这些所谓原则奉为教条或者秘方,以为兢兢业业地遵循这些,空喊几个口号,就可以写出好的代码.同时,他 ...

  4. 设计模式之美——DRY原则 和 迪米特法则

    DRY原则 Don't Repeat Yourself.中文直译为:不要重复自己.即,不要写重复的代码. 我们主要讲三种典型的代码重复情况:实现逻辑重复.功能语义重复和代码执行重复. 实现逻辑重复 p ...

  5. python dry原则_python使用建议与技巧分享(一)

    这是一个系列文章,主要分享python的使用建议和技巧,每次分享3点,希望你能有所收获. 1 如何创建指定长度且有特定值的list 不推荐方式 list1 = [0, 0, 0, 0, 0, 0, 0 ...

  6. python dry原则_关于Python 的这几个技巧,你应该知道

    随着大数据时代的到来,我们每天都在接触爬虫相关的事情,这其中就不得不提及Python这门编程语言.我已经使用Python编程有多年了,即使今天我仍然惊奇于这种语言所能让代码表现出的整洁和对DRY编程原 ...

  7. 通过 Python 装饰器实现DRY(不重复代码)原则

    通过 Python 装饰器实现DRY(不重复代码)原则 英文原文:DRY Principles through Python Decorators Python装饰器是一个消除冗余的强大工具.随着将功 ...

  8. 设计模式之美总结(设计原则篇)

    title: 设计模式之美总结(设计原则篇) date: 2022-10-27 17:31:42 tags: 设计模式 categories: 技术书籍及课程 cover: https://cover ...

  9. 设计原则与思想:设计原则12讲

    文章目录 设计原则与思想:设计原则(12讲) 理论一:对于单一职责原则,如何判定某个类的职责是否够"单一"? 如何理解单一职责原则(SRP)? 如何判断类的职责是否足够单一? 类的 ...

最新文章

  1. 理解五个基本概念,让你更像机器学习专家
  2. xm console无法联接guest问题的解决
  3. 【快速幂+中等难度】Calculation 哈工大HITOJ2901
  4. 虚拟化:IT技术的第三次革命
  5. 广联达2018模板算量步骤_工程人必须掌握:这9份软件算量教程+24份算量计算表,无偿分享...
  6. 科大星云诗社动态20210824
  7. 逆向查找_CTFer成长之路--一道数独逆向题目解题过程(算法分析、查找线索)...
  8. MySQL数据库 基本操作语句
  9. numpy 平方_NumPy入门指南
  10. 前端笔记之ES678WebpackBabel(上)初识ES678Babellet和const解构语法
  11. 现代优化计算方法_【公开课】供应链库存优化与需求预测管理
  12. cake-build -.Net Core 跨平台构建自动化系统
  13. python如何使用sdk_如何通过Python访问Kvaser CANlib 软件开发包|Kvaser CANlib SDK的应用...
  14. 考研刷题小程序的盈利模式分析
  15. Gallery与Imageswitch完美结合 做相册一绝啊
  16. 记一次重大的生产事故
  17. 一个命令禁用baloo_file及baloo_file_extractor
  18. 【正点原子FPGA连载】 第七章 Verilog HDL语法 摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0
  19. MySQL创建用户,更改密码
  20. copy-to-clipboard 的拷贝使用

热门文章

  1. 《MySQL DBA修炼之道》——2.2 官方版本的安装
  2. Jenkins的制品管理
  3. 信贷系统搭建——实现登录界面与验证
  4. mysql 分组之后 取分组之后最新的数据
  5. 中国运营商考虑用WiFi亭代替公共电话亭
  6. 微信小程序学习之路——API媒体
  7. 计算机文字录入标准,计算机文字录入员考试大纲标准.doc
  8. mysql myisam 主键关联_MySQL中myisam和innodb的主键索引有什么区别?
  9. 基于Java的多元化智能选课系统 毕业设计-附源码040909
  10. Happy Programming Contest(01背包)