我应该重用变量吗?


59

我应该重用变量吗?

我知道许多最佳实践表明您不应该这样做,但是,稍后,当不同的开发人员调试代码并具有3个看起来相似的变量时,唯一的区别是它们是在代码中的不同位置创建的,困惑。单元测试就是一个很好的例子。

但是,我确实知道,最佳实践通常都是反对的。例如,他们说不要“覆盖”方法参数。

最佳做法甚至是反对使以前的变量为空(在Java中,Sonar会在您分配null给变量时发出警告,因为Java 6不需要调用它来调用垃圾回收器。您无法始终控制哪些警告已关闭;大多数情况下默认设置为开启。)


11
从Java 6开始?在任何版本的Java中,您都不需要为变量分配null。当变量超出范围时,它将释放对其对象的引用。
凯文·潘科

35
重复使用变量是代码混淆器使用的第一件事
Lelouch Lamperouge

7
变量重用与为变量选择合适的标识符冲突。在现代语言中,变量重用实际上并没有任何优势胜过拥有良好标识符的优势。
Joren

4
另请注意,并非所有重用都是重用。古老的FORTRAN程序员,甚至那些已经转而使用其他语言的程序员,在我们所有的DO循环中(例如-循环)计数器。我们习惯于看到诸如SUM = SUM + A(I,K)* B(K,J)或sum + = a [i] [k] * b [k] [j];之类的东西。
John R. Strohm

1
当使用非常有限的资源(几千字节的RAM)对微控制器进行编程时,重用变量是合理的。
m.Alin 2011年

Answers:


130

仅当您的方法很长并且按顺序执行多个任务时,才会出现问题。这使得代码本身很难理解(并因此得以维护)。重用变量还增加了额外的风险,使代码更难遵循且更容易出错。

IMO的最佳做法是使用足够短的方法仅做一件事,从而消除整个问题。


单元测试呢?例如,我需要测试第二次执行该方法后,是否仍然按预期工作。
IAdapter 2011年

20
@IAdapter,单元测试代码是一个不同的问题,其中的标准可能比生产代码更宽松。尽管如此,使其可读性强且易于维护仍然是重中之重。您也可以(并且应该)从单元测试中提取方法,以使其简短易读(并消除了重用变量的需要)。
彼得Török

3
@IAdapter,对于您描述的场景,我不会重用变量。合适的事情是,result1 = foo(); result2 = foo(); Assert.AreEqual(result1, result2);在这种情况下,我看不到很多地方需要重用变量。
JSBձոգչ2011年

1
@IAdapter for(int i = 0; i <2; i ++){result = foo(); assertEquals(expectedResult,result);}
Bill K

2
+1-这是编写较小方法时的良好代码味道。

49

方法中的变量重用是您应该重构/拆分它的强烈标志。

因此,我的回答是您不应该重用它们,因为如果这样做,那么以后对其进行重构将变得更加困难。


19

这取决于。

可能正是出于保存某种数据的目的而创建了一些变量,这些数据在函数执行期间可能会发生变化。这里想到了返回码,例如:

void my_function() {
    HRESULT errorcode;
    errorcode = ::SomeWindowsApiFunction();
    if (FAILED(errorcode)) { /* handle error */ }
    errorcode = ::AnotherWindowsApiFunction();
    if (FAILED(errorcode)) { /* handle error */ }
}

变量名称可以很清楚地表明此变量打算存储什么。我认为诸如此类的其他用例也是可能的,其中变量在概念上是一个容器,在功能过程中,该容器在逻辑上由非常相似的事物的不同实例使用。

但是,通常,只有在可能的代码阅读者绝对清楚的情况下,才应这样做。在任何情况下,除了可能不考虑代码易读性的极端优化之外,都不应仅仅因为类型合适而重用变量。

所有这些基本上都源于变量命名的良好实践。名称应该说明一切。如果很难将所有重复使用的确切目的简称为短名称,则最好仅使用不同的变量。


15

