Saturday, January 12, 2008

Readability in Validation

My favourite pattern for validation code is to have one method per validation rule. I think it makes it easier to read and manage the rules.

For instance, I like to be able to write something like this:
[Validate]
protected bool EndDateMustBeAfterStartDate()
{
return EndDate > StartDate;
}
I like to refine the pattern a little, in order to:

(a) Construct error messages based on the method names. That makes the code more concise and encourages good method names. E.g. the failure message from the rule above will be "End date must be after start date".
(b) Allow you to, alternatively, specify dynamically-generated failure messges from within the method body (see below)
(c) Easily specify if the rule is not applicable in a particular situation (again see below).

I've attached some sample code (with tests).

By the way, I once wrote some code to fire rules automatically, whenever a property used by the rule was changed. But that's not included here. In this sample, you have to ask the system explictly to run the rules at a particular time.

Here are some example rules, and the code to call them:
public class Animal
{
...
public string AnimalName
{
get { return _animalName; }
set { _animalName = value; }
}

public int NumberOfLegs
{
get { return _numberOfLegs; }
set { _numberOfLegs = value; }
}

[Validate]
protected Result NumberOfLegsMustBeEven()  
{
return NumberOfLegs % 2 == 0;
}

[Validate]
protected Result MythicalSpeciesAreNotAllowed()
{
if (AnimalName == "Unicorn" || AnimalName == "Dragon")
return Validation.Error("'" + AnimalName + "' is not a real species");
else
return Validation.NotApplicable;
}  

// Note the use of the OR operator here to give a compact syntax for returning a custom error
[Validate]
protected Result NumberOfLegsMustBePlausible()
{
return NumberOfLegs <= 10 || Validation.Error("Your '" + AnimalName +  "' has too many legs");
} 

...
// execute like this
foreach(Result r in Validator<Animal>.ExecuteRulesOn(myAnimal))
if(!r.Pass)

// or, like this, if you don't want to use the generic Validator class
Validator v = Validator.GetInstanceFor(typeof(Animal));
foreach (Result r in v.ExecuteRulesOn(myAnimal))
...

2 Comments:

Anonymous Demis Bellot said...

I like your approach to validation though I think the validation rules should be separate from the Entity model as the rules polutes the model and different users of the model can have different validation rules.

maybe you could do something like:

class AnimalValidation<Animal>
: EntityValidation<Animal>
{
Result NumberOfLegsMustBeEven() {...}

Result MythicalSpeciesAreNotAllowed(){...}

}

class EntityValidation<T>
{
T Entity {get;set;}
List<Result> Validate(){/* validate all subclass 'Result Methods()'*/}
}

[test]
public void TestAllFailed()
{
Animal animalThatFailsAllRules = new Animal("Unicorn", 11);

Assert.AreEqual(2, new AnimalValidation{Entity =animalThatFailsAllRules}.Validate().Count);
}

Sat May 03, 08:19:00 PM PDT  
Blogger John Rusk said...

Hi Demis,

I wondered about that myself. I decided not to do it because the validation methods could no longer access the properties directly, they would have to do so indirectly, via the Entity property in your example.

Also, in practice I have found that it feels reasonably natural to put validation rules on the model (at least it does in my particular situation). To me, "are you valid" is a reasonable question to ask a domain entity, and I would expect the entity to give the same answer to all users.

John

PS: Sorry about the delay in replying to your comment.

Fri May 16, 06:25:00 PM PDT  

Links to this post:

Create a Link

<< Home