例如,要在Android中保持CPU正常运行,我可以使用以下代码:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc");
wakeLock.acquire();


,但我认为局部变量powerManagerwakeLock可以是已消除:

((PowerManager)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag")
    .acquire();


类似的场景出现在iOS警报视图中,例如:from

UIAlertView *alert = [[UIAlertView alloc]
    initWithTitle:@"my title"
    message:@"my message"
    delegate:nil
    cancelButtonTitle:@"ok"
    otherButtonTitles:nil];
[alert show];

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [alertView release];
}


至:

[[[UIAlertView alloc]
    initWithTitle:@"my title"
    message:@"my message"
    delegate:nil
    cancelButtonTitle:@"ok"
    otherButtonTitles:nil] show];

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
    [alertView release];
}


如果在作用域中仅使用一次局部变量,是否要消除该变量是一种好习惯?

评论

不必要。有时,它使使用一次性变量的代码更加清晰,并且在大多数体面的编程语言中,这些附加变量的运行时成本很少(如果有的话)。

这也使单步执行带有调试器的代码更加困难。并且您还必须确保(取决于语言)第一个表达式不是NULL还是错误。

不,不是。实际上,引入局部变量来中断方法调用的链是一种好习惯。生成的机器代码可能是相同的,并且几乎可以保证源代码更具可读性,因此更好。

试试看现在插入调试器,并尝试查看PowerManager或WakeLock的状态。意识到你的错误。以前我一直都这么想(“所有这些本地人怎么了?”),直到我不得不花大部分时间研究代码。

即使在您的示例中,您的“消除”版本也具有滚动条,并且无法完全显示在我的屏幕上,这使得阅读起来非常困难。

#1 楼

阅读代码的次数要多于编写代码的次数,因此,您应该怜悯这个可怜的人,因为他们从现在起六个月后必须阅读代码(可能是您自己),并努力争取最清晰,最容易理解的代码。我认为,具有局部变量的第一种形式更容易理解。我看到三行中有三个动作,而不是一行中有三个动作。

如果您认为自己通过摆脱局部变量来进行任何优化,则不是。现代编译器会将powerManager放入寄存器1中(无论是否使用局部变量)以调用newWakeLock方法。 wakeLock也是如此。因此,无论哪种情况,您最终都得到相同的编译代码。是次要的细节。

评论


您不知道是否有很多代码,优化器可能内联了一些函数...否则,您同意,首先努力提高可读性是正确的做法。

– Matthieu M.
17年1月4日在9:30

好吧,我想相反,如果我看到一个局部变量在使用它的每个函数上都被初始化了多次,而不是在可能的时候成为一个属性,我会寻找原因,基本上我会仔细阅读这些行并将它们进行比较。确保它们相同。所以我会浪费时间。

–沃尔夫特
17年1月4日在9:46

@Walfrat局部变量被多次初始化?哎哟。没有人建议重用本地人:)

–罗安
17年1月4日在10:01

@Walfrat成员会产生副作用,仅应在您特别希望维持对公共成员的调用之间保持状态时使用。这个问题似乎是关于使用本地存储来临时存储中间计算。

– Gusdor
17年1月4日在10:38



问:如果可以,是否应该消除局部变量?答:不可以。

–西蒙
17年1月6日在15:42

#2 楼

一些高度评价的评论说明了这一点,但是我没有看到任何答案,因此我将其添加为答案。决定此问题的主要因素是:
可调试性
通常,开发人员花费的时间和精力要比编写代码多得多。
使用局部变量,您可以:


分配点的断点(以及所有其他断点修饰,例如条件断点等)


检查/监视/打印/更改-局部变量的值


捕获由于强制类型转换引起的问题。


具有清晰的堆栈跟踪(XYZ行有一个操作代替之10)


没有局部变量,上述所有任务要么更加艰巨,极其艰巨,要么完全不可能完成,这取决于您的调试器。
因此,请关注臭名昭著的maxim(编写代码的方式就像您是下一个在自己之后要维护它的开发人员一样,是一个疯狂的疯子,知道您的住所),并且在更容易调试的方面犯了错误,这意味着使用局部变量。

