如何在Java中发现潜在的未检查异常?

如何在Java中发现潜在的未检查异常?,java,exception,exception-handling,static-analysis,software-quality,Java,Exception,Exception Handling,Static Analysis,Software Quality,根据Java规范,Java编译器根据“throw”语句和方法签名自动验证是否捕获了所有已检查的异常,并忽略未检查的异常 然而,有时开发人员发现可以抛出哪些未经检查的异常会很有用,例如,在开发人员倾向于期望出现已检查的异常(如Long.parseLong)的情况下,某些第三方代码可能会抛出未经检查的异常。或者,开发人员可能会抛出一个未检查的异常作为未来已检查异常的占位符,并忘记替换它 在这些示例中,理论上可以找到这些未捕获的未检查异常。在第一种情况下,Long.parseLong的签名表示它抛出N

根据Java规范,Java编译器根据“throw”语句和方法签名自动验证是否捕获了所有已检查的异常,并忽略未检查的异常

然而,有时开发人员发现可以抛出哪些未经检查的异常会很有用,例如,在开发人员倾向于期望出现已检查的异常(如Long.parseLong)的情况下,某些第三方代码可能会抛出未经检查的异常。或者,开发人员可能会抛出一个未检查的异常作为未来已检查异常的占位符,并忘记替换它

在这些示例中,理论上可以找到这些未捕获的未检查异常。在第一种情况下,Long.parseLong的签名表示它抛出NumberFormatException,在第二种情况下,源代码可用,因此编译器知道抛出了哪些未检查的异常

我的问题是:是否有一个工具可以报告这些案例?或者是一种让Java编译器暂时处理未检查异常的方法,即检查异常?这对于手动验证和修复可能导致整个线程或应用程序在运行时崩溃的潜在错误非常有用

