======================== Less Code is Better Code - stop cutting and pasting identical code ======================== -IAN! idallen@idallen.ca The ability to cut-and-paste code easily has produced a whole generation of programmers who write too much code. You must minimize the amount of code you write, to minimize the effort needed to maintain the code. If you duplicate code, you more than duplicate the amount of effort needed to maintain it. Here are some examples of "too much code". -------------------------------------------------------------------------- You want to use a temporary file name containing the user name $USER and the current process ID $$. WRONG WAY: grep foo /etc/passwd >"/tmp/$USER.$$.txt" grep bar /etc/passwd >>"/tmp/$USER.$$.txt" diff /etc/passwd "/tmp/$USER.$$.txt" rm "/tmp/$USER.$$.txt" - changing the file name requires changing four lines of code! - you have three chances to spell the name wrong when you edit it; you might miss changing one of the four names RIGHT WAY (less duplicate code): tmpfile="/tmp/$USER.$$.txt" grep foo /etc/passwd >"$tmpfile" grep bar /etc/passwd >>"$tmpfile" diff /etc/passwd "$tmpfile" rm "$tmpfile" - the file name is set in one place - changing it is easy - spelling the name wrong in some of the commands is impossible, since the name is contained in the variable -------------------------------------------------------------------------- You want to process an input file using the above commands. The file might come from a command line argument or by prompting and reading. WRONG WAY: if [ "$#" -ge 1 ]; then # input comes from command line arguments tmpfile="/tmp/$USER.$$.txt" grep foo "$1" >"$tmpfile" grep bar "$1" >>"$tmpfile" diff "$1" "$tmpfile" rm "$tmpfile" else # no command line arguments; prompt and read input echo 1>&2 "Enter a file name:" read filename || exit $? tmpfile="/tmp/$USER.$$.txt" grep foo "$filename" >"$tmpfile" grep bar "$filename" >>"$tmpfile" diff "$filename" "$tmpfile" rm "$tmpfile" fi - you have written the entire algorithm twice over, once for the command line argument case and once for the prompted input case - changing the algorithm (e.g. changing "grep" to "grep -v") requires changing the code in two places (you might miss one!) - much of the code is duplicated RIGHT WAY (less duplicate code): - separate the gathering of input from the processing step: # FIRST: gather input if [ "$#" -ge 1 ]; then # input comes from command line arguments filename="$1" else # no command line arguments; prompt and read input echo 1>&2 "Enter a file name:" read filename || exit $? fi # SECOND: process the input tmpfile="/tmp/$USER.$$.txt" grep foo "$filename" >"$tmpfile" grep bar "$filename" >>"$tmpfile" diff "$filename" "$tmpfile" rm "$tmpfile" - the duplicated code has been removed - the algorithm is implemented in only one place, not two - the obtaining of the file name is now separate from the algorithm - the algorithm is generalized and parametrized to work no matter how you might obtain a file name Don't duplicate code. Parametrize the code and set the parameters using conditional logic ahead of the code. Use functions, if your programming language allows them. Less code is better code! -------------------------------------------------------------------------- You want to generate different output for different pathnames: WRONG WAY: if [ -r "$foo" ] ; then if [ -d "$foo" ] ; then echo "The directory '$foo' is readable." elif [ -f "$foo" ] ; then echo "The file '$foo' is readable." else echo "The pathname '$foo' is readable." fi else if [ -d "$foo" ] ; then echo "The directory '$foo' is not readable." elif [ -f "$foo" ] ; then echo "The file '$foo' is not readable." else echo "The pathname '$foo' is not readable." fi fi - the "echo" statement is repeated six times (!) with small changes - adding a new type (e.g. symlink) means adding testing logic and duplicating two more echo statements with small changes - checking for write permission means doubling the size of the code, producing every combination of read and write RIGHT WAY (less duplicate code): # find out what the pathname is and set a type type='pathname' if [ -d "$foo" ] ; then type='directory' elif [ -f "$foo" ] ; then type='file' fi # find out the permissions and set what we can do cando='not readable' if [ -r "$foo" ] ; then cando='readable' fi # produce the output for the given type echo "The $type '$foo' is $cando." - now there is only one output statement to edit, not six! - easy to add or remove new types (e.g. symlink, fifo, etc.) - adding a check for write permission adds four lines and changes the single output echo statement - easy to modify (no repeated changes needed) Less code is better code!