Validation 编写验证代码时使用什么来代替异常?
我正在编写一些验证代码,不知道如何将验证消息传递回调用代码 我想到了异常,但我认为不应该在用户输入验证中使用异常。正如@Blowdart所说: 例外情况并非如此 控制流动机制。用户经常会得到错误的密码,这不是问题 例外情况。例外情况应该是非常罕见的, UserHasDiedata键盘类型的情况 发件人:。我将这种情绪扩展到用户可能输入的所有“不正确”的用户输入Validation 编写验证代码时使用什么来代替异常?,validation,oop,language-agnostic,Validation,Oop,Language Agnostic,我正在编写一些验证代码,不知道如何将验证消息传递回调用代码 我想到了异常,但我认为不应该在用户输入验证中使用异常。正如@Blowdart所说: 例外情况并非如此 控制流动机制。用户经常会得到错误的密码,这不是问题 例外情况。例外情况应该是非常罕见的, UserHasDiedata键盘类型的情况 发件人:。我将这种情绪扩展到用户可能输入的所有“不正确”的用户输入 所以问题是用什么来代替例外。在某些情况下,我可以使用一个IsValid…方法返回一个bool来验证有效性,但是如果我想将一条错误消息传递
所以问题是用什么来代替例外。在某些情况下,我可以使用一个
IsValid…
方法返回一个bool来验证有效性,但是如果我想将一条错误消息传递回去呢?我是否应该创建一个自定义的“ValidationError”对象,带有消息属性?什么是合理的,什么是最不令人惊讶的(最好是一种经过尝试和测试的模式)?如果我以一种真正面向对象的方式来做这件事,我会坚持关注点分离原则,并组成一个类链,每个类处理输入-验证-输出过程中的一个单独步骤
假设我们正在从用户输入的字符串解析日期
我的第一个类将封装原始值并尝试解析日期(伪代码):
在我们的ValidationResult类中,我们可能会找到一些状态属性(OK,Failed),任何直接提供的错误消息,或者作为键,然后在消息目录中查找,等等
这样,我们可以隔离关注点,只处理UI层上的错误消息,同时能够独立地使用和重用验证逻辑
用户经常会得到错误的密码,这不是一个例外情况
是和否。是否抛出异常取决于您所问的问题。在用户登录的过程中,在您得出用户是否可以登录的结论之前,通常会问很多问题。你越是将代码分解成专门的部分,在其中一些部分提出异常就越有意义
假设您在HTTP上下文中以以下方式指定登录过程:
从请求中获取用户名*和密码*
从数据库*中按用户名获取用户记录*
检查记录的密码*是否等于*输入的密码
如果是,则启动会话
如果上述任何步骤未成功完成,请输出相应的错误消息
上面标有星号的任何项目都可能失败:
请求不能包含用户名或密码
可能没有此用户名的用户记录,或者数据库可能已关闭
无论出于何种原因,记录可能没有密码和/或已损坏。无论出于何种原因,存储的密码可能使用不受支持的哈希算法,因此无法进行比较
很明显,在这个过程中,有许多情况是理想的例外情况。测试密码的实际函数可能不会在密码仅为false的情况下引发异常;这应该是一个布尔返回值。但出于其他原因,它仍可能抛出异常。如果正确使用异常,您将得到如下代码(伪代码):
所以,是的,这是一个非常简单的过程,但实际上每一步都有一个或多个例外情况。您会问“用户在请求中发送的用户名是什么?”这个问题,如果因为用户没有发送任何用户名而没有回答这个问题,您就有一个例外情况。异常在这里大大简化了控制流,而不是试图用if..else
来覆盖这些情况
如果用户名无效或密码不正确,则不例外。
(摘自您引用的答案。)
如您所见,我们通过尝试从数据库中获取用户名的记录来测试用户名是否“有效”。如果我们有一个函数,其目的是从数据库中获取用户的记录,而没有这样的记录,那么异常是完全有效的响应。如果我们定义了这个函数来测试这样的记录是否存在,并且null
或false
是一个有效的返回值,那就好了。但在本例中,我们并没有这样写,坦率地说,这导致了我发现更简单的控制流
现在,只有密码验证本身不使用例外,因为问题是“此密码与该密码匹配吗?”,答案可以是肯定或否定。再次,只有在出现不受支持的散列算法等异常情况时,才能对此问题没有答案,并且完全可以保证出现异常
说到这里,您可能会注意到,除了数据库中真正致命的情况外,这些情况中的大多数都不会从表面上导致异常。此处的组件预期并处理其子组件视为例外的某些情况。这里的代码是询问问题,并准备作为其中一些问题的答案进行处理。也就是说,一条说“异常不应该在流程X、Y或Z中使用,因为它不够异常”的一般规则太教条了。它取决于每一段代码的目的,即是否保证例外
说到这里,您要问的是某种形式的表单验证。上面的代码显示了一种情况,其中两条数据可能各自无效,并且它使用的异常最终仍然会导致“yes”或“no”响应。当然,您可以将其封装在如下对象中:
val = new LoginFormValidator()
val.setDataFromRequest(request)
val.validate()
if (val.isValid) {
print 'Hurray'
} else {
print 'You have errors:'
for (error in val.errors) {
print error.fieldName + ': ' + error.reason
}
}
public Result CouldBeVoid()
{
bool IsOk;
// implementation
return IsOk ?
Result.Success() :
Result.Fail("Something went wrong") ;
}
public Result<int> CouldBeInt()
{
bool IsOk;
// implementation
return IsOk ?
Result.Success(intValue) :
Result.Fail("Something went wrong") ;
}
var result = CouldBeVoid();
if(!result)
// do something with error message
var result = CouldBeInt()
if(result)
// do something with int value
else
// do something with error message
此验证器是否在内部为您需要的任何内容使用异常
try {
username = request.get('username')
password = request.get('password')
user = db.get(username=username)
if (user.password.matches(password)) {
session.start()
} else {
print 'Nope, try again'
}
} catch (RequestDoesNotHaveThisDataException) {
logger.info('Invalid request')
response.status(400)
} catch (UserRecordNotFoundException) {
print 'Nope, try again'
} catch (UnsupportedHashingAlgorithmException, PasswordIsNullException) {
logger.error('Invalid password hash for user ' + user.id)
response.status(500)
print 'Sorry, please contact our support staff'
} catch (DatabaseDownException e) {
// mostly for illustration purposes,
// this exception should probably not even be caught here
logger.exception('SEND HALP!')
throw e
}
val = new LoginFormValidator()
val.setDataFromRequest(request)
val.validate()
if (val.isValid) {
print 'Hurray'
} else {
print 'You have errors:'
for (error in val.errors) {
print error.fieldName + ': ' + error.reason
}
}
public (bool Success, string ErrorMessage) DoSomething()
{
// implementation here
}
public (bool Success, someType Value, string ErrorMessage) DoSomething()
{
// implementation here
}
public class Result
{
public static Result Success()
{
return new Result(true, null);
}
public static Result Fail(string errorMessage)
{
return new Result(false, errorMessage);
}
protected Result(bool success, string errorMessage)
{
Success = success;
ErrorMessage = errorMessage;
}
public bool Success {get; private set;}
public string ErrorMessage {get; private set;}
}
public class Result<T>
{
public static Result<T> Success(T value)
{
return new Result(true, null, value);
}
public new static Result<T> Fail(string errorMessage)
{
return new Result(false, errorMessage, default(T));
}
private Result<T>(bool success, string errorMessage, T value)
: base(success, errorMessage)
{
Value = value;
}
public T Value {get; private set;}
}
public Result CouldBeVoid()
{
bool IsOk;
// implementation
return IsOk ?
Result.Success() :
Result.Fail("Something went wrong") ;
}
public Result<int> CouldBeInt()
{
bool IsOk;
// implementation
return IsOk ?
Result.Success(intValue) :
Result.Fail("Something went wrong") ;
}
var result = CouldBeVoid();
if(!result)
// do something with error message
var result = CouldBeInt()
if(result)
// do something with int value
else
// do something with error message