命名块来限制变量范围:好主意?

时间:2008-10-15 16:42:13

标签: java coding-style

多年来,我一直在使用命名块来限制临时变量的范围。我从未在其他任何地方看到过这种情况,这让我想知道这是不是一个坏主意。特别是因为Eclipse IDE默认将这些标记为警告。

我认为,在我自己的代码中,我已经使用了这个效果。但是,由于优秀的程序员在看到它时不信任它是不恰当的,我真的有两种方法可以从这里开始:

  1. 避免这样做,或
  2. 宣传它,希望它会成为一个成语。
  3. 示例(在更大的方法中):

    final Date nextTuesday;
    initNextTuesday: {
        GregorianCalendar cal = new GregorianCalendar();
        ... // About 5-10 lines of setting the calendar fields
        nextTuesday = cal.getTime();
    }
    

    这里我使用GregorianCalendar来初始化日期,我想确保我不会意外地重复使用它。

    有些人评论说你实际上不需要命名块。虽然这是真的,但原始块看起来更像是一个bug,因为意图不明确。此外,命名的东西鼓励你思考块的意图。这里的目标是识别不同的代码部分,而不是为每个临时变量赋予自己的范围。

    很多人评论说最好直接采用小方法。我同意这应该是你的第一直觉。但是,可能有几个缓解因素:

    • 要考虑一个命名区块,代码应该是简短的一次性代码,永远不会在其他地方调用。
    • 命名块是一种快速组织超大方法的方法,无需使用十几个参数创建一次性方法。当一个类不稳定时,尤其如此,输入可能会随版本而变化。
    • 创建一种新方法可以鼓励其重用,如果用例不完善,这可能是不明智的。一个命名的区块(在心理上,至少)更容易丢弃。
    • 特别是对于单元测试,您可能需要为一次性断言定义十几个不同的对象,它们只是不同,以至于您无法(尚)找到将它们合并为少量方法的方法,你也不能想办法用一英里长的名字来区分它们。

    使用命名范围的优点:

    1. 不能意外重用临时变量
    2. 有限范围为垃圾收集器和JIT编译器提供了有关程序员意图的更多信息
    3. 块名称提供了对代码块的注释,我发现它比开放式注释更具可读性
    4. 使用大方法将代码重构为小方法更容易,反之亦然,因为命名块比非结构化代码更容易分离。
    5. 缺点:

      不是惯用语:没有看到使用命名块的程序员(即除了我之外的所有人)都认为它有问题,因为他们无法找到对块名称的引用。 (就像Eclipse一样。)让事情成为惯用语是一场艰苦的战斗。

      它可以作为不良编程习惯的借口,例如:

      • 制作庞大的,单一的方法,其中几种小方法更清晰。
      • 压痕层太深,无法轻易阅读。

      注意:基于一些深思熟虑的回答,我已经广泛地编辑了这个问题。谢谢!

14 个答案:

答案 0 :(得分:26)

我只是直接重构为更小的方法。如果一个方法足够大以至于需要像这样分解,那么如果可能

,它真的需要分解成多个方法。

虽然限制范围很好,但这并不是命名块的用途。它是单一的,很少是一件好事。

答案 1 :(得分:14)

如果这很糟糕,那为什么这是该语言的一个功能!它有一个目的,你找到了它。

我经常编写与您的示例完全相同的代码。当你想要初始化一个变量时,需要做一些计算来计算应该是什么,并且这涉及到几个变量...那么你不希望这些变量在你的整个范围内闲逛函数,然后一个小范围包含初始化工作很棒。

迷你范围是将代码分解为“段落”的简单方法。如果您拆分成方法,那么当这些方法不能从其他任何地方调用时,您可以使代码更难导航,并且具有需要执行它们的一系列顺序。

它始终是一种平衡,但如果您认为它最容易维护,并且它实际上为未来的代码读者增加了价值,如果它全部内联,那就去吧。

没有严格的规则。有时候我会让同事过度地把所有东西都放在自己的方法或类或文件中,这让我感到有点厌倦,这成了导航的噩梦。某处有一个很好的平衡!

答案 2 :(得分:10)

有时我使用未命名的块来隔离准备一些不可变的东西所需的可变事物。我没有使用标签,而是将块放在不可变变量声明下。

final String example;
{
   final StringBuilder sb = new StringBuilder();
   for(int i = 0; i < 100; i++)
     sb.append(i);
   example = sb.toString();

}

当我发现该块的其他一些用途,或者只是认为它在阻碍时,我把它变成了一种方法。

答案 3 :(得分:9)

在我的书中使用块来限制范围是一种很好的技巧。

但是既然你使用标签来做评论的工作,为什么不只是使用实际的评论呢?这将消除对未引用标签的混淆。

答案 4 :(得分:5)

这是我第一次看到其他人使用积木。噢!我以为我是唯一一个。我知道我没有发明它 - 记得在某个地方读它 - 可能来自我以前的C ++世界。

我不使用标签,只是评论我在做什么。

我不同意所有要求你将其提取到方法中的人。我们在这些块中提供的大部分内容都不是真正可重用的块。它在大型初始化中是有意义的,是的,我使用了块来防止COPY / PASTE错误。

BR,
〜A

答案 5 :(得分:3)

如果您有5到10行代码可以安全地放入这样的块中,那么相同的代码也可以被提取到方法中。

这看起来似乎只是语义上的差异,但至少在提取到方法后,您将获得重用能力的好处。

