如何处理API方法抛出的基础异常?

6

假设我被迫使用一个库API方法,该方法会抛出某种不明确的基本异常;例如Java中的“throws Exception”。假设我没有修改库源代码的选项,每次从自己的方法中调用API方法时都必须处理基本异常。为了提供一些背景,如果没有干预,我的代码可能看起来像这样:

public void myMethod() throws Exception { // I don't want to do this.
    someAPIObject.theirMethod(); // API method throwing base exception.
}

这里可能是我调用的API方法:

public void theirMethod() throws Exception { // This is my problem.
    // Does stuff that could cause problems, but is
    // lazy and implicitly throws base exception.
    // Now it's my problem!
}

我的问题是:我该如何处理在我的方法中抛出的基本异常?我认为保留所有原始异常信息并传播比基础异常更有用的内容对我最有利。例如,我考虑捕获和存储基本异常在我的异常类型中,并抛出该异常:

public void myMethod() {
    try {
        someAPIObject.theirMethod(); // API method throwing base exception.
    } catch (Exception e) {
        throw new MySpecificException(e); // Re-throw my own exception.
    }
}

我不是在寻求意见,而是寻找一些确实且简单明了的证据(优点),证明为什么一个特定的解决方案是一个好的解决方案,以及任何注意事项(缺点)。我的重点是Java,但我也很想了解一般概念或“最佳实践”。


4
我很失望这个问题被投了关闭票。这是软件开发中一个非常真实的问题。虽然没有一个正确的答案,但是有一些客观的解决方案,并且可以提供引用原因。 - VGR
它是抛出Exception本身还是某个未知的子类?如果是前者,instanceof对你没有任何作用。 - ssube
@ssube 在这种情况下,是的,它本身会抛出异常。我进行了一些澄清。 - Arcensoth
如果你很幸运的话,被抛出的Exception可能只是某个内部库抛出的其他异常的包装器。cgetCause()会返回任何内容吗?我已经从太多应该知道得更好的公司看到了这种情况。祝好运…… - Kevin Krumwiede
2个回答

3
这个问题无疑会被关闭为“范围过大”或“基于观点”,但在它被关闭之前,我会提供我的意见。
你的第二个代码示例应该(几乎总是)是最好的选择:
public void myMethod() {
    try {
         someAPIObject.theirMethod(); // API method throwing base exception.
    } catch (Exception e) {
        throw new MySpecificException(e); // Re-throw my own exception.
    }
}

我的主要原因是我不想要一个泄漏的抽象。例如,假设我有一些用于访问用户的存储库,其接口如下:
public interface UserRepository {
    public User byId(UserId id);
}

我将这个实现放在了一个MySql数据库中,因此有以下具体类:

public class MySqlUserRepository implements UserRepository {
    public User byId(UserId id);
}

在这个类中,我需要处理JDBC异常。如果我让它们通过接口传播,就像这样:

public interface UserRepository {
    public User byId(UserId id) throws SqlException;
}

现在我的代码的客户端知道它正在后台使用JDBC。如果我像你一样进行包装,那么底层数据存储将完全封装,这是首先具有抽象的要点之一。如果我提供一个使用其他数据存储(例如Redis)的实现,则SqlException不再有意义,但我需要更新合同以使方法抛出特定数据存储可能引发的异常。


你的回答非常好地概括了为什么这个问题不是基于观点的。 - Kevin Krumwiede
关于泄漏的抽象,这是一个很好的观点。我相信我不想让基本异常从API传播到我的方法之外。 - Arcensoth

2
假设您已经知道API可能抛出的完整异常子类列表,那么您最好使用运行时检查“破解”异常类型,并将该方法包装到自己的包装器中以抛出特定的异常。
这里有一个例子:假设API可以抛出三个特定的 Exception 子类 - 分别是 ExceptionOne ExceptionTwo ExceptionThree 。您可以构建一个如下的包装器:
class SomeApiWrapper {
    public void myMethod() throws ExceptionOne, ExceptionTwo, ExceptionThree {
        try {
            someAPIObject.theirMethod();
        } catch (Exception e) {
            crackException(e);
        }
    }
    private static void crackException(Exception e) throws ExceptionOne, ExceptionTwo, ExceptionThree {
        if (e instanceof ExceptionOne) throw (ExceptionOne)e;
        if (e instanceof ExceptionTwo) throw (ExceptionTwo)e;
        if (e instanceof ExceptionThree) throw (ExceptionThree)e;
        throw new RuntimeException("Caught an exception of unexpected type", e);
    }
}

您的API的用户将不必捕获Exception(这是一件非常糟糕的事情),并保留原始异常中嵌入的所有信息。

我不知道异常子类,这真正是问题的关键所在。也许我在原帖中没有表述清楚。不过,为了以后参考,我会记住这个技巧的。 - Arcensoth
@Arcensoth 如果您不知道异常的前提条件,但是您可以访问已编译的JAR文件,那么您可以编写一个小程序来查找程序中定义的所有Exception子类,并从那里开始解决问题。我还建议尝试联系您的库开发人员,看看他们是否能够提供任何有关此问题的见解。 - Sergey Kalinichenko

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接