Monday, 17 January 2011

Assumptions in software

Its no secret that I prefer beautiful code.  I prefer code that reads like a set of comments - which provide a running commentary of the execution path with zero ambiguity and certain clarity that makes its intent crystal clear.

This makes good sense when you realise code is the only medium which allows us to communicate with machines - it allows us to speak the same language.  And just as in English, if you do not directly specify each and every possible fact - assumptions get made.

Now if an assumption is not was as intended strange results may occur - some may even say exceptional results. Bill Gates famously assumed in 1981 "640K ought to be enough for anybody" and we all know how wrong that was.

Exceptional circumstances are dealt with by most modern programming languages (in particular .NET) with - you guessed it - Exceptions. These set of classes are the equivalent of the software going crazy and refusing to carry on doing anything until the assumption is resolved - this is good, as we know that assumptions at best cause ambiguity and at worst are a show-stopper.

My beef is with a certain construct  that goes by the name "Else".  The problem is it encourages assumptions to be made but the main problem with this is computers don't have intuition.  Imagine the following scenario:

A school want to carry out a survey of the average age of students and would like to use some software to take input and perform the calculation.  I know if this was paper-based and an age came back as 135 my intuition would tell me this cannot be true - so I would write the software to also discard such a condition.

Once done the code looks like this:

private void addAge(int userInputtedAge)
        {
            if (userInputtedAge < 100)
            {
                //They've entered a valid range of ages
                calculateMean();
            }
            else
            {
                //The age wasn't less than 100
                MessageBox.Show("Please enter a valid age");
            }
        }

As you can see here using the else construct you have potentially introduced a bug if the user entered a perfectly valid int value of -10.

If I had re-written this software without using the else construct I would be forced to manually check for each expected condition:

private void addAge(int userInputtedAge)
        {
            if (userInputtedAge > 100)
            {
                //Bigger than 100
                throw new ArgumentOutOfRangeException();
            }
            if (userInputtedAge < 0)
            {
                //Less than zero
                throw new ArgumentOutOfRangeException();
            }

            calculateMean();
        }

Writing without the else has forced me to stop being lazy - and in the process I removed a potentially serious bug.

"So what?" you might be thinking.  The fact of the matter is any assumptions included in your code have the scope of being manipulated and abused.  Here is a real life example I found.

My conclusion is simple - if I'm expecting a particular range of inputs I should specifically test for each one.  If an input is not what is expected it is therefore exceptional and hence the software should throw a tantrum rather than carry on regardless.  Software should be designed in a way that causes bugs to show themselves - not hide away quietly.

So please - next time you think of writing Else stop and think "Do I really mean anything else?"

2 comments:

  1. Yes, but have you ever thought of getting rid of if's?

    Else is not necessarily a lazy construct. Sometimes the final result of user input is unknown. The real issue is that people don't do proper bounds checking and use if's improperly. I actually like the Lisp way here: if is a two-condition syntax: (if (test) (result) (else-statement). There is no else-if, that is a nested if. Your only other option is cond (which is arguably the single most powerful switching condition in any language). One of the major benefits of cond is that nothing *has* to fire: you need to specify a "t" condition for it to be able to run a specified code.

    But, maybe I'm approaching your point from another angle. Maybe "else" is bad (directly). Maybe instead there should only be "if" and "else if". Then, you would have a structure like:
    if test:
       result1
    else if test2:
       result2
    else if true:
       result3
    Immediately this cries out the very same question you are asking, "Do I really mean to have this handle all other conditions?"

    ReplyDelete
  2. I am in full agreement with you about about people using If/Else in situations where bounds need to be properly checked - this in fact highlights my problem with them.

    If/Else is probably one of the most basic programming concepts to understand - it's structured English and it reads exactly how you would explain the situation out loud. It is, unfortunately, this property which makes it easy to abuse. I believe there are too many programmers out there who just type out If/Else all day long without giving a second thought to whether what they're typing is the best way or safest way.

    This method relies completely on testing to catch the problems - whereas code should be written defensively. Again code should be written to show up bugs and not hide them away.

    I believe Else has its place - but currently it's being massively abused due to the ease of understanding its nature without a proper consideration of the consequences.

    ReplyDelete