简体   繁体   中英

Proper way to validate an input

What is the most efficient way to check an input?

  1. How can I prevent having nested and bloated if statements?
  2. Is using exceptions the right way of doing it? If not, what kind of an approach should I follow?

Bad example (I think):

public int doSthWithAge(int age)
{
    if (age > 0)
    {
        if (age > 100)
        {
            throw new AgeIsTooHighException();
        }
    }
    else
    {
        throw new NoWayException();
    }
...
}

But what are the good ways?

(If you'll give any language-specific information, please do it as if I do the validation in C# -syntax-wise-)

A common object-oriented technique for validation is model your validation rules as first-class objects:

  1. Define a common interface for validating a particular type of data
  2. Implement a collection of classes or functions conforming to that interface
  3. Loop over each function/object in this collection and invoke the validation method. The return value is either true/false or perhaps an object describing the validation failure (null if the validation rule passed). Build a list of validation failures while iterating over the rule collection
  4. Present the validation failures to the user in an appropriate manner

You'll see this technique in use by many libraries out there.

Example:

// the entity you want to validate
public class Person
{
    public int Age { get; set; }
    public string Name { get; set; }
}

public class ValidationFailure
{
    public ValidationFailure(string description) { Description = description; }
    public string Description { get; set; }
    // perhaps add other properties here if desired
}

// note that this is generic and can be reused for any object to validate
public interface IValidationRule<TEntity>
{
    ValidationFailure Test(TEntity entity);
}

public class ValidatesMaxAge : IValidationRule<Person>
{
    public ValidationFailure Test(Person entity)
    {
        if (entity.Age > 100) return new ValidationFailure("Age is too high.");
    }
}

public class ValidatesName : IValidationRule<Person>
{
    public ValidationFailure Test(Person entity)
    {
        if (string.IsNullOrWhiteSpace(entity.Name))
            return new ValidationFailure("Name is required.");
    }
}

// to perform your validation
var rules = new List<IValidationRule> { new ValidatesMaxAge(), new ValidatesName() };
// test each validation rule and collect a list of failures
var failures = rules.Select(rule => rule.Test(person))
    .Where(failure => failure != null);
bool isValid = !failures.Any();

Advantages of this design:

  • Conforming to an interface will promote consistency in your code patterns
  • One class or function per validation rule keeps your rules atomic, readable, self documenting, reusable
  • Follows the single responsibility principle for the validation rule classes, and helps simplify your code that needs to perform the validation
  • Reduces cyclomatic complexity (fewer nested if statements), because it's a more object-oriented or functional approach rather than procedural
  • Allows introduction of dependency injection for individual validation rule classes, which is useful if you're accessing a database or service class for validation

Edit : @Mgetz mentions input validation vs business validation, which is also an important consideration. The class-per-rule approach described above is based on a system I work with every day. We use this more for business logic validation within a service class (it's a complex enterprise system with lots of business rules), and the design serves our purposes well. The specific rules I wrote above are very simple and do look more like input validation. For input validation, I'd recommend a lighter weight approach and to keep it separate from business logic validation when appropriate.

Another implementation of this design is to have a single validator class per entity, and use something more lightweight such as lambdas for individual validation rules. This is used by the popular Fluent Validation library, for example. This is great for user input validation, as it allows for less code to do simple validation, and encourages you to keep the input validation separate from the business logic validation:

// Example using the FluentValidation library
public class PersonValidator : AbstractValidator<Person>
{
    public PersonValidator()
    {
        RuleFor(p => p.Age).LessThan(100);
        RuleFor(p => p.Name).NotEmpty();
    }
}

With validation efficiency is not usually the concern. There are two types of validation that you should be concerned with:

  1. Input validation, eg will what the user is sending to the server malicious and intended to break/break into my application
  2. Business validation, which should happen in your business logic, and should be about maintaining valid, consistent values.

Skipping over either of these is a very very good way to end up with either a hacked or badly broken application.

If you're using ASP.net there are a plethora of libraries (mostly from Microsoft) to do the former, Anti-XSS lib etc.

The latter would be most efficiently preformed in your shared business logic in the entities themselves. EG your user entity should not allow an age of -1.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM