==================================== Avoiding deeply nested IF statements (Structured/Un-Structured) ==================================== -IAN! idallen@idallen.ca This file deals with when you might violate structured programming principles to make your code smaller and easier to read and modify. Shell scripts are typically small and easy to re-use and modify. There is no compile/link/run cycle - the script is directly interpreted by the shell. These characteristics mean that scripts are often written to be *easy to re-use and modify*. -------------------------------------- Deep nesting in structured programming -------------------------------------- Here is some typical deeply-nested IF code found in structured programming: if [ -e "$file" ] ; then if [ -r "$file" ] ; then if [ -w "$file" ] ; then if [ -x "$file" ] ; then if [ -s "$file" ] ; then echo "File '$file' is not empty and has RWX permissions" return=0 else echo 1>&2 "$0: File '$file' is empty (zero size)" return=1 fi else echo 1>&2 "$0: File '$file' has no X permissions" return=2 fi else echo 1>&2 "$0: File '$file' has no W permissions" return=3 fi else echo 1>&2 "$0: File '$file' has no R permissions" return=4 fi else echo 1>&2 "$0: File '$file' does not exist" return=5 fi exit $return From the point of view of structured programming, the above 28 lines of code are good because they have one entry point (the top of the file) and one exit point (the bottom of the file). However, the code is larger than it needs to be and is hard to modify and re-use due to the deep nesting. To add, delete, or re-order an IF clause requires changing the indentation of all the other nested IF clauses. The multiple ELSE parts of the IF statements are separated a long way from the corresponding IF tests, making the code more difficult to understand. One might rework the code to move the error message clauses to the top, so that they sit right under their corresponding IF statements; but, this still doesn't improve the nesting: if [ ! -e "$file" ] ; then echo 1>&2 "$0: File '$file' does not exist" return=5 else if [ ! -r "$file" ] ; then echo 1>&2 "$0: File '$file' has no R permissions" return=4 else if [ ! -w "$file" ] ; then echo 1>&2 "$0: File '$file' has no W permissions" return=3 else if [ ! -x "$file" ] ; then echo 1>&2 "$0: File '$file' has no X permissions" return=2 else if [ ! -s "$file" ] ; then echo 1>&2 "$0: File '$file' is empty (zero size)" return=1 else echo "File '$file' is not empty and has RWX permissions" return=0 fi fi fi fi fi exit $return Some relief from the deep indentation problems can be had by replacing "ELSE IF" by ELIF (not all programming languages have this feature!): if [ ! -e "$file" ] ; then echo 1>&2 "$0: File '$file' does not exist" return=5 elif [ ! -r "$file" ] ; then echo 1>&2 "$0: File '$file' has no R permissions" return=4 elif [ ! -w "$file" ] ; then echo 1>&2 "$0: File '$file' has no W permissions" return=3 elif [ ! -x "$file" ] ; then echo 1>&2 "$0: File '$file' has no X permissions" return=2 elif [ ! -s "$file" ] ; then echo 1>&2 "$0: File '$file' is empty (zero size)" return=1 else echo "File '$file' is not empty and has RWX permissions" return=0 fi exit $return The above structured code is only 20 lines - a 28% improvement over the original 28-line version. The re-ordering, adding, or deleting of any of IF clauses is easier than before; since, the indentation level is now constant for every clause. Less code is better code. ------------------------------------------------- Multiple exit points reduce need for deep nesting ------------------------------------------------- The long chain of IF/ELIF clauses of the structured example, above, still has some disadvantages: 1. The first IF clause differs in syntax from the remainaing ELIF clauses, so you can't simply cut-and-paste to re-order them. 2. The long list of negated IF/ELIF tests puts the "exit" code far away from the start of the list. 3. Syntactically, the chain of five IF/ELIF clauses is *one* very long, single statement, not five separate statements. If any part of this long statement is wrong (e.g. missing semicolon), the whole statement will be syntactically wrong. This makes debugging harder. Below is code that produces the same output, without the deep nesting, and without the need to link the entire block of code together using ELIF (which is not available in some programming languages): if [ ! -e "$file" ] ; then echo 1>&2 "$0: File '$file' does not exist" exit 5 fi if [ ! -r "$file" ] ; then echo 1>&2 "$0: File '$file' has no R permissions" exit 4 fi if [ ! -w "$file" ] ; then echo 1>&2 "$0: File '$file' has no W permissions" exit 3 fi if [ ! -x "$file" ] ; then echo 1>&2 "$0: File '$file' has no X permissions" exit 2 fi if [ ! -s "$file" ] ; then echo 1>&2 "$0: File '$file' is empty (zero size)" exit 1 fi echo "File '$file' is not empty and has RWX permissions" exit 0 The above 22 lines of code are not in good "structured programming" style, since the code has multiple exit points, not just one. However, the code is 20% shorter than the original example, is easy to modify, easy to re-use, and is easy to read. You can add, modify, re-order, or delete IF clauses without changing any indentation. No ELSE clauses are needed, since each IF clause simply exits the script. Each of the five IF clauses is a separate statement. Both the structured and unstructured versions of this code are correct; both get you full marks. My preference is for code that is easy to read, understand, and modify, even if the code violates "structured programming" rules (one entry / one exit). Less code is better code. -------- Bad code -------- Here is code that looks similar to the structured programming example given earlier; but, you will note that this code has the worst of both worlds - it has multiple exit points *and* deep nesting: if [ -e "$file" ] ; then if [ -r "$file" ] ; then if [ -w "$file" ] ; then if [ -x "$file" ] ; then if [ -s "$file" ] ; then echo "File '$file' is not empty and has RWX permissions" exit 0 else echo 1>&2 "$0: File '$file' is empty (zero size)" exit 1 fi else echo 1>&2 "$0: File '$file' has no X permissions" exit 2 fi else echo 1>&2 "$0: File '$file' has no W permissions" exit 3 fi else echo 1>&2 "$0: File '$file' has no R permissions" exit 4 fi else echo 1>&2 "$0: File '$file' does not exist" exit 5 fi I would deduct marks for the above code. It does not adhere to structured programming rules; since, it has multiple exit points and not just one; and, it also has deep nesting. Given that the code author is willing to abandon structured programming principles and use multiple exit points, s/he should have restructured the code to remove the indentation and the redundant ELSE clauses and make the code smaller and easier to read and modify. The above code is much too long for what it does; it is full of EXIT statements that precede unnecessary ELSE clauses. It is a large example of this common coding redundancy: if ...something... ; then ...stuff1... exit 1 else ...stuff2... fi The above 6-line IF statement is equivalent to this 5-lines of code: if ...something... ; then ...stuff1... exit 1 fi ...stuff2... Adding an ELSE clause after an IF clause that returns or exits is unnecessary; the ELSE is not needed after an "exit" statement since control will never flow around the ELSE. Don't do that. Less code (and less required indentation) is better code.