重构与解构之法

344 阅读7分钟

译前:最近一直在思考自己的代码,好像都是顺序编程,或者只会简单的把重复的代码进行函数封装,一直在思考自己如何去用面向对象的思想重构自己的代码,至今还没求到方法。 先把大佬的文章进行翻译学习,希望能获得一些启发,帮助我今后的工作开发与真实重构的场景!

重构与解构

当大多数开发人员谈论“重构代码”(refactoring the code)时,他们真正的意思是“重组代码”(restructuring the code)——或者说重写它。当然是为了让它变得更好并修复一些错误。这将是重新组织代码、更改建模和沿途修改某些行为的混合体——最好的!

当然我说的重构并不是这个意思。

当我说“重构”时,我指的是 Martin Fowler 对它的定义:

重构(名词) :对软件内部结构所做的更改,以便在不改变其可观察到的行为的情况下更容易理解和更简单地进行修改。

修复 BUG 不是重构。添加新功能不是重构。但是我们可能会在修复错误或添加功能之前进行重构。

实际上,这样做是个好主意。

将这两个阶段区分开来也是一个好主意。

图片.png

不要重构和行为改变相混合

在最近的一篇文章中,Jason Swett 分享了这个非常重要的建议:don’t mix refactorings with behavior changes

Jason 解释了为什么混合这些会出现问题:

  1. 它增加了在没有意识到的情况下破坏某些东西的风险。
  2. 很难找到是什么变化导致了错误。
  3. 它使代码更难审查。

相反,他建议在一个新分支中重构代码并将该分支合并到主分支中,这样您就可以将其恢复到您的功能/错误修复分支中。

我认为这是一个很好的建议。

根据我的经验,还有其他事情可以提供帮助:

  1. 包含重构的提交与引入更改的提交不同,即使没有创建分支。
  2. 更频繁地提交,所以 1) 更容易做到。
  3. 在您的提交消息前加上R重构和C其他类型的更改。它使审查代码变得更加容易。这是我的Arlo 提交符号的轻量级版本。
  4. 学习和使用自动重构。一些 IDE 开箱即用。其他人有扩展。自动重构既快速又安全——即使您没有测试。重构一件事 > 提交。继续前行。

我认为这不仅仅是一种技术。这是一种心态。

它改变了我编码的方式,就像学习 git 一样。我以前不知道你可以那样工作。花了一些时间和练习,直到我终于理解它。现在,感觉很自然。它使我的工作更加安全和简单。我更清楚发生了什么,所以我可以把我最好的品质投入到我的工作中。🏆

把它想象成戴着两顶不同的帽子:

  1. 使用 重构帽子🎩 ,您不能更改代码的工作方式。
  2. 使用 Change hat 🧢 ,您可以更改代码行为,但您会尽量减少这种更改。

当你处理代码时,你会不断地在这两者之间切换。但是你会意识到这样做。

所以你想修复一个错误。但是你在这里看到一些命名错误的变量。您戴上重构帽🎩,并重命名变量。Commit. Done.

然后,戴上更改帽🧢, 将缺失的回退值添加到应有的位置。这样做时,您会意识到如果使用保护子句而不是嵌套,这段代码会更容易阅读。STOP!

你戴着改变帽子🧢,所以你不应该重构。

你实际上有两个选择:

  1. 提交更改。戴上 重构帽子🎩 并使用保护条款。再次提交。
  2. 还原更改。戴上 重构帽子 🎩 并重构代码。犯罪。戴上 更改帽子🧢 ,对转换后的代码进行更改。

在这种情况下,我可能会做 1)。在其他情况下,我会做 2)。

你可以做很多 2) 并发送一个包含所有这些重构的中间 Pull Request。它们不应该改变代码行为,所以它们很容易审查。它们还使我以后进行实际更改变得更加容易!

或者,您可能会在同一个 PR 中同时发布重构和代码更改。如果它们在不同的提交中,那很好——理想情况下,首先重构提交。

实际变化很小,因此更容易审查。

真正重要的是将重构动作与其余更改区分开来。

解构

我想关注一种特殊类型的重构:内联。

例如,内联变量。 这是一个重构动作。代码将表现相同,无论变量是否被提取或内联(和复制)。

仔细想想,Inline Variable 就是 Extract Variable 的逆向操作。提取变量是我们为减少重复、增加抽象并整体上使代码更易于阅读而采取的常见举措。

那么你到底为什么要内联变量呢?🤨

这是一个有趣的问题。虽然一些编辑器已自动执行提取变量重构,但大多数编辑器不会为您内联。感觉就像二流重构。然而,我认为这是一个核心。

内联变量有不同的原因:

  • 变量名只告诉你表达式本身。在这种情况下,变量添加了不必要的间接寻址。内联降低了认知复杂性(移动部分更少)。
  • 该变量妨碍了另一次重构。当以前的抽象不再是最好的,而你想走另一条路时,就会发生这种情况。

我发现自己经常使用这种重构来删除不再使用的临时变量。例如:

function addParens(value) {
  const string = `(${value})`
  return string
}

字符串中间变量不会增加太多价值。我们陷入场景#1。我会内联这个以消除噪音:

function addParens(value) {
  return `(${value})`
}

将内联视为分解

当我使用 Legacy Code 时,我破坏了很多。我暂时内联一些东西,以便更好地理解代码的作用。那么,我回头。或者也许我以不同的方式提取东西。

分解是一种认知重构。使代码不那么抽象可以帮助您处理正在发生的事情。大脑中可以容纳的东西越来越少。少来回滚动。一些抽象不再是必需的,比如上面的这个临时变量。摆脱间接可以使代码更易于维护。

在以不同方式重构代码之前,分解也是一个常见的举动。旧的抽象可能不再足够好。需求一直在变化,我们应该不断地调整代码。

在这篇博文中,Jimmy Bogard 说明了您将如何分解以准备提取域代码。

重构

归根结底,良好的抽象是一个平衡问题。需要实践和经验才能找到使您的代码更易于维护的最佳点。

如果你想走这条路,我有一些建议给你。

了解您的编辑器可以为您自动执行哪些操作。一些像 Jetbrains 的 IDE(ReSharper、Webstorm、PHPStorm……)可以做很多事情!像 VS Code 这样的其他工具有扩展来帮助你这样做——如果你正在用 VS Code 做 JS/TS,你应该看看 Abracadabra

对于无法自动化的,学习动作。Martin Fowler 的 Refactoring 一书是参考

图片.png

如果您正在寻找更具交互性的东西,我认为Refactoring Guru会很有用。我喜欢他们的目录有公共代码示例。对福勒的书的一个很好的补充。

这个话题太有趣了,我自己正在上一门课程。我的目标是教会 JavaScript 开发人员安全地重构遗留代码库。与书本不同,该课程将是互动的——我在做事时学得更好,而且我知道我不是唯一一个 😉

我还利用自己的经验构建了一条对 JS 开发人员有意义的学习路径:从常见的开始,然后学习需要掌握基础知识的更复杂的动作,等等。

如果您对此感兴趣,请告诉我。当我有更多细节时,我会给你一些更新。

无论如何,请通过这 5 个编码练习练习您的重构技能。它们专为使用 Legacy Code 而设计!

您自己的重构经验是什么?你习惯换帽子吗?🍻

原文链接:Refactoring vs. Defactoring

作者:Nicolas Carlo

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 10 天,点击查看活动详情