在验证数据时抛出异常是好还是坏?

时间:2009-10-01 14:29:52

标签: validation exception

在验证数据时,我养成了执行以下操作的习惯:

注意:我的每张支票都没有单独的布尔值。这只是一个例子。

另一个注意事项:测试期间的任何错误处理都已正确完成。 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);
}

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

17 个答案:

答案 0 :(得分:58)

我个人喜欢为业务规则验证抛出异常(而不是用户输入验证),因为它会强制在上游处理问题。如果我的业务对象返回了某种验证结果,则调用者可以忽略它。如果你愿意,叫我牛仔:)

这里的每个人都在重复“例外情况适用于特殊情况”这一短语,但这并不能理解为什么在特殊情况下使用它们是不好的。我需要更多。抛出异常的性能是否真的那么糟糕?有没有可用的基准?

答案 1 :(得分:31)

我将在这里重复咒语:应该在特殊情况下抛出异常。无效的输入数据确实不是那么特别。

答案 2 :(得分:18)

我支持MusiGenesis的答案。

此外...


抛出异常的性能是一千条指令。与最终用户时间相比,它没什么,但在内部代码中它很慢。

另一个问题是,使用例外情况,您的验证仅限于报告第一次失败(下次发现下一次失败时您必须再次执行此操作)。

答案 3 :(得分:12)

除了经常重复的声明“例外情况适用于特殊情况”之外,这里还有一个额外的澄清规则:

  

如果用户造成了这种情况,那就不例外了。

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

答案 4 :(得分:7)

在标题中,您将其称为“验证”数据。这可能发生在几个层面上。在您检查用户输入数据的GUI中(接近),您应该期待错误并有办法报告错误。在这种情况下,例外情况不合适。

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

答案 5 :(得分:5)

这取决于 - 如果您希望数据存在并且没有数据是意外的,那么抛出异常就可以了。抛出异常非常昂贵(缓慢)但是处理意外情况的最佳方法。

答案 6 :(得分:3)

所以也许在某些语言中,异常抛出和捕获是“代价高昂的”,但在其他语言中,抛出和捕获异常正是所谓的。

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

所以......我想我所说的是,如果你没有异常处理架构支持捕获它们并与它们做合理的事情,那么例外情况只会很糟糕,你捕获器所要做的就是退出或做其他同样不合适的事情。

答案 7 :(得分:2)

这是不好的行为。例外情况适用于例外条件。他们需要资源来生成堆栈等。不应该使用例外来指示流程。

答案 8 :(得分:2)

通常,不建议使用Exceptions来实现条件流。做这样的事情会更好

  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) {
     ..
  }

如果出现无法解决问题的情况,您应该抛出异常。更高层次的软件将有机会捕获异常并对其做一些事情 - 即使这只是使应用程序崩溃。

答案 9 :(得分:1)

我普遍同意“异常应该例外”的规则,但我可能会对Python做出异常(ha!),它可以既高效又被认为是使用try的好方法...除了控制流程。

例如,请参阅Using Exceptions For Other Purposes

答案 10 :(得分:1)

我建议使用问题中描述的异常(对于函数内的流控制)错误通常不是最好的主意。我进一步说,验证抛出例外并不是最好的方法;而是返回一个布尔值并存储可以访问的验证错误消息列表。如果在无效对象上调用它,则附带的save方法可能/应该抛出异常。

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

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

  • 点击保存
  • 收到错误消息
  • 纠正错误
  • 再次点击“保存”
  • 获取其他错误消息。 烦。

作为用户,我希望立即返回所有验证错误,以便我可以在重新尝试之前纠正所有错误。

答案 11 :(得分:1)

当您去杂货店询问卖家是否有奶酪,而卖家回答没有时,这是否是意外或特殊的回应? 如果你做了同样的事情,但卖家只是看着你没有回应怎么办!

另一个例子,你正在和你的朋友聊天并询问是否有问题,你可能会得到 2 个回复:

  • 他们告诉你他们因为某事而难过。
  • 或者他们只是看着你,什么也不说,转身走开,你确定这意味着你有很大的麻烦:)

与异常相同,意外行为是异常,但无效但预期的响应不应该 - 恕我直言 - 抛出异常。

答案 12 :(得分:0)

只有您的数据验证处于紧密循环中才真正重要。对于大多数情况,只要您在代码中一致,那么您选择的内容并不重要。

如果你有很多代码看起来像上面的样本那么你可能想要通过引入一个帮助方法来清理它......

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

(另外,这样做有助于解决诸如“validCheckOne = false”与“validCheckOne == false”之类的错误:)

答案 13 :(得分:0)

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

当涉及到异常时,涉及很多参数。我们可以为从性能到异常哲学的任何方向辩护。这些听起来对我来说都是正确的。

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

当我们想要验证某些内容时,我们还想知道(登录或向用户显示)参数无效时出了什么问题。甚至认为其中存在验证层,例如业务验证和用户输入验证。

例如,在处理用户输入时,可能会发生很多奇怪的情况。来自网站的粘贴数据充满了隐藏的字符(\ t \ n等),错别字,以及非常大量的案例,特定的异常可能允许比使用简单的“ false”返回更精确地对使用进行进一步分析或消息

答案 14 :(得分:0)

嗯,我知道这是一个老问题。但是,我会在这里为那些像我一样落在这里的Googler发表自己的看法:

  1. 如果您使用的语言不支持try / catch功能,请避免 抛出数据验证例外;
  2. 请勿抛出,呼叫者或 alserwhere;
  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);
}

答案 15 :(得分:0)

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

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

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

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

答案 16 :(得分:0)

在测试中,当然,但在实际环境中,您希望它们永远不会被提升。 您希望将代码重构到系统中的所有数据都在源头进行验证的程度,并且用户或生成系统输入的系统会收到问题通知。 如果您错过了某些东西,应该会发生异常,并且应该是一个处理得当的回退。 您可以单独存储导致这些异常的任何内容,这样它们就不会在未经检查的情况下进入您的系统。 你不想要,例如超出值范围的无效值会扭曲您的结果。