搜索
首页web前端js教程清洗你的代码:别让我思考

Washing your code: don’t make me think

您正在阅读我关于干净代码的书“清洗代码”的摘录。提供 PDF、EPUB、平装本和 Kindle 版本。立即获取副本。


聪明的代码是我们在工作面试问题或语言测验中可能会看到的东西,当他们希望我们知道我们以前可能从未见过的语言功能是如何工作的时。我对所有这些问题的回答是:“它不会通过代码审查”。

有些人将简洁清晰度混淆。短代码(简洁)并不总是最清晰的代码(清晰),通常情况恰恰相反。努力让代码更短是一个崇高的目标,但它不应该以牺牲可读性为代价。

在代码中表达相同想法的方法有很多种,有些比其他更容易理解。我们应该始终致力于减少下一个阅读我们代码的开发人员的认知负担。每当我们偶然发现一些不太明显的东西时,我们就浪费了我们的大脑资源。

信息:我从 Steve Krug 的同名网络可用性书中“窃取”了本章的名称。

JavaScript 的暗模式

让我们看一些例子。尝试涵盖答案并猜测这些代码片段的作用。然后,数数你猜对了多少个。

示例1:

const percent = 5;
const percentString = percent.toString().concat('%');

此代码仅将 % 符号添加到数字中,应重写为:

const percent = 5;
const percentString = `${percent}%`;
// → '5%'

示例2:

const url = 'index.html?id=5';
if (~url.indexOf('id')) {
  // Something fishy here…
}

~ 符号称为 按位 NOT 运算符。它在这里的有用作用是,仅当 indexOf() 返回 -1 时,它才返回一个假值。这段代码应该重写为:

const url = 'index.html?id=5';
if (url.includes('id')) {
  // Something fishy here…
}

示例 3:

const value = ~~3.14;

按位 NOT 运算符的另一个晦涩用法是丢弃数字的小数部分。使用 Math.floor() 代替:

const value = Math.floor(3.14);
// → 3

示例 4:

if (dogs.length + cats.length > 0) {
  // Something fishy here…
}

这个很快就可以理解了:它检查两个数组中的任何一个是否有任何元素。不过,最好说清楚一点:

if (dogs.length > 0 && cats.length > 0) {
  // Something fishy here…
}

示例 5:

const header = 'filename="pizza.rar"';
const filename = header.split('filename=')[1].slice(1, -1);

这个问题我花了一段时间才明白。假设我们有 URL 的一部分,例如 filename="pizza"。首先,我们用 = 分割字符串并获取第二部分“pizza”。然后,我们将第一个和最后一个字符切片以获得披萨。

我可能会在这里使用正则表达式:

const header = 'filename="pizza.rar"';
const filename = header.match(/filename="(.*?)"/)[1];
// → 'pizza'

或者更好的是 URLSearchParams API:

const header = 'filename="pizza.rar"';
const filename = new URLSearchParams(header)
  .get('filename')
  .replaceAll(/^"|"$/g, '');
// → 'pizza'

不过这些引用很奇怪。通常我们不需要在 URL 参数周围加上引号,因此与后端开发人员交谈可能是个好主意。

示例 6:

const percent = 5;
const percentString = percent.toString().concat('%');

在上面的代码中,当条件为真时,我们向对象添加一个属性,否则我们什么都不做。当我们显式定义要解构的对象而不是依赖于虚假值的解构时,意图更加明显:

const percent = 5;
const percentString = `${percent}%`;
// → '5%'

我通常更喜欢对象不改变形状,所以我会移动值字段内的条件:

const url = 'index.html?id=5';
if (~url.indexOf('id')) {
  // Something fishy here…
}

示例7:

const url = 'index.html?id=5';
if (url.includes('id')) {
  // Something fishy here…
}

这个奇妙的单行代码创建了一个充满从 0 到 9 的数字的数组。Array(10) 创建了一个包含 10 个 元素的数组,然后 keys() 方法返回键(从 0 开始的数字)到 9) 作为迭代器,然后我们使用扩展语法将其转换为普通数组。爆炸头表情符号…

