发布于 2026-01-06 6 阅读
0

我们都应该编写 WET 代码

我们都应该编写 WET 代码

作为一名开发者,你最先学到的就是,代码要“好”,就必须遵循 DRY 原则。DRY 代码几乎成了一种荣誉勋章——你做得越多,你的开发水平就越高。毕竟,如果代码写了两遍,怎么能算得上简洁呢?而且你也知道,删除代码总是比添加代码更好。另外,当你需要修改代码时该怎么办?难道要——天哪——在两个地方都修改一遍吗?这已经成了我们的第二天性,我甚至见过开发者为了避免重复编写相同的函数序列,把辅助函数嵌套在辅助函数里。
这种对 DRY 原则的执着其实对我们有害。它虽然是一条易于遵循的经验法则,但却阻碍了我们深入思考代码的复杂性所在。更重要的是,它还会带来一个经常被忽视的代价——过早的抽象。我们一心想简化代码,结果却操之过急——在还没弄清楚哪些代码部分是真正共享的之前就这么做了。最终导致抽象层臃肿不堪,充斥着各种标志和条件,这些都是为了应对所有用例并避免重复而匆忙堆砌起来的。


我曾经在一家公司工作,整个系统只有一个弹出组件。如果系统里没有那么多弹出窗口,这倒也无妨。当然,我们有信息弹出窗口、警告弹出窗口、确认弹出窗口和错误弹出窗口。但我们还有表单弹出窗口、包含多个操作的弹出窗口、会跳转到其他页面的弹出窗口,以及会覆盖其他弹出窗口的弹出窗口。撇开糟糕的用户体验不谈,开发者的体验也同样糟糕,因为所有这些弹出窗口最终都由同一个组件创建。这个通用的“模态”组件可以接收一个类型(例如 `<type>` 或error` <type> alert`),以及许多不同的标志(例如 `<flag>` isFormisDismissable`<flag> isSecondLevel`、`<flag>` 等)和函数(例如onClose` <flag>`、 onConfirm`<flag> `、`<flag>` onSubmitonSave`<flag>` 等)。然后,组件本身为每个参数都编写了条件语句,从而产生了几乎无限多的组合(以及无数的 bug)。这简直是个怪物。
你知道吗?所有参与系统构建的资深团队成员,竟然没有一个人觉得这有什么问题。他们觉得这完全不符合 DRY 原则!我们当时只有一个弹出组件,却在整个系统中反复使用!即便它复杂到我这个新人完全看不懂,那又怎样?对他们来说,这很容易理解,因为他们加入的时候组件还比较小巧易读,然后他们逐步修改,这些修改对他们来说都很容易理解。但等我接手的时候,它已经变得极其复杂,根本无法理解或维护。
这就是 DRY 原则如何掩盖了过早的抽象。第一个开发者心想:“这两个东西很相似,我把它们抽象成一个函数就行了。”下一个开发者来了,看到了这个抽象,发现它包含了她需要的大部分功能。她不想重复代码,所以决定重用这个抽象,只是添加一个条件。接下来几个考虑重用这个抽象的人也都这么做了。没有人想重复代码,因为我们都被教导 DRY 原则至上,而且他们都认为自己做的改动是合理的。因为他们了解并理解代码,所以他们认为代码本身很容易理解,他们的修改不会增加多少复杂性。但最终,大量的条件和标志会使代码难以管理,最终它会像所有糟糕的抽象一样——被彻底重写。


就在我遇到这个弹出窗口事件的同时,我碰巧遇到了一位经验丰富的开发者朋友。我告诉他我理解这个新代码库有多么困难,他说:“我不相信DRY(Don't Repeat Yourself,不要重复自己)原则,我信奉WET原则。” WET,也就是“把所有东西都写两遍”(缩写真有趣!)。
WET原则背后的逻辑是:实际上,把东西写两遍的成本并没有那么高。复制部分代码对包大小的影响相对较小。如果我需要修改它们呢?嗯,我可以再写两遍。所以,在一段代码被使用三次之前,真的没有必要对其进行抽象。
同时,在代码被使用三次之前,我很难确定究竟应该提取哪些内容——哪些是真正共享的,哪些看起来是共享的,但实际上只是只与其中两个实例相关的特殊情况。拥有三个类似的代码实例,可以帮助我们开始识别模式——哪些代码片段在我们的代码库中可能真正具有多种用途,哪些代码应该放在一起,哪些代码虽然可以一起使用,但实际上应该分开。
想象一下,如果这些弹出窗口是用 WET 代码编写的:第一个需要弹出窗口的开发人员只需……根据自己的用例创建一个弹出窗口。下一个开发人员也会这样做。第三个弹出窗口则需要一些思考和重新设计:假设系统现在有一个确认弹出窗口和一个错误弹出窗口,并且需要添加一个表单弹出窗口——这三个弹出窗口的哪些部分是共享的,可以从抽象中受益?是样式吗?是关闭函数吗?
你会注意到这种方法的一些特点:

  1. 实际上,这样做比仅仅凭直觉将类似的代码简化为共享抽象层要花费更多的时间和精力。
  2. 当你认真思考这类抽象概念时,你很可能会发现共享代码比你想象的要少。
  3. 在这个流程结束时,团队可能没有共享的组件,但会拥有一些共享的功能。目标不是尽可能多地共享,而是只共享实际需要的部分。

编写 WET 代码比编​​写 DRY 代码更难,但绝对值得,尤其如果你希望代码库能够长久运行。它能防止过早进行抽象。它能让你更容易地看出哪些功能是真正共享的,应该一起抽象,哪些功能只是相邻的,可能需要单独抽象,以避免耦合。它还能生成更小的抽象层,更容易理解和维护。
这才是我们都应该遵循的编码方式。

文章来源:https://dev.to/nettab/we-should-all-be-writing-wet-code-3d95