重构的艺术:编写更好代码的 5 个技巧 摆脱 switch 语句 使条件更具描述性 使用保护子句避免嵌套 if 语句 避免代码重复 函数应该只做一件事 结论

2025-05-25

重构的艺术:编写更好代码的 5 个技巧

摆脱 switch 语句

使你的条件具有描述性

使用保护子句来避免嵌套 if 语句

避免代码重复

函数应该只做一件事

结论


糟糕的代码也能发挥作用。我们都知道这一点。开发人员多年来一直在编写代码,却从未想过自己写得是否正确。这很正常,不是吗?毕竟,我们已经承受着跟上行业发展和工作需求的压力…… 

答案是否定的。编写糟糕的代码是有代价的。你是否遇到过这样的情况:几周后, 你还是无法理解自己的代码,不得不花费数小时甚至数天的时间才能弄清楚到底发生了什么?

解决这个(极其)常见问题的方法是让你的代码尽可能清晰易懂。我甚至可以说,即使是非技术人员也应该能够理解你的代码。是时候放下借口,提升你的代码质量了 

编写简洁的代码其实并不复杂。本教程将通过实际示例,向您展示 5 种改进代码的简单技巧:

  1. 摆脱 switch 语句
  2. 使你的条件具有描述性
  3. 使用保护子句来避免嵌套 if 语句
  4. 避免代码重复
  5. 函数应该只做一件事

摆脱 switch 语句

我们通常使用 switch 语句来避免冗长的 if else if 语句。然而,switch 语句非常冗长,难以维护,甚至更难调试。它们会使我们的代码变得杂乱无章,而且在我看来,它们的语法很奇怪,让人不舒服。添加更多 case 时,我们必须手动添加每个 case 和 break 语句,这很容易出错。 

我们来看一个 switch 语句的例子:

function getPokemon(type) {
let pokemon;
switch (type) {
case 'Water':
pokemon = 'Squirtle';
break;
case 'Fire':
pokemon = 'Charmander';
break;
case 'Plant':
pokemon = 'Bulbasur';
break;
case 'Electric':
pokemon = 'Pikachu';
break;
default:
pokemon = 'Mew';
}
return pokemon;
}
console.log(getPokemon('Fire')); // Result: Charmander

想象一下,我们需要在 switch 语句中添加更多 case 的情况。我们需要编写的代码量相当大。我们最终可能会复制粘贴代码,我们都知道结果会怎样。 

那么,我们如何避免使用 switch 语句呢?答案是使用对象字面量。对象字面量简单易写、易读、易维护。我们都习惯在 JavaScript 中处理对象,而且它的语法比 switch 语句新颖得多。以下是一个例子:

如你所见,我们可以使用|| 运算符添加默认值。如果在pokemon 对象中找不到该类型,则getPokemon函数将返回 'Mew' 作为默认值。

注意:你可能已经注意到了,我们在函数外部而不是内部声明了 pokemon 对象。这样做是为了防止每次执行函数时都创建它。 

我们也可以使用map来实现同样的效果。map 是键值对的集合,就像对象一样。区别在于,map 允许使用任意类型的键,而对象只允许使用字符串作为键。此外,map 还具有一系列有趣的属性和方法。您可以点击此处了解更多关于 map 结构的信息

使用地图的方法如下:

如您所见,当用对象文字或映射替换 switch 语句时,我们的代码看起来更加清晰和直接。 


使你的条件具有描述性

编写代码时,条件语句必不可少。然而,它们很容易失控,最终变得难以理解。这导致我们要么不得不写注释来解释语句的作用,要么不得不花费宝贵的时间追溯自己的代码才能理解到底发生了什么。这很糟糕。

请看一下以下声明:

如果我们只看上一个函数中 if 语句内部的代码,很难理解到底发生了什么。我们的代码不够清晰,而不清楚的代码只会导致技术债务、错误严重的麻烦。
 
我们如何改进我们的条件判断呢?答案是:将其提取到函数中。方法如下:

通过将条件提取到一个具有描述性名称的函数中:isGameLost(),我们的 checkGameStatus 函数现在一目了然。为什么?因为我们的代码信息丰富,它告诉我们发生了什么,而这正是我们应该始终努力追求的。 


使用保护子句来避免嵌套 if 语句

嵌套的 if 语句是我们在代码中遇到的最糟糕的东西之一。我见过嵌套了 10 层的 if 语句……相信我,要完全理解那段代码到底是怎么回事简直是一场噩梦。下面是一个嵌套 if 语句的示例(虽然只有三层,但我可不是个怪咖):

你可能需要花几分钟时间,从头到尾阅读一遍,才能跟上函数的流程。嵌套的 if 语句阅读和理解都很困难。那么,我们如何摆脱令人讨厌的嵌套 if 语句呢?通过反转逻辑并使用我们所谓的“保护子句”。

在计算机编程中,保护是一个布尔表达式,如果程序要在相关分支中继续执行,则该表达式必须计算为真。 - 维基百科

通过反转函数的逻辑,并将导致提前退出的条件放在函数的开头,它们将充当保护子句,并且只有在所有条件都满足的情况下才允许函数继续执行。这样,我们就可以避免使用 else 语句。以下是如何重构上一个函数以使用保护子句的方法:

