Don't Repeat Yourself
初见端倪
DRY,第一次看到这个词,是在《程序员修炼之道》里,一本在大学时就听说的书,但一直没有读,直到工作两年再次拾起,仿佛遇到故友。
方法的DRY
事实上,从大一开始的时候我就一直执行DRY原则,那个时候学习面向过程(C语言)。因为我很懒,所以有的代码不想写两边,便抽成一个公共函数。
变量的DRY
直到毕业,我还会看到有的同学会写出下面的代码。
1 | if (isValid(getFromDB())) { |
显然,查询了2次数据库,这是没有必要的。更好的写法应该是:
1 | Object dbValue = getFromDB() |
以前我也认为这很简单,事实上还是有很多人做不到。
复用变量可以说是复用的最小粒度。
有时候,我们也可以使用懒加载工具来更好的控制非必要执行:
1 | Lazy<Object> dbValueLazy = Lazy.of(() -> getFromDB()) |
实际工作中,我还没见过有人这么写。
类的DRY
模板方法设计模式非常直观执行DRY原则,比如我们常用的继承。
功能的DRY——组件化、微服务、DDD、框架化
一个功能可以是一个方法,比如一个工具方法,也可以是一个类或一组类。在Spring里,可以是一套Controller、Service、Repository的组合,提供一套能力,看起来更像是一个领域(domain)。
原则上,能力越抽象,可复用性就会越强,但过度抽象也会导致代码复杂度的提升和可读性的降低。
我认为,理想的项目应该存在大量的开箱即用的工具,这些工具可以降低业务代码量。
比如下面一段非常常见的代码:判断不合法然后抛出异常。
1 | if (!isValid(value)) { |
如果我们封装了工具方法,我们在业务代码里只需1行即可。函数实现参考了guava。
1 | checkArgument(isValid(value), "invalid value: %s", value) |
这种做法还统一了抛出异常的类型,更重要的是隐藏了抛出的具体异常实现,仅表达了我们的目的:校验参数,至于以后抛出的是BusinessException还是IllegalArgumentException,仅需改一个地方即可。
对于上面抛出的异常,Spring可以使用统一的全局异常处理器和ResponseBodyAdvice,但如果没有相关工具,我们每个Controller的代码可能是:
1 |
|
如果有,那么我们的业务代码更简单:
1 |
|
然后在全局异常处理器中分别处理BusinessException异常和其他异常。
服务的DRY——平台化
在微服务架构里,我们希望我们的服务提供单一能力,但不是一蹴而就的。
当一个业务成熟后,我们也会希望把业务能力通用化、标准化、平台化。
比如之前做人群标签,项目初期仅考虑人群,项目成熟之后,我们后来会想做一个通用标签平台,不仅仅对于用户,我们提前约定好标签数据的格式。然后将之前的人群标签,按照新的平台标准格式接入到标签平台。
比如对于支付能力,在我们作完充值、购买后,我们希望支付能力是通用的,所以做了支付中台,隐藏每种支付渠道的实现,对外提供标准的下单、查询、回调等能力,然后将充值、购买都接入支付中台。
再比如各种云服务商、各种XaaS平台。
程序的DRY
java开始的口号便是“Write once, run anywhere”,也是一种DRY。
日常运维DRY——开发自动化运维工具
现在,排查问题总免不了查询MySQL,使用查询工具Navicat、DataGrip等,但我们总是需要写很多重复的SQL,并且DB的结果并不直观,除非你记住表里的1、2、3分别代表什么。
为此,我在业余时间开发了一个查询工具网站,预设了一些我常用的表,以及每个表的筛选字段。也对一些表增加了跳转,比如一个表有user_id字段,增加一个链接,链接到对应的用户详情表。这样,以前需要写模板SQL的,现在只需要点点点即可。也可以在一个页面同时展示所有用户相关的表,一览无余。后来该工具在部门内做了推广,被评价“一个朴实而又不失优雅,简单而又不失自然的小工具”。
DRY的一些问题
失去灵活性。如果想在共用的程序里执行一些定制化逻辑,看起来会比较困难,并且破坏了封装原则(也或许证明当下是过度封装)。这种情况,应放弃DRY,或者应该使用更细粒度的DRY,避免过度封装
性能损失。一个DRY模块内部或许是高效的,但模块之间的交互,比如函数调用、类的创建和销毁、微服务间的RPC调用,都会导致性能损失。但从系统维护成本、代码可读性、系统隔离等方面考虑,这种消耗通常是值得的。
遏制创新。这是平台化后的问题,我认为也属于过渡封装。所有的接入方都要按照平台的要求接入,如果某项功能平台不支持,则没办法(可以给平台提需求,但排期不可控),或者使用一些比较trick的方法实现。我们在享受平台便利的同时,平台也在限制我们思考与创新。
生活上的DRY
生活上,有时我们需要重复,比如学习一项技能、坚持读书健身,这些重复是有意义的。
但我们也能看到一些DRY的场景,比如一些大而全的APP、聚合网站等,尤其是聚合社交网络
比如我们要在社交网络上发表动态,有微博、X、知乎等很多平台,如果我们每个都发一边也是很麻烦,于是就有类似的聚合平台,关联上每个平台的账号,发一次即可同步到其他平台。
又比如现在的智能家居,开门自动化等操作,都是为了减少生活中的重复。
重复的生活也会对人的心理健康产生负面影响。
所以有一种说法便是,懒是社会进步的推动力,人会想办法让自己可以懒,因为我们想懒,所以我们必须创造各种工具,提高效率。
总结
无论我们写代码、还是系统设计、日常生活,DRY无处不在。时刻提醒自己,尽量不要重复你自己!