瞎扯淡 Why Sometimes I Write WET Code

hooopo · 2018年09月07日 · 最后由 bkbabydp 回复于 2019年12月22日 · 3120 次阅读

当我刚学会写代码的时候,自己也算是一个 DRY 狂魔,恨不得把能复用的都抽出公共 method 和 class。随着接触的项目越来越多,修改所谓 DRY 代码遇到各种麻烦,才开始意识到有些 DRY 是没有必要的,DRY 强迫症并不总是给项目可维护带来帮助。最近时不时看到有大牛谈论 DRY 的话题,虽然不完全认同,但也受到一些启发:

DRY code is least expensive to change when the change is universally applicable eg. all files


WET (duplicated) code is least expensive to change when the change only applies to one context eg. one file


how often are changes universally applicable? how often are they contextual?


I’d rather make an easy change to 3 files than a hard change to 1 file.


I’d rather make a hard change to 1 file than easy change to 20 files

大家在工作中一定遇到过这样的情况:有的代码虽然写的很工整,抽象得看起来很完美,但你拿到手上改起来却想骂娘;有些代码看起来不那么 DRY,但修改起来确实最容易的。

有时候,我们高估 WET 代码的修改困难程度,尤其是 Ruby、Python 这样动态语言,在没那么熟悉业务的情况下,大家还是通过编辑器全局搜索、替换,其实替换一处和替换 N 处的代价是相当的,而过于抽象或抽象不好的代码,遇到逻辑变化,你只能深入业务去改了,这是 Hard 模式…

另外一个经验是,新增和删除方法或功能是简单的,修改功能或方法是复杂的。

最后,该 DRY 还是要去 DRY 的,知道原则很重要,但知道什么时候打破原则更重要。

LINKS:

补个 link https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstraction

有时候可以在干湿之间寻找一个平衡点。一次编写到处复制不可行,一次编写到处使用不现实,可以想办法少复制几份,比如对于小部分共享某个更改的函数,单独复制一份修改,然后在这个模块内部 DRY。

DRY 的代码特点是“改了一个地方,所有相关的地方都被影响” 。有时候这是好事,另一些时候则是坏事。我觉得用 reason to change 来衡量代码是否需要可重用是个可行的做法。如果两段代码实现相同,但是为了不同的业务目的,那就让它重复。

另外也赞同接口设计比实现更重要。接口定义得良好,实现烂一点影响范围也有限,并且容易重构过来。个人感觉过度重视局部代码的精巧结构,以 DRY 为主要的衡量指标,甚至以实现来反过来影响接口设计是很多新手常犯的错误。

因为自己写的大部分代码生命周期都不长,一般都不考虑 dry。反正这部分代码,活不过三个版本就被删掉了😂

不太理解 WET code,

如果方法 A,被调用了 3 次。有新的需求,发现方法 A 不好被复用,也可以复制一个方法 A1 啊,这样只要去维护两分代码就可以,也比维护 4 份好啊。。。

当然我也认同有的时候复制粘贴也是有意义的,比如无法抽象出来一个概念、抽象出来的概念过于复杂,或者抽象带来的便利不大等等

yfractal 回复

举个例子,我遇到的大部分"DRY"是这样的,既然项目后台都是 CRUD,为什么要重复一百遍 CRUD 写后台,那么我们抽出一个可以复用的 RailsAdmin or xxxAdmin 吧,这个抽象呢 80% 时候是非常便利的,写起来非常爽,可是随着需求的变化,RailsAdmin 满足不了需求了,你要钻进 RailsAdmin 的源码去各种 monkeypatch,甚至还是有些地方不那么容易改,这就是过早抽象的代价,就如上面说的:I’d rather make an easy change to 3 files than a hard change to 1 file;CRUD 虽然重复,但其实是容易改的,抽象出一个可以复用的虽然 DRY,但抽象不完美的时候是不容易改的,即便只要改一处。

这个例子举得可能有点跳,但 method 和 class 甚至是服务都是同一个道理。

知道原则很重要,但知道什么时候打破原则更重要。

👍

就是够屌的话就无招胜有招咯

写第三次再提炼。

DRY 应该是被迫的,就是你写着写着发现这里可以抽象出一个公共的方法,不然代码就显得很重复,那么就去抽象一下好了。而不是提前就把内部的抽象层级以及依赖设计完整,再去挨个实现它这样。其次我觉得二楼说的这个命名的问题确实很重要,如果抽象出的方法在业务上确实是同一件事,用同一个方法名称可以准确表达出它的涵义,DRY 并不会影响其他程序员维护你写的这段代码,这时这个抽象就很合适。