如你所见,代码简洁了许多,也更容易理解了。我们只需顺着函数的自然流程往下读,就能明白函数的功能,而不像以前那样需要先从上往下读。 


避免代码重复

重复代码总是会带来糟糕的结果。它会导致诸如“我在这里修复了这个 bug,但忘了在那里修复”或“我需要修改/添加一个新功能,却不得不在五个不同的地方修改”这样的情况。 
正如DRY(不要重复自己)原则所述: 

每条知识或逻辑在系统内都必须具有单一、明确的表示。

因此,代码越少越好:它可以节省我们的时间精力,更容易维护,并减少出现错误的机会。

那么,我们该如何摆脱重复的代码呢?答案并不总是那么简单,但将逻辑提取到函数/变量中通常效果很好。让我们看一下下面的代码,这是我在重构应用程序时遇到的:

你可能已经注意到,这两个函数中的 for 循环完全相同,除了一个小细节:我们想要的新闻类型是JavaScript 新闻还是Rust新闻。为了避免这种重复,我们可以将 for 循环提取到一个函数中,然后在getJavascriptNewsgetRustNewsgetGolangNews函数中调用它。具体方法如下:

将 for 循环提取到 getNewsContent 函数之后,我们的getJavascriptNewsgetRustNewsgetGolangNews函数就变成了简单、清晰的单行代码

进一步重构

然而,你是否意识到,除了传递给getNewsContent函数的字符串类型之外,这两个函数完全相同?这在我们重构代码时经常发生。通常情况下,一个修改会导致另一个修改,如此反复,直到重构后的代码大小只有原来的一半。让你的代码告诉你它需要什么:

进一步重构:我们的getJavascriptNewsgetRustNewsgetGolangNews函数去哪儿了?我们用 getNews 函数替换了它们,该函数接收新闻类型作为参数。这样,​​无论我们添加多少种新闻类型,我们始终使用相同的函数。这被称为抽象,它允许我们重用函数,因此非常有用。抽象是我在代码中最常用的技术之一。

额外奖励:使用 ES6 特性让 for 循环更具可读性

我发誓,这是最后一次重构了。for 
循环的可读性确实很差。ES6 数组函数的引入,让我们 95% 的情况下都能避免使用它们。在我们的例子中,我们可以使用Array.filter和Array.map的组合来替代原来的循环:

  • 使用 Array.filter 我们只返回类型与作为参数传递的类型相同的元素。  
  • 使用 Array.map,我们只返回项目对象的 内容属性,而不是整个项目。

恭喜!经过三次简单的重构,我们最初的三个函数精简成了两个,这让理解和维护都更加容易。此外,通过抽象,我们让getNews函数实现了可复用性。


函数应该只做一件事

函数应该只做一件事,而且只能做一件事。执行多项操作的函数是万恶之源,也是我们在代码中可能遇到的最糟糕的事情之一(以及嵌套的 if 语句)。它们很混乱,使我们的代码难以理解。以下是一个来自实际应用程序的复杂函数的示例:

注意:由于此示例不需要事件监听器的处理程序,因此我选择删除它们。

正如你所见,它很混乱,很难理解其中发生了什么。一旦出现任何错误,查找和修复它们将非常困难。我们如何改进startProgram函数呢?答案是将通用逻辑提取到函数中。方法如下:

让我们来看看对startProgram函数所做的更改

首先,我们通过使用guard clause去掉了 if else 语句。然后,我们将启动数据库所需的逻辑提取到initDatabase函数中,并将添加事件监听器的逻辑提取setListeners函数中。

打印员工列表的逻辑稍微复杂一些,因此我们创建了三个函数:printEmployeeListformatEmployeeListgetEmployeeList

getEmployeeList负责employeeList.json发出GET 请求,并以 json 格式返回响应。 

然后, printEmployeeList函数会调用该函数,该函数接收员工列表,并将其传递给formatEmployeeList函数,该函数会格式化并返回该列表。之后,该列表就会被打印出来。 

如您所见,每个函数只负责做一件事。

我们仍然可以对该函数进行一些修改,说实话,应用程序迫切需要将视图与控制器分离,但总的来说,我们的startProgram函数现在更具信息量,理解它的功能绝对没有任何困难。几个月后我们再回过头来看这段代码,也不会有任何问题。


结论

程序员是唯一负责编写优质代码的人。我们都应该养成从第一行代码开始就编写优质代码的习惯。编写简洁的代码并不复杂,这样做对您和您的同事都有好处。

通过应用本教程中展示的 5 种简单技巧,您的代码质量将得到显著提高,您的工作效率也将得到显著提高。 

如果您有任何疑问,请随时提问。感谢您的阅读。

文章来源:https://dev.to/nyagarcia/the-art-of-refactoring-5-tips-to-write-better-code-12if
PREV
一篇博文涵盖所有 React Hooks 和概念!🤗 Ohk,但是 React 中的状态是什么? useState() useEffect() useContext() useReducer() useReducer() 和 useContext() useCallback() useMemo() useRef()
NEXT
现代 Angular