我不重用变量的最大原因(尤其是在单元测试中)是因为它引入了不必要的代码路径,这是很难测试和调试的。好的单元测试应该独立于其他测试,并且在单元测试夹具中重用类(实例)级别变量时,必须确保在每次测试之前声明其状态。好的单元测试还可以隔离缺陷,因此从理论上讲,每个测试用例(方法)应仅针对被测系统断言1种行为。如果您的测试方法是这样编写的,则很少或不需要重用方法级别的变量。最后,在支持闭包和异步处理的语言中,如果要在整个方法中重用变量,则很难推断到底发生了什么。


所以我应该为单个方法调用编写类似-testSomething和assert的测试方法-testSomethingTwoInvocations和仅针对第二次调用的assert?
IAdapter 2011年

2
是。它可以帮助您确定是第一次还是第二次失败。mspec可能对此有所帮助,它的继承树将非常有用。
布莱恩·伯特彻

如果在表示“类变量”或“实例变量”时使用“变量”,则需要更清楚地表达自己。
gnasher729

13

您应该使用不同的变量。如果您担心您的同事会感到困惑,请给他们起清楚地说明其角色的名字。
重复使用变量可能会在将来引起混乱。最好现在将其清除。
在某些情况下,可以重复使用相同的变量名。例如i在一个简单的计数循环中。在这些情况下,您当然应该确保变量在它们自己的范围内。

编辑:重用变量有时是违反单一责任原则的标志。检查是否在相同角色中使用了重复使用的变量。如果是这样,则可能根本不会再使用它(尽管可能最好还是使用两个不同的变量来限制所述变量的范围)。如果以不同的角色使用它,则您的手将违反SRP。


4

在任何情况下,无论其他答案给出了什么好建议,您都可能希望重用变量:当编译器需要帮助时。

在某些情况下,您的编译器可能不够聪明,以至于无法意识到代码的下一部分不再使用某个寄存器分配的变量。因此,它将不会在理论上免费使用下一个变量的寄存器,并且生成的代码可能不是最佳的。

请注意,我不知道任何当前的主流编译器都无法正确捕捉这种情况,因此,除非您知道编译器正在生成次优代码,否则永远不要这样做。如果要使用自定义编译器为特殊的嵌入式系统进行编译,则可能仍会遇到此问题。


17
-1,除非您可以指出实际情况,并且这种情况确实会发生,并且重用变量会带来明显的不同。这是对一个理论问题的理论解决,而不是一个很好的解决方案。编译器决定将变量放到哪里的问题;如果您的编译器生成的代码不良,并且确实起到了作用,则请修复或替换编译器。
迦勒

3
@Caleb-您并非总是可以访问所开发平台的编译器源代码,尤其是专有嵌入式平台。我也DID在我回答说,我不知道任何目前主流的编译器(或事实上口译/ VM)的不正确的,我已经看过了所有的情况下进行这种活跃度分析。但是,我使用过去(大约十年前)非常简陋的自定义编译器来开发专有的嵌入式系统。该系统的功能非常有限,编译器也是如此,因此确实发生了这种情况。
Joris Timmermans

2
+1我确实在过去的项目中做了这样的重用(用ANSI C编写的高度优化的媒体代码)。据我所记得,差异在组装中很容易看到,而在基准测试中则可以测量。从来没有做过,并且希望永远不要在Java中进行这样的重用。
蚊蚋

由于多种原因,@ Caleb修复或替换编译器可能不是一个选择,而您只需要对已有的内容进行修改即可。

@ThorbjørnRavnAndersen是否可以想象有人可能被迫使用糟糕的编译器,而且性能可能如此关键,以至于您需要再次猜测编译器并操纵代码以重用寄存器?是。有可能吗?否。MadKeithV同意他不能提供真实的例子。这是最佳做法吗?是好的风格吗?它是代码质量的指标吗?当然不。这是书面问题的有用答案吗?尊重MadKeithV,但我认为不是。
卡雷布(Caleb)

2

我会说不。

考虑一下这种情况:您的程序崩溃了,您需要通过检查核心转储来弄清楚发生了什么……您能看到区别吗?;-)


如果您的核心转储来自优化的构建,则无论如何它都可能使变量共享内存。因此,它没有那么有用。
温斯顿·埃韦特