写 Ruby 的时候强迫症犯了非常致命,class_eval、method_missing 一多,很容易找不到方法定义的位置,改起来也很难受。

hooopo 回复

我想我大概理解一些了。过早、过度的抽象可能会不利于维护!

DRY 也是有代价的,要有判断和取舍 👍

可读性 > 可扩展 > 优雅

@Rei 这个人疯狂灌贴 😟

nouse 回复

认可这个看法,我着手了一年多静态语言之后,虽然 eval / mm / reflect 很灵活,现在反过来就不再爱这些 EMR 了。

如果是在小公司呆着,而且自己写项目,后者小团队,大家互相不干扰干活,是挺好的。

如果碰到是华为、外包、要求严格的企业,他们就不让这么干了,因为他们崇尚 接口设计,而且也不允许使用灵活的语言。

接口定义得良好,实现烂一点影响范围也有限,并且容易重构过来。

不过对于 Python,我建议 @hooopo尤其是 Ruby、Python 这样动态语言,在没那么熟悉业务的情况下 删掉 Python,因为 Python 其实是一门很不灵活的语言。

Python 的 private 根本不是大众口味的面向对象继承的,更像是 C++ 的 private 继承,到了后面的类没法好好取出来,只能通过 Parent_method,因此我觉得 Python 是怪味的,一开始就不旨意你去 call parent private。

C++ 有 public 也有 protected 也有 private 继承模式,组合起来应对各种变态的场景,但是我觉得 Python 学到了它的坏处,同时在动态语言中多重继承。

Python 这么做是为了让 object plain(扁平)得成个 dict,所有 object 都是 meta-table,而且还要是 C-flavor 的 map。

怎么评价呢,反正个人的观点是认为 Python 就是模仿着强类型静态的模式和论调去进行动态编程,然而最终编码和行为一定是诡异的。

所以 Python 的 object 让人用起来很刻板,写起来也感觉很死板。

不像 Ruby,Ruby 从语法上光扬了 EMR,更偏向于 Java 真正将 object 当成有血有肉的 object 的 flavor。

但是 Python 的刻板和死板好处就是对于数据结构初学者有好处,刚学计算机、编程的也很好接受基本类型,是字典就是字典,类就是个字典,对象也是个字典,数据结构就是 struct 套 struct,加上 Python 很少的语法糖,可以让新手很好入门。不用想太多。

Ruby Java 适合进阶玩家,已经有了一套体系,在明白了基础之后,再去带有感性、抽象思维去使用才会好用。

EMR 就是充斥了大量的 感性,就像人编码往往带有个性,这时候不是所有 team member 都理解自己的 imagination,这时候就会带来维护的困难,甚至自己也不记得当初 how to imagine this design 而维护困难。

所以很多公司选择 Python 而不是 Ruby,也有这方面的原因。

如果说:

可读性 > 可扩展 > 优雅

那么,Ruby EMR 之后产生了 DSL,是很优雅,很抽象,但是很可能别人是看不懂的,跟读唐诗文言文一样,得背负着可能无法读懂的框架的问题。DSL 赞扬了个性与隐晦,对于扩展与可读性也是不好下手的事情。

作为企业,企业要尽可能考虑快速生产、迭代、编辑维护(Edit),在经济极端时刻必须极力消灭隐晦,宁可让你去写着从头到尾过程编程,连类(class)都不写,从一个又一个 file module import a import b 变量,跟 C 一样这里那里 extern,甚至干脆全局变量的无脑的 Python,也不会让你去 OOP,让你去 EMR,因为 EMR 是他们的大敌。

所以 Ruby 是一门很可怜的语言,企业不敢用,尤其在现在霜月时刻(涉及敏感 ZZ 问题,不讨论)。

同时,看到最近 Python 在拼命招人,感觉对编码越来越没兴趣了,感觉社会趋向变成了网站编辑、没智商的调包侠的世界了。

解决过分 DRY 或不够 DRY 问题,可简单定 2 个原则:

  1. 不过早“优化” - 先不要假想着将来会有人“重用”这个方法,将来太多不确定性。
  2. “事不过三”原则 - 出现 2 处重复不过份,当第 3 次出现重复时就要考虑 DRY 一下了。(Tips:可以利用工具如 rubycritic、flay 或 flay 等来帮忙找出重复的地方)
Rei 回复

第一阶段:见山是山,见水是水; 第二阶段:见山不是山,见水不是水; 第三阶段:见山还是山,见水还是水

似乎原地绕了一个圈,实际上比第一阶段更拿捏自如了。

重构亦然。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号