A fluent interface helps simplify the expression of multiple guard checks, but doesn’t avoid the trapdoor effect in which the first guard fail causes a break – because none of the guard state is propagated from check to check.
Aggregating state
To avoid the trapdoor problem and aggregate the guard result state as it’s passed down the chain, then break if needed in some chain terminator.
The state object could just contain a list of exceptions to add to:
public class GuardState { private _exceptions = new List(1); public List Exceptions { get { return _exceptions; } } public object Value { get; set; } public string Name { get; set; } }
Typical guards would then just add an exception to the list and pass it on:
public static GuardState IsNotNull( this GuardState state) { if(state.Value == null) { state.Exceptions .Add(new ArgumentNullException(state.Name)); } return state; }
Finally, we need to terminate the guard chain and take action:
public static void Check(this GuardState state) { if(state.Exceptions.Count > 0) { var ex = new GuardStateException(state.Exceptions); throw ex; } }
This means you can now gather all useful state in one go before throwing, which then means you have a chance of solving multiple problems before releasing a fix. This is a rough draft – for example you might want to only instantiate the exception list on the first fail to limit memory usage – but it’s usable.
If it’s used in a high throughput environment memory pressure might be important, even though the guard state objects wouldn’t make it out of gen0. Rick Brewster came up with a syntax that sacrifices a little clarity to tweak the memory use of his guard interface – which I’ll talk about briefly in the next post.
No comments:
Post a Comment