【问题标题】:Java: Exceptions as control flow?Java:异常作为控制流?
【发布时间】:2010-12-05 12:20:29
【问题描述】:

我听说对控制流使用异常是不好的做法。您对此有何看法?

public static findStringMatch(g0, g1) {

    int g0Left = -1;
    int g0Right = -1;
    int g1Left = -1;
    int g1Right = -1;

//if a match is found, set the above ints to the proper indices
//...
//if not, the ints remain -1

        try {
            String gL0 = g0.substring(0, g0Left);
            String gL1 = g1.substring(0, g1Left);

            String g0match = g0.substring(g0Left, g0Right);
            String g1match = g1.substring(g1Left, g1Right);

            String gR0 = g0.substring(g0Right);
            String gR1 = g1.substring(g1Right);

            return new StringMatch(gL0, gR0, g0match, g1match, gL1, gR1);
        }
        catch (StringIndexOutOfBoundsException e) {
            return new StringMatch(); //no match found
        }

因此,如果未找到匹配项,则整数将为 -1。当我尝试获取子字符串g0.substring(0, -1) 时,这将导致异常。然后该函数只返回一个表示未找到匹配项的对象。

这是不好的做法吗?我可以手动检查每个索引,看看它们是否都是 -1,但这感觉需要做更多的工作。

更新

我已经删除了 try-catch 块并将其替换为:

    if (g0Left == -1 || g0Right == -1 || g1Left == -1 || g1Right == -1) {
        return new StringMatch();
    }

哪个更好:检查每个变量是否为 -1,还是使用布尔值 foundMatch 跟踪并在最后检查?

【问题讨论】:

    标签: java exception control-flow


    【解决方案1】:

    通常,异常是昂贵的操作,顾名思义,异常情况。因此,在控制应用程序流的上下文中使用它们确实被认为是不好的做法。

    特别是在您提供的示例中,您需要对提供给 StringMatch 构造函数的输入进行一些基本验证。如果它是一个在某些基本参数验证失败的情况下返回错误代码的方法,您可以避免事先检查,但事实并非如此。

    【讨论】:

    • +1:同意。 Joshua Bloch 在 Effective Java 中给出了同样的建议。最好先明确地测试你的论点。
    • 参见 Effective Java 第 9 章中 Bloch 的第 57 条。
    • 同意,但我试图让服务器套接字运行并且超时编码在异常中,因此您将在示例代码中看到其中的一些,但代码正确的是查找匹配项(或不) 以便将异常用于异常情况,例如“基本参数验证失败”或空指针等。
    • @Nicholas,这显然是一个设计决定。连接超时是该套接字通信中的异常情况。即使它在您的服务器应用程序中可能很常见。
    【解决方案2】:

    我已经对此进行了一些测试。在现代 JVM 上,它实际上不会对运行时性能产生太大影响(如果有的话)。如果您在打开调试的情况下运行,那么它确实会大大减慢速度。

    查看details以下内容

    (我还应该提到,我仍然认为这是一种不好的做法,即使它不会影响性能。最重要的是,它反映了可能难以测试的糟糕算法设计)

    【讨论】:

      【解决方案3】:

      是的,这是一种不好的做法,尤其是当您有办法避免异常时(在尝试对其进行索引之前检查字符串长度)。 Try 和 catch 块旨在将“正常”逻辑与“异常”和错误逻辑分开。在您的示例中,您已将“正常”逻辑传播到异常/错误块中(未找到匹配项并非异常)。您还滥用了substring,因此您可以利用它产生的错误作为控制流。

      【讨论】:

        【解决方案4】:

        程序流应该尽可能直线(因为即使应用程序变得相当复杂),并使用标准控制流结构。下一个接触代码的开发人员可能不是您,并且(正确地)误解了您使用异常而不是条件来确定控制流的非标准方式。

        在一些遗留代码重构期间,我现在在这个问题上与稍微不同的倾向作斗争。

        我发现这种方法的最大问题是使用 try/catch 会破坏正常的编程流程。

        在我正在处理的应用程序中(这与您应用的示例不同),异常用于从给定结果的方法调用中进行通信(例如,查找帐号但没有找到它)发生了。这会在客户端创建意大利面条式代码,因为调用方法(在非异常事件或正常用例事件期间)会中断它在调用之前执行的任何代码并进入 catch 块。这在一些很长的方法中重复了很多次,使得代码很容易被误读。

        对于我的情况,一个方法应该根据它的签名返回一个值,除了真正的异常事件。异常处理机制的目的是在异常发生时采取另一条路径(尝试从方法内恢复,以便您仍然可以正常返回)。

        在我看来,如果您将 try/catch 块的范围非常严格,您可以做到这一点;但我认为这是一个坏习惯,可能导致代码很容易被误解,因为调用代码会将任何抛出的异常解释为 'GOTO' 类型的消息,从而改变程序流程。我担心虽然这个案例没有落入这个陷阱,但这样做往往会导致编码习惯导致我现在生活的噩梦。

        而那个噩梦并不令人愉快。

        【讨论】:

        • 不确定是否非常相似,但我依赖 3rd 方库的代码,通过 try catch 封装/定位我自己的代码以抑制我知道的专有自己的异常,我没有意识到第 3 方 ftp 正在发出 ftp 中断的重要消息,而不是返回不同的结果,所以我在日志/抑制中没有任何内容。我本可以只压制我自己的自定义每个项目继承的消息。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-10-18
        • 1970-01-01
        • 1970-01-01
        • 2011-12-29
        • 2018-11-11
        相关资源
        最近更新 更多