答案 6 :(得分:2)

仅仅因为它们的存在并不意味着它们应该被使用。通过使用新的私有方法可以更好地获得使用命名块所获得的大部分优势。

  1. 您将无法使用新方法
  2. 中声明的临时变量
  3. GC和JIT编译器将使用新方法收集相同的信息
  4. 使用新方法的描述性名称(在您的案例中使用“private Date initNextTuesday()”)将允许自我评论代码优势
  5. 当您已经“预先考虑”它
  6. 时,无需重构代码

    除了这些好处之外,您还可以获得代码重用的好处,并且会缩短您的长方法。

答案 7 :(得分:2)

我会使用带注释的块而不是在那里添加标签。

当我看到一个标签时,我不能假设其他任何东西都没有引用该块。

如果我更改了块的行为,那么标签名称可能不再合适。但我不能只是伸出手来改变它:我将不得不查看方法的其余部分来确定哪个标签正在调用块。在这一点上,我会发现它是一个未引用的标签。

在这个实例中使用注释更加清晰,因为它描述了块的行为,而没有对维护者施加任何额外的工作。

答案 8 :(得分:1)

这是我书中的一项很好的技巧。管理大量的一次性方法是邪恶的,你提供命名块的原因是好的。

生成的字节码是什么样的?那是我唯一的犹豫。我怀疑它剥离了块名称,甚至可能从更大的优化中获益。但你必须检查。

答案 9 :(得分:1)

对不起复活,但我没有看到有人提到我认为非常重要的一点。让我们来看看你的例子:

final Date nextTuesday;
initNextTuesday: {
    GregorianCalendar cal = new GregorianCalendar();
    ... // About 5-10 lines of setting the calendar fields
    nextTuesday = cal.getTime();
}

在此处包含此初始化逻辑可让您更轻松地了解您是从上到下阅读文件并关注每一行。但想想你如何阅读代码。你是从文件的顶部开始阅读并继续到底部吗?当然不是!你唯一能做的就是在代码审查期间。相反,您可能有一个基于先前知识的起点,堆栈跟踪等。然后您在执行路径中向下/向上钻取,直到找到您要查找的内容。 根据执行路径优化阅读,而非代码审核。
阅读使用nextTuesday的代码的人是否真的想知道它是如何初始化的?我认为他们需要的唯一信息是下周二有一个Date。所有这些信息都包含在其声明中。这是一个完美的代码示例,应该分解为私有方法,因为没有必要理解读者关心的逻辑

final Date nextTuesday;
initNextTuesday: {
    GregorianCalendar cal = new GregorianCalendar();
    //1
    //2
    //3
    //4
    //5
    nextTuesday = cal.getTime();
}

<强> VS

final Date nextTuesday = getNextTuesday();

您希望通过模块阅读哪一篇?

答案 10 :(得分:1)

名称块有帮助:使用break作为转到形式

使用break作为goto的文明形式。

class Break {
    public static void main(String args[]) {
        boolean t = true;
        first: {
            second: {
                third: {
                    System.out.println("Before the break.");
                    if (t)
                        break second; // break out of second block
                    System.out.println("This won't execute");
                }
                System.out.println("This won't execute");
            }
            System.out.println("This is after second block.");
        }
    }
}

使用break从嵌套循环中退出

class BreakLoop4 {
    public static void main(String args[]) {
        outer: for (int i = 0; i < 3; i++) {
            System.out.print("Pass " + i + ": ");
            for (int j = 0; j < 100; j++) {
                if (j == 10)
                    break outer; // exit both loops
                System.out.print(j + " ");
            }
            System.out.println("This will not print");
        }
        System.out.println("Loops complete.");
    }
}

来源Link

答案 11 :(得分:0)

我在一些c#中做过这个。我不知道你可以命名这些块,但我必须尝试看看它是否也适用于c#。

我认为范围块可能是一个不错的主意,因为您可以封装特定于代码块中某些内容的代码,您可能不希望将其拆分为自己的函数。

至于嵌套它们的缺点,我认为更多的是程序员的错误,而不是范围阻塞自己。

答案 12 :(得分:0)

命名范围在技术上是可行的,只是它们不经常以这种方式使用。因此,当其他人来维护您的代码时,可能不会立即明白为什么他们在那里。恕我直言私人帮助方法将是一个更好的选择......

答案 13 :(得分:0)

我喜欢使用块来限制var范围。 这么多次我被大范围的短暂变量所迷惑,这些变量在使用后会立即消失。冗长的方法+许多非最终变量使得很难推断编码人员的意图,特别是在评论很少的情况下。考虑到我在方法中看到的大部分逻辑如下所示

Type foo(args..){
    declare ret
    ...
    make temp vars to add information on ret
    ...

    make some more temp vars to add info on ret. not much related to above code. but previously declared vars are still alive
    ...


    return ret
}

如果vars的范围小于整个方法体,我可以很快忘记它们中的大多数(好的)。

此外,我同意私人物品太多或太少会导致意大利面条代码。

实际上我在寻找的东西就像函数式语言中的嵌套方法一样,而且它在Java中的堂兄似乎是{ BLOCK }(内部类和labmda表达式不适合这个......)。

但是,我更喜欢使用一个未命名的块,因为这可能会误导那些试图找到标签引用的人,而且我可以用注释块更好地解释。

对于使用私有方法,我会将其视为使用块的下一步。