评论


我要补充一点,当一行上有许多调用时,堆栈跟踪的用处要小得多。如果您在第50行有一个空指针异常,并且该行有10个调用,则不会使事情变窄。在生产应用程序中,这通常是从缺陷报告中获得的大部分信息。

– JimmyJames
17年1月4日在15:40

单步执行代码也要困难得多。如果存在call()-> call()-> call()-> call(),则很难进入第三个方法调用。如果有四个局部变量,则容易得多

– gnasher729
17年1月4日在19:44

@FrankPuffer-我们必须处理现实世界。调试器不执行您的建议的地方。

–DVK
17年1月6日,0:14

@DVK:实际上,调试器比我认为允许您检查或监视任何表达式的调试器还要多。 MS Visual Studio(自2013版)具有针对C ++和C#的此功能。 Eclipse为Java提供了它。在另一个评论中,Faerindel提到了JS的IE11。

–弗兰克·普弗(Frank Puffer)
17年1月6日在10:12



@DVK:是的,许多现实世界中的调试器没有按照我的建议去做。但这与我的评论的第一部分无关。我只是觉得疯狂,以为要维护我的代码的人将主要通过调试器与之交互。调试器对于某些特定目的非常有用,但是如果他们获得了分析代码的主要工具,我会说有些严重错误,我会尝试对其进行修复,而不是使代码更具可调试性。

–弗兰克·普弗(Frank Puffer)
17年1月6日在15:30

#3 楼

只有使代码更易于理解。在您的示例中,我认为这会使阅读起来更加困难。

对于任何受人尊敬的编译器,消除已编译代码中的变量都是微不足道的操作。您可以自己检查输出以进行验证。

评论


这与样式的使用和变量的使用一样重要。现在,该问题已被编辑。

–乔德雷尔
17年1月6日在8:51



#4 楼

您的问题“如果仅在范围中使用一次,则消除局部变量是一种好习惯吗?”测试错误的标准。局部变量的效用不取决于其使用次数,而是取决于它是否使代码更清晰。用有意义的名称标记中间值可以提高在某些情况下的清晰度,例如您所呈现的那种,因为它会将代码分解为更小,更易消化的块。修改后的代码,因此应保持不变。实际上,我会考虑从修改后的代码中提取一个局部变量以提高清晰度。

我不希望局部变量对性能产生任何影响,即使存在局部变量除非代码在程序中对速度至关重要的部分处于非常紧密的循环中,否则它可能太小而不值得考虑。

评论


我认为这与样式和将空格用作任何东西有很大关系。问题已被编辑。

–乔德雷尔
17年1月6日在8:53

#5 楼

也许。

如果涉及类型转换,我个人会犹豫消除局部变量。由于方括号的数量开始接近我的心理极限,我发现您的精简版本难以读懂。它甚至引入了一套新的括号,在使用局部变量的稍微冗长的版本中是不需要的。
在我看来,这是更好的折衷方案:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc").acquire();


因此保留一个局部变量而放弃另一个局部变量。在C#中,由于类型转换已经提供了类型信息,因此我将在第一行使用var关键字。

#6 楼

尽管我接受偏爱局部变量的答案的正确性,但我还是扮演了恶魔的拥护者,并提出了相反的观点。

我个人还是反对纯粹将局部变量用于文档目的,尽管原因本身表明该做法确实有价值:局部变量有效地伪造了代码。

在您的情况下,主要问题是缩进而不是缺少变量。您可以(并且应该)格式化代码,如下所示:

((PowerManager)getSystemService(POWER_SERVICE))
        .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag")
        .acquire();


将其与具有局部变量的版本进行比较: >
使用局部变量:名称为阅读器添加了一些信息,并且类型明确指定,但是实际的方法调用不那么明显,并且(我认为)它的含义不清晰。

不使用局部变量:更简洁,方法调用更可见,但是您可能需要有关返回值的更多信息。但是,有些IDE可以向您显示此信息。

另外三个评论:


我认为,添加没有功能用途的代码有时会使读者感到困惑必须意识到它确实没有功能用途,只是存在于“文档”中。例如,如果此方法长100行,那么除非稍后阅读了整个方法,否则显然在以后的某个时候不需要局部变量(以及方法调用的结果)。
添加局部变量意味着您必须指定它们的类型,这在代码中引入了一个依赖关系,否则该依赖关系就不会存在。如果方法的返回类型发生了微小变化(例如,已重命名),则您将不得不更新代码,而没有局部变量则不会。
如果调试器不显示方法返回的值,则使用局部变量进行调试可能会更容易。但是解决方法是修复调试器中的缺陷,而不是更改代码。


评论


关于您的进一步评论:1.如果遵循以下约定:局部变量的声明应尽可能靠近使用它们的位置,并且您的方法保持合理的长度,那么这应该不是问题。 2.不能使用具有类型推断的编程语言。 3.编写良好的代码通常根本不需要调试器。

–罗伯特·哈维(Robert Harvey)
17年1月4日在15:28



您的第一个代码示例仅在您使用流畅的界面或“构建器”来制作对象时有效,这些模式我不会在代码中的任何地方使用,而只会选择性地使用。

–罗伯特·哈维(Robert Harvey)
17年1月4日在15:29



我认为这个问题假设局部变量在功能上是多余的,因此这种情况需要接口的类型。如果不是通过各种扭曲的话,我们可能可以实现删除本地人的功能,但是我想到了我们正在研究的简单情况。

–rghome
17年1月4日在15:35

我发现您的变体的可读性甚至更差。我可能接触到VB.net的次数过多,但是虽然看到您的样本时我的第一个消息是“ WITH语句去了哪里?我是否意外删除了它?”我需要仔细查看发生了什么事情,而几个月后我需要重新查看代码时,这不好。

–Tonny
17年5月5日在9:04

@Tonny对我来说,它更具可读性:当地人不会添加任何从上下文中看不出来的东西。我有我的Java头,所以没有with语句。这将是Java中的常规格式约定。

–rghome
17年1月5日,9:54



#7 楼

这里的动机很紧张。临时变量的引入可以使代码更易于阅读。但是,它们还可以防止其他可能的重构,例如提取方法和用查询替换临时表,从而使它们变得更难看。在适当的情况下,后面这些类型的重构通常比temp变量提供更多的好处。

关于这些后面的重构的动机,Fowler写道:



“温度的问题在于它们是临时的和局部的。因为只能在使用它们的方法的上下文中看到它们,所以温度倾向于鼓励使用更长的方法,因为这是达到温度的唯一方法。通过用查询方法替换temp,该类中的任何方法都可以获取该信息。这有助于为该类提供更简洁的代码。“


所以,是的,请使用临时文件,以使代码更具可读性,尤其是对于您和您的团队来说这是本地规范时。但是请注意,这样做有时会使发现较大的替代改进变得更加困难。如果您可以提高自己的能力去感知何时值得去没有这种临时性的事物,并且在这样做时变得更加舒适,那么那可能是一件好事。

FWIW我个人避免阅读Fowler的书《 Refactoring》十年来,因为我想在这么简单的话题上没什么可说的。我完全错了。当我最终阅读它时,它改变了我的日常编码习惯,变得更好了。

评论


好答案。我编写的最好的代码(从其他人那里看到的)通常有很多小的(2行,3行)方法可以代替此类临时变量。与此相关的是,这种有争议的私有方法会发臭的方法。...经过深思熟虑,我经常发现它是正确的。

– Stijn de Witt
17年1月7日在22:08

该问题中的临时工已经涉及功能,因此该答案与OPs问题无关。

–user949300
17年1月7日在22:22

@ user949300我认为这无关紧要。是的,OP的特定示例中的临时值是函数或方法的返回值。但是OP非常清楚,这只是一个示例方案。实际的问题是更为笼统的“我们应该在可能的时候消除临时变量吗?”

–乔纳森·哈特利
17年1月9日15:58

好的,我被卖了,**试图**改变我的投票。但是,通常当我超出OP问题的有限范围时,就会感到沮丧。所以可能是善变的... :-)。呃,直到您编辑,我才能更改我的投票,无论如何...

–user949300
17年1月9日在16:34



@ user949300圣牛!经过深思熟虑后才改变主意的人!先生,您真是罕见,我向您请了我的礼帽。

–乔纳森·哈特利
17年1月9日在17:47

#8 楼

好吧,如果可以使代码更具可读性,则消除局部变量是一个好主意。那种长的单行代码是不可读的,但是它遵循了非常冗长的OO风格。如果您可以将其简化为

acquireWakeLock(POWER_SERVICE, PARTIAL, "abc");


,那么我想这可能是个好主意。也许您可以引入辅助方法将其简化为类似的内容;如果这样的代码多次出现,那么它很值得。

#9 楼

让我们在这里考虑Demeter定律。在LoD上的Wikipedia文章中指出:

函数的Demeter定律要求对象O的方法m只能调用以下对象的方法:[2]

O本身
m的参数
在m内创建/实例化的任何对象
O的直接组成对象
O可以访问的全局变量,范围为m


遵循该法则的后果之一是,应避免在长的点缀在一起的字符串中调用其他方法返回的对象的方法,如上面的第二个示例所示。 :
((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag").acquire();

真的很难弄清楚这是怎么回事。要理解它,您必须先了解每个过程的作用,然后再解密在哪个对象上调用哪个函数。第一个代码块
PowerManager powerManager=(PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock=powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"abc");
wakeLock.acquire();

清楚地显示了您要执行的操作-您正在获取PowerManager对象,从PowerManager获取WakeLock对象,然后获取akeLock。上面的代码遵循LoD的规则#3-您在代码中实例化了这些对象,因此可以使用它们来完成所需的操作。
也许另一种思考的方法是在创建软件时记住应该写清楚,而不是为了简洁。所有软件开发中的90%是维护。永远不要编写不愿维护的代码。
祝你好运。

评论


我会对如何将流畅的语法适合此答案感兴趣。使用正确的格式,流畅的语法具有很高的可读性。

– Gusdor
17年1月4日在13:56

那不是“得墨meter耳法则”的意思。得墨meter耳定律不是点数练习;从本质上讲,它意味着“只与您的直属朋友交谈”。

–罗伯特·哈维(Robert Harvey)
17年1月4日在15:32



通过中间变量访问相同的方法和成员仍然严重违反Demeter定律。您只是模糊了它,没有解决它。

–乔纳森·哈特利
17年1月5日,9:13

就“得墨meter耳法律”而言,两个版本都是“坏”的。

–绿巨人
17年1月5日,9:13

@Gusdor同意,这更多地是对问题示例中样式的评论。现在,该问题已被编辑。

–乔德雷尔
17年1月6日在8:58

#10 楼

需要注意的一件事是,代码经常被读取到不同的“深度”。此代码:

PowerManager powerManager = (PowerManager)getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "abc");
wakeLock.acquire();


容易“浏览”。这是3条陈述。首先,我们提出一个PowerManager。然后我们得出一个WakeLock。然后我们acquire。只需查看每一行的开始,我就能很容易地看到这一点。简单的变量赋值确实很容易被部分识别为“ Type varName = ...”,而在精神上略过“ ...”。同样,最后一个语句显然不是赋值的形式,而是仅包含两个名称,因此“主要要旨”立即显而易见。如果我只是想回答“此代码的作用是什么?”,这通常就是我所需要的。

如果我要跟踪一个我认为是在这里的细微错误,那么显然我需要更详细地介绍这一点,并且实际上会记住“ .. 。但是单独的语句结构仍然可以帮助我一次执行一个语句(特别是在我需要更深入地执行每个语句所调用的内容的情况下特别有用;当我回来时,我已经完全理解“一个单元”并可以继续执行下一条语句。)

((PowerManager)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag")
    .acquire();


现在只剩下一条语句了。顶层结构不太容易阅读。在OP的原始版本中,没有换行符和缩进以可视方式传达任何结构,我不得不计算括号将其解码为3个步骤。如果某些多部分表达式彼此嵌套而不是按方法调用链排列,那么它看起来仍然可能与此类似,因此我必须谨慎对待,不要计算括号。如果我确实相信缩进,只是略过最后一点作为所有假定的要点,那么wakeLock会自动告诉我什么?

但是有时候,这可能就是您想要的。如果我中途进行转换并写道:

WakeLock wakeLock =
     ((PowerManeger)getSystemService(POWER_SERVICE))
    .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLockTage");
wakeLock.acquire();


现在,这可以快速浏览“获取.acquire(),然后WakeLock”。比第一个版本更简单。显而易见,获得的是acquire。如果获取WakeLock只是一个子细节,对于此代码而言并不重要,但是PowerManager很重要,那么它实际上可以帮助掩埋wakeLock的内容,因此,如果您只是尝试略过,可以自然地跳过它快速了解此代码的功能。而不是命名它表明它仅使用过一次,有时这才是重要的(如果范围的其余部分很长,我将不得不阅读所有内容以判断它是否再次使用;尽管使用了显式子范围如果您的语言支持,则可以是另一种解决方法。)

这表明,这全都取决于上下文和您要交流的内容。就像用自然语言编写散文一样,总是有很多方法可以编写给定的代码,这些信息在信息内容上基本上是等效的。与使用自然语言编写散文一样,在它们之间进行选择通常不应采用诸如“消除仅出现一次的任何局部变量”之类的机械规则。相反,您如何选择写下代码将强调某些事情而不再强调其他事情。您应该根据您实际要强调的内容,有意识地做出这些选择(包括出于技术原因有时编写不太可读的代码的选择)。尤其要考虑什么将为那些只需要“了解要点”(不同层次的)代码的读者提供服务,因为这种情况比非常接近的逐表达式阅读要频繁得多。

评论


对我来说,即使我不了解API,但即使没有变量,代码的作用也很明显。 getSystemService(POWER_SERVICE)获取电源管理器。 .newWakeLock获取唤醒锁。 .acquire获取它。好的,我不知道所有的类型,但是如果需要,我可以在IDE中找到。

–rghome
17年1月6日在15:09



对您来说,代码应该执行的操作可能很明显,但是您不知道它实际执行的操作。如果我正在寻找错误,那么我所知道的是,某个地方的某些代码无法实现应有的功能,因此我必须找到哪个代码。

– gnasher729
17年1月7日在13:44

@ gnasher729是的。当您需要仔细阅读所有细节时,我就举了一个寻找错误的例子。真正有助于查找未执行应做的代码的事情之一就是能够轻松查看原始作者的意图。

–本
17年1月7日在21:29

当我回来时,我已经完全理解“一个单元”,然后可以继续进行下一个声明。其实不行实际上,局部变量妨碍了对此的全面理解。因为他们仍然挥之不去……占用了精神空间……接下来的20条语句中他们将要发生的事情……鼓声……如果没有当地人,您将确保物体已经消失并且无法做任何事情与他们在下一行。无需记住它们。由于相同的原因,我喜欢从方法中尽快返回。

– Stijn de Witt
17年1月7日在22:19

#11 楼

这甚至是一个具有自己名称的反模式:Train Wreck。已经阐明了避免更换的几个原因:


更难于阅读
更难于调试(同时监视变量和检测异常位置)
违反法律Demeter(LoD)的值

请考虑此方法是否从其他对象了解太多。方法链接是可以帮助您减少耦合的替代方法。

还请记住,对对象的引用确实很便宜。

评论


嗯,修改后的代码不是在进行方法链接吗?编辑阅读链接的文章,所以我现在看到了区别。相当不错的文章,谢谢!

– Stijn de Witt
17年1月7日在22:30



感谢您的审查。无论如何,方法链接可能在某些情况下可行,而仅保留变量可能是适当的决定。知道人们为什么不同意您的回答总是很高兴的。

– Borjab
17年1月8日在16:31

我想“火车残骸”的反面是“本地线”。它是在两座主要城市之间的火车,停在之间的每个乡村,以防万一有人想要上下车,即使大多数日子没有人这样做。

–rghome
19年5月14日在11:17



#12 楼

陈述的问题是“如果可以,是否应该消除局部变量”?

不,您不应该仅仅因为可以就消除它。

您应该在商店中遵守编码准则。

在大多数情况下,局部变量使代码更易于阅读和调试。

我喜欢您对PowerManager powerManager所做的操作。对我来说,该班级的一个小写字母意味着这只是一次使用。

如果不该使用变量,它将占用昂贵的资源。许多语言都有需要清除/释放的局部变量语法。在C#中正在使用。

using(SQLconnection conn = new SQLconnnection())
{
    using(SQLcommand cmd = SQLconnnection.CreateCommand())
    {
    }
}


评论


这不是真的与局部变量有关,而是与资源清理有关...我不太精通C#,但是如果使用using(new SQLConnection()){/ * ... * /}也不合法,我会感到惊讶(是否有用是另一回事:)。

– Stijn de Witt
17年1月7日在22:29

#13 楼

其他答案中未提及一个重要方面:每当添加变量时,都会引入可变状态。这通常是一件坏事,因为它会使您的代码更复杂,因此更难以理解和测试。当然,变量的范围越小,问题就越小。

您实际上想要的不是变量的值可以修改,而是一个临时常量。因此,如果您的语言允许,请考虑在您的代码中表达这一点。在Java中,可以使用final;在C ++中,可以使用const。在大多数功能语言中,这是默认行为。

的确,局部变量是可变状态的危害最小的类型。成员变量可能会引起更多麻烦,而静态变量甚至更糟。我仍然发现,在您的代码中尽可能准确地表达应该执行的操作仍然很重要。而且,稍后可以修改的变量与中间结果的唯一名称之间存在巨大差异。因此,如果您的语言允许您表达这种差异,请做到。

评论


我没有拒绝您的回答,但我想这与以下事实有关:除非您正在谈论某种长期运行的方法,否则局部语言在命令式语言中通常不被视为“状态”。我同意使用final / const作为最佳实践,但是引入变量以简单地中断链式调用几乎不会导致可变状态引起的问题。实际上,使用局部变量有助于处理可变状态。如果一个方法可以在不同的时间返回不同的值,那么您可能会遇到一些非常有趣的错误。

– JimmyJames
17年1月6日在16:00

@JimmyJames:在我的回答中添加了一些解释。但是您的评论中有一部分是我不理解的:“使用局部变量有助于处理可变状态”。你能解释一下吗?

–弗兰克·普弗(Frank Puffer)
17年1月6日在19:31

例如,假设我需要检查一个值,如果该值超过10,则会发出警报。假设此值来自方法getFoo()。如果我避免使用局部声明,则将以if(getFoo()> 10)alert(getFoo());结尾。但是getFoo()在两个不同的调用上可能返回不同的值。我可以发送一个值小于或等于10的警报,这充其量是令人困惑的,并且会作为缺陷再次出现。并发使得这种事情变得更加重要。本地分配是原子的。

– JimmyJames
17年1月6日在22:00

很好的一点。也许是我不喜欢这些本地对象的原因。...您将在调用此方法后对该PowerManager实例进行一些操作吗?让我检查一下方法的其余部分。而您得到的WakeLock内容...您正在做什么(再次扫描其余方法)... mmm又什么也没有...好,那么为什么它们在那里?哦,是的,它应该更具可读性...但是如果没有它们,我相信您不会再使用它们了,因此我不必阅读本方法的其余部分。

– Stijn de Witt
17年1月7日在22:14

发言者在最近的一次演讲中认为,表明变量是最终变量的最佳方法是编写显而易见的变量不变的简短方法。如果方法中的局部变量需要final,则您的方法太长。

–user949300
17年1月7日在22:27