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

Posted

技术标签:

【中文标题】命名块来限制变量范围:好主意吗?【英文标题】:Named blocks to limit variable scope: good idea? 【发布时间】:2010-09-17 08:30:44 【问题描述】:

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

我认为,在我自己的代码中,我已经使用它取得了很好的效果。但是由于它是不习惯的,以至于优秀的程序员在看到它时会不信任它,所以我真的有两条路可以走:

    避免这样做,或者 推广它,希望它成为一个成语。

示例(在更大的方法中):

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

这里我使用 GregorianCalendar 只是为了初始化一个日期,并且我想确保我不会意外重用它。

有些人评论说您实际上不需要命名块。虽然这是真的,但原始块看起来更像是一个错误,因为意图尚不清楚。此外,命名某些东西会鼓励您考虑块的意图。此处的目标是识别不同的代码部分,而不是为每个临时变量赋予其自己的范围。

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

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

使用命名作用域的优点:

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

缺点:

不是惯用的:没有见过命名块的这种用法的程序员(即除了我之外的所有人)认为这是错误的,因为他们找不到对块名称的引用。 (就像 Eclipse 所做的那样。)让某些东西变得惯用是一场艰苦的战斗。

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

制作庞大的整体方法,其中几个小方法会更清晰。 缩进层太深,难以阅读。

注意:我根据一些深思熟虑的回答对这个问题进行了广泛的编辑。谢谢!

【问题讨论】:

你实际上不需要在块上贴标签。 这无关,但 Sun 不推荐使用 Calendar cal = Calendar.getInstance(); ? 关于它是非惯用的:并非一切都必须是惯用的。一种风格是惯用的,它必须在经常出现的情况下有用。但是,您所描述的对于非常不寻常的情况是一个完美的最佳解决方案(一个代码块可以自己做,但是太小/不可重用而不能成为自己的方法。)不,并非所有事情都需要它是自己的方法,方法太多就像意大利面条太少一样。因此,总而言之,如果您足够专业,能够感受到从中受益的确切情况,请继续使用命名块,否则不要。 另一个注意事项:对我来说,未命名块的意图比命名块的意图要清楚得多。一个未命名的块只是说“范围限制”。一个名字只会造成更多的混乱,并导致在该块内搜索 continue/break 语句,如果没有任何代码,就会感觉不专业。 Eclipse(默认)会警告您“标签 initNextTuesday 从未被明确引用”。尽管可以关闭警告,但大多数开发人员不会对 Eclipse 进行个性化设置,并且可能会看到黄色曲线。我个人认为 cmets 解释你限制范围比块上的标签更好。 【参考方案1】:

我会直接重构为更小的方法。如果一个方法足够大以至于需要像这样分解,那么如果完全可能的话,它确实需要分解成多个方法。

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

【讨论】:

什么是命名块? 我只见过它们用于循环中断/继续。 这里的“命名块”是指带有标签的块。您可以在任何语句上放置标签,但是它(当前)仅对块或控制语句有意义。可以将标签附加到 break 和 continue 语句以指定它们应用到哪个(间接)封闭语句。【参考方案2】:

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

我经常按照您的示例编写代码。当你想初始化一个变量时,需要做一些计算来确定它应该是什么,并且这涉及到几个变量......那么你不希望这些变量在你的整个范围内徘徊函数,然后包含初始化的小范围非常有用。

迷你作用域是一种将代码分解为“段落”的简单方法。如果您拆分为多个方法,那么当这些方法没有从其他任何地方调用并且具有需要执行的串行顺序时,您可以使代码更难导航。

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

没有硬性规定。有时我有点厌倦了同事过度地将所有内容放入自己的方法或类或文件中,这成为导航的噩梦。某处有一个很好的平衡!

【讨论】:

我同意。我认为我们只应该创建一个方法,如果它被调用或可以从多个地方或从同一个地方多次调用不同的参数。对我来说,如果有一种方法的唯一目的是将一个较长的方法构造成几个较短的方法,那将是非常令人困惑的。如果它只有一个目的,那么有一个更长的方法会更清楚。如果两种方法有非常不同的目的,那么它们当然应该分开。【参考方案3】:

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

final String example;

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


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

【讨论】:

【参考方案4】:

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

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

【讨论】:

【参考方案5】:

这是我第一次看到其他人使用积木。哇!我以为我是唯一的一个。我知道它不是我发明的——记得在某处读过它——可能来自我以前的 C++ 世界。

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

我不同意所有要求您将其提取到方法中的人。我们在这些块中所做的大多数事情都不是真正可重用的块。这在大型初始化中是有意义的,是的,我使用了块来防止复制/粘贴错误。

BR, ~A

【讨论】:

【参考方案6】:

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

这似乎只是语义上的差异,但至少通过提取到方法中,您将获得重用能力的好处。

【讨论】:

【参考方案7】:

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

    您将无法使用新方法中声明的临时变量 GC 和 JIT 编译器将使用新方法收集相同的信息 为新方法使用描述性名称(在您的情况下使用“private Date initNextTuesday()”)将具有自我注释代码的优势 如果您已经“预分解”了代码,则无需重构代码

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

【讨论】:

【参考方案8】:

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

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

如果我更改块的行为,那么标签名称可能不再合适。但我不能只是伸手去改变它:我必须查看方法的其余部分以确定哪个标签正在调用块。到那时我会发现它是一个未引用的标签。

在这种情况下使用注释更清楚,因为它描述了块的行为,而不会给维护者带来任何额外的工作。

【讨论】:

【参考方案9】:

在我的书中,这是一个很好的技术。管理大量一次性方法是邪恶的,而您为块命名的原因是好的。

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

【讨论】:

【参考方案10】:

很抱歉重新提出这个问题,但我没有看到有人提到我认为非常重要的一点。让我们看看你的例子:

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();

对比:

final Date nextTuesday = getNextTuesday();

在阅读模块的过程中,您更愿意阅读哪个?

【讨论】:

我猜你的意思是长代码应该进入方法,而原始帖子说短代码应该留在一个块中。两者都适合我。【参考方案11】:

名称块有帮助:使用 break 作为 Goto 的一种形式

使用 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

【讨论】:

【参考方案12】:

我已经在我的一些 c# 中做到了这一点。我不知道你可以命名这些块,我得试试看它是否也适用于 c#。

我认为作用域块可能是一个好主意,因为您可以将特定于某些内容的代码封装在代码块中,而您可能不想将其拆分为自己的函数。

至于嵌套它们的缺点,我认为这更多是程序员的错误,而不是范围块本身。

【讨论】:

【参考方案13】:

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

【讨论】:

【参考方案14】:

我喜欢使用块来限制 var 范围的想法。 很多时候,我对给定大范围的短期变量感到困惑,使用后应该立即消失。长方法 + 许多非最终变量使得很难推断编码器的意图,尤其是在 cmets 很少见的情况下。考虑到我在方法中看到的大部分逻辑如下所示

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 表达式不适用于此..)。

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

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

【讨论】:

以上是关于命名块来限制变量范围:好主意吗?的主要内容,如果未能解决你的问题,请参考以下文章

Cassandra Scaling:为多节点 Cassandra DB 使用通用挂载是个好主意吗?

通过 Spring MVC 框架包含其他 JSP 是个好主意吗?

redis的key有长度限制么?

限制头文件中“使用命名空间”的范围

GTM 数据层限制

redis的key有长度限制么?