我为我的应用程序不期望的每个条件创建了异常。UserNameNotValidException, PasswordNotCorrectException等。

然而,我被告知我不应该为这些条件创造例外。在我的UML中,那些是主要流程的异常,那么为什么它不应该是异常呢?

是否有创建异常的指导或最佳实践?


当前回答

我想说,基本上每一个原教旨主义都会导致地狱。

您当然不希望以异常驱动流结束,但是完全避免异常也是一个坏主意。你必须在两种方法之间找到平衡。我不会为每种异常情况创建异常类型。这是没有成效的。

我通常更喜欢创建两种基本类型的异常,它们在整个系统中使用:LogicalException和TechnicalException。如果需要,可以通过子类型进一步区分这些类型,但通常不是没有必要。

技术异常指的是真正意想不到的异常,比如数据库服务器宕机,到web服务的连接抛出IOException等等。

另一方面,逻辑异常用于将不太严重的错误情况传播到上层(通常是一些验证结果)。

请注意,即使是逻辑异常也不是为了定期使用来控制程序流,而是为了突出显示流何时应该真正结束的情况。在Java中使用时,这两种异常类型都是RuntimeException子类,错误处理是高度面向方面的。

因此,在登录示例中,创建类似AuthenticationException的东西并通过枚举值(如UsernameNotExisting、PasswordMismatch等)来区分具体情况可能是明智的。这样就不会产生巨大的异常层次结构,并且可以将捕获块保持在可维护的级别。您还可以轻松地使用一些通用的异常处理机制,因为您已经对异常进行了分类,并且非常清楚要向用户传播什么以及如何传播。

我们的典型用法是,当用户输入无效时,在Web服务调用期间抛出LogicalException。异常被编组到SOAPFault详细信息,然后在客户机上再次被解组到异常,这将导致在某个web页面输入字段上显示验证错误,因为异常已正确映射到该字段。

这当然不是唯一的情况:您不需要点击web服务来抛出异常。你可以在任何特殊情况下自由地这样做(比如在你需要快速失败的情况下)——这都取决于你的判断。

其他回答

我个人的指导方针是:当发现当前代码块的基本假设为假时抛出异常。

例1:假设我有一个函数,它应该检查任意类,如果该类继承自List<>,则返回true。这个函数问一个问题:“这个对象是List的后代吗?”这个函数永远不会抛出异常,因为它的操作中没有灰色地带——每个单独的类要么继承了List<>,要么继承了List<>,所以答案总是“是”或“否”。

Example 2: say I have another function which examines a List<> and returns true if its length is more than 50, and false if the length is less. This function asks the question, "Does this list have more than 50 items?" But this question makes an assumption - it assumes that the object it is given is a list. If I hand it a NULL, then that assumption is false. In that case, if the function returns either true or false, then it is breaking its own rules. The function cannot return anything and claim that it answered the question correctly. So it doesn't return - it throws an exception.

这与“负载问题”逻辑谬误相当。每个函数都问一个问题。如果给出的输入使该问题成为谬误,则抛出异常。对于返回void的函数,这条线很难画出来,但底线是:如果函数对其输入的假设违反了,它应该抛出异常,而不是正常返回。

这个等式的另一方面是:如果你发现你的函数经常抛出异常,那么你可能需要改进它们的假设。

异常与返回错误代码参数应该是关于流控制的,而不是哲学(错误有多“异常”):

void f1() throws ExceptionType1, ExceptionType2 {}

void catchFunction() {
  try{
    while(someCondition){
      try{
        f1(); 
      }catch(ExceptionType2 e2){
        //do something, don't break the loop
      }
    }
  }catch(ExceptionType1 e1){
    //break the loop, do something else
  }

}

异常是一种代价高昂的效果,例如,如果您有一个用户提供了无效的密码,那么通常更好的方法是返回一个失败标志,或其他一些无效的指示。

这是由于异常处理的方式,真正的错误输入和唯一的关键停止项应该是异常,而不是失败的登录信息。

我有三种情况。

Bad or missing input should not be an exception. Use both client side js and server side regex to detect, set attributes and forward back to the same page with messages. The AppException. This is usually an exception that you detect and throw with in your code. In other words these are ones you expect (the file does not exist). Log it, set the message, and forward back to the general error page. This page usually has a bit of info about what happened. The unexpected Exception. These are the ones you don't know about. Log it with details and forward them to a general error page.

希望这能有所帮助

To my mind, the fundamental question should be whether one would expect that the caller would want to continue normal program flow if a condition occurs. If you don't know, either have separate doSomething and trySomething methods, where the former returns an error and the latter does not, or have a routine that accepts a parameter to indicate whether an exception should be thrown if it fails). Consider a class to send commands to a remote system and report responses. Certain commands (e.g. restart) will cause the remote system to send a response but then be non-responsive for a certain length of time. It is thus useful to be able to send a "ping" command and find out whether the remote system responds in a reasonable length of time without having to throw an exception if it doesn't (the caller would probably expect that the first few "ping" attempts would fail, but one would eventually work). On the other hand, if one has a sequence of commands like:

  exchange_command("open tempfile");
  exchange_command("write tempfile data {whatever}");
  exchange_command("write tempfile data {whatever}");
  exchange_command("write tempfile data {whatever}");
  exchange_command("write tempfile data {whatever}");
  exchange_command("close tempfile");
  exchange_command("copy tempfile to realfile");

人们会希望任何操作的失败都能中止整个序列。虽然可以检查每个操作以确保操作成功,但如果命令失败,让exchange_command()例程抛出异常会更有帮助。

实际上,在上面的场景中,有一个参数来选择一些失败处理模式可能会有所帮助:从不抛出异常,仅为通信错误抛出异常,或者在命令没有返回“成功”指示的任何情况下抛出异常。