我最近一直在研究Puppeteer并将其迁移到TypeScript。这为我提供了做一些重构的机会,今天我想分享我最近做的一次重构,以删除直通变量。
什么是穿通变量?
穿透变量是指在它被交给实际使用的地方之前,通过多个方法调用传递的变量:
通常情况下,发生这种情况的原因是:
- 需要该数据的对象无法创建它
- 创建数据的对象(在上面的例子中,
A)曾经需要它,但由于功能或行为的改变,现在不需要了。
虽然我们在这篇文章中没有专门讨论React,但你会看到这种情况经常发生在React的道具上。这被称为"道具钻孔",也是你应该警惕的事情。
处理穿透式变量
值得注意的是,穿透式变量并不总是可以避免的,而且往往是首选的解决方案。旁通变量的解决方法可能很简单--将值的创建转移到需要它的地方是最简单的解决方法--但如果你受到限制,旁通变量的明确性往往比任何其他解决方案更可取。
虽然它让你跳过了一两个圈,但下面的代码是明确的,并告诉你正在发生的全部故事:
class A {
constructor() {
this.value = new SomeValue()
this.b = new B(this.value)
}
}
class B {
constructor(value) {
this.c = new C(value)
}
}
class C {
// somewhere in C we use the value
}
这绝对不是你见过的最漂亮的代码,但它可以有条不紊地被遵循。任何为C ,在没有明确传递数值的情况下访问变量的解决方案都会为开发者引入一些间接性的东西。例如,如果你选择把值放在全局范围内*(我不推荐这样做,但这是一个有用的例子!*),你必须弄清楚这个值来自哪里:
class C {
doSomething() {
// woah, where does this come from?!!
console.log(globalStuff.value)
}
}v
即使是像React的Context API这样更复杂的方法也会受到这个问题的影响。通常这是一个很好的权衡,值得一试,但这仍然是你必须考虑的问题。在构建软件的过程中,总是没有银弹的。
修复简单的案例
值得庆幸的是,我在Puppeteer代码库中处理的具体案例比较容易处理;没有理由不在需要数据的地方创建数据。这是最好的解决方法;把分散在三个文件中的代码移到一个文件中,几乎总是一种改进,因为在任何时候,你脑子里要记住的东西都会减少:
看一下进行修改的拉动请求,你可以看到我们在代码行数方面出现了净负值(这并不总是最有用的指标,但在这里是好的),而且我们在这个过程中简化了类。 在Puppeteer的例子中,我们有:
BrowserContext创建一个 ,初始化一个 ,传递队列实例。TaskQueueTarget classTarget类使用TaskQueue实例,并将其传递给Page构造函数。Page类使用了队列实例。
这不仅是非常机械的代码来传递所有这些值,而且还用它们不需要的知识污染了多个类。上面唯一真正关心 TaskQueue 的类是Page 。但是因为我们在BrowserContext 中创建了这个值,所以它和Target 现在都必须知道一个任务队列以及如何传递它。因此,这一改变不仅删除了代码行,而且还将需要了解任务队列的类的数量减少了66%!如果这还不够的话,我们还可以将其称为 "任务队列"。
如果这还不够,BrowserContext 少了一个实例变量,Target 少了一个实例变量和构造函数参数,Page少了一个构造函数参数。因此,这个小小的PR在减少代码的复杂性方面起到了很大的作用。
请注意这样的情况;它们经常作为重构的意外副产品被留下,它们可以提供一种简单、低风险的方式来消除代码库中的一些混乱。