First, once users click OK, the error message is gone. When they start fixing errors, there's no reminder of what the errors were. No big deal, but at work people get interrupted all the time. Even a one minute interruption will make someone lose track.

The second problem: users might have made more than one mistake. Say a user got both pounds and ounces wrong. S/he gets an error message about the first problem, then the program stops. The user fixes that problem, only to get another error message. It might be more helpful to show both errors at one time.

Better error reporting

Here's what we want to happen when both pounds and ounces have bad input:

Differences from before:

Both errors are reported.

The error messages stay on the screen.

We should make other changes, too. A human might have two conversions to make, not just one. Here's a human, to remind you what they are.

Two eyes, one to three noses, you get the idea.

Here's what might happen. The human does the first conversion, without any typing mistakes.

She starts on the second task. She makes a typing mistake in ounces. She meant to type 5, but missed a little, and also hit the R key, just below 5:

The program shows an error, and also erased the output from the previous task – 1.588. Leave it there, and there's a small chance that a human who gets interrupted might think that 1.588 was the right answer. Erase the output cell when the Convert button is pressed, and that won't be an issue.

So that's one change: erase the output each time the program runs.

OK, now the human corrects the mistake and hits Convert:

The program erased the error message. Leave it there, and the human might think she hadn't fixed the mistake.

So, each time the program runs:

Erase the output.

Erase error messages.

Initializing

The two erasures are done every time the program runs. Geeks call that initializing, or initing, the program.

The flag pattern

Check input 1
Check input 2
Check input 3
if anything went wrong then
end
end if
process
output

The flag pattern helps. A flag is a variable that shows whether something happened. It's usually a Boolean, a data type. Booleans are either True, or False, and nothing else. Instead of using booleans for flags, you could use Strings ("yes" or "no"), Integers (1 or 0), whatevs. Booleans happen to be convenient for flags, but use what you want.

Pattern

Flag

Situation:

You want to keep track of whether something happened. For example, whether one of several input errors happened.

Actions:

Use a variable as a flag. Write code that initializes it, sets it if some condition occurs, then checks it. See this pattern's Explanation for an example.

Validation Sub

Here's the original validation code for pounds and ounces. The differences between validating pounds and validating ounces are marked.

'Validate pounds

userInput = Cells(3, 2)

If Not IsNumeric(userInput) Then

MsgBox "Sorry, pounds must be a number."

End

End If

If userInput < 0 Then

MsgBox "Sorry, pounds cannot be negative."

End

End If

pounds = userInput

'Validate ounces

userInput = Cells(4, 2)

If Not IsNumeric(userInput) Then

MsgBox "Sorry, ounces must be a number."

End

End If

If userInput < 0 Then

MsgBox "Sorry, ounces cannot be negative."

End

End If

ounces = userInput

The code is almost identical, so you should think, "Aha! Time for a Sub." The differences are:

The cell to check.

The label to use for the variable in the error messages ("pounds" or "ounces").

Those differences become params for the Sub. Here's one way to do it.

'Check that the value in a cell is a number that is not negative.

'Params:

' row: the row the cell to check is in, e.g., 3.

' dataLabel: text describing the data in the cell, e.g., "pounds".

' dataOK: returns true if the data is OK, false if it is not.

Sub checkCellValue(row As Integer, dataLabel As String, dataOK As Boolean)

Dim userInput As String

Dim errorMessage As String

dataOK = True

'Check if numeric.

userInput = Cells(row, 2)

If Not IsNumeric(userInput) Then

errorMessage = "Sorry, " & dataLabel & " must be a number."

dataOK = False

'Less than zero?

ElseIf userInput < 0 Then

errorMessage = "Sorry, " & dataLabel & " cannot be negative."

dataOK = False

End If

'Bad thing happened?

If Not dataOK Then

'Show the error.

Cells(row, 3) = errorMessage

Cells(row, 3).Font.Color = vbRed

End If

End Sub

​

The first few lines document the Sub, explaining each param. That's common. Human programmers often work in groups. One human might write checkCellValue, and the other humans use it. Documenting the params helps those other humans.

Notice the first two params. They're the differences betwixt uses of the sub. To use it to check pounds:

checkCellValue 3, "pounds", poundsOK

To check ounces:

checkCellValue 4, "ounces", ouncesOK

The third param is the result of the check.

Notice the message variable. Why does it exist? Another way to write the code:

'Check if numeric.

userInput = Cells(row, 2)

If Not IsNumeric(userInput) Then

Cells(row, 3) = "Sorry, " & dataLabel & " must be a number."

Cells(row, 3).Font.Color = vbRed

dataOK = False

ElseIf userInput < 0 Then

Cells(row, 3) = "Sorry, " & dataLabel & " cannot be negative."

Cells(row, 3).Font.Color = vbRed

dataOK = False

End If

​

Say the Bad Boss Human wanted errors to look like this:

With this code…

'Check if numeric.

userInput = Cells(row, 2)

If Not IsNumeric(userInput) Then

Cells(row, 3) = "Sorry, " & dataLabel & " must be a number."

Cells(row, 3).Font.Color = vbMagenta

dataOK = False

ElseIf userInput < 0 Then

Cells(row, 3) = "Sorry, " & dataLabel & " cannot be negative."

Cells(row, 3).Font.Color = vbMagenta

dataOK = False

End If

​

… we need to make the change for every error message. Two error messages, two changes. Eight error messages, eight changes.

With this code…

'Bad thing happened?

If Not dataOK Then

'Show the error.

Cells(row, 3) = errorMessage

Cells(row, 3).Font.Color = vbMagenta

End If

​

… we make the change only once, no matter how many error checks we have. Fewer chances of mistakes.

Klaus

Do real programmers think about things like that?

Tara

Aye. Programmers are always asked to change code. Making changes easy and less error prone is a Big Deal for RL programmers.

Big Idea

Make programs easy to change

Programmers spend a lot of time changing programs that have already been written. Make it easy,

The main program doesn't call checkCellValue directly. Instead, the main program calls the sub getInput, which does all the input validation, and returns a flag showing whether the input was all OK, or if there were any errors. The main program checks that flag to decide whether to continue.

Here's getInput:

'Input

Sub getInput(dataOK As Boolean, pounds As Single, ounces As Single)

Dim poundsOK As Boolean

Dim ouncesOK As Boolean

'Validate pounds.

checkCellValue 3, "pounds", poundsOK

'Validate ounces.

checkCellValue 4, "ounces", ouncesOK

If poundsOK And ouncesOK Then

'Input valid, check return the input data.

pounds = Cells(3, 2)

ounces = Cells(4, 2)

dataOK = True

Else

dataOK = False

End If

End Sub

It calls getInput twice, once for each input cell. If both are OK, then it grabs data from the cells into the variables pounds and ounces, and sets the flag dataOK to True. ​ Subs calling Subs is common.

Some questions for the class.

Tara

Why did Klaus and Jeremy create the sub getInput?

Jeremy

Hmmm… because it's one of the chunks in IPO?

Tara

Right! One reason to create a Sub is to make a chunk. It makes your program easier to understand, easier to test, and easier to change. It also makes it easier to have different people write different parts of the program.

So that's why they created a sub called getInput.

Why did they create the sub checkCellValue?

Lily

DRY. Don't repeat yourself. One Sub to do all of the input validation.