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


72

验证数据时,我已经习惯了执行以下操作:

注意:每张支票我都没有真正的布尔值。这仅是示例。

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

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


2
您的意思是validCheckOne == false等,对吗?否则,您将
无法

7
@nashwan,我想您已经错过了问题的重点。
MoSlo 2013年

1
不,只是强调你真正的意思validCheckTwo =假等
bytedev

Answers:


68

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

这里的每个人都在重复“例外是针对特殊情况”的说法,但实际上并没有使人理解为什么在例外情况下使用例外是不好的。我还需要更多。抛出异常对性能的影响真的那么糟糕吗?有基准可用吗?


这最好地表明了我的初衷。除了简单的数据验证(startDate先于endDate等)之外,“业务规则中的特殊情况”会迫使上游执行。我的问题是我习惯于进行所有验证(当然,我需要更正此问题)。除了“例外情况”论证之外,谁能证明抛出异常对堆栈有不利影响吗?例如更多的内存使用或较慢的执行?
MoSlo

2
这个答案描述了为什么在特殊情况下使用它们会很不好。简而言之,它迫使您处理异常,现在您必须区分SQL异常和验证异常。除了您的验证框架的可维护性外,我不会担心性能。
杰伊

3
对于应用程序程序员来说,习惯于仅几个特定的​​异常并适当地使用它们是很直观的,而不是使各种自发明的返回码甚至字符串混乱。我喜欢在上游放置异常处理程序的想法,这些处理程序可以完成他们的工作-将严重错误记录到数据库中,向系统管理员发送电子邮件,向最终用户显示有意义的消息,等等。服务器端数据验证也可以被认为是现代Web应用程序的例外,因为基本输入验证通常可以使用Javascript完成。
JustAMartin

7
我认为,“例外情况特殊情况”,这句话是一种被误解的说法。您的操作因未满足业务规则而无法继续进行,对这么多人来说这不是什么例外?
亚历山大·德克

2
验证应与命令分开。第一个只是在更改数据状态之前检查数据是否有效,因此应该仅返回验证结果。另一个实际上正在改变系统的状态,应该接收经过验证的数据。当由于系统中的其他更改而使验证数据不再有效时,这是抛出异常的好时机。
亚当·莫斯钦斯基(AdamMoszczyński)

36

我将在此重复这一口头禅:在特殊情况下,应执行异常抛出。无效的输入数据确实不是那么例外。


54
典型的StackOverflow:努力回答棘手的有趣问题,然后从成为第一个制定一些简单规则的人那里获得大多数代表点。叹。
MusiGenesis'2009年

22
我从来没有同意这个口头禅,特别是在Java中,这种语言带有已检查的异常(您可以强制调用者处理异常)。另一种选择是返回错误并将错误处理程序滚动到调用方中。您为什么选择实现自定义错误处理程序,而不是免费获得良好实现的错误处理程序,该语言不仅可能会比您要做的要好,而且还会为您提供(使用经过检查的异常的语言)编译器实施
Falaina

4
不同意,有时用户输入某些内容可能会违反业务规则(并非所有用户输入的验证都与检查他们输入的有效电子邮件地址有关)。在这种情况下,请在应用程序代码中引发异常,进行处理,然后从中显示验证错误。
bytedev 2013年

5
这是文字游戏,我还没有读过任何令人信服的论点,为什么会这样。使用异常并捕获它们确实可以清理代码,尤其是在多层系统中。
thomas-peter 2013年

2
假设验证仅针对“输入”的数据,这是一个糟糕的假设,很可能是您正在使用Web服务,并且无效数据确实是例外,因此应将其视为此类。我认为这个问题在语义,例如,断言必须在条件失败时抛出异常,通过语义,我们对验证是否有类似的理解?它应该返回true还是false还是抛出异常?似乎没有答案
fd8s0

25

我支持MusiGenesis的回答。

另外...


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

另一个问题是,使用“例外”,您的验证仅限于报告第一个失败(并且下次必须再次执行所有操作才能找到下一个失败)。


16

除了经常重复声明“例外是针对特殊情况”之外,我还喜欢以下一条澄清的规则:

如果是用户造成的,那不是例外。

异常是针对系统端的事情(服务器宕机,资源不可用),而不是针对做奇怪事情的用户,因为所有用户都在做奇怪事情。


2
但是,如果我在客户端(Web浏览器)的Web应用程序中进行了一些基本验证,该怎么办?然后不能将服务器端验证视为例外吗?它通常不希望用户绕过JavaScript验证,从而为何使用多个错误代码,怪异的循环与休息,打扰ifS和没有什么,如果是这样诱人的抛出DataValidationException,然后会显示给用户一些通用的消息“字段X的值错误”。
JustAMartin

2
这是我正在考虑的相同问题。在列表中建立所有错误,并针对所有错误引发异常。然后,您可以采用一种通用的方式来处理它们,而只需编写一次即可。否则,编码人员必须处理每个重复代码的调用的响应,并可能错过检查。
Daniel Lorenz

7

在标题中,您称其为“验证”数据。这可以在几个层面上发生。在检查用户输入的数据的GUI中(附近),您应该期望出现错误,并可以通过多种方式将错误报告回来。在这种情况下,异常是不合适的。

但是数据验证也可以发生在其他边界,例如业务规则类之间。在那里,数据中的错误很少见并且是意外的。当您检测到一个时就应该扔。


您是否正在写类似“验证公共方法的参数”之类的东西?
KLE

6

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


4

因此,也许在某些语言中,异常的抛出和捕获是“昂贵的”,但在另一些语言中,异常的抛出和捕获正是我们所需要的。

例如,在Smalltalk中,可以快速构建多层异常捕获解决方案。验证过程可以收集表示特定输入数据集所有错误的任何数量的异常。然后,将它们全部扔给更高级别的捕获器,负责格式化人类可读的解释,再次解释输入错误的所有内容。反过来,它将在格式中引发单个异常,以及格式说明。

所以...我想我想说的是,只有当您没有异常处理架构来支持捕获异常并对其进行合理的处理,并且捕获程序要做的全部工作就是退出或异常时,异常才很容易抛出其他同样不合适的东西。


1
我完全同意。另一个示例是OCaml。Ocaml支持非常轻量级的异常,例如生成堆栈跟踪之类的事情,因此使其完全可以用作控制流机制。
Falaina

2

这是不良行为。例外适用于特殊条件。它们占用资源来生成堆栈等。异常不应用于指示流程。


4
错,这正是异常的作用。它们是流控制的一种形式,与它们一起获得价值。
user207421

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

当您无法采取任何措施时,应该抛出异常。较高的软件层将有机会捕获异常并对其进行处理-即使那只是使应用程序崩溃。


为什么会有while循环?如果没有错误,它将永远旋转。
菲利普·戴维斯

我猜想,一旦检测到错误,就可以使用循环轻松地中断验证。但是您是对的,当没有错误时,需要在while循环中进行最后的中断。也许最好将检查放入自己的方法中,该方法返回错误消息。除了这些问题,我将投票给这个答案。
Oben Sonne

@Philip Davis:您对无限循环是正确的。最后我忘记了休息。
kgiannakakis

永远不要使用while(true),如果有人出现并编辑循环中的内容,则可能会遇到无限循环错误。
bytedev 2013年

2

我会建议使用异常的问题(一个函数中进行流量控制)的描述是错误的通常不是个好主意。我会进一步说验证不是引发异常的最佳方法。而是返回一个布尔值,并存储可以访问的验证错误消息列表。如果在无效对象上调用,则伴随的save方法可能/应该引发异常。

因此,如果验证失败,则可以向用户显示错误消息(记录,返回等)。如果验证通过,则可以调用保存。如果在无效对象上调用保存,则将获取适当的异常。

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

  • 点击保存
  • 收到错误消息
  • 更正错误
  • 再次点击保存
  • 获取其他错误消息。烦死了

作为用户,我希望一次返回所有验证错误,这样我可以在再次尝试之前更正所有错误。


1

我通常同意“例外应该是例外”规则,但是我可能会为Python设置一个例外(ha!),在这种情况下,使用try ...可以有效并且被认为是良好的实践,除了控制流程。

例如,请参阅将异常用于其他目的


0

仅当您的数据验证处于紧密循环中时才真正重要。在大多数情况下,选择什么都没有关系,只要您的代码一致即可

如果您有很多类似于上面的示例的代码,那么您可能希望通过引入一个辅助方法来清理它...

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

(此外,这样做将有助于零错误,例如“ validCheckOne = false”与“ validCheckOne == false” :)


0

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

当涉及到异常时,涉及很多争论。我们可以为从性能到异常哲学的任何方向辩护。他们对我来说听起来都不错。

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

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

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


0

好吧,我知道这是一个老问题。但是,对于那些像我这样落在这里的Google员工,我会在这里发表我的看法:

  1. 如果您使用的语言没有很好的try / catch支持,请使用AVOID THROWING异常进行数据验证。
  2. 不要抛出调用者或其他地方不会处理的异常;
  3. 如果您需要验证其余接收到的数据,请勿抛出异常;
  4. 您可以THROW在情况下,代码块无法继续而无需对无效数据的除外; 如果不中断该过程,则可能会得到未处理的异常。

一个例子:

/*
 * 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);
}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.