我们可以使用 for 循环重写它:

const value = ~~3.14;

尽管我喜欢避免代码中出现循环,但循环版本对我来说更具可读性。

中间的某个地方将使用 Array.from() 方法:

const value = Math.floor(3.14);
// → 3

Array.from({length: 10}) 创建一个包含 10 个 未定义 元素的数组,然后使用 map() 方法,我们用 0 到 9 的数字填充该数组。

我们可以使用 Array.from() 的映射回调将其写得更短:

if (dogs.length + cats.length > 0) {
  // Something fishy here…
}

显式的map()可读性稍好一些,我们不需要记住Array.from()的第二个参数的作用。此外,Array.from({length: 10}) 比 Array(10) 更具可读性。虽然只有一点点。

那么,你的分数是多少?我想我的应该是3/7左右。

灰色地带

一些模式介于聪明和可读性之间。

例如,使用布尔值过滤掉虚假的数组元素(本例中为 null 和 0):

if (dogs.length > 0 && cats.length > 0) {
  // Something fishy here…
}

我觉得这种模式可以接受;虽然它需要学习,但它比其他选择要好:

const header = 'filename="pizza.rar"';
const filename = header.split('filename=')[1].slice(1, -1);

但是,请记住,这两种变体都会过滤掉 falsy 值,因此如果零或空字符串很重要,我们需要显式过滤未定义或 null:

const header = 'filename="pizza.rar"';
const filename = header.match(/filename="(.*?)"/)[1];
// → 'pizza'

让代码的差异变得明显

当我看到两行看起来相同的棘手代码时,我认为它们在某些方面有所不同,但我还没有看到差异。否则,程序员可能会为重复的代码创建一个变量或函数,而不是复制粘贴它。

例如,我们有一段代码可以为我们在项目中使用的两个不同工具(Enzyme 和 Codeception)生成测试 ID:

const header = 'filename="pizza.rar"';
const filename = new URLSearchParams(header)
  .get('filename')
  .replaceAll(/^"|"$/g, '');
// → 'pizza'

很难立即发现这两行代码之间的差异。还记得那些需要找出十个不同点的图片吗?这就是这段代码对读者的作用。

虽然我通常对极端的代码干燥持怀疑态度,但这是一个很好的例子。

信息:我们在分而治之或合并与放松章节中详细讨论了“不要重复自己”原则。

const percent = 5;
const percentString = percent.toString().concat('%');

现在,毫无疑问,两个测试 ID 的代码是完全相同的。

让我们看一个更棘手的例子。假设我们对每个测试工具使用不同的命名约定:

const percent = 5;
const percentString = `${percent}%`;
// → '5%'

这两行代码之间的差异很难注意到,而且我们永远无法确定名称分隔符(- 或 _)是这里唯一的差异。

在有这样需求的项目中,这种模式很可能会出现在很多地方。改进它的一种方法是创建为每个工具生成测试 ID 的函数:

const url = 'index.html?id=5';
if (~url.indexOf('id')) {
  // Something fishy here…
}

这已经好多了,但还不够完美——重复的代码仍然太大。让我们也解决这个问题:

const url = 'index.html?id=5';
if (url.includes('id')) {
  // Something fishy here…
}

这是使用小函数的极端情况,我通常会尽量避免如此拆分代码。然而,在这种情况下,它效果很好,特别是如果项目中已经有很多地方我们可以使用新的 getTestIdProps() 函数。

有时,看起来几乎相同的代码存在细微的差异:

const value = ~~3.14;

这里唯一的区别是我们传递给函数的参数具有很长的名称。我们可以在函数调用中移动条件:

const value = Math.floor(3.14);
// → 3

这消除了类似的代码,使整个代码片段更短且更易于理解。

