The Arrow Anti-pattern
The Arrow Ani-pattern is a name given to the resulting structure produced by using excessive nested conditional operators. The following pseudo-code demonstrates why the name is apt:
if( ... ) { if( ... ) { if( ... ) { if( ... ) { if( ... ) { //--> do something } } } } }
The structure above becomes increasingly problematic as each additional nested condition adds to the cognitive load required to parse the represented logic. According to studies performed by Noam Chomsky and Gerald Weinberg in 1986, few people can easily understand more than three levels of nested conditional statements. It is therefore recommended that techniques for restructuring the code be sought out when this pattern begins to emerge.
Guard Clauses
One approach to refactoring the arrow anti-pattern is to use guard clauses. Guard clauses are a form of assertion which makes a tangible contribution to the logic of the method. By restructuring nested conditional statements to successive conditional exit points, the code becomes easier to understand and eases further refactoring efforts such as method extraction or clause reordering.
void SomeProcess() { if( ... ) return; if( ... ) return; if( ... ) return; if( ... ) return; if( ... ) return; // do something }
Method Extraction
Another approach especially suited to blocks comprised of nested looping structures is the extraction of segments into separate methods. For each block forming the body of a loop statement, extract the body into a separate method which can then be examined independently of the containing loop statement:
Before
void SomeProcess() { while( ... ) { while( ... ) { while( ... ) { while( ... ) { } } } } }
After
void SomeProcess() { while( ... ) { MethodA(); } } void MethodA() { while( ... ) { MethodB(); } } void MethodB() { while( ... ) { MethodC(); } } void MethodC() { while( ... ) { MethodD(); } }
Logical And/Or
In cases where the nested structure is comprised solely of checks against the state of an object, the conditions can be extracted into a collection of Boolean methods and combined to form a more concise and readable guard clause:
void SomeProcess() { if(ConditionA() && ConditionB() && ConditionC() && (ConditionD1() || ConditionD2()) && ConditionE()) { // do something } }
Composite Specification Chain
As a variant to the previous technique, the Specification pattern can be used in the form of a composite specification chain to render a concise and readable guard clause. Using this technique, specification classes are written to test each case, and the resulting set of specifications are composed into a single composite specification which can evaluate the state of the given object. Due to the encapsulation of the evaluation logic in the form of separate specification classes, this technique has the added benefit of enabling substitution of the specification implementation via the Strategy pattern for test isolation or other variable composition needs.
void SomeProcess() { ISpecification<SomeType> specification = new IsConditionA() .And(new IsConditionB()) .And(new IsConditionC()) .And(new IsConditionD1().Or(IsConditionD2())) .And(new IsConditionE()); specification.IsSatisfiedBy(this).Then(() => { /* do something */ }); }
Conclusion
The techniques presented here are a few approaches to helping achieve more readable and maintainable code by eliminating the arrow anti-pattern. Through the judicious application of these techniques, developers can in small ways leave their code base cleaner than they found it.