当然,优化的构建对于调试不是很有用!但无论如何,您仍然可以选择使用调试版本...并且在连接调试器后,无需从函数开始就一步一步地了解变量的变化,因为它们别!:-D
fortran

2

不,您没有在改善机器正在运行的代码(...汇编代码)。保留重用编译器用于该编译器变量的任何内存。通常,它只是一个寄存器,而重用并没有给您带来任何好处。专注于使代码更易于阅读。


0

这取决于。在动态语言中,类型驻留在值而不是变量上,那么如果我对一个函数(例如,是一个字符串)有一个命名良好的参数,我对算法进行了更改,这意味着它总是在被解释为整数后才使用,然后,作为初稿,我可能会做

scrote = int(scrote)

但是我最终希望更改发送给函数的类型,并注意参数类型的更改。


1
在大量代码的中间放置“ scrote = int(scrote)”这样的行是使维护程序员疯狂的好方法。
2011年

问题很可能与您提到的“大量代码”有关。
Paddy3118

正如公认的答案一样,重用变量实际上只是一长段代码的问题。基本上,在任何情况下,如您编写“ scrote = int(scrote)”之类的内容,我都希望将int(scrote)放在新变量中,或者最好将int(scrote)传递给新函数。
2011年

0

首先,查看您的开发环境。如果由于在不同作用域中具有五个具有相同名称的变量而在调试过程中遇到问题,并且调试器没有向您显示这些变量中的哪个是您需要的,那么不要使用五个具有相同名称的变量。有两种方法可以实现:使用一个变量,或使用五个不同的名称。

您的调试器也可能使调试具有多个变量的函数变得很痛苦。如果具有20个变量的功能比具有16个变量的功能更难调试,则您可以考虑将5个变量替换为1个。(“可能考虑”与“应该始终”不同))。

只要该变量始终具有相同的用途,就可以在多个位置使用一个变量。例如,如果十个函数调用返回一个错误代码,并且每次调用都会立即处理该错误代码,则使用一个变量而不是10。但是对于完全不同的事物,请勿使用相同的变量。就像使用“名称”作为客户名称,然后在10行之后使用相同的变量作为公司名称一样,这很糟糕,并且会给您带来麻烦。更糟糕的是,使用“ customerName”作为客户名称,然后在10行中使用相同的变量“ customerName”作为公司名称。

重要的是,没有什么是铁律。一切都有优点和缺点。如果“最佳做法”暗示一件事,而您却有理由说这在您的特定情况下不是一个好主意,那么就不要这样做。


0

首先,查看您的开发环境。如果由于在不同作用域中具有五个具有相同名称的变量而在调试过程中遇到问题,并且调试器没有向您显示这些变量中的哪个是您需要的,那么不要使用五个具有相同名称的变量。有两种方法可以实现:使用一个变量,或使用五个不同的名称。

您的调试器也可能使调试具有多个变量的函数变得很痛苦。如果具有20个变量的功能比具有16个变量的功能更难调试,则您可以考虑将5个变量替换为1个。(“可能考虑”与“应该始终”不同))。

只要该变量始终具有相同的用途,就可以在多个位置使用一个变量。例如,如果十个函数调用返回一个错误代码,并且每次调用都会立即处理该错误代码,请使用一个变量而不是10。这有一个问题:通常,编译器会在您使用未初始化的变量时告诉您。但是在这里,如果您在调用6之后读取了错误代码,但是在调用5之后实际上并未更改该变量,则在没有编译器警告的情况下,您将获得无用的数据。因为变量已经初始化,所以现在没有数据。

重要的是,没有什么是铁律。一切都有优点和缺点。如果“最佳做法”暗示一件事,而您却有理由说这在您的特定情况下不是一个好主意,那么就不要这样做。


-2

需要维护状态或存储常量时,请使用全局变量。通过重用变量,您仅​​重用了指向内存中某个位置的名称。初始化上的描述性名称只会使您更加清楚(假设使用Java且没有原始的OCD)。

除非您是手工进行代码混淆或使其他开发人员恼火。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.