【问题标题】:Should I avoid using Java Label Statements?我应该避免使用 Java 标签语句吗?
【发布时间】:2010-09-07 23:18:57
【问题描述】:

今天我有一位同事建议我重构我的代码以使用标签语句来控制通过我创建的 2 个嵌套 for 循环的流程。我以前从未使用过它们,因为我个人认为它们会降低程序的可读性。但是,如果论点足够可靠,我愿意改变使用它们的想法。人们对标签声明有何看法?

【问题讨论】:

  • 我喜欢你的开放态度 :) “我愿意改变使用它们的想法”
  • (因为可以将循环放入方法中,这完全等同于单/多/提前退出方法。)

标签: java loops


【解决方案1】:

如果您可以跳过两个循环(或包含 switch 语句的循环),则可以更容易地表达许多算法。不要为此感到难过。另一方面,它可能表明解决方案过于复杂。所以退后一步看看问题。

有些人更喜欢对所有循环采用“单进单出”的方法。也就是说,完全避免中断(和继续)和提前返回循环。这可能会导致一些重复的代码。

我强烈避免做的是引入辅助变量。在状态中隐藏控制流会增加混乱。

将标记的循环分成两种方法可能很困难。异常可能太重了。尝试单进单出的方法。

【讨论】:

  • 除了结论性的“尝试单入口,单出口方法”评论外,会对此表示赞同。
  • 我认为偶尔试一试尺寸是值得的。你总是有 ^Z。
  • “在状态中隐藏控制流会增加混乱”一般来说是个好建议
【解决方案2】:

标签就像 goto 的:谨慎使用它们,只有当它们使您的代码更快并且更重要的是,更易于理解时,

例如,如果您处于六级深度的大循环中,并且遇到了使循环的其余部分无法完成的条件,那么在条件语句中设置 6 个额外的陷阱门以提前退出循环是没有意义的。

标签(和 goto 的)并不邪恶,只是有时人们会以不好的方式使用它们。大多数时候,我们实际上是在尝试编写我们的代码,以便您和下一个出现的程序员可以理解。使其超快是次要问题(提防过早的优化)。

当标签(​​和 goto 的)被滥用时,它们会降低代码的可读性,这会给您和下一个开发人员带来痛苦。编译器不在乎。

【讨论】:

  • 是的,+1:GOTO 不会杀死应用程序 - 程序员会杀死应用程序。
  • 从历史上看,程序员曾使用 GOTO 来杀死应用程序。 GOTO 名声不好是有原因的。
  • 这一切都归结为 Dijkstra 的信“反对 Goto 语句的案例”/“Go To 语句被认为是有害的”,它反对它,因为倾向于将程序制作成意大利面条代码而不是你的代码会发现一些更有条理的东西。实际上,您一直在使用 goto(作为中断、继续、尝试/捕获等)。它几乎总是将执行向前抛出。如果您不习惯于只向前移动,则可以将函数的复杂性提高一个数量级。
  • 向后跳转可能让你今天很容易理解,但下一个开发者或 3 年后的你会想要杀死节省 5 分钟工作并留给他们 3 小时工作的 SOB正在解析函数的流程。
【解决方案3】:

您需要标签的情况很少,而且由于很少使用,因此可能会造成混淆。但是,如果您需要使用一个,那就使用一个。

顺便说一句:这会编译并运行。

class MyFirstJavaProg {  
        public static void main(String args[]) {
           http://www.javacoffeebreak.com/java101/java101.html
           System.out.println("Hello World!");
        }
}

【讨论】:

  • 是的,一个好的语法高亮应该能清楚地说明原因。
  • 这是一个糟糕的面试问题,除非它是针对一家代码库完全被混淆的公司。你永远不会在野外看到这个,如果你看到了,快速的网络搜索会告诉你它的含义。您可以在更重要的事情上测试潜在的开发人员,而不是愚蠢的小谜语。
  • @studro 代码突出显示和格式化将向您展示有关内容;) 关键是,您总是会在其他人的代码中找到与您做事或期望的方式不同的东西以及您如何处理。重要的是你如何回答问题,而不是你给出的答案。面试应该是关于你知道什么、你想和什么一起工作以及你如何处理各种做事方式的测验。
  • 这绝对是晦涩难懂的;说“这就是你处理它的方式”很好,但对于一个碰巧知道它的可怜的开发人员来说,这是一个噱头,对于一个没有在野外遇到它的优秀开发人员来说,这是一个排除因素(因为它没有根本不会经常发生)。 “我会在 Google 上搜索‘Java 中的标签’”可能不会在采访中表达出来。
【解决方案4】:

我很想知道您的标签替代方案是什么。我认为这几乎可以归结为“尽早返回”与“使用变量来保存返回值,并且只在最后返回”的论点。

当您有嵌套循环时,标签是相当标准的。它们真正降低可读性的唯一方法是其他开发人员以前从未见过它们并且不理解它们的含义。

【讨论】:

  • 一种递归方法。或者你可以有条件地打破,如果(发现)打破;并在所有循环结束时使用它。您也许还可以将内部循环拆分为方法,并返回而不是中断。您还应该质疑提前中断的好处,您能否从并行化或不同的算法中获得更多性能。有几次,我什至意识到嵌套是不必要的,循环可以简单地重构为一个接一个地运行。
【解决方案5】:

我使用 Java 标记的循环来实现 Sieve 方法来查找素数(为项目 Euler 数学问题之一完成),与嵌套循环相比,它的速度提高了 10 倍。例如 if(某些条件) 回到外循环。

private static void testByFactoring() {
    primes: for (int ctr = 0; ctr < m_toFactor.length; ctr++) {
        int toTest = m_toFactor[ctr];
        for (int ctr2 = 0; ctr2 < m_divisors.length; ctr2++) {
            // max (int) Math.sqrt(m_numberToTest) + 1 iterations
            if (toTest != m_divisors[ctr2]
                        && toTest % m_divisors[ctr2] == 0) {
                continue primes; 
            }
        } // end of the divisor loop
    } // end of primes loop
} // method

我问过一个 C++ 程序员,标记循环有多糟糕,他说他会谨慎使用它们,但它们偶尔会派上用场。例如,如果您有 3 个嵌套循环,并且在某些情况下您想返回最外层的循环。

所以它们有其用途,这取决于您要解决的问题。

【讨论】:

    【解决方案6】:

    我从未见过在 Java 代码中“广泛使用”的标签。如果您真的想打破嵌套循环,请查看是否可以重构您的方法,以便提前返回语句执行您想要的操作。

    从技术上讲,我想提前退货和贴牌之间没有太大区别。但实际上,几乎每个 Java 开发人员都看到了早期的回报并且知道它做了什么。我猜很多开发者至少会对一个标签感到惊讶,并且可能会感到困惑。

    我在学校学习过单入口/单出口的正统观念,但后来我开始欣赏早期的 return 语句和打破循环作为简化代码并使代码更清晰的一种方式。

    【讨论】:

      【解决方案7】:

      我会在某些地方支持它们,我发现它们在这个例子中特别有用:

      
      nextItem: for(CartItem item : user.getCart()) {
      
        nextCondition : for(PurchaseCondition cond : item.getConditions()) {
           if(!cond.check())
              continue nextItem;
           else
              continue nextCondition;
      
        }
        purchasedItems.add(item);
      }
      

      【讨论】:

      • 好吧,continue nextCondition 是多余的,只是噪音。
      【解决方案8】:

      我认为使用新的 for-each 循环,标签可以非常清晰。

      例如:

      sentence: for(Sentence sentence: paragraph) {
        for(String word: sentence) {
          // do something
          if(isDone()) {
            continue sentence;
          }
        }
      }
      

      我认为通过将标签与新 for-each 中的变量相同,这看起来非常清楚。事实上,也许Java应该是邪恶的,并为每个变量添加隐式标签heh

      【讨论】:

      • 好吧,continue sentence; 是否继续处理句子,(which?)的单词由内部循环处理:它继续_下一个句子_/同一段落.
      【解决方案9】:

      我从不在代码中使用标签。我更喜欢创建一个守卫并将其初始化为 null 或其他不寻常的值。这个守卫通常是一个结果对象。我没有看到我的任何同事使用标签,也没有在我们的存储库中找到任何标签。这实际上取决于您的编码风格。在我看来,使用标签会降低可读性,因为它不是一种常见的构造,而且通常不会在 Java 中使用。

      【讨论】:

        【解决方案10】:

        是的,你应该避免使用标签,除非有特定的理由使用它们(它简化算法实现的例子是相关的)。在这种情况下,我建议添加足够的 cmets 或其他文档来解释其背后的原因,这样以后就不会有人出现并从“改进代码”或“摆脱代码异味”的概念中破坏它或其他一些潜在的 BS 借口。

        我会将这类问题等同于决定何时应该或不应该使用三元 if。主要理由是它会阻碍可读性,除非程序员非常小心地以合理的方式命名事物,否则使用诸如标签之类的约定可能会使事情变得更糟。假设使用“nextCondition”和“nextItem”的示例使用“loop1”和“loop2”作为他的标签名称。

        个人标签是对我来说没有多大意义的功能之一,除了汇编或 BASIC 和其他类似受限的语言。 Java 有很多更传统/常规的循环和控制结构。

        【讨论】:

          【解决方案11】:

          我发现标签有时在测试中很有用,可以将通常的设置、练习和验证阶段以及相关语句分组。例如,使用 BDD 术语:

          @Test
          public void should_Clear_Cached_Element() throws Exception {
              given: {
                  elementStream = defaultStream();
                  elementStream.readElement();
                  Assume.assumeNotNull(elementStream.lastRead());
              }
              when:
                  elementStream.clearLast();
              then:
                  assertThat(elementStream.lastRead()).isEmpty();
          }
          

          您的格式选择可能会有所不同,但核心思想是,在这种情况下,标签在构成您的测试的逻辑部分之间提供了明显的区别,比 cmets 更好。我认为Spock 库正是基于这个特性来声明它的测试阶段。

          【讨论】:

            【解决方案12】:

            就我个人而言,每当我需要使用嵌套循环并且最里面的循环必须跳出所有父循环时,我只需在满足我的条件时将所有内容写在一个带有 return 语句的方法中,它更具可读性和逻辑性。

            示例使用方法:

              private static boolean exists(int[][] array, int searchFor) {
                for (int[] nums : array) {
                  for (int num : nums) {
                    if (num == searchFor) {
                      return true;
                    }
                  }
                }
                return false;
              }
            

            使用标签的示例(imo 可读性较差):

            boolean exists = false;
            existenceLoop:
            for (int[] nums : array) {
              for (int num : nums) {
                if (num == searchFor) {
                  exists = true;
                  break existenceLoop;
                }
              }
            }
            
            return exists;
            

            【讨论】:

              猜你喜欢
              • 2013-02-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-10-14
              • 1970-01-01
              • 2010-09-23
              • 2018-06-16
              • 2011-12-31
              相关资源
              最近更新 更多