编辑:在回答了一些问题后,我必须强调,我的目标不是找到系统中可能出现的未检查异常的详尽列表,而是找到未检查异常导致的潜在错误。我认为可以归结为两种情况:

  • 一个方法的签名表明它抛出了一个未检查的异常,而调用方没有捕获它
  • 方法体抛出显式未检查的异常,调用方无法捕获它们

  • 无法获取可能未检查异常的列表。由于它们不是在任何地方声明的(它们是动态创建的),如果没有非常特定的代码分析器工具,这是不可能的——即使这样,它也可能无法从编译的库类中捕获一些

    <> P>对于棘手的事情进行预测的例子,考虑分配内存的任何东西都会抛出内存异常,甚至可以反例实例化异常,这几乎不可能用任何静态分析工具找到。
    如果你真的有妄想症,你可能会捕捉到
    RuntimeException
    ,这应该会得到你想要处理的所有未检查的异常——这不是一种推荐的策略,但可以防止你的程序因未知/看不见的未来错误而失败。

    在许多情况下不可能找到未检查的异常,或者,您可能会得到一长串可能发生的异常

    什么时候不可能找到未检查的异常?假设您想要调用接口的方法。一个实现可能抛出某些未经检查的异常,其他实现则不会。编译器无法知道,因为它只在运行时才知道


    为什么会有一长串可能的例外情况?好的,几乎每个方法都可能抛出
    NullPointerException
    ,以防您提供未显式检查的
    null
    参数。如果选中它们,则可能会出现
    IllegalArgumentException
    。此外,此方法调用的每个方法也可能抛出不同的未检查异常,这些异常必须添加到列表中。您可以随时遇到
    ClassNotFoundError
    OutOfMemoryError
    ,也必须将其添加到该列表中…

    图像一个简单的方法,如

    public Foo bar(String input) {
      return new Foo(input.charAt(0));
    }
    
    仅此方法就可以抛出至少一个NullPointerException或一些OutOfBounds事件

    关键是:为了查看“所有”潜在异常,您的工具必须检查进入应用程序的每一行代码(编译的或源代码)

    我看不出这怎么可能是“可管理的”

    情况变得更糟,如果

    public Foo(char whatever) {
      String klazz = ... god knows 
      throw (RuntimeException) Class.forName(klazz).newInstance();
    
    当然,对于反射代码已检查的异常,这需要一些try/catch;但关键是:了解潜在异常的整个宇宙可能会在为您绘制的“相当大的地图”中结束。它有如此多的路径/分支,以至于你永远找不到其中0.001%的有趣路径

    我的问题是:是否有一个工具可以报告这些案例

    阿福,不

    或者是一种让Java编译器暂时处理未检查异常的方法,即检查异常

    阿福,不

    虽然这样的工具在理论上是可能的(有一些注释1),但在实践中它们几乎是无用的。如果您只依赖于方法的本地分析,大多数普通Java都会被标记为可能引发大量未检查的异常。其中一些可以通过一些简单的非局部分析排除,但这不足以避免过度的“误报”


    在我看来,没有实际的方法来消除所有运行时异常

    您应该做的是结合以下实践,以减少使其成为生产代码的bug数量(包括意外的运行时异常)

    • 彻底和系统的测试;e、 g.通过开发自动化单元和系统测试套件,并使用覆盖率工具帮助您识别尚未测试的代码路径

    • 使用静态分析工具,如PMD和FindBugs,可以检测特定类别的问题。您可以通过使用诸如
      @NotNull
      之类的注释来帮助这些和类似的工具

    • 代码审查

    • 遵循良好的编码实践,尤其是在开发多线程代码时

    但是请注意,这些实践是昂贵的,并且它们不能消除所有的bug


    其他一些答案似乎是建议您捕获所有异常(例如
    Exceptionvar supertypeEdges = Common.universe().edgesTaggedWithAny(XCSG.Supertype)
    var errors = supertypeEdges.reverse(Common.typeSelect("java.lang", "Error"))
    var uncheckedExceptions = supertypeEdges.reverse(Common.typeSelect("java.lang", "RuntimeException"))
    show(errors.union(uncheckedExceptions))
    
    public class Analysis {
    
        // execute show(Analysis.run()) on the Atlas shell
        public static Q run(){
            Q supertypeEdges = Common.universe().edgesTaggedWithAny(XCSG.Supertype);
            Q errors = supertypeEdges.reverse(Common.typeSelect("java.lang", "Error"));
            Q uncheckedExceptions = supertypeEdges.reverse(Common.typeSelect("java.lang", "RuntimeException"));
            Q typeOfEdges = Common.universe().edgesTaggedWithAny(XCSG.TypeOf);
            Q thrownUncheckedThrowables = typeOfEdges.predecessors(errors.union(uncheckedExceptions)).nodesTaggedWithAny(XCSG.ThrownValue);
    
            AtlasSet<Node> uncaughtThrownUncheckedThrowables = new AtlasHashSet<Node>();
            for(Node thrownUncheckedThrowable : thrownUncheckedThrowables.eval().nodes()){
                if(ThrowableAnalysis.findCatchForThrows(Common.toQ(thrownUncheckedThrowable)).eval().nodes().isEmpty()){
                    uncaughtThrownUncheckedThrowables.add(thrownUncheckedThrowable);
                }
            }
    
            Q uncaughtThrownUncheckedThrowableMethods = Common.toQ(uncaughtThrownUncheckedThrowables).containers().nodesTaggedWithAny(XCSG.Method);
            Q callEdges = Common.universe().edgesTaggedWithAny(XCSG.Call);
            Q rootMethods = callEdges.reverse(uncaughtThrownUncheckedThrowableMethods).roots();
            Q callChainToUncaughtThrowables = callEdges.between(rootMethods, uncaughtThrownUncheckedThrowableMethods);
            return callChainToUncaughtThrowables.union(Common.toQ(uncaughtThrownUncheckedThrowables));
        }
    
    }
    
    public class Test {
    
        public static void main(String[] args) {
            foo();
        }
    
        private static void foo(){
            throw new Pig("Pigs can fly!");
        }
    
        public static class Pig extends RuntimeException {
            public Pig(String message){
                super(message);
            }
        }
    
    }