每当我们遇到使代码略有不同的条件时,我们应该问自己:这个条件真的有必要吗?如果答案是“是”,我们应该再问自己一次。通常,对于特定条件并没有真正需求。例如,为什么我们甚至需要为不同的工具分别添加测试ID?我们不能配置其中一个工具来使用另一个工具的测试 ID 吗?如果我们挖掘得足够深,我们可能会发现没有人知道答案,或者最初的原因不再相关。

考虑这个例子:

if (dogs.length + cats.length > 0) {
  // Something fishy here…
}

此代码处理两种边缘情况:当 assetDir 不存在时,以及当 assetDir 不是数组时。此外,对象生成代码是重复的。 (我们不要谈论嵌套三元组......)我们可以摆脱重复和至少一个条件:

if (dogs.length > 0 && cats.length > 0) {
  // Something fishy here…
}

我不喜欢 Lodash 的castArray() 方法将 undefined 包装在数组中,这不是我所期望的,但结果仍然更简单。

避免走捷径

CSS 具有简写属性,开发人员经常过度使用它们。这个想法是单个属性可以同时定义多个属性。这是一个很好的例子:

const header = 'filename="pizza.rar"';
const filename = header.split('filename=')[1].slice(1, -1);

等同于:

const header = 'filename="pizza.rar"';
const filename = header.match(/filename="(.*?)"/)[1];
// → 'pizza'

一行代码而不是四行,仍然很清楚发生了什么:我们在元素的所有四个边上设置了相同的边距。

现在,看这个例子:

const percent = 5;
const percentString = percent.toString().concat('%');

要了解他们的作用,我们需要知道:

  • margin属性有四个值时,顺序为上、右、下、左;
  • 当有三个值时,顺序为上、左/右、下;
  • 当它有两个值时,顺序是上/下,左/右。

这会造成不必要的认知负担,并使代码更难以阅读、编辑和审查。我避免使用这种简写。

速记属性的另一个问题是它们可以为我们不打算更改的属性设置值。考虑这个例子:

const percent = 5;
const percentString = `${percent}%`;
// → '5%'

该声明设置了 Helvetica 字体系列,字体大小为 2rem,并使文本变为斜体和粗体。我们在这里没有看到的是,它还将行高更改为默认值正常。

我的经验法则是仅在设置单个值时才使用简写属性;否则,我更喜欢手写属性。

这里有一些很好的例子:

const url = 'index.html?id=5';
if (~url.indexOf('id')) {
  // Something fishy here…
}

以下是一些需要避免的示例:

const url = 'index.html?id=5';
if (url.includes('id')) {
  // Something fishy here…
}

虽然简写属性确实使代码更短,但它们通常会使其难以阅读,因此请谨慎使用它们。

编写并行代码

消除条件并不总是可能的。但是,有一些方法可以使代码分支中的差异更容易被发现。我最喜欢的方法之一就是我所说的并行编码

考虑这个例子:

const value = ~~3.14;

这可能是我个人的烦恼,但我不喜欢返回语句处于不同的级别,这使得它们更难以比较。让我们添加一个 else 语句来解决这个问题:

const value = Math.floor(3.14);
// → 3

现在,两个返回值都处于相同的缩进级别,使它们更容易比较。当没有条件分支处理错误时,此模式有效,在这种情况下,尽早返回将是更好的方法。

信息:我们在避免条件章节中讨论提前退货。

这是另一个例子:

if (dogs.length + cats.length > 0) {
  // Something fishy here…
}

在此示例中,我们有一个按钮,其行为类似于浏览器中的链接,并在应用程序中显示确认模式。 onPress 属性的相反条件使这个逻辑很难看出。

让两个条件都为正:

if (dogs.length > 0 && cats.length > 0) {
  // Something fishy here…
}

现在,很明显我们可以根据平台设置 onPress 或 link 属性。

我们可以停在这里或更进一步,具体取决于组件中 Platform.OS === 'web' 条件的数量或我们需要有条件设置的 props

我们可以将条件道具提取到一个单独的变量中:

const header = 'filename="pizza.rar"';
const filename = header.split('filename=')[1].slice(1, -1);

