处理Java异常的9个最佳实践

Java的异常处理不是一个简单的话题。 初学者很难理解,甚至经验丰富的开发人员也可能花几个小时讨论何时应该抛出异?;虼硪斐!?/p>

这就是为什么大多数开发团队都有自己使用异常的规则。 如果你是一个新手,你可能会惊讶于这些规则与你之前使用过的规则完全不同。

无论规则如何不同,或多或少都遵循以下规则。 以下是帮助你入门或改进异常处理的9个最佳实践。

1.在finally块中的回收资源或使用Try-With-Resource语句

经常在try中使用资源,比如InputStream ,之后需要关闭它。 这些情况下一个常见错误是在try块结束时关闭资源。

public void doNotCloseResourceInTry() {
    FileInputStream inputStream = null;
    try {
        File file = new File("./tmp.txt");
        inputStream = new FileInputStream(file);
        // use the inputStream to read a file
        // do NOT do this
        inputStream.close();
    } catch (FileNotFoundException e) {
        log.error(e);
    } catch (IOException e) {
        log.error(e);
    }
}

如果没有抛出异常,这种方法似乎完全正常。 try块中的所有语句都将被执行,资源将被关闭。

如果有异常被抛出。 这意味着你可能无法到达try块的末尾。 因此,资源无法被关闭。

所以,你应该将所有清理代码放入finally块或使用try-with-resource语句。

使用Finally块

与try块的最后几行相比,finally块始终被执行。 这可以在成功执行try块之后或在catch块中处理异常之后发生。 因此,所有已打开的资源可以确保被清理。

public void closeResourceInFinally() {
    FileInputStream inputStream = null;
    try {
        File file = new File("./tmp.txt");
        inputStream = new FileInputStream(file);
        // use the inputStream to read a file
    } catch (FileNotFoundException e) {
        log.error(e);
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.error(e);
            }
        }
    }
}

Java 7的try-with-resource语句

另一种选择是使用try-with-resource语句

前提是资源实现了AutoCloseable接口, 大多数Java标准资源都实现了AutoCloseable接口。 你在try子句中打开资源后,它将在try块执行后自动关闭。

public void automaticallyCloseResource() {
    File file = new File("./tmp.txt");
    try (FileInputStream inputStream = new FileInputStream(file);) {
        // use the inputStream to read a file
    } catch (FileNotFoundException e) {
        log.error(e);
    } catch (IOException e) {
        log.error(e);
    }
}

2.抛出具体的异常而不是通用的异常

异常越具体越好。 请记住,不熟悉代码的同事,或者你可能在几个月后调用你的方法并处理异常。

因此,请务必尽可能多地提供信息。 这使您的API更容易理解。 您的方法的调用者将能够更好地处理异常或通过额外的检查来避免它 。

总是尝试找到最适合您的异常事件的类,例如抛出NumberFormatException而不是IllegalArgumentException 。 并避免抛出通用的异常 。

public void doNotDoThis() throws Exception {
    ...
}
public void doThis() throws NumberFormatException {
    ...
}

3.在文档中说明你的异常

只要在方法签名中指定了异常 ,就应该在Javadoc中记录它 。 这与前面的最佳实践具有相同的目的:为调用者提供尽可能多的信息,以便他可以避免或处理异常。

因此,请确保向Javadoc添加@throws声明并描述可能导致异常的情况。

/**
 * This method does something extremely useful ...
 *
 * @param input
 * @throws MyBusinessException if ... happens
 */
public void doSomething(String input) throws MyBusinessException {
    ...
}

4.使用描述性信息来定义异常类

尽可能准确地描述问题,并提供最相关的信息来理解异常事件。

别误会我的意思; 你不应该写一段文字。 但你应该用1-2个短句来解释异常的原因。 这有助于您的运营团队了解问题的严重性,还可以让您更轻松地分析任何服务事件。

如果你抛出一个特定的异常,它的类名很已经描述了具体错误。 你就无需提供大量其他信息。 一个很好的例子是NumberFormatException 。 当你的参数是一个字符串而不是整数的时候,它会被类java.lang.Long的构造函数抛出。

try {
    new Long("xyz");
} catch (NumberFormatException e) {
    log.error(e);
}

NumberFormatException类的名称已经告诉你错误的原因。 如果异常类的名称看不出原因,则需要在异常内容中提供所需的信息。

17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"

5.尽可能首先捕捉最具体的异常

大多数IDE都可以帮你完成。 当你尝试首先捕获不太具体的异常时,它们会报告代码块无法被访问。

比如,如果你首先捕获IllegalArgumentException ,则永远不会到达处理更具体的NumberFormatException的catch块,因为它是IllegalArgumentException的子类。

始终首先捕获最具体的异常类,并将不太具体的catch块添加到列表的末尾。

你可以在以下代码段中看到此类try-catch语句的示例。 第一个catch块处理所有NumberFormatException ,第二个catch块处理非NumberFormatException的IllegalArgumentException 。

public void catchMostSpecificExceptionFirst() {
    try {
        doSomething("A message");
    } catch (NumberFormatException e) {
        log.error(e);
    } catch (IllegalArgumentException e) {
        log.error(e)
    }
}

6.不要捕获Throwable

Throwable是所有异常和错误的超类。 你可以在catch子句中捕获它,但你永远不应该这样做!

如果在catch子句中捕获了Throwable ,它不仅会捕获所有异常; 它还会捕获所有错误。 JVM抛出错误以指示应用程序无法处理的严重问题。 典型的例子是OutOfMemoryError或StackOverflowError 。 两者都是由应用程序无法控制的情况引起的,无法处理。

所以,最好不要捕获Throwable,除非你完全确定你需要捕获它。

public void doNotCatchThrowable() {
    try {
        // do something
    } catch (Throwable t) {
        // don't do this!
    }
}

7.不要忽略捕捉到的异常

刚开始的时候,开发人员可能非常确定这个异常永远不会发生,他甚至加上一个着名的“这将永远不会发生”的评论。

public void doNotIgnoreExceptions() {
    try {
        // do something
    } catch (NumberFormatException e) {
        // this will never happen
    }
}

可是,请永远不要忽视异常。 你不知道代码将来会如何变化。

你至少应该写一条日志消息,告诉大家不可想象的事情刚刚发生。

public void logAnException() {
    try {
        // do something
    } catch (NumberFormatException e) {
        log.error("This should never happen: " + e);
    }
}

8.处理异常是,不要仅仅记录然后就重新抛出它

这可能是最常见的错误做法, 你可以找到许多类似的代码:

try {
    new Long("xyz");
} catch (NumberFormatException e) {
    log.error(e);
    throw e;
}

在发生异常时记录异常可能会很直观,然后重新抛出它以便调用者可以适当地处理它。 但这样做会造成错误被重复记录在日志中。

17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"
Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.(Long.java:965)
at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)

其他消息也不会添加任何信息。 如最佳实践#4中所述,异常消息应描述异常事件。 堆栈跟踪告诉您抛出异常的类,方法和行。

如果需要添加其他信息,则应捕获异常并将其包装在自定义异常中。

public void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e);
    }
}

因此,如果你捕捉了异常,请完整的处理它,而不是仅仅记录然后有抛出。 如果不需要处理,就在方法签名中抛出异常让调用者来处理。

9.保留原始异常

有时候我们捕获系统异常以后,会将其包装成自定义异常。

请确保将原始异常被保留。 Exception类提供了接受Throwable作为参数的特定构造函数方法。 如果你不传递原始异常,将丢失原始异常的堆栈跟踪和消息。

public void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e);
    }
}

总结

正如您所看到的,当您抛出或捕获异常时,您应该考虑许多不同的事情。 其中大多数都旨在提高代码的可读性或API的可用性。

异常是Java的错误处理机制。 你应该与同事一起讨论如何使用异常,以便每个人都能认同并以相同的规则使用异常。

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容

  • 在 Java 中,异常处理是个很麻烦的事情。初学者觉得它很难理解,甚至是经验丰富的开发者也要花费很长时间决定异常是...
    OSC开源社区阅读 1,110评论 3 51
  • Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言。Java 技术具有卓越的通用性、高效性、平台移植性和...
    Java小辰阅读 342评论 0 1
  • 引言 在程序运行过程中(注意是运行阶段,程序可以通过编译),如果JVM检测出一个不可能执行的操作,就会出现运行时错...
    Steven1997阅读 2,422评论 1 6
  • 今日体验:这段时间一直没休息,并且天特别的热工作量还大,感觉自己快要垮掉了,今天下班早,早点休息吧,核心:身体要适...
    房傲东阅读 46评论 0 0
  • 参加家庭能量改造课程有感 杨晓姝 2017年3月11日至12日,这两天参加李新异老师的家庭能量改造课程,收获不少,...
    杨晓姝阅读 805评论 0 0