Java 反对已检查异常的案例

Java 反对已检查异常的案例,java,exception,checked-exceptions,Java,Exception,Checked Exceptions,多年来,我一直无法对以下问题找到一个恰当的答案:为什么一些开发人员如此反对检查异常?我有过无数次的对话,在博客上读过一些东西,读过布鲁斯·埃克尔(Bruce Eckel)说过的话(我看到的第一个公开反对他们的人) 我目前正在编写一些新代码,并非常仔细地注意如何处理异常。我试图看到“我们不喜欢检查异常”人群的观点,但我仍然看不到 我的每一次谈话都以同一个问题结束,而这个问题没有得到回答。。。让我来设置一下: 总的来说(从Java的设计方式来看) 错误是指不应该被抓到的东西(VM对花生过敏,有人把

多年来,我一直无法对以下问题找到一个恰当的答案:为什么一些开发人员如此反对检查异常?我有过无数次的对话,在博客上读过一些东西,读过布鲁斯·埃克尔(Bruce Eckel)说过的话(我看到的第一个公开反对他们的人)

我目前正在编写一些新代码,并非常仔细地注意如何处理异常。我试图看到“我们不喜欢检查异常”人群的观点,但我仍然看不到

我的每一次谈话都以同一个问题结束,而这个问题没有得到回答。。。让我来设置一下:

总的来说(从Java的设计方式来看)

  • 错误
    是指不应该被抓到的东西(VM对花生过敏,有人把一罐花生掉在上面)
  • RuntimeException
    是针对程序员做错的事情(程序员从数组末尾走出来)
  • Exception
    (除了
    RuntimeException
    )用于程序员无法控制的事情(写入文件系统时磁盘已满,进程的文件句柄已达到限制,您无法再打开任何文件)
  • Throwable
    只是所有异常类型的父级
我听到的一个常见论点是,如果发生异常,那么开发人员要做的就是退出程序

我听到的另一个常见论点是,检查异常会使重构代码变得更加困难

对于“我要做的就是退出”论点,我说即使你退出,你也需要显示一条合理的错误消息。如果你只是在处理错误上下赌注,那么当程序在没有明确说明原因的情况下退出时,你的用户不会太高兴

对于“它使重构变得困难”的人群来说,这表明没有选择适当的抽象级别。与其声明一个方法抛出一个
IOException
,不如将
IOException
转换成一个更适合当前情况的异常

使用
catch(Exception)
(或者在某些情况下使用
catch(Throwable)
包装Main以确保程序可以正常退出,我没有任何问题,但是我总是捕获我需要的特定异常。这样做至少可以显示适当的错误消息

人们永远不会回答的问题是:

如果抛出
RuntimeException
子类而不是
Exception
子类,那么你怎么知道呢 你应该接住吗

如果答案是catch
Exception
,那么您处理程序员错误的方式与处理系统异常的方式相同。我认为这是错误的

如果您捕获了Throwable,那么您正在以相同的方式处理系统异常和VM错误(以及类似的错误)。这在我看来是错误的

如果答案是只捕获已知抛出的异常,那么如何知道抛出了哪些异常?当程序员X抛出一个新异常并忘记捕获它时会发生什么?这对我来说似乎非常危险

我想说,显示堆栈跟踪的程序是错误的。不喜欢检查异常的人不会有这种感觉吗

所以,如果你不喜欢检查异常,你能解释一下为什么不喜欢,并回答一个没有得到回答的问题吗


我不是在寻找何时使用这两种模型的建议,我要寻找的是为什么人们从
RuntimeException
扩展,因为他们不喜欢从
Exception
扩展,和/或为什么他们捕获一个异常,然后重新抛出一个
RuntimeException
,而不是向他们的方法添加抛出不喜欢检查异常的动机。

我最初同意你的观点,因为我一直支持检查异常,并开始思考为什么我不喜欢在.Net中没有检查异常。但后来我意识到我实际上不喜欢检查异常

为了回答您的问题,是的,我喜欢我的程序显示堆栈跟踪,最好是真正丑陋的堆栈跟踪。我希望应用程序爆炸成一堆可怕的、您可能希望看到的最丑陋的错误消息

原因是,如果它这样做了,我必须修复它,我必须马上修复它。我想马上知道有问题

您实际处理了多少次异常?我不是说捕获异常--我是说处理异常?写以下内容太容易了:

try {
  thirdPartyMethod();
} catch(TPException e) {
  // this should never happen
}
我知道你可以说这是一种不好的做法,“答案”是做一些例外的事情(让我猜猜,记录它?),但在现实世界中(tm),大多数程序员就是不这样做

因此,是的,如果我不必这样做,我不想捕捉异常,我希望我的程序在我出错时会轰然爆炸。无声失败是最糟糕的结果。

反对检查异常的一个论点(来自joelonsoftware.com):

理由是我认为例外并不比什么更好。 “goto’s”自20世纪60年代以来就被认为是有害的,因为它们创造了 从一个代码点到另一个代码点的突然跳转。事实上,它们是 明显比后藤的更糟:

  • 它们在源代码中是不可见的。看一段代码, 包括可能引发或不引发异常的函数,没有 查看可能引发哪些异常以及从何处引发异常的方法。这意味着 即使是仔细的代码检查也不会发现潜在的bug
  • 它们为函数创建了太多可能的退出点 代码,您确实必须考虑所有可能的代码路径 您的函数。每次调用可以引发 例外,而不是当场抓住它,你为它创造了机会 由终止函数引起的意外错误
    public [int or IOException] writeToStream(OutputStream stream) {
        [void or IOException] a= stream.write(mybytes);
        if (a instanceof IOException)
            return a;
        return mybytes.length;
    }
    
    try {
        httpconn.setRequestMethod("POST");
    } catch (ProtocolException e) {
        throw new CanNeverHappenException("oh dear!");
    }
    
    try {
      // do stuff
    } catch (AnnoyingcheckedException e) {
      throw new RuntimeException(e);
    }
    
    try {
      // do stuff
    } catch (AnnoyingCheckedException e) {
      // do nothing
    }
    
      if (f = fopen("goodluckfindingthisfile")) { ... } 
      else { // file not found ...
    
       f = fopen("goodluckfindingthisfile");
       f.read(); // BANG! 
    
    try {
      f = new FileInputStream("goodluckfindingthisfile");
    }
    catch (FileNotFoundException e) {
      // deal with it. No really, deal with it!
      ... // this is me dealing with it
    }
    
    public FileInputStream(String name)
                    throws FileNotFoundException
    
    public RowData getRowData(int row) 
    
    public RowData getRowData(int row) throws CheckedInvalidRowNumberException
    
    if (model.getRowData().getCell(0).isEmpty())
    
    "Error: could not find the file 'goodluckfindingthisfile'"
    
    "Internal error occured: IllegalArgumentException in ...."
    
    public RuntimeException(Throwable cause)
    
    try {
      overzealousAPI(thisArgumentWontWork);
    }
    catch (OverzealousCheckedException exception) {
      throw new RuntimeException(exception);  
    }
    
    catch (Exception e) { /* TODO deal with this at some point (yeah right) */}
    
    try {
        conn.close();
    } catch (SQLException ex) {
        // Do nothing
    }
    
    try {  
        // Run the update code on the Swing thread  
        SwingUtilities.invokeAndWait(() -> {  
            try {
                // Update UI value from the file system data  
                FileUtility f = new FileUtility();  
                uiComponent.setValue(f.readSomething());
            } catch (IOException e) {  
                throw new UncheckedIOException(e);
            }
        });
    } catch (InterruptedException ex) {  
        throw new IllegalStateException("Interrupted updating UI", ex);  
    } catch (InvocationTargetException ex) {
        throw new IllegalStateException("Invocation target exception updating UI", ex);
    }
    
    private void UpdateValue()  
    {  
       // Ensure the update happens on the UI thread  
       if (InvokeRequired)  
       {  
           Invoke(new MethodInvoker(UpdateValue));  
       }  
       else  
       {  
           // Update UI value from the file system data  
           FileUtility f = new FileUtility();  
           uiComponent.Value = f.ReadSomething();  
       }  
    }  
    
    @Override
    public void clear()  
    {  
       try  
       {  
           backingImplementation.clear();  
       }  
       catch (CheckedBackingImplException ex)  
       {  
           throw new IllegalStateException("Error clearing underlying list.", ex);  
       }  
    }  
    
    try {
         ... do something ...
    } catch (Throwable throwable) {
         ApplicationContext.getExceptionService().handleException("Handle this exception", throwable);
    }
    
    try{
       methodDeclaringCheckedException();
    }catch(CheckedException e){
       logger.error(e);
    }
    
    try{
        methodDeclaringCheckedException();
    }catch(CheckedException e){
        exceptionHandler.handleError(e);
    }
    
    class WidgetList extends AbstractList<Widget> {
        private static final int SIZE_OF_WIDGET = 100;
        private final RandomAccessFile file;
    
        public WidgetList(RandomAccessFile file) {
            this.file = file;
        }
    
        @Override
        public int size() {
            return (int)(file.length() / SIZE_OF_WIDGET);
        }
    
        @Override
        public Widget get(int index) {
            file.seek((long)index * SIZE_OF_WIDGET);
            byte[] data = new byte[SIZE_OF_WIDGET];
            file.read(data);
            return new Widget(data);
        }
    }
    
    @Override
    public int size() {
        try {
            return (int)(file.length() / SIZE_OF_WIDGET);
        } catch (IOException e) {
            throw new WidgetListException(e);
        }
    }
    
    public static class WidgetListException extends RuntimeException {
        public WidgetListException(Throwable cause) {
            super(cause);
        }
    }
    
    class Util {
        /**
         * Throws any {@link Throwable} without needing to declare it in the
         * method's {@code throws} clause.
         * 
         * <p>When calling, it is suggested to prepend this method by the
         * {@code throw} keyword. This tells the compiler about the control flow,
         * about reachable and unreachable code. (For example, you don't need to
         * specify a method return value when throwing an exception.) To support
         * this, this method has a return type of {@link RuntimeException},
         * although it never returns anything.
         * 
         * @param t the {@code Throwable} to throw
         * @return nothing; this method never returns normally
         * @throws Throwable that was provided to the method
         * @throws NullPointerException if {@code t} is {@code null}
         */
        public static RuntimeException sneakyThrow(Throwable t) {
            return Util.<RuntimeException>sneakyThrow1(t);
        }
    
        @SuppressWarnings("unchecked")
        private static <T extends Throwable> RuntimeException sneakyThrow1(
                Throwable t) throws T {
            throw (T)t;
        }
    }
    
    @Override
    public int size() {
        try {
            return (int)(file.length() / SIZE_OF_WIDGET);
        } catch (IOException e) {
            throw sneakyThrow(e);
        }
    }
    
    try {
        ...
    } catch (Throwable t) { // catch everything
        if (t instanceof IOException) {
            // handle it
            ...
        } else {
            // didn't want to catch this one; let it go
            throw t;
        }
    }
    
    public void writeTo(OutputStream out) throws IOException;
    
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    try {
        someWidget.writeTo(out);
    } catch (IOException e) {
        // can't happen (although we shouldn't ignore it if it does)
        throw new RuntimeException(e);
    }
    
    try {
        ...
    } catch (SomeStupidExceptionOmgWhoCares e) {
        e.printStackTrace();
    }
    
    try {
        ...
    } catch (SomethingWeird e) {
        logger.log(e);
    }