然后,使用它而不是每次都硬编码整个条件:

const header = 'filename="pizza.rar"';
const filename = header.match(/filename="(.*?)"/)[1];
// → 'pizza'

我还将 target 属性移至 Web 分支,因为应用程序无论如何也不会使用它。


当我二十多岁的时候,记住事情对我来说并不是什么问题。我可以回忆起我读过的书以及我正在从事的项目中的所有功能。现在我四十多岁了,情况不再是这样了。我现在看重不使用任何技巧的简单代码;我重视搜索引擎、快速访问文档以及帮助我​​推理代码和导航项目的工具,而无需将所有内容都记在脑子里。

我们不应该为现在的自己编写代码,而应该为几年后的自己编写代码。思考是困难的,而编程需要大量的思考,即使不需要破译棘手或不清楚的代码。

开始思考:

  • 当你觉得自己很聪明并编写了一些简短、聪明的代码时,请考虑是否有一种更简单、更易读的方法来编写它。
  • 使代码略有不同的条件是否确实必要。
  • 快捷方式是否使代码更短但仍然可读,或者只是更短。

如果您有任何反馈,请发送给我、发推文、在 GitHub 上提出问题,或发送电子邮件至 artem@sapegin.ru。获取您的副本。

以上是清洗你的代码:别让我思考的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
JavaScript是用C编写的吗?检查证据JavaScript是用C编写的吗?检查证据Apr 25, 2025 am 12:15 AM

是的,JavaScript的引擎核心是用C语言编写的。1)C语言提供了高效性能和底层控制,适合JavaScript引擎的开发。2)以V8引擎为例,其核心用C 编写,结合了C的效率和面向对象特性。3)JavaScript引擎的工作原理包括解析、编译和执行,C语言在这些过程中发挥关键作用。

JavaScript的角色:使网络交互和动态JavaScript的角色:使网络交互和动态Apr 24, 2025 am 12:12 AM

JavaScript是现代网站的核心,因为它增强了网页的交互性和动态性。1)它允许在不刷新页面的情况下改变内容,2)通过DOMAPI操作网页,3)支持复杂的交互效果如动画和拖放,4)优化性能和最佳实践提高用户体验。

C和JavaScript:连接解释C和JavaScript:连接解释Apr 23, 2025 am 12:07 AM

C 和JavaScript通过WebAssembly实现互操作性。1)C 代码编译成WebAssembly模块,引入到JavaScript环境中,增强计算能力。2)在游戏开发中,C 处理物理引擎和图形渲染,JavaScript负责游戏逻辑和用户界面。

从网站到应用程序:JavaScript的不同应用从网站到应用程序:JavaScript的不同应用Apr 22, 2025 am 12:02 AM

JavaScript在网站、移动应用、桌面应用和服务器端编程中均有广泛应用。1)在网站开发中,JavaScript与HTML、CSS一起操作DOM,实现动态效果,并支持如jQuery、React等框架。2)通过ReactNative和Ionic,JavaScript用于开发跨平台移动应用。3)Electron框架使JavaScript能构建桌面应用。4)Node.js让JavaScript在服务器端运行,支持高并发请求。

Python vs. JavaScript:比较用例和应用程序Python vs. JavaScript:比较用例和应用程序Apr 21, 2025 am 12:01 AM

Python更适合数据科学和自动化,JavaScript更适合前端和全栈开发。1.Python在数据科学和机器学习中表现出色,使用NumPy、Pandas等库进行数据处理和建模。2.Python在自动化和脚本编写方面简洁高效。3.JavaScript在前端开发中不可或缺,用于构建动态网页和单页面应用。4.JavaScript通过Node.js在后端开发中发挥作用,支持全栈开发。

C/C在JavaScript口译员和编译器中的作用C/C在JavaScript口译员和编译器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。

JavaScript在行动中:现实世界中的示例和项目JavaScript在行动中:现实世界中的示例和项目Apr 19, 2025 am 12:13 AM

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器