何时抛出exception?
我有我的应用程序不期望的每个条件创build的例外。 UserNameNotValidException
, PasswordNotCorrectException
等
不过,有人告诉我,我不应该为这些情况创build例外。 在我的UML中,主stream是exception的,为什么它不是一个例外呢?
任何创build例外的指导或最佳实践?
我的个人指南是:当发现当前代码块的基本假设是错误时抛出exception。
例1:说我有一个函数,应该检查一个任意类,如果该类inheritance自List <>,则返回true。 这个函数提出这个问题:“这个对象是List的后代吗? 这个函数不应该抛出一个exception,因为它的操作中没有灰色区域 – 每一个类都有或者没有从Listinheritance,所以答案总是“是”或“否”。
例2:假设我有另外一个检查List <>的函数,如果它的长度大于50则返回true,如果长度小于则返回false。 这个函数提出这个问题:“这个列表是否有超过50个项目?” 但是这个问题做了一个假设 – 它假定它给出的对象是一个列表。 如果我递给它一个NULL,那么这个假设是错误的。 在这种情况下,如果函数返回true 或者 false,那么它正在违反自己的规则。 该函数不能返回任何东西,并声称它正确地回答了这个问题。 所以它不会返回 – 它会抛出一个exception。
这与“加载问题”的逻辑谬误是可比的。 每个function都提出一个问题。 如果给出的input使得这个问题成为一个谬论,那就抛出一个exception。 这条线很难用返回void的函数来绘制,但底线是:如果函数关于其input的假设被违反了,它应该抛出exception而不是正常返回。
这个等式的另一面是:如果你发现你的函数经常抛出exception,那么你可能需要改进他们的假设。
因为他们是通常会发生的事情。 例外不是控制stream程机制。 用户经常得到密码错误,这不是一个例外情况。 例外应该是一个真正罕见的事情, UserHasDiedAtKeyboard
types的情况。
我的小指导深受伟大的书“代码完整”的影响:
- 使用exception来通知不应该被忽略的事情。
- 如果错误可以在本地处理,则不要使用exception
- 确保例外与您的其他例程处于相同的抽象级别。
- 应该为例外情况保留例外 。
如果用户名无效或密码不正确,则不是例外。 在正常的操作stream程中,这些是你应该预料到的。 例外情况是不属于正常程序操作的部分,而且非常罕见。
编辑:我不喜欢使用exception,因为你不能通过查看调用告诉如果一个方法抛出一个exception。 这就是为什么只有在不能以体面的方式处理这种情况的时候,才会使用例外(认为“内存不足”或者“电脑着火”)。
一个经验法则是在通常无法预测的情况下使用例外。 例如数据库连接,磁盘上丢失的文件等。对于您可以预测的情况,即用户尝试使用错误的密码login,您应该使用返回布尔值的函数,并知道如何正确处理这种情况。 您不想因为有人输错密码而抛出exception而突然终止执行。
其他人build议,不应该使用exception,因为如果用户input错误,则在正常stream程中预期不良login。 我不同意,我不理解。 将其与打开文件进行比较..如果文件不存在或者由于某种原因不可用,那么框架将引发exception。 使用上面的逻辑这是微软的一个错误。 他们应该返回一个错误代码。 parsing,networking请求等,等等。
我不认为一个正常stream量的login部分不好,这是例外。 通常用户input正确的密码,并且文件确实存在。 例外情况是例外情况,使用例外情况是完全正常的。 通过在堆栈中传递n个级别的返回值来复杂你的代码是一种浪费能量,并会导致代码混乱。 做最简单的事情,可能工作。 不要过早地通过使用错误代码进行优化,特殊的定义很less发生,除非抛出exception,否则exception不会花费任何代价。
例外是一个有点昂贵的影响,例如,如果你有一个用户提供了一个无效的密码,通常是一个更好的主意,传回一个失败标志,或其他一些指标,它是无效的。
这是由于处理exception的方式,真正的错误input以及唯一的关键停止项目应该是例外,而不是失败的login信息。
我想你应该抛出一个例外,当你没有办法摆脱你目前的状态。 例如,如果你正在分配内存,并没有任何分配。 在你提到的情况下,你可以清楚地从这些状态中恢复,并可以将错误代码返回给你的调用者。
我会说在什么时候使用exception没有硬性规定。 但是有很好的理由使用或不使用它们:
使用例外的理由:
- 常见的代码stream程更清晰
- 可以作为一个对象返回复杂的错误信息(虽然这也可以通过引用传递的错误“out”参数来实现)
- 语言通常提供一些工具,用于在发生exception时pipe理整洁的清理(尝试/最终在Java中,在C#中使用,在C ++中使用RAII)
- 如果没有抛出exception,执行有时可能比检查返回码更快
- 在Java中,检查exception必须被声明或捕获(尽pipe这可能是一个原因)
不使用例外的理由:
- 有时候,如果error handling很简单,那就太过分了
- 如果没有logging或声明exception,则可能通过调用代码来取消exception,这可能比调用代码忽略返回代码更糟糕(应用程序出口与无提示失败 – 更糟糕的可能取决于场景)
- 在C ++中,使用exception的代码必须是exception安全的(即使不抛出或捕获它们,但是间接调用抛出函数)
- 在C ++中,很难判断函数何时可能抛出,因此如果使用它们,你必须对exception安全性有偏见
- 与检查退货标志相比,抛出和捕捉exception通常要贵得多
一般来说,我更倾向于在Java中使用exception,而不是在C ++或C#中,因为我认为,声明或不声明的exception基本上是函数的正式接口的一部分,因为更改exception保证可能打破调用代码。 在Java IMO中使用它们的最大优点是你知道你的调用者必须处理这个exception,这就提高了正确行为的机会。
正因为如此,在任何语言中,我总会从一个普通的类中派生出一层代码或API中的所有exception,以便调用代码始终可以保证捕获所有exception。 另外,我认为在编写API或库时抛出特定于实现的exception类是不好的(例如,从低层包装exception,使得调用者接收到的exception在接口上下文中是可以理解的)。
请注意,Java区分了常规和运行时exception,因为后者不需要声明。 当你知道错误是程序中的一个错误的结果时,我只会使用运行时exception类。
exception类就像“正常”类。 当“是”不同types的对象时,您创build一个新类,并具有不同的字段和不同的操作。
作为一个经验法则,您应该尝试在例外数量和例外的粒度之间取得平衡。 如果你的方法抛出了超过4-5个不同的exception,你可能可以将它们中的一些合并成更多的“一般”exception(例如在你的情况下为“AuthenticationFailedException”),并使用exception消息来详细说明出了什么问题。 除非你的代码以不同的方式处理它们,否则不需要创build许多exception类。 如果是这样,你可能应该返回一个枚举出现的错误。 这种方式有点清洁。
如果代码在一个循环内部运行,可能会导致一个又一个的exception,那么抛出exception并不是一件好事,因为它们对于大的N来说非常慢。但是,如果性能不是一个问题。 只要确保你有一个基本的exception,他们都inheritance,称为BaseException或类似的东西。 BaseExceptioninheritanceSystem.Exception,但是所有的exceptioninheritance了BaseException。 你甚至可以有一个exceptiontypes的树来分组相似的types,但这可能会或可能不会矫枉过正。
所以,简单的回答是,如果它不会导致显着的性能损失(除非抛出很多例外情况,否则不应该这样做),那就继续吧。
我同意japollock在那里的方式 – 抛出一个接受,当你不确定一个操作的结果。 调用API,访问文件系统,数据库调用等。任何时候,你都在移动你的编程语言的“界限”。
我想补充,随意抛出一个标准的例外。 除非你打算做一些“不同的”(忽略,电子邮件,日志,显示twitter鲸鱼图片等),那么不要打扰自定义exception。
一般而言,您希望针对您的应用程序中可能发生的任何exception事件抛出exception,
在你的例子中,这两个exception看起来像你通过密码/用户名validation调用它们。 在这种情况下,可以认为有人会输错用户名/密码并不是特例。
它们是UML主stream的“例外”,但在处理过程中更多的是“分支”。
如果你试图访问你的passwd文件或数据库,并且不能,这将是一个例外情况,并将保证抛出一个exception。
首先,如果您的API的用户对特定的细粒度故障不感兴趣,那么对它们有特定的例外是没有任何价值的。
由于通常不可能知道对用户有用的东西,所以更好的方法是具有特定的exception,但要确保它们inheritance自一个普通的类(例如,std :: exception或C ++中的派生类)。 这可以让你的客户如果他们select,捕捉特定的例外,或者如果他们不在乎更普遍的例外。
例外是针对exception行为,错误,失败等事件。 function行为,用户错误等应由程序逻辑来处理。 由于帐户或密码错误是login例程中逻辑stream程的预期部分,因此它应该能够毫无例外地处理这些情况。
我有使用例外的哲学问题。 基本上,你正在等待一个特定的场景发生,但是不是明确地处理它,而是把问题推到“别处”。 而“别处”的地方可以是任何人的猜测。
我想说,通常每一个原教旨主义都会导致地狱。
你当然不希望以exception驱动stream程结束,但完全避免exception也是一个坏主意。 你必须在两种方法之间find平衡。 我不会做的是为每个特殊情况创build一个exceptiontypes。 这不是生产力。
我通常更喜欢的是创build两个基本types的exception,在整个系统中使用: LogicalException和TechnicalException 。 如果需要,这些可以通过亚型进一步区分,但通常不是必需的。
技术上的exception表示真正意想不到的exception,如数据库服务器closures,连接到Web服务抛出IOException等等。
另一方面,逻辑exception被用来将较不严重的错误情况传播到上层(通常是一些validation结果)。
请注意,即使是逻辑exception也不是定期用来控制程序stream程,而是强调当stream程真正结束时的情况。 在Java中使用时,这两种exceptiontypes都是RuntimeException子类,error handling是高度面向方面的。
所以在login例子中,创build类似于AuthenticationException的东西可能是明智的,并通过枚举值区分具体的情况,如UsernameNotExisting , PasswordMismatch等等。然后,你将不会有一个巨大的exception层次结构,并可以保持catch块在可维护的水平。 你也可以很容易地使用一些通用的exception处理机制,因为你有例外分类,并且非常清楚要传播给用户以及如何传播。
我们的典型用法是在用户input无效时在Web Service调用期间抛出LogicalException。 exception被编组到SOAPFault的细节,然后在客户端得到反编组的exception,导致在某个网页input字段上显示validation错误,因为exception有适当的映射到该字段。
这当然不是唯一的情况:你不需要打networking服务来抛出exception。 您可以在任何特殊情况下自由使用(例如在您需要快速启动的情况下) – 这完全由您自行决定。
抛出exception的经验法则很简单。 您在代码进入“无法修复的无效状态”时这样做。 如果数据受到威胁,或者您无法回避到目前为止发生的处理,则必须终止处理。 事实上你还能做什么? 你的处理逻辑最终会在别处失败。 如果你可以以某种方式恢复,那么这样做,不要抛出exception。
在你的特定情况下,如果你被迫做一些像接受取款一样愚蠢的事情,只有在检查用户/密码时,你应该抛出一个exception来通知发生了不好的事情,并防止进一步的损害。
避免抛出exception的主要原因是抛出exception涉及很多开销。
下面的文章指出的一个例外就是例外情况和错误。
错误的用户名不一定是程序错误,但用户错误…
这是.NET中的例外的一个体面的起点: http : //msdn.microsoft.com/en-us/library/ms229030(VS.80).aspx
抛出exception会导致堆栈放松,这会产生一些性能影响(被承认,现代pipe理环境已经改进了)。 仍然重复地在嵌套情况下抛出和捕获exception将是一个坏主意。
可能比这更重要的是,例外情况是针对特殊情况的。 他们不应该用于普通的控制stream程,因为这会损害你的代码的可读性。
我有三种types的条件,我抓住了。
-
不好的或缺less的input不应该是一个例外。 同时使用客户端js和服务器端正则expression式来检测,设置属性,并转发回消息的同一页面。
-
AppException。 这通常是您在代码中检测并抛出的exception。 换句话说,这些是你期望的(文件不存在)。 logging日志,设置消息,然后转回到常规错误页面。 这个页面通常有一些有关发生的事情的信息。
-
意想不到的例外。 这些是你不知道的。 logging详细信息并将其转发到一般错误页面。
希望这可以帮助
安全与您的示例相混淆:您不应该告诉攻击者存在用户名,但密码错误。 这是额外的信息,你不需要分享。 只是说“用户名或密码不正确”。
简单的答案是,每当一个操作是不可能的(因为任何一个应用程序或因为它会违反业务逻辑)。 如果一个方法被调用,并且不可能做什么方法被写入,抛出一个exception。 一个很好的例子是,如果不能使用提供的参数创build实例,构造函数总是抛出ArgumentExceptions。 另一个例子是InvalidOperationException,当由于其他成员或类的成员的状态而无法执行操作时,将引发InvalidOperationException。
在你的情况下,如果像Login(username,password)这样的方法被调用,如果用户名无效,抛出UserNameNotValidException或者PasswordNotCorrectException(如果密码不正确)是正确的。 用户不能使用提供的参数(s)login(即不可能,因为它会违反authentication),所以抛出一个exception。 虽然我可能有你的两个exceptioninheritance自ArgumentException。
话虽如此,如果你不想抛出exception,因为login失败可能是非常普遍的,一种策略是创build一个方法,返回代表不同的失败的types。 这是一个例子:
{ // class ... public LoginResult Login(string user, string password) { if (IsInvalidUser(user)) { return new UserInvalidLoginResult(user); } else if (IsInvalidPassword(user, password)) { return new PasswordInvalidLoginResult(user, password); } else { return new SuccessfulLoginResult(); } } ... } public abstract class LoginResult { public readonly string Message; protected LoginResult(string message) { this.Message = message; } } public class SuccessfulLoginResult : LoginResult { public SucccessfulLogin(string user) : base(string.Format("Login for user '{0}' was successful.", user)) { } } public class UserInvalidLoginResult : LoginResult { public UserInvalidLoginResult(string user) : base(string.Format("The username '{0}' is invalid.", user)) { } } public class PasswordInvalidLoginResult : LoginResult { public PasswordInvalidLoginResult(string password, string user) : base(string.Format("The password '{0}' for username '{0}' is invalid.", password, user)) { } }
大多数开发人员被教导避免exception,因为抛出它们造成的开销。 注重资源是件好事,但通常不会牺牲应用程序的devise。 这可能是你被告知不要抛出你的两个exception的原因。 是否使用exception通常归结为发生exception的频率。 如果这是一个相当常见的或相当可预期的结果,那么大多数开发人员会避免exception,而是由于假定的资源消耗而创build另一种表示失败的方法。
下面是一个使用Try()模式避免在刚描述的场景中使用exception的例子:
public class ValidatedLogin { public readonly string User; public readonly string Password; public ValidatedLogin(string user, string password) { if (IsInvalidUser(user)) { throw new UserInvalidException(user); } else if (IsInvalidPassword(user, password)) { throw new PasswordInvalidException(password); } this.User = user; this.Password = password; } public static bool TryCreate(string user, string password, out ValidatedLogin validatedLogin) { if (IsInvalidUser(user) || IsInvalidPassword(user, password)) { return false; } validatedLogin = new ValidatedLogin(user, password); return true; } }
在我看来,最基本的问题应该是,如果发生某种情况,是否希望调用者想要继续正常的程序stream程。 如果你不知道,或者有单独的doSomething和trySomething方法,前者返回一个错误,后者不返回,或者有一个例程接受一个参数来指示在失败时是否抛出exception)。 考虑一个类将命令发送到远程系统并报告响应。 某些命令(例如,重新启动)将导致远程系统发送响应,但是在一定的时间长度内无响应。 因此,能够发送“ping”命令并且发现远程系统是否在合理的时间长度内进行响应是有用的,如果不是,则不必抛出exception(调用者可能期望前几个“平“的尝试将失败,但最终会工作)。 另一方面,如果有一个命令序列如下:
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(“复制临时文件到实际文件”);
人们会希望任何操作的失败都会中止整个序列。 虽然可以检查每个操作以确保它成功,但如果命令失败,则exchange_command()例程将引发exception更有帮助。
实际上,在上面的场景中,有一个参数来select一些失败处理模式可能是有帮助的:从不抛出exception,仅为通信错误抛出exception,或者在命令不返回“成功“指示。
对我来说,当所需的技术或业务规则失败时,应抛出exception。 例如,如果一个汽车实体与4个轮胎arrays相关联…如果一个轮胎或更多的轮胎是空的…应该被解雇的exception“NotEnoughTiresException”,因为它可以被捕获在系统的不同级别,意思是通过日志。 此外,如果我们只是试图控制零点,并防止汽车的实例化。 我们永远也找不到问题的根源,因为轮胎本来就不应该是空的。
You may use a little bit generic exceptions for that conditions. For eg ArgumentException is meant to be used when anything goes wrong with the parameters to a method (with the exception of ArgumentNullException). Generally you would not need exceptions like LessThanZeroException, NotPrimeNumberException etc. Think of the user of your method. The number of the conditions that she will want to handle specifically is equal to the number of the type of the exceptions that your method needs to throw. This way, you can determine how detailed exceptions you will have.
By the way, always try to provide some ways for users of your libraries to avoid exceptions. TryParse is a good example, it exists so that you don't have to use int.Parse and catch an exception. In your case, you may want to provide some methods to check if user name is valid or password is correct so your users (or you) will not have to do lots of exception handling. This will hopefully result in more readble code and better performance.
Ultimately the decision comes down to whether it is more helpful to deal with application-level errors like this using exception handling, or via your own home-rolled mechanism like returning status codes. I don't think there's a hard-and-fast rule about which is better, but I would consider:
- Who's calling your code? Is this a public API of some sort or an internal library?
- What language are you using? If it's Java, for example, then throwing a (checked) exception puts an explicit burden on your caller to handle this error condition in some way, as opposed to a return status which could be ignored. That could be good or bad.
- How are other error conditions in the same application handled? Callers won't want to deal with a module that handles errors in an idiosyncratic way unlike anything else in the system.
- How many things can go wrong with the routine in question, and how would they be handled differently? Consider the difference between a series of catch blocks that handle different errors and a switch on an error code.
- Do you have structured information about the error you need to return? Throwing an exception gives you a better place to put this information than just returning a status.
There are two main classes of exception:
1) System exception (eg Database connection lost) or 2) User exception. (eg User input validation, 'password is incorrect')
I found it helpful to create my own User Exception Class and when I want to throw a user error I want to be handled differently (ie resourced error displayed to the user) then all I need do in my main error handler is check the object type:
If TypeName(ex) = "UserException" Then Display(ex.message) Else DisplayError("An unexpected error has occured, contact your help desk") LogError(ex) End If
Some useful things to think about when deciding whether an exception is appropriate:
-
what level of code you want to have run after the exception candidate occurs – that is, how many layers of the call stack should unwind. You generally want to handle an exception as close as possible to where it occurs. For username/password validation, you would normally handle failures in the same block of code, rather than letting an exception bubble up. So an exception is probably not appropriate. (OTOH, after three failed login attempts, control flow may shift elsewhere, and an exception may be appropriate here.)
-
Is this event something you would want to see in an error log? Not every exception is written to an error log, but it's useful to ask whether this entry in an error log would be useful – ie, you would try to do something about it, or would be the garbage you ignore.
"PasswordNotCorrectException" isn't a good example for using exceptions. Users getting their passwords wrong is to be expected, so it's hardly an exception IMHO. You probably even recover from it, showing a nice error message, so it's just a validity check.
Unhandled exceptions will stop the execution eventually – which is good. If you're returning false, null or error codes, you will have to deal with the program's state all by yourself. If you forget to check conditions somewhere, your program may keep running with wrong data, and you may have a hard time figuring out what happened and where .
Of course, you could cause the same problem with empty catch statements, but at least spotting those is easier and doesn't require you to understand the logic.
So as a rule of thumb:
Use them wherever you don't want or simply can't recover from an error.