Named blocks to limit variable scope: good idea?

2019-01-16 11:25发布

For years, I've been using named blocks to limit the scope of temporary variables. I've never seen this done anywhere else, which makes me wonder if this is a bad idea. Especially since the Eclipse IDE flags these as warnings by default.

I've used this to good effect, I think, in my own code. But since it is un-idiomatic to the point where good programmers will distrust it when they see it, I really have two ways to go from here:

  1. avoid doing it, or
  2. promote it, with the hope that it will become an idiom.

Example (within a larger method):

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

Here I'm using a GregorianCalendar just to initialize a date, and I want to make sure that I don't accidentally reuse it.

Some people have commented that you don't actually need to name the block. While that's true, a raw block looks even more like a bug, as the intent is unclear. Furthermore, naming something encourages you to think about the intention of the block. The goal here is to identify distinct sections of code, not to give every temporary variable its own scope.

Many people have commented that it's best to go straight to small methods. I agree that this should be your first instinct. However, there may be several mitigating factors:

  • To even consider a named block, the code should be short, one-off code that will never be called elsewhere.
  • A named block is a quick way to organize an oversized method without creating a one-off method with a dozen parameters. This is especially true when a class is in flux, and the inputs are likely to change from version to version.
  • Creating a new method encourages its reuse, which may be ill-advised if the use cases aren't well-established. A named block is easier (psychologically, at least) to throw away.
  • Especially for unit tests, you may need to define a dozen different objects for one-off assertions, and they are just different enough that you can't (yet) find a way to consolidate them into a small number of methods, nor can you think of a way to distinguish them with names that aren't a mile long.

Advantages of using the named scope:

  1. Can't accidentally reuse temporary variables
  2. Limited scope gives garbage collector and JIT compiler more information about programmer intent
  3. Block name provides a comment on a block of code, which I find more readable than open-ended comments
  4. Makes it easier to refactor code out of a big method into little methods, or vice versa, since the named block is easier to separate than unstructured code.

Disadvantages:

Not idiomatic: programmers who haven't seen this use of named blocks (i.e. everyone but me) assume it's buggy, since they can't find references to the block name. (Just like Eclipse does.) And getting something to become idiomatic is an uphill battle.

It can be used as an excuse for bad programming habits, such as:

  • Making huge, monolithic methods where several small methods would be more legible.
  • Layers of indentation too deep to read easily.

Note: I've edited this question extensively, based on some thoughtful responses. Thanks!

14条回答
我只想做你的唯一
2楼-- · 2019-01-16 11:34

Using blocks to limit scope is a good technique in my book.

But since you're using the label to do the work of a comment, why not just use an actual comment instead? This would remove the confusion about the unreferenced label.

查看更多
Summer. ? 凉城
3楼-- · 2019-01-16 11:40

Sorry for resurrecting this, but I didn't see anyone mention what I consider to be a very important point. Let's look at your example:

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

Including this initialization logic here makes it easier to understand if you're reading the file from top to bottom and care about every line. But think about how you read code. Do you start reading from the top of a file and continue to the bottom? Of course not! The only time you would ever do that is during a code review. Instead, you probably have a starting point based on previous knowledge, a stack trace, etc. Then you drill further down/up through the execution path until you find what you're looking for. Optimize for reading based on execution path, not code reviews.
Does the person reading the code that uses nextTuesday really want to read about how it's initialized? I would argue that the only information that they need is that there's a Date corresponding to next Tuesday. All of this information is contained in its declaration. This is a perfect example of code that should be broken into a private method, because it isn't necessary to understand the logic that the reader cares about.

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

vs:

final Date nextTuesday = getNextTuesday();

Which would you rather read on your way through a module?

查看更多
Juvenile、少年°
4楼-- · 2019-01-16 11:42

I'd use a block with a comment rather adding a label there.

When I see a label, I can't assume that nothing else is referencing the block.

If I change the behavior of the block, then the label name may not be appropriate any more. But I can't just reach out and change it: I'll have to look through the rest of the method to determine what label is calling out to the block. At which point I'll figure out that it's an unreferenced label.

Using a comment is clearer in this instance, because it describes the behavior of the block without imposing any extra work on the part of the maintainer.

查看更多
对你真心纯属浪费
5楼-- · 2019-01-16 11:45

This is the 1st time I am seeing someone else using blocks. whew! I thought I was the only one. I know that I didn't invent it -- remembered reading it somewhere -- possibly from my previous C++ world.

I don't use the labels, though and just comment what I'm doing.

I don't agree with all the guys that are asking you extract it into a method. Most of the things we don in such blocks aren't really reusable blocks. It makes sense in a big initialization AND YES, I've used blocks to prevent COPY/PASTE errors.

BR,
~A

查看更多
虎瘦雄心在
6楼-- · 2019-01-16 11:46

Just because they exist doesn't mean they should be used. Most of the advantages gained from using named blocks are better gained by using a new private method.

  1. You won't be able to use the temporary variables declared in the new method
  2. The GC and JIT Compiler will glean the same info by using a new method
  3. Using a descriptive name for the new method (using "private Date initNextTuesday()" in your case) will allow for the self commenting code advantage
  4. No need to refactor code when you have already "pre-factored" it

In addition to these benefits, you also get code reuse benefits and it will shorten your long methods.

查看更多
Summer. ? 凉城
7楼-- · 2019-01-16 11:46

Name Blocks helps: Using break as a Form of Goto

Using break as a civilized form of 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.");
        }
    }
}

Using break to exit from nested loops

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.");
    }
}

Source Link

查看更多
登录 后发表回答