关于异常处理:如何避免java中的许多try catch块

How to avoid many try catch blocks in java

我对Java非常陌生,并且尝试使用catch块来处理异常。

这大概就是我要做的,而且必须有一个更好的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    try {
        JSONObject jsonObject = new JSONObject(jsonString);
        int aCount = jsonObject.getInt("acount");
        String devTok = jsonObject.getString("dt");
        String qURL = jsonObject.getString("qu");
        try {
            DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
            Key qKey = KeyFactory.createKey("qu", qURL);
            int dsACount = (Integer) datastore.get(qKey).getProperty(kLastKnownANumber);
            //..etc.. more try catch blocks needed
        } catch (EntityNotFoundException e) {
            e.printStackTrace();
        }
    } catch (com.google.appengine.repackaged.org.json.JSONException e) {
        e.printStackTrace();
    }

以同样的方式嵌入了更多的Try-Catch块,因此在最后,只有一块Catch块。否则应该如何处理异常,Eclipse不断要求我用一个try-catch块或"add-throws声明"包围。

有时我想捕获某些异常,例如,如果它找不到实体,我想打印"找不到实体"之类的内容,如果JSON字符串无法解析为对象,我想打印"无法解析JSON"之类的内容。

(我习惯于objective-c,其中are delegate methods for failure,或者该方法返回空值,并且您已将指针传递给[指向]nserrror对象,该对象将被"填充",是否有地方了解try catch?)


如果您所要做的就是捕获它们并打印堆栈跟踪,而不管异常类型如何,您只需将代码包装在一个大的try/catch块中。为了节省许多"捕获",可以捕获java.lang.Throwable,这是所有异常实现的接口。如果没有,您可以为每种类型的检查异常(您调用的throw代码)都有一个catch,并具体地处理它们。

Eclipse一直要求您这样做,因为如果未检测到异常或被调用方抛出,则Java代码将不会编译。

+将此评论添加到答案中(谢谢,Paul Tomblin):

在生产质量应用程序中,您将记录跟踪,添加一些逻辑,以便以正确的方式处理异常,采用备用流,和/或将其重新包装为另一个异常并抛出它,等等。所有这些都取决于您试图解决的特定问题。


异常处理的思想是,您可以在程序流中的一些点上处理错误,在这些点上您可以有意义地处理它们。不是像在C中那样检查每个函数的返回值,在大多数情况下,除了进一步传递错误之外,您不能做任何明智的事情,而是在程序的明智点安装一个try/catch块:

基本上,只要有一点你可以对错误做出有意义的反应,那么就抓住那个错误,然后把其他的一切都传下去。这样,只有在从错误中进行合理的恢复时才调用错误处理。

例如,最坏的情况是,如果有任何错误阻止程序有意义地执行,那么您可能根本就无法捕获任何内容,而只是让操作系统处理这种情况(好吧,可能只需一次尝试/捕获就可以生成一条友好的错误消息)。

示例(在C++中,对不起,我不能键入Java盲):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int main()
{
  try {
    while (masterloop()) { }
  catch (...) {
    LOG("Fatal program error, terminating!"); // nothing else we can do!
  }
}

/* lots of program logic */

void process_image()
{
  try {
    Image im = load_image_from_disk();
    /* ... */
  }
  catch (const OutOfMemoryExc & e) {
    LOG("Not enough memory to process the image.");
    return;
  }
  catch (const DataErrorExc & e) {
    LOG("Could not read the image data.");
    return;
  }
  catch (...) {
    throw; // pass everything else along
  }
}

在本例中,我们可能会尝试处理一个图像,但由于一些预期的原因(内存不足或无法读取图像)而失败。在这种情况下,我们不做任何工作就回来,让程序正常地继续运行。所有其他错误都会传播到更高的点。最重要的是,我们不需要一直丢弃带有错误检查和响应的实际图像处理功能,任何代码都可以抛出我们两个好的异常中的一个而不再担心。

道德:如果你在任何地方都尝试/抓住障碍物,那你就错了。


我知道这里有很多答案,它们很好地涵盖了如何构造try/catch块。然而,我认为困扰你的一件事是重要的…缩进和代码增长(…因为我知道这不是代码的缩进或数量,而是通过包装和转移代码而隐含的复杂性,并且在开始尝试和封闭捕获之间的时间越来越长,我不能用一个词来表达这种忧虑。

解决这个问题的方法是将代码中不同的位重构为函数。我知道这是一个简单的答案,但这是一个很好的方法,可以隔离单个任务,并使错误处理与需要它的代码相对应,而不使用嵌套的try/catch块在垂直和水平方向上填充内容。

您可以将这些方法设置为私有的,因为它们可能只用于内部使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private Integer getDatastoreACount() {
    try {
        DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
        Key qKey = KeyFactory.createKey("qu", qURL);
        return (Integer) datastore.get(qKey).getProperty(kLastKnownANumber);
        //..etc.. more try catch blocks needed
    } catch (EntityNotFoundException e) {
        // expects an Integer return, so need to deal with this
        // but for simplicity I'm just simply recycling 'e'
        throw e;
    }
}

public void parseJSON(String jsonString) {
    try {
        JSONObject jsonObject = new JSONObject(jsonString);
        int aCount = jsonObject.getInt("acount");
        String devTok = jsonObject.getString("dt");
        String qURL = jsonObject.getString("qu");
        Integer dsACount = getDatastoreACount();
        //etc etc
    } catch (com.google.appengine.repackaged.org.json.JSONException e) {
        e.printStackTrace();
    }
}


您可以在同一次尝试中捕获多个异常,例如

1
2
3
4
5
6
7
8
9
try{

  xyz;

}catch(NullPointerException npx){
  npx.getMessage();
}catch(ArrayOutOfBoundsException ax){
  ax.getMessage();
}

另外,通过在方法签名中将异常声明为throws,您可以向上传递异常。


如果你只是这样做:

1
2
3
4
5
6
7
try {
  do smth
  try {
    do smth more
    ...
  } catch (Exception1 e1) {reaction to e1}
} catch (Exception2 e2) {reaction to e2}

您可以在一个try块中完成所有操作:

1
2
3
4
5
6
7
try {
  do smth
  do smth more
  ...
}
catch (Exception1 e1) {reaction to e1}
catch (Exception2 e2) {reaction to e2}

如果只打印异常,也可以将其分解为一个catch块:

1
2
3
4
5
6
try {
  do smth
  do smth more
  ...
}
catch (Exception e) {e.printStackTrace();}

但是,如果你想做更多的事情,即使e1被抛出,也不是这样,比如:

1
2
3
4
5
6
7
8
try {
  do smth
  try {
    do smth more
    ...
  } catch (Exception1 e1) {reaction to e1}
  do smth even if e1 was thrown
} catch (Exception2 e2) {reaction to e2}

最后一个例子不能写得更短。


这里有两个基本的代码样式选择(不涉及更改方法签名)

方法1:将所有内容放在一个try catch中,并具有多个catch块,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try {
    JSONObject jsonObject = new JSONObject(jsonString);
    int aCount = jsonObject.getInt("acount");
    String devTok = jsonObject.getString("dt");
    String qURL = jsonObject.getString("qu");
    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    Key qKey = KeyFactory.createKey("qu", qURL);
    int dsACount = (Integer) datastore.get(qKey).getProperty(kLastKnownANumber);
    //..etc.. more try catch blocks needed
} catch (EntityNotFoundException e) {
    e.printStackTrace();
} catch (com.google.appengine.repackaged.org.json.JSONException e) {
    e.printStackTrace();
}

方法2:将代码分解为每个部分都有一个catch,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
String qURL = null;
try {
    JSONObject jsonObject = new JSONObject(jsonString);
    int aCount = jsonObject.getInt("acount");
    String devTok = jsonObject.getString("dt");
    String qURL = jsonObject.getString("qu");
} catch (EntityNotFoundException e) {
    e.printStackTrace();
}

try {    
    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    Key qKey = KeyFactory.createKey("qu", qURL);
    int dsACount = (Integer) datastore.get(qKey).getProperty(kLastKnownANumber);
} catch (EntityNotFoundException e) {
    e.printStackTrace();
}

方法2是推荐的方法,因为它可以明显地看出哪些行正在抛出哪些异常,并且通常将代码分割成自然的处理块。


我喜欢把调用放在静态方法后面,只是为了保持整洁。例如,这里是我的约简集JSON值调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static boolean setJsonValue(JSONObject j,String key,Object value)
{
    try
    {
        if(value instanceof Integer)
        {
            // numbers are special. We want them unquoted.
            int valueI = (Integer)value;
            j.put(key,valueI);
        }
        else
            j.put(key,value);
        return true;
    }
    catch (JSONException e)
    {
        // do nothing, it turns out
        return false;
    }
}

…然后我忽略返回值,因为我很糟糕。

在某些地方,我有一个类似的get方法,如果失败,它将返回空值。你明白了。


如果您有从异常中恢复的方法,则应使用Try/Catch块,例如,如果要检查字符串是否为有效整数,则可以编写一个方法(这是一个跛脚方法,但只是为了展示其思想):

1
2
3
4
5
6
7
8
9
public boolean isInteger(String str) {
    try {
        new Integer(str);
    }
    catch(NumberFormatException e) {
        return false;
    }
    return true;
}

如果您没有从异常中恢复的方法,您所要做的就是打印堆栈跟踪,那么建议将throws声明(如Eclipse建议的那样)添加到方法中,并让调用方处理异常(或将其抛出给调用方)。

如果您想处理一些异常并抛出其他异常,也可以这样做。


首先,从设计的角度来看,捕获和打印异常并不是一件好事。出了点问题,你的代码一直以同样的方式运行,就好像它是正确的一样。这通常不正确。所以:也许您的方法需要抛出这些异常而不是捕获它们。也许只有呼叫者才能决定如果发生这种情况会发生什么。

否则,我唯一能提供的清理代码在语法上的外观的建议就是告诉您可以编写:

1
2
3
4
5
6
7
try {
  ...
} catch (...) {
  ...
} catch (...) {
  ...
}

您还可以捕获更广泛的异常类,如Exception,只需编写一个catch块,但这是一个糟糕的设计。在Java 7中,您将能够捕获一个块中的几个异常类型。


如果您有一个代码块,其中可能会引发多个类型的异常,则可以声明两个单独的catch块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
    JSONObject jsonObject = new JSONObject(jsonString);
    int aCount = jsonObject.getInt("acount");
    String devTok = jsonObject.getString("dt");
    String qURL = jsonObject.getString("qu");

    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
    Key qKey = KeyFactory.createKey("qu", qURL);
    int dsACount = (Integer) datastore.get(qKey).getProperty(kLastKnownANumber);
} catch (EntityNotFoundException e) {
    e.printStackTrace();
} catch (com.google.appengine.repackaged.org.json.JSONException e) {
    e.printStackTrace();
}
//..etc.. as many catch blocks as needed

或者,如果你不关心异常的确切类型,你可以有一个ONYL一个catch块,它捕获EDCOX1×1(或者EDCOX1,2);我不能确切地记住Java中异常的超类是什么。

我现在要说的另一点是,您可能没有最模块化的代码。记住,有一件事做得好的代码就是好的模块化代码。如果您发现有许多嵌套的黑色(无论是try/catch块、if/else块等),您可能希望检查是否可以将一些代码提取到自己的方法中。当必须处理许多异常时,这也可能使代码看起来更好。