Nullifying Null

time to read 3 min | 597 words

One of the more annoying problems with  building rules that are also code is that you have to deal with code related issues. One of the more common ones is NullReferenceException.

For example, let us say that we have the following rule:

when Order.Amount > 10 and Customer.IsPreferred:
      ApplyDiscount 5.precent 

We also support a mode in which a customer can create an order without actually registering on the site (anonymous checkout).

In this scenario, the Customer property is null. We can rewrite the rule to look like this:

when Order.Amount > 10 and Customer is not null and Customer.IsPreferred:
ApplyDiscount 5.precent

But I think that this is extremely ugly. We can also decide to return a default instance of the customer when it is not there, but here I want to show you another way to handle this. We define the rule as invalid when Customer is not there, so it should not be run. The question is how we can know that.

The dirty way is to do something like this:

var referencesCustomer = File.ReadAllText(ruleName).Contains("Customer");
if(referencesCustomer && Customer == null)
   return;

If you gagged when seeing this code, that is a good sign. Let us solve this properly. First, we want some help from the compiler, so let us inspect the when() meta method that we have seen in the previous post a little closer.

[Meta]
public static ExpressionStatement when(Expression condition, BlockExpression action)
{
	var ctor = new BlockExpression();

	var conditionFunc = new Block();
	conditionFunc.Add(new ReturnStatement(condition));

	ctor.Body.Add(
		new BinaryExpression(
			BinaryOperatorType.Assign,
			new ReferenceExpression("Condition"),
			new BlockExpression(conditionFunc)
			)
		);

	Expression serialize = new CodeSerializer().Serialize(condition);
	Builder.Revisit(serialize);

	ctor.Body.Add(
		new BinaryExpression(
			BinaryOperatorType.Assign,
			new ReferenceExpression("ConditionExpression"),
			serialize
			)
		);

	ctor.Body.Add(
		new BinaryExpression(
			BinaryOperatorType.Assign,
			new ReferenceExpression("Action"),
			action
			)
		);

	return new ExpressionStatement(
		new MethodInvocationExpression(
			ctor
			));
}

We take the cal to the when method and transform it to the following code:

delegate
{
	Condition = () => Order.Amount > 10 && Customer.IsPreferred;
	ConditionExpression = (Expression<Func<bool>>)() => Order.Amount > 10 && Customer.IsPreferred;
	Action = delegate
	{
		// not interesting for this post
	};
}();

I am translating to C# 3.0 here in order to make it easier to grasp the concept. The real code is in Boo, of course, and is more interesting. The most fascinating concept here is the use of CodeSerializer, which will turn the condition that we passed into an AST that we can access at runtime. I tried to simulate that by doing an explicit cast to expression tree, which would give similar result in C#).

Having the AST of the code at runtime, even if we don't want to change it (a totally different concept) is incredibly powerful. In this case, we are going to use this to detect when we are referencing a null property and marking the rule as invalid.

Here is the code:

public void Evaluate()
{
	var references = new List<string>();
	new InlineVisitor
	{
		OnReferenceExpression = r => references.Add(r.Name);
	}.Visit(ConditionExpression);
	if(references.Contains("Customer") && Customer == null)
		return;// rule invalid
	if(Condition())
		Action();
}

This is a very simple example of how you can add smarts to the way that your code behaves. This technique is the foundation for a whole host of options. I am using similar approaches for adaptive rules and for auditable actions. Fun stuff, if I say so myself.