【问题标题】:Different styles of flow of program?不同风格的程序流程?
【发布时间】:2010-08-18 18:54:37
【问题描述】:

我是一名计算机科学专业的学生,​​因此我知道的不多。

我最近与一位刚获得 (java) 软件开发人员工作的朋友交谈。他告诉我,在他的工作中,有一个人对 C++ 非常有经验,但不幸的是,他每次用 java 编写代码时,都是使用 try-catch 来控制程序的流程。据我的朋友说,这是 Java 中的错误风格。这是真的?在 C++ 和 Java 之间使用 try-catch(-finally in java) 有什么区别(如果有的话)?

【问题讨论】:

  • +1 对 John Weldon 的回答。请注意,Java 生态系统的一部分(并且是 Java 的特殊性)真正令人讨厌的方面之一是某些 API/框架过度使用 checked 异常。基本上强迫 API 用户通过捕捉毫无意义的无检查异常来控制他们的程序流,这些异常根本不是异常的。一些框架,比如 Spring,被正确设计并且真的不赞成这种非常糟糕的做法。 Joshua Bloch 在他的 Effective Java 中建议首选“状态测试方法”而不是不必要地抛出(未)检查的异常。
  • 您好,感谢您的回答。 “基本上强迫 API 用户通过捕获毫无意义的无检查异常来控制他们的程序流程,因为这些异常根本不例外。”。那么这就是 C++/Java 之间 try-catch 的区别吗?另外,您是什么意思“已检查”异常?干杯
  • javapractices.com/topic/TopicAction.do?Id=129。 Checked exceptions本质上是对非异常流控制异常的滥用。它们也是维护的噩梦。

标签: java c++


【解决方案1】:

使用 try-catch 控制程序的流程在任何地方都是错误的......异常处理就是它所说的:异常情况的处理。

当然,对于每条规则,都有十几个必要偏差的反例,但一般来说:不要控制程序流,有例外。

当您预计在正常操作环境中会引发某些异常,并且您根据这些异常做出合乎逻辑的决定时,就会使用异常来控制程序的流程。

例如用伪代码控制程序流程:

try {
  write update to file
} catch (IOException) {
  write update to alternate file
}

在这种情况下,最好在盲目执行写入之前实际测试路径是否存在。

我删除了权限检查注释,因为它是一个不好的例子

一个很好的异常处理用法:(又是伪代码)

try {
  do stuff
} catch(OutOfMemoryException) {
  fail gracefully (don't try and do something else to achieve the same result)
}

【讨论】:

  • 嗨。感谢您的回答。是否可以显示一些代码,其中 try-catch 正在控制流程,以及类似的情况,它没有?谢谢
  • 这是一个非常好的问题@Muggen;我会尝试更新我的答案,但我认为这值得自己提出一个问题。
  • @Muggen 如果你有一个打开的文件,你应该能够阅读,但如果你不能,这是一个例外情况。如果用户尝试登录但输入了无效密码,这是正常的流程情况,应该像这样对待,而不是使用 try-catch
  • @Muggen;另一种说法是:如果您能想到在正常使用流程中可能发生的场景,您可能不想使用异常来处理它。 (关于随之而来的一般性答案的所有警告)
  • 这是一个很好的答案,但我想评论一下“权限检查”示例 - 在执行操作之前尝试自己检查权限通常不是好设计选择。通常,您应该尝试操作并处理失败。这是因为检查权限可能非常复杂,并且很难匹配系统可能执行的检查。更简单的示例可能是在使用它之前检查空引用而不是捕获 NullPointerException 或确定数组的大小而不是捕获 IndexOutOfBoundsException。
【解决方案2】:

我想说,从程序架构的角度来看,异常的目的在 Java 和 C++ 中是相同的。它们应该用于处理异常情况,而不是直接处理程序的正常流程。

反对使用异常的最常见论点是它们很慢。与大多数基于代码速度的论点一样,如今它几乎总是无关紧要,尤其是在正确使用异常时。

【讨论】:

  • 嗨。 “...尤其是在正确使用异常时。”。异常如何使用不当?
  • 无论何时用于控制程序的流程,而不是处理异常情况。例如,如果您可以检查数组的长度,请不要使用索引越界异常来知道您传递了索引数组的长度,而是简单地让 for 循环在适当的点终止。
  • 在这种情况下,“他们很慢”是“这不是他们的目的”的代理论点。有些人不能很好地理解这样的高级符号推理,所以你必须给他们举例。当您使用超出其预期用途范围的事物时,您会遇到问题(该用途的速度很慢就是一个例子)。如果有人碰巧解决了缓慢问题,那么这个世界仍然不正常。
  • 这里的关键词是“正常”。所有异常都控制流程。
【解决方案3】:

我认为他的意思是他将异常用于非异常条件,例如正常循环终止。大多数提供异常的语言(包括 C++)的程序员都认为这是一个禁忌。

这样做的原因是异常往往会拖累相当多的信息。在某些平台上,它们将整个调用堆栈带到异常点,以便在一个漂亮的调试消息中显示它。如果您真正想要的只是无条件控制跳转,那将非常昂贵。

相反,您应该使用 breakreturn 之类的语句,甚至(在真正的紧要关头)goto

【讨论】:

  • 另一方面,这是 Python 中的“是-是”,其中 每个 for 循环 通过处理 StopIteration 异常来终止。 ;)
  • @UncleBens - 非常感谢您验证我的胡思乱想。我想至少有一个在那里。 :-)
