return 语句之前的局部变量,这有关系吗?

Posted

技术标签:

【中文标题】return 语句之前的局部变量,这有关系吗?【英文标题】:Local variables before return statements, does it matter? 【发布时间】:2015-10-22 09:40:44 【问题描述】:

对不起,如果这是一个新手问题,但我找不到答案。 这样做更好吗:

int result = number/number2;
return result;

或:

return number/number2;

我知道整数使用内存,所以我猜它会稍微降低性能?但另一方面,它使内容更清晰,尤其是当 int/string 是一个长计算时。

【问题讨论】:

除非您真正尝试优化性能,否则您应该始终优先考虑可读性。 我猜你不会使用像 number/number2 这样简单的 helper int 吗? 当您想在离开函数之前检查返回值时,声明一个局部变量有助于调试。 【参考方案1】:

编辑:如果像我一样,您使用的 Kotlin 比 Java 还多,知道 IntelliJ 在 Kotlin 中也对此进行检查也很重要:

变量只在后面的返回中使用,应该被内联

此检查报告仅在下一个 return 语句中使用的局部变量或其他变量的精确副本。在这两种情况下,最好内联这样一个变量。


实际上有一个从 PMD 继承的名为 Unnecessary Local Before Return 的 SonarQube 规则讨论了这一点。它说:

避免不必要地创建局部变量。

这条规则是 SSLR 规则的later replacedVariables should not be declared and then immediately returned or thrown,它保持相同的位置:

声明一个变量只是为了立即返回或抛出它是一个错误 练习。一些开发人员认为这种做法改进了代码 可读性,因为它使他们能够明确地命名正在发生的事情 回来。但是,此变量是内部实现细节 不会暴露给方法的调用者。 方法名 应该足以让来电者确切知道会发生什么 返回

我完全同意。

IntelliJ(或至少是 android Studio)也有针对这种情况的警告:

变量只在后面的返回中使用,可以内联

此检查报告仅在下一次返回中使用的局部变量或其他变量的精确副本。在这两种情况下,最好内联这样一个变量。


我认为在这种情况下根本不需要担心性能问题。话虽如此,正如@***soft 在他的评论中提到的那样,JIT 很可能会内联变量,无论哪种方式,您最终都会得到相同的结果。

【讨论】:

我实际上不同意 SSLR 规则。通常这会使方法更容易调试,因为在从方法返回之前调试器中有一个步骤,可以观察到结果。其成本是堆栈上的一个局部变量....基本上可以免费清理。 在调试时,您不一定需要在 return 语句之前检查值,然后再返回它。我相信拥有更简洁的代码比更容易调试的代码更重要。否则我们总是会有很多局部变量来帮助我们调试每个操作结果。 @Jared 大多数调试器允许您在遇到断点时评估返回语句,从而使变量即使在调试时也无用。 在尝试了***.com/questions/5010362/… 此处提供的解决方案之后,实际上很清楚复杂表达式的返回值调试并不是很好用。因此,分配返回变量会使调试变得容易得多。特别是如果您必须从多个返回链中追踪某些内容。 同意调试的好处。另一个好处是命名返回值。如果你遵循 demeter 法则(不进入你的对象)并使用清晰的变量名,程序员应该能够从上下文中得出这个想法(包含方法名、方法名或你要返回其结果的操作的名称),但这并不总是正确的,因为跨上下文命名变量很困难。将Long.parseLong(price.getTotalValue()) 放入像finalCostAfterAdjusting 这样的变量中可以为程序员添加一些有用的上下文。【参考方案2】:

选择认为更具可读性的版本。

存在命名变量提高可读性的合理情况。例如

public String encrypt(String plainString)

    byte[] plainBytes      = plainString.getBytes(StandardCharsets.UTF_8);
    byte[] hashPlainBytes  = enhash( plainBytes, 4 );
    byte[] encryptedBytes  = doAes128(Cipher.ENCRYPT_MODE , hashPlainBytes );
    String encryptedBase64 = Base64.getEncoder().withoutPadding().encodeToString(encryptedBytes);
    return encryptedBase64;


public String decrypt(String encryptedBase64)

    byte[] encryptedBytes = Base64.getDecoder().decode(encryptedBase64);
    byte[] hashPlainBytes = doAes128(Cipher.DECRYPT_MODE , encryptedBytes );
    byte[] plainBytes     = dehash( hashPlainBytes, 4 );
    String plainString = new String(plainBytes, StandardCharsets.UTF_8);
    return plainString;

在某些情况下,我们需要与返回类型不同类型的变量。这会影响类型转换和推理,从而产生显着的语义差异。

Foo foo()            vs.        Foo foo()
                               
                                    Bar bar = expr;
    return expr;                    return bar;
                               

【讨论】:

【参考方案3】:

编译器通常足够聪明,可以适当地优化这类事情。请参阅***上的data-flow optimizations。

在这种情况下,即使您自己没有指定一个,它也可能需要分配一个临时变量来存储结果。

编辑:***soft 关于字节码编译器是正确的:

$ cat a.java
class a 
   public static int a(int x, int y) 
     return x / y;
   

   public static int b(int x, int y) 
     int r = x/y;
     return r;
   

   public static int c(int x, int y) 
     final int r = x/y;
     return r;
   

$ javap -c a
Compiled from "a.java"
class a 
  a();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static int a(int, int);
    Code:
       0: iload_0
       1: iload_1
       2: idiv
       3: ireturn

  public static int b(int, int);
    Code:
       0: iload_0
       1: iload_1
       2: idiv
       3: istore_2
       4: iload_2
       5: ireturn

  public static int c(int, int);
    Code:
       0: iload_0
       1: iload_1
       2: idiv
       3: istore_2
       4: iload_2
       5: ireturn

【讨论】:

Java 字节码编译器本身可能无法捕捉到这一点,除非您将变量声明为 final,但我非常怀疑 JIT 编译器不会内联该变量。 也许您应该添加第三种情况,将变量声明为final 谢谢。 final 与否,它仍然有所作为。现在我想知道 JIT 是否真的会处理它。 P.S.感谢您的反编译...整洁的说明... :)

以上是关于return 语句之前的局部变量,这有关系吗?的主要内容,如果未能解决你的问题,请参考以下文章

异常捕获try----catch

函数之局部变量和使用global语句

Python函数之返回值作用域和局部变量

原子线程围栏:为啥这个非原子变量存在数据竞争?这有关系吗?

返回类型和 return 语句

Visual Studio natvis 语句的局部变量