Validation 编写验证代码时使用什么来代替异常?

Validation 编写验证代码时使用什么来代替异常?,validation,oop,language-agnostic,Validation,Oop,Language Agnostic,我正在编写一些验证代码,不知道如何将验证消息传递回调用代码 我想到了异常,但我认为不应该在用户输入验证中使用异常。正如@Blowdart所说: 例外情况并非如此 控制流动机制。用户经常会得到错误的密码,这不是问题 例外情况。例外情况应该是非常罕见的, UserHasDiedata键盘类型的情况 发件人:。我将这种情绪扩展到用户可能输入的所有“不正确”的用户输入 所以问题是用什么来代替例外。在某些情况下,我可以使用一个IsValid…方法返回一个bool来验证有效性,但是如果我想将一条错误消息传递

我正在编写一些验证代码,不知道如何将验证消息传递回调用代码

我想到了异常,但我认为不应该在用户输入验证中使用异常。正如@Blowdart所说:

例外情况并非如此 控制流动机制。用户经常会得到错误的密码,这不是问题 例外情况。例外情况应该是非常罕见的, UserHasDiedata键盘类型的情况

发件人:。我将这种情绪扩展到用户可能输入的所有“不正确”的用户输入


所以问题是用什么来代替例外。在某些情况下,我可以使用一个
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