I describe a technique in the C# language, and very small supporting library (hence ‘micro-framework’), for implementing business-logic–level tests which may be either short-cut evaluated (when you require a boolean yes/no), or fully evaluated (when you requite a list of ‘reasons why not’).
Motivation
A recent work project required business-logic related restrictions to certain operations. For example:
You can only raise a service call if: you are an engineer or their manager, and the customer has a support contract, and the date of the support contract has not expired.
In other words:
You cannot raise a service call if:
- You are not an engineer or the manager of an engineer, or
- The customer does not have a support contract, or
- The date of the support contract has expired.
In other other words: they may be one or more reasons why the user cannot do something. Each of these tests potentially requires database access, and hence we want to avoid evaluating all of them if we do not have to.
There are many of these tests and each is used in essentially 2 ways:
- Asking whether we can perform the operation, or not. For example: should we display the ‘raise call’ button? We need a boolean yes/no result which says whether the operation is allowed. (And if we already know that the first condition fails, there’s no point in making expensive database calls to check the other conditions.)
- Telling the user ‘why not’. You cannot do X because: A, B and C. In this case we do want to evaluate all the conditions, so that we can report which conditions are preventing the user from doing X.
Often in my code I wish to ‘AssertConditionX’ (e.g., AssertCanRaiseCall
). If the tests all pass, I do nothing, but if any of them fail I throw an exception reporting all failed tests. (The exception is either the ‘reason’ exception for a failed condition, or a ‘wrapper’ exception, which in turn contains a list of ‘reasons’ exceptions.)
So, version 1
My first attempts at this involved writing 2 methods for each test, with business logic repeated in each one. This is horribly redundant, but run-time-efficient, since if I only require the boolean result, I can short-circuit evaluate the condition (i.e., bail out on the first failed condition). Where we’re using these tests frequently, this is a consideration.
public void AssertCanRaiseCall(User user){
var problems = new List<BusinessException>(); if (!IsEngineer(user) || !IsEngManager(user)) problems.Add(new BusinessException.NotAllowed(user, "raise call")); // ...test other conditions...
if (problems.Count > 0) // Exception-throwing logic. throw (problems.Count == 1) ? problems[0] : new MultipleBusinessException(problems); }
public bool CanRaiseCall(User user){ // Repeat logic, except with short-circuit evaluation: if (!IsEngineer(user) || !IsEngManager(user)) return false; // ...and so on... /*Otherwise:*/ return true; }
…which is kinda unwieldy. In the end, I generally implemented a common ‘GetProblems’ method and had the two other methods call it. (In cases where I needed short-circuit evaluation in the ‘Can…’ method for efficiency reasons, I had to duplicate the logic as before.)
Version 2
However, the code is still repetitious and contains much boilerplate code, so the next iteration implemented two improvements: factor out the exception-throwing logic into a common method called MaybeThrow
, and implement some operator overloading to allow me to compose BusinessExceptions using operator ‘+’. The latter provided a syntactically light way of constructing my MultipleBusinessExceptions
object. So the code now looked like this:
public void AssertCanRaiseCall(User user){ // Factored out logic. This method becomes mere syntactic sugar. MaybeThrow(RaiseCallProblems(User user)); }
public bool CanRaiseCall(User user){ // Inefficient to execute, (but much more efficient to write): return RaiseCallProblems(user) == null; }
// Returns a single BusinessException, possibly a MultipleBusinessExceptions wrapper: private BusinessException RaiseCallProblems(User user){ var problems = (BusinessException)null; if (!IsEngineer(user) || !IsEngManager(user)) problems += new BusinessException.NotAllowed(user, "raise call"); // ...and so on... return problems; }
Bah. We’re still writing 3 methods for each condition. The first two are pure boilerplate. And the Can… method is not short-circuit-evaluating the tests, unless I implement it by hand.
Also, using the ‘+’ operator for constructing MultipleBusinessExceptions
is a cute trick, (and readable), but it’s not idiomatic, (and the implementation is hard to make both efficient and correct).
The ideal would be something which employed a lazy sequence of BusinessException
s. It would be a first class object of which we could ask ‘are there any problems’ or ‘throw the problems as an exception’ as necessary.
Version 3
Lazy evaluation of a collection… Hmm, sounds like a job for an iterator. So why not use C# iterator blocks?
Ta-da!:
public IEnumerable<BusinessException> WhyNotRaiseCall(User user){ if (!IsEngineer(user) || !IsEngManager(user)) yield return new BusinessException.NotAllowed(user, "raise call"); // …and ‘yield return’ exceptions for other problems as required. }
This gives us our lazy list of exceptions (the code only tests the conditions while we continue to ask it for more exceptions).
The way iterator blocks work in C#, our method has to be defined to return precisely an IEnumerable
or an IEnumerator
(or one of the generic versions). However, due to the magic of ‘extension methods’, we can define our own methods on IEnumerable<BusinessException>
:
// Extension methods live here... // We only write this code once. public static class WhyNotExtensions { // Were there no problems? public bool IsOk(this IEnumerable<BusinessException> self){ using(var e = self.Enumerator){ return ! e.MoveNext(); } } // Throw any and all problems as an exception. // Do nothing if there were no problems. public void AssertOk(this IEnumerable<BusinessException> self){ using(var e = self.Enumerator){ if (e.MoveNext()){ // If we have ANY errors, throw them: var first = e.Current; if (! e.MoveNext()){ // If there is no more than one, throw it directly: throw first; }else{ // We have more than one; aggregate them: var problems = new List<BusinessException>(); problems.Add(first); do{ problems.Add(e.Current); }while(e.MoveNext()); throw new MultipleBusinessException(problems); } } } } }
…which means that we can write code like this:
database.WhyNotRaiseCall(user).AssertOk()
or
if (database.WhyNotRaiseCall(user).IsOk()) DoThing();
…which is nice.
Now we are only writing a single method for each business logic test, so the code is clearer. Moreover, we don’t need the explicit ‘problems’ variable, so each business logic test is two lines shorter (since we don’t have to define or return the ‘problems’ variable). And when we only need a boolean result we only evaluate up to the first failing condition.
Difficulties and Problems
When testing IsOk
, we’re still doing a tiny bit more work than strictly necessary, since we’re still constructing the first exception, even though it is never used. (Ideally the C# implementation of iterator blocks would decouple the implementation of MoveNext
and Current
, so that Current
was only calculated when asked for… though having said that, it’s not awful.)
It seems ugly to have to declare my WhyNot… methods as returning IEnumerable<BusinessException>
. I’d much prefer if it returned a custom class (ReasonsWhyNot
), with a AssertOk()
method and a IsOk
property. However, there is no way to do this without requiring a lot more boilerplate code. So-called ‘iterator blocks’ in C# have to be whole methods, and have to be declared to return an IEnumerable
or an IEnumerator
(so I couldn’t get away with passing the iterator logic to a constructor for my custom object, without writing a helper method which returned the IEnumerable
).
Okay, so I have to use extension methods to implement my ‘WhyNot’ interface. Even so, it’s annoying that IsOk
has to be a method rather than a property: there are no such thing as ‘extension properties’ in .NET.
Another problem with implementing my interface with extension methods is that client code has to import the static extension class, (and has to be written in a language which supports extension methods).
Another way to get around all of these problems would be to create some kind of domain-specific language for constructing collections of tests out of closures (i.e., each condition is of type Func<BusinessException>
). However, that approach sacrifices some of the simplicity and language support of the method described here.
Further work
The (bizarrely-named) Hamcrest library for Java introduces a kind of predicate language, allowing assertions to be constructed like: assertThat(someValue, allOf(greaterThan(3), lessThan(5)))
—the advantage being that there is no need to write code such as assertOrFail(someValue > 3 && someValue < 5, "someValue needs to be >3 and <5")
; the library can generate the descriptions for you (and, potentially, internationalise them).
The technique described in this paper could incorporate such a predicate DSL as part of its test conditions. This would give the advantage that the programmer would be required to write less explanatory exception message text. (Surely Hamcrest can’t be too difficult to port to .NET…)
Summary
In summary, C# iterator blocks in conjunction with devious use of extension methods can be used to construct complex business logic tests from which you sometimes want a yes/no answer, and sometimes a detailed breakdown of ‘why not?’.
It cleanly breaks out the list of business conditions, which may be written simply and ideomatically, and provides a way to query them in a relatively efficient way.
It coincidentally demonstrates a ‘non collection’ use of iterator blocks in C#.
Very impressive logical progression of thought married with your customary mastery of your subject, Mr Forrest.
*IMPRESSIVE*