当前置方法未被调用时,应抛出什么异常?

6
我有一个方法getUser,它从数据库中检索用户。该方法要求您通过userExists(String username)方法验证用户是否实际存在。
如果调用getUser方法时用户不存在,我想抛出未经检查的异常,但是哪种异常在这里最合适呢?我考虑过IllegalArgumentException,但感觉不完全正确,因为某些输入在某些情况下可能是可以的,而在其他情况下则不可以,它们并不严格“非法”。您有什么建议吗?

但是如果在调用userExists和getUser之间的时间内删除了用户,怎么办?您能保证这绝不可能是一种竞态条件吗? - hlovdal
@hlovdal,然后我们会为那个异常抛出另一个异常。 - Pacerier
3个回答

13

对我来说,IllegalArgumentException表示参数不合法且总是不合法的。 我会使用IllegalStateException异常来表示要检查用户无效的对象状态。

但是,如果您有足够具体的异常,可以创建自己的异常。

public class UsernameNotCheckedException extends IllegalStateException {
    public UsernameNotCheckedException(String message) {
        super(message);
    }
}

这可能会使调试变得更容易。

NumberFormatExceptionIllegalArgumentException 的一个子类。如果你尝试解析数字 12QW4,它将导致抛出 NumberFormatException,并且无法后续使其成为有效的参数。即它与任何状态无关。

IllegalStateException 的 Javadoc 描述如下:

表示方法在不合适或不适当的时间被调用。换句话说,Java 环境或 Java 应用程序未处于请求操作的适当状态。


2
比我的回答更好、更完整,1+。我会删除我的回答。 - Hovercraft Full Of Eels
2
添加了您关于使调试更容易的评论。;) - Peter Lawrey
1
不同意!根据JDK javadoc,IllegalStateException表示“在非法或不适当的时间调用方法”。换句话说,调用方法的是对象的状态,而不是参数不正确。选择其他异常会更好。 - Bohemian
我同意Bohemian的观点。如果提供的用户名无效,则它是一个无效参数,应该抛出IllegalArgumentException异常。 - Jesse Barnum
@Jesse,用户名没有问题。可以使用不同的方法调用用户名,这不会改变String的状态,因为它是不可变的。只有被调用的对象的状态意味着该方法在该时间不能使用该用户名进行调用。 - Peter Lawrey
显示剩余5条评论

3

IllegalStateException不是正确的选择。 IllegalStateException指的是调用其方法的对象的状态-即调用它的this的状态不正确。在您的情况下,this(即数据库服务器)没有问题,而是“用户”不正确。

IllegalArgumentException是正确的选择-是“用户”不正确,而不是服务器。

另一个有效的选项是抛出自己的特定于域的异常,例如UnknownUserException,但如果此情况“罕见”或意外且无法恢复,则可以使用IllegalArgumentException


1

我不会抛出任何异常,只会简单地返回null。或者我会抛出一个名为UserDoesNotExistException的功能性异常。

这是我的理由:IllegalStateException用于当用户调用某个方法时该对象的状态不允许时使用。在这里,导致异常的不是对象的状态,而是用户在数据库中不存在。

您可能会认为用户应该先调用userExists,甚至可以使用实例字段记住使用此参数调用它的事实,以便getUser可以抛出IllegalStateException,而无需进入数据库。

但是,这里的问题是,在之前调用userExists可能并没有任何帮助:它将执行查询以检查用户是否存在,然后getUser将执行第二个查询,并且无法保证找到该用户,因为另一个事务可能已将其删除。


我从 OP 那里得到的印象是,如果 userExists() 返回 true,则 getUser() 保证返回一个结果。在这种情况下,以非存在的用户名调用 getUser() 显然是一个编程错误,此时未经检查的异常是最好的选择。返回 null 只会不可避免地导致空指针异常,而该异常将无法清晰地指出发生错误的位置。 - Jesse Barnum
我有相反的印象,因为术语“数据库”让我认为用户来自共享的、并发访问的数据库而不是一个私有的锁定数据结构。JPA的EntityManager.find方法(在我理解中)执行与getUser相同的操作,返回null。 - JB Nizet
应用层可以防止用户名被更改或删除,这将保证userExists()方法的真实结果总是从getUser()得到非空结果。在EntityManager.find()的情况下,抛出未经检查的异常是没有意义的,因为在该层中,它对业务逻辑没有了解,并且查询不存在的记录可能是完全有效的(这正是userExists()方法中发生的情况)。 - Jesse Barnum
但是你的观点很有道理,如果在调用userExists()和getUser()之间可以更改或删除用户名,则抛出未经检查的异常不是正确的做法。在这种情况下,我宁愿抛出已检查的异常而不是返回null。 - Jesse Barnum
我同意。但是我们没有足够的信息来知道并做出正确的决定。 - JB Nizet

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