在捕获异常后返回null是不良设计吗?

30

我经常遇到同样的问题,当一个有返回值的函数捕获了异常时,我不知道该返回什么。下面的代码片段说明了我的问题。

public Object getObject(){
  try{
    ...
    return object;
  }
  catch(Exception e){
    //I have to return something here but what??
    return null; // is this a bad design??
  }
}

我的问题是:

  • return null 是不好的设计吗?
  • 如果是的话,什么被认为是更干净的解决方案?

谢谢。


我假设你已经知道了,但是catch(Exception e)几乎总是一个不好的主意。如果try{}块中的代码编写得很好,你应该对它将抛出哪个异常有一个很好的想法,然后你可以只捕获那个异常。 - Tyler
13个回答

52

如果你真的无法处理异常,就不要捕获它。记录日志并不算是处理错误。最好通过抛出异常将其上抛到可以处理的人手中。

如果你必须返回一个值,并且 null 是唯一明智的选择,那么没有什么问题。只需记录下来,并向用户清楚地说明应该做什么。编写一个单元测试,演示抛出异常,这样接替你的开发人员就能看到接受的惯用法是什么。它还可以测试确保你的代码在应该抛出异常时确实抛出了异常。


19
“而记录日志并不被视为处理错误。” -- 我希望每个人都知道... - Dan Soap

29
我经常遇到同一个问题,当在带有非void返回值的函数中捕获异常时,我不知道应该返回什么。
如果您不知道要返回什么,那么就意味着您不知道如何处理异常。在这种情况下,请重新抛出它。请不要默默地吞下它。而且请不要返回null,您不想强制代码的调用者编写以下内容:
Foo foo = bar.getFoo();
if (foo != null) {
    // do something with foo
} 

这是我个人的看法,这种设计很糟糕,我个人讨厌不得不写空值检查(很多时候,null被用在应该抛出异常的地方)。
所以,像我说的那样,在方法中添加一个throws子句,要么完全删除try/catch块,要么保留try/catch块(如果有必要,例如需要处理多个异常),并将异常原样重新抛出或包装在自定义异常中。
相关问题

+1 对于个人仇恨的加分。 - BalusC

8
首要的是,我不喜欢返回null。这是用户必须明确记住处理为特殊情况(除非他们期望一个null - 这是否有文档记录)。如果他们幸运的话,他们会立即解除引用并遇到错误。如果他们不幸,他们将把它放在集合中,并在以后遇到同样的问题。
我认为你有两个选择:
1. 抛出异常。这样客户端就必须以某种方式处理它(出于这个原因,我要么记录它并/或使其被检查)。缺点是异常很慢,不应该用于控制流程,所以我只在特殊情况下使用它(双关语)
2. 您可以使用NullObject模式。
我遵循一种编码风格,很少返回null。如果/当我这样做时,那是明确记录的,因此客户端可以进行适当处理。

NullObject几乎总是最好的选择。如果返回null是一个问题,那就不要返回null。 - blank
+1 for NullObject模式 - 这可能是处理“null”情况的最佳方式 - 当然,除非人们期望为null! :) - aperkins
我很好奇为什么有人给这个点了踩。有任何线索吗? - Brian Agnew

6
异常表示异常情况。假设您的代码应该返回一个对象,但在这个过程中出了问题(网络错误、内存不足等),因此您不应该只返回null而不加处理。
然而,在许多情况下,您可以在文档中看到当出现某些条件时,方法会返回null。该类的客户端可以依靠此行为并处理返回的null,这没有什么不好的地方。请注意,在第二个使用示例中,这不是异常情况 - 该类被设计为在特定条件下返回null - 因此这样做是完全可以的(但一定要记录此意图行为)。
因此,归根结底,这取决于您是否无法返回对象,因为有些异常阻止了您的路线,还是您根本没有对象可返回,这是绝对可以的。

4
我喜欢那些建议抛出异常的回复,但这意味着您已经将异常处理设计到软件架构中。
错误处理通常包括三个部分:检测、报告和恢复。根据我的经验,错误可以分为不同的严重程度类别(以下是缩略列表):
- 仅用于调试的日志记录 - 暂停正在进行的操作并向用户报告,等待响应以继续。 - 放弃并通过道歉对话框终止程序。
应该将您的错误分类,并尽可能通用和一致地处理。如果每次编写新代码时都必须考虑如何处理每个错误,那么您的软件就没有有效的错误处理策略。我喜欢有一个报告功能,如果继续取决于用户的选择,则启动用户交互。
因此,是否返回null(如果我曾经看到过一个老掉牙的模式)取决于记录错误的函数是什么,如果函数失败并返回null,调用者可以/必须做什么以及错误的严重程度是否需要额外的处理。

2

这是你的代码,而且并不是一个坏的解决方案。但是如果你要分享你的代码,就不应该使用它,因为它可能会抛出意外的异常(例如空指针异常)。

当然,你可以使用

public Object getObject() throws Exception {}

这可以为父函数提供可用信息,并警告可能发生的问题。


2

最终,异常应该由控制器捕获。
将 <null> 传递给控制器没有意义
最好将原始异常抛出/返回到堆栈上。


1

基本上我会赞同Duffymo的看法,稍微加上一点:

就像他所说的,如果你的函数调用者不能处理异常并恢复,那么不要捕获异常。让更高级别的函数来捕获异常。

如果调用者需要采取某些措施但应该适当地自己终止,只需重新抛出异常。例如:

SomeObject doStuff()
  throws PanicAbortException
{
  try
  {
    int x=functionThatMightThrowException();
    ... whatever ...
    return y;
  }
  catch (PanicAbortException panic)
  {
    cleanUpMess();
    throw panic; // <-- rethrow the exception
  }
}

你也可以重新打包异常,例如...

catch (PanicAbortException panic)
{
  throw new MoreGenericException("In function xyz: "+panic.getMessage());
}

我在想是否应该总是进行清理,那么最好将其放在finally块中,仍然不要捕获和重新抛出。而且我认为更通用的异常在这里并没有添加太多新信息。 - duffymo
在我的示例中,我假设“cleanup”意味着某种异常后的清理,而不是无论是否抛出异常都必须执行的一般清理。这可能意味着记录错误、重置计数器、关闭文件等操作。 - Jay
我想我的有关重新打包错误的段落太过简略,无法为讨论增添太多内容。我的意思是,我经常有一个函数捕获几个特定的异常——SQL 错误、数字格式异常等,并将它们全部重新打包成一个通用异常抛给自己的调用者。原因是不需要在上层声明和单独捕获所有特定的异常,因为它们是实现相关的,因此不是接口的一部分。 - Jay

1

正如Joshua Bloch在书 "Effective Java" 中所说,null是Java的关键字。

这个单词表示一个内存位置没有指向任何其他内存位置的指针:我认为最好将有关功能域的行为(例如:您等待一种A对象,但却收到一种B对象)与低级域的行为(例如:内存不可用)进行分开编码。

在您的例子中,我会修改代码:

public Object getObject(){
  Object objectReturned=new Object();
  try{
    /**business logic*/
  }
  catch(Exception e){
    //logging and eventual logic leaving the current ojbect (this) in a consistent state
  }
  return objectReturned;
}

缺点是在每次调用getObject()时创建一个完整的对象(然后在返回的对象不被读取或写入的情况下)。

但我更喜欢拥有相同的无用对象,而不是NullPointerException,因为有时这个异常很难修复。


1
这就是为什么很多Java代码都充斥着if (x!=null) {...}语句块。不要自己制造空指针异常。

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