💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、豆包、星火、月之暗面及文生图、文生视频 广告
曾用名:移除对参数的赋值(Remove Assignments to Parameters) 曾用名:分解临时变量(Split Temp) ![](https://box.kancloud.cn/8fae2dd32fbd7724d58078b808ae1820_452x232.jpeg) ``` let temp = 2 * (height + width); console.log(temp); temp = height * width; console.log(temp); ``` ![](https://box.kancloud.cn/a3bed334e2e1f6d1a46c5039deb25af9_91x152.jpeg) ``` const perimeter = 2 * (height + width); console.log(perimeter); const area = height * width; console.log(area); ``` ### 动机 变量有各种不同的用途,其中某些用途会很自然地导致临时变量被多次赋值。“循环变量”和“结果收集变量”就是两个典型例子:循环变量(loop variable)会随循环的每次运行而改变(例如`for(let i=0; i<10; i++)`语句中的`i`);结果收集变量(collecting variable)负责将“通过整个函数的运算”而构成的某个值收集起来。 除了这两种情况,还有很多变量用于保存一段冗长代码的运算结果,以便稍后使用。这种变量应该只被赋值一次。如果它们被赋值超过一次,就意味它们在函数中承担了一个以上的责任。如果变量承担多个责任,它就应该被替换(分解)为多个变量,每个变量只承担一个责任。同一个变量承担两件不同的事情,会令代码阅读者糊涂。 ### 做法 - 在待分解变量的声明及其第一次被赋值处,修改其名称。 > 如果稍后的赋值语句是“`i=i+`某表达式形式”,意味着这是一个结果收集变量,就不要分解它。结果收集变量常用于累加、字符串拼接、写入流或者向集合添加元素。 - 如果可能的话,将新的变量声明为不可修改。 - 以该变量的第二次赋值动作为界,修改此前对该变量的所有引用,让它们引用新变量。 - 测试。 - 重复上述过程。每次都在声明处对变量改名,并修改下次赋值之前的引用,直至到达最后一处赋值。 ### 范例 下面范例中我要计算一个苏格兰布丁运动的距离。在起点处,静止的苏格兰布丁会受到一个初始力的作用而开始运动。一段时间后,第二个力作用于布丁,让它再次加速。根据牛顿第二定律,我可以这样计算布丁运动的距离: ``` function distanceTravelled (scenario, time) {  let result;  let acc = scenario.primaryForce / scenario.mass;  let primaryTime = Math.min(time, scenario.delay);  result = 0.5 * acc * primaryTime * primaryTime;  let secondaryTime = time - scenario.delay;  if (secondaryTime > 0) {   let primaryVelocity = acc * scenario.delay;   acc = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass;   result += primaryVelocity * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime;  }  return result; } ``` 真是个丑陋的小东西。注意观察此例中的`acc`变量是如何被赋值两次的。`acc`变量有两个责任:第一是保存第一个力造成的初始加速度;第二是保存两个力共同造成的加速度。这就是我想要分解的东西。 > 在尝试理解变量被如何使用时,如果编辑器能高亮显示一个符号(symbol)在函数内或文件内出现的所有位置,会相当便利。大部分现代编辑器都可以轻松做到这一点。 首先,我在函数开始处修改这个变量的名称,并将新变量声明为`const`。接着,我把新变量声明之后、第二次赋值之前对`acc`变量的所有引用,全部改用新变量。最后,我在第二次赋值处重新声明`acc`变量: ``` function distanceTravelled (scenario, time) {  let result;  const primaryAcceleration = scenario.primaryForce / scenario.mass;  let primaryTime = Math.min(time, scenario.delay);  result = 0.5 * primaryAcceleration * primaryTime * primaryTime;  let secondaryTime = time - scenario.delay;  if (secondaryTime > 0) {   let primaryVelocity = primaryAcceleration * scenario.delay;   let acc = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass;   result += primaryVelocity * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime;  }  return result; } ``` 新变量的名称指出,它只承担原先`acc`变量的第一个责任。我将它声明为`const`,确保它只被赋值一次。然后,我在原先`acc`变量第二次被赋值处重新声明`acc`。现在,重新编译并测试,一切都应该没有问题。 然后,我继续处理`acc`变量的第二次赋值。这次我把原先的变量完全删掉,代之以一个新变量。新变量的名称指出,它只承担原先`acc`变量的第二个责任: ``` function distanceTravelled (scenario, time) {  let result;  const primaryAcceleration = scenario.primaryForce / scenario.mass;  let primaryTime = Math.min(time, scenario.delay);  result = 0.5 * primaryAcceleration * primaryTime * primaryTime;  let secondaryTime = time - scenario.delay;  if (secondaryTime > 0) {   let primaryVelocity = primaryAcceleration * scenario.delay;   const secondaryAcceleration = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass;   result += primaryVelocity * secondaryTime +    0.5 * secondaryAcceleration * secondaryTime * secondaryTime;  }  return result; } ``` 现在,这段代码肯定可以让你想起更多其他重构手法。尽情享受吧。(我敢保证,这比吃苏格兰布丁强多了——你知道他们都在里面放了些什么东西吗?1 ) ### 范例:对输入参数赋值 另一种情况是,变量是以输入参数的形式声明又在函数内部被再次赋值,此时也可以考虑拆分变量。例如,下列代码: ``` function discount (inputValue, quantity) {  if (inputValue > 50) inputValue = inputValue - 2;  if (quantity > 100) inputValue = inputValue - 1;  return inputValue; } ``` 这里的`inputValue`有两个用途:它既是函数的输入,也负责把结果带回给调用方。(由于JavaScript的参数是按值传递的,所以函数内部对`inputValue`的修改不会影响调用方。) 在这种情况下,我就会对`inputValue`变量做拆分。 ``` function discount (originalInputValue, quantity) {  let inputValue = originalInputValue;  if (inputValue > 50) inputValue = inputValue - 2;  if (quantity > 100) inputValue = inputValue - 1;  return inputValue; } ``` 然后用变量改名(137)给两个变量换上更好的名字。 ``` function discount (inputValue, quantity) {  let result = inputValue;  if (inputValue > 50) result = result - 2;  if (quantity > 100) result = result - 1;  return result; } ``` 我修改了第二行代码,把`inputValue`作为判断条件的基准数据。虽说这里用`inputValue`还是`result`效果都一样,但在我看来,这行代码的含义是“根据原始输入值做判断,然后修改结果值”,而不是“根据当前结果值做判断”——尽管两者的效果恰好一样。 1苏格兰布丁(haggis)是一种苏格兰菜,把羊心等内脏装在羊胃里煮成。由于它被羊胃包成一个球体,因此可以像球一样踢来踢去,这就是本例的由来。“把羊心装在羊胃里煮成……”,呃,有些人难免对这道菜恶心,Martin Fowler想必是其中之一。——译者注