在验证数据时抛出异常是一个好主意还是坏主意?

在验证数据时,我养成了以下习惯:

注意: 对于每个检查,我并没有单独的布尔值。

另一个注意: 测试期间的任何错误处理都是正确的。Try-catch 中抛出的唯一异常是我自己的异常。

try {
if (validCheckOne = false) {
throw new Exception("Check one is bad");
}
if (validCheckTwo = false) {
throw new Exception("Failed because of check2");
}
if(validCheckTen = false) {
throw new Exception("Yet another failure on your part: check10.");
}
} catch(Exception e) {
MessageBox.Show("Your stupid data is wrong! See for yourself: " + e.Message);
}

这是不好的做法吗? 抛出异常会降低程序的执行速度还是不可取?

37980 次浏览

我将在这里重复这句箴言: 抛出异常应该在特殊情况下完成。无效输入的数据实际上没有那么特殊。

这是不良行为。很特别条件例外。他们利用资源来生成堆栈等等。异常不应用于指定流程流。

这取决于——如果您期望数据在那里,并且没有数据是意料之外的,那么抛出异常是可以的。抛出异常代价很高(速度很慢) ,但却是处理意外情况的最佳方式。

我支持 MusiGenesis 的回答。

另外..。


抛出异常的 表演是一千条指令。与最终用户的时间相比,这算不了什么,但是在内部代码中,这个过程非常缓慢。

另一个问题是,使用异常时,您的验证仅限于报告第一个失败(下次您必须重新执行此操作才能发现下一个故障)。

一般来说,不建议使用异常来实现条件流。做这样的事情会更好

  error = false;
while(true) {
if(validCheckOne == false) {
msg = "Check one is bad";
error = true;
break;
}


if(validCheckTwo == false) {
msg = "Check two is bad";
error = true;
break;
}
...
break;
}
if (error) {
..
}

当出现无法处理的情况时,应该引发异常。更高层次的软件将有机会捕获异常并对其进行处理——即使这只会使应用程序崩溃。

在标题中,你称之为“验证”数据。这可能发生在几个层面上。在检查用户输入数据的 GUI 中,应该预料到会出现错误,并且有办法将错误报告回来。在这种情况下,例外是不合适的。

但是数据验证也可以发生在其他边界,比如业务规则类之间。在那里,数据中的错误是不常见的和意外的。当你发现一个的时候,你应该扔出去。

只有在数据验证处于紧密循环的情况下,这才真正重要。对于大多数情况,只要在代码中是 始终如一,选择什么并不重要。

如果您有很多类似于上面示例的代码,那么您可能希望通过引入一个 helper 方法来抛出..。

private void throwIf( bool condition, String message )
{
if( condition )
throw new ApplicationException( message );
}

(同样,这样做将有助于找出错误,比如“ validCheckOne = false”与“ validCheckOne = = false”:)

除了经常重复的“特殊情况才会有例外”的说法之外,这里还有一条我开始喜欢的澄清规则:

如果是用户引起的,那也不是例外。

例外情况是系统端的事情(服务器关闭,资源不可用) ,而不是用户做奇怪的事情,因为所有用户都做奇怪的事情。

就我个人而言,我喜欢为业务规则验证抛出异常(对于用户输入验证就不那么喜欢了) ,因为这会迫使问题在上游处理。如果我的业务对象返回某种类型的验证结果,调用方可能会忽略它。如果你愿意,可以叫我牛仔:)

这里的每个人都在重复“例外是针对特殊情况的”这句话,但是这并没有给出任何理解,为什么在非特殊情况下使用它们是不好的。我需要更多证据。抛出异常对性能的影响真的那么糟糕吗?是否有任何可用的基准?

我通常同意“例外应该是例外”的规则,但我可能会做一个例外(哈!)在 Python 中,除了控制流之外,使用 try... 可以是有效的,也被认为是很好的实践。

例如,请参见 为其他目的使用异常

因此,也许在某些语言中,抛出和接住异常是“昂贵的”,但在其他语言中,抛出和接住异常正是需要的。

例如,在 Smalltalk 中,可以快速构建多层异常捕获解决方案。验证传递可以收集任意数量的异常,这些异常表示特定输入数据集中的所有错误。然后它会把它们全部扔给一个更高级别的捕手,负责格式化一个人类可读的解释,再一次,所有的输入错误。反过来,它会抛出一个单一的异常进一步链,以及格式化的解释。

所以... ... 我想我要说的是,如果没有异常处理架构支持捕获异常并对其进行合理处理,而且捕获器所做的只是 EXIT 或者做其他同样不合适的事情,那么抛出异常才是糟糕的。

我建议使用问题中描述的异常(用于函数中的流控制)通常不是最好的办法。更进一步说,验证 投掷异常不是最好的方法; 相反,返回一个布尔值并存储可以访问的验证错误消息列表。如果对无效对象调用异常,则附带的 save 方法可能/应该引发异常。

因此,如果验证失败验证错误消息可以显示给用户(记录,返回。无论如何)。如果验证通过,则可以调用 save。 如果对无效对象调用 save,那么会得到一个适当的异常。

示例代码的另一个潜在问题(当然取决于需求)是,它只会抛出发生的第一个验证错误。想象一下用户的观点:

  • 点击保存
  • 得到一个错误消息
  • 正确的错误
  • 再次点击保存
  • 获取不同的错误消息

作为一个用户,我更希望一次性得到所有验证错误的返回,这样我就可以在重试之前纠正所有错误。

这个问题仍然很有趣,主要是因为答案。

当涉及到例外情况时,会有很多争论。我们可以在任何方向上捍卫一个点,从性能到异常原理。我觉得他们都是对的。

但有时我们必须坚持一个方向。在这种情况下,我认为这是验证本身。

当我们想要验证某些东西时,我们也想知道(记录或向用户显示)当参数无效时出了什么问题。即使有像业务验证和用户输入验证这样的验证层。

例如,在处理用户输入时,可能会发生许多奇怪的情况。一个粘贴的数据从一个网站充满了隐藏的字符(t n 等) ,输入错误,和一个真正的巨大种情况下,一个特定的异常可以允许进一步分析或消息的使用比一个简单的“错误”返回更精确。

我知道这是个老问题了。但是我要把我的意见告诉那些像我一样倒在这里的谷歌员工:

  1. 如果您使用的语言不支持 try/catch < strong > AVOID 为数据验证抛出 异常;
  2. 不要抛出调用方或 其他地方;
  3. 如果需要验证接收到的其余数据,请不要抛出 异常;
  4. 在代码块无法继续的情况下,可以将 作为异常 没有无效的数据; 如果你不中断进程 可以得到一个未处理的异常;

举个例子:

/*
* Here it's a common problem i have: Someone pass a list of products i need to
* retrieve from the database and update some information;
*/


//This is a class to represent the product
function Product(id, name, price) {
this.id = id;
this.name = name;
this.price = price;
}


//This is an example function to retrieve the product from the database
function findProductInDatabase(productId) {


//If the product exists on the database, the function will return it
if (productId == 12) {
var product = new Product(12, "Book", 20.5);
return product;
}
	

//If the product do not exists, it will return null
return null;
}


//This is a function that will receive the productID and will update the received parameters
function updateProduct(productId, newProductName, newProductPrice) {


var productFromDatabase = null;
var errorMessage = "";
	

//Retrieve the product
productFromDatabase = findProductInDatabase(productId);


//If the product do not exist, i need to interrupt de method imediatily and alert the caller
if (!productFromDatabase) {
throw "Product not found";
}
	

//Validate the other parameters, but in this case	i can validate all the parameters
if (newProductPrice < 10) {
errorMessage += "the price is too low";
}
	

if (newProductName.includes("<")) {
		

//If already has a error message in the variable i append " and " to the message make sense
if (errorMessage) {
errorMessage += " and ";
}
		

errorMessage += "the new name has invalid characters";
}
	

if (errorMessage) {
//if theres any error, i will throw a exception with the messages
throw errorMessage;
}
}


//This parte is where the method id called;
try {
updateProduct(9, "Book", 10.5);
} catch (exception) {
console.log("Case 1: " + exception);
}
try {
updateProduct(12, "<Book", 9);
} catch (exception) {
console.log("Case 2: " + exception);
}

当你去杂货店问卖家有没有奶酪,卖家回答说没有,这是一个意想不到的或特殊的反应吗? 如果你做了同样的事情,但是卖家只是看着你,没有回应,那该怎么办!

另一个例子,你和你的朋友聊天,问他是否有什么问题,你可能会得到两个回答:

  • 他们告诉你他们因为一些事情而悲伤。
  • 或者他们只是看着你,什么也不说,转过身走开,你确定这意味着你有大麻烦了:)

与异常一样,意外行为也是异常,但无效但预期的响应不应抛出异常。

我经常为验证编写类似的代码,特别是在 Express.js 和类似的请求/响应循环样式的应用程序中。当某些内容无效时,我抛出一个 ValidationError,它被顶级错误处理程序捕获,它知道发送一个包含附加信息的422响应到 ValidationError。

这是处理验证的一种非常方便的方法。您不必传递错误对象(在某些情况下,可能需要通过十几个堆栈帧)。这是触发无效输入响应的一种简单而一致的方法。这种方法没有遇到任何严重的问题。

我考虑了与此实践相关的“不要使用异常进行流控制”准则,并决定利大于弊。我想说,如果您理解“不要使用异常进行流控制”背后的原因,但是您确定在某种情况下使用 无论如何是一个好主意,那么请继续并执行它。我们不必对这些事情太武断。

抛出异常的速度相对较慢,但是只有在循环中重复抛出异常才有意义。

在测试中,当然,但是在生活环境中,你会希望他们永远不会长大。 您希望重构代码,以便在源代码中验证系统中的所有数据,并通知用户或生成系统输入的系统。 如果您遗漏了某些内容,那么应该会发生异常,并且异常应该是可以得到妥善处理的备份。 您可以分别存储导致这些异常的任何东西,这样它们就不会在未经首先检查的情况下进入您的系统。 你不想要的,例如一个无效的值落在一个值的范围之外,以扭曲你的结果。