【解决方案4】:

我希望 C++ 和 Java 世界在他们的观点上大体相似:

  1. 使用 try/catch 块的流控制不是很明显
  2. 在性能方面它的成本很高。实际上,这可能不是问题,但在许多情况下(例如紧密循环),它肯定会对您产生影响。

【讨论】:

    【解决方案5】:

    try/catch唯一目的是流量控制。它旨在让我们跳出任何循环,甚至可以越过调用者到达更高级别的函数。

    但是,它是为特殊情况设计的,例如使用空引用、除法溢出或文件 I/O 失败。这些是阻止程序继续运行的错误,必须处理。一般来说,它们很难预测或非常罕见。

    当出现异常情况时,最好的办法是改变控制流,以便将异常抛到尽可能高的位置,以便找到愿意处理它的 catch 块。中间的任何方法都会被中止和解除,即使它没有代码来检查结果。

    当人们抱怨使用try/catch 进行流量控制时,他们应该的意思是它不适用于非异常情况下的流量控制。如果您只想退出循环,请使用breakreturn;不要抛出异常然后捕获它。这不是例外的用途,也不是它们的好处。

    【讨论】:

    • 很好的解释@Steven。我不确定是否值得对我的答案投反对票,但仍然很好:)
    • @John:你得到了 20 票。一票否决至少会让人们停下来寻找替代方案。
    【解决方案6】:

    try-catch 块旨在在所有语言中执行相同的操作 - 异常是出现问题时恢复的众多方法之一。在java中,以下两条语句是相同的:

    if(x != null){
      //do the normal thing
    }
    else{
      //recover from the error
    }
    

    try{
      //do the normal thing
    }
    catch(NullPointerException ex){
      //recover
    }
    

    但是,出于多种原因,使用异常绝不是一个好主意:

    • 与抛出异常相关的成本(即在 Java 中生成 Throwable)
    • 控制流并不是很明显——例如,如果一个 NullPointerException 可以被 try 块中的多个语句抛出,你怎么知道是哪一个在 catch 块中抛出了它
    • 等等等等

    【讨论】:

    • 嗨。感谢您的回答。 “你怎么知道是哪个人把它扔进了 catch 块”是我的另一个问题。所以你几乎建议 try-catch 应该只在特殊情况下使用,或者我理解错了?干杯
    • 在说这些结构相同或等效时要小心。如果“// 做正常的事情”首先做了一些没有不正确地使用null 引用的事情,那么第二个例子将做一些在第一个例子中不会发生的工作(可能有可见的副作用)。
    • 等等,这不是一个好主意?!异常非常适合它们的设计目的:异常。与结果代码不同,它们不能被忽略,它们会自动冒泡,直到找到愿意处理它们的代码。永远不要说永远,尤其是对于用途明确的事物。
    • 你说得有道理,我应该更准确地使用 never。我的意思是“大多数人会同意使用异常作为 if 语句的替代方法通常不是一个好主意。但是,大多数人会同意异常有利于处理异常情况。请记住,它们不是唯一的方式”
    【解决方案7】:

    他可能想要的是多种不执行代码的方法......所以而不是(在 java 中)

    if (cond1) {
      some_code();
      if (cond2) {
        some_more_code();
        if (condN) {
          ad_infinitum
        }
      }
    }
    

    这可以通过扔一些垃圾来“变平”......

    try {
      if (!cond1) throw new Throwable();
      some_code();
      if (!cond2) throw new Throwable();
      some_more_code();
      if (!condN) throw new Throwable();
      ad_infinitum();
    } catch (Throwable ignore) { }
    

    问题是如果抛出了一些真正的异常怎么办?我认为如果你的同事想要压平缩进,他应该写的是这样的:

    do {
      if (!cond1) break;
      some_code();
      if (!cond2) break;
      some_more_code();
      if (!condN) break;
      ad_infinitum();
    } while (false);
    

    由于 do/while 总是执行,我们可以使用 break 来近似 goto(只要它都转到同一个位置)。

    【讨论】:

      【解决方案8】:

      如果没有示例代码,您的问题就太模糊了。 C++ 中没有finally

      EDIT 通常,您不想使用exceptions 作为控制流程的手段。 Exceptions 应该仅用于异常(意外情况)情况。

      【讨论】:

      • 实际上这个答案开始解决OP提出的“其他”问题:java和C++之间的异常处理有什么区别
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-26
      • 1970-01-01
      • 2015-01-26
      • 2012-11-06
      • 2013-06-10
      • 1970-01-01
      相关资源
      最近更新 更多