DRY stands for Don't Repeat Yourself. One of the Big Ideas of programming.
So far, each of the subs has been called just once per program. Let's do something different.
Retaxing
Remember that strange tax return program? The worksheet:
Let's add input validation. We'll just concentrate on the income part:
Here's the input code without validation.
incomeWages = Cells(5, 2)
incomeInvestments = Cells(6, 2)
incomeImaginary = Cells(7, 2)
incomeBeauty = Cells(8, 2)
All of the input is supposed to be numeric, zero or greater, with no empty cells. If there's an error, let's display an error message to the right of the cell, like this:
Here's code that would check wages income and investments income:
'Check wages income
userInput = Cells(5, 2)
If Not IsNumeric(userInput) Then
Cells(5, 3) = "Please enter a number."
Cells(5, 3).Font.Color = vbRed
End
End If
If userInput < 0 Then
Cells(5, 3) = "Please enter a number greater than zero."
Cells(5, 3).Font.Color = vbRed
End
End If
incomeWages = userInput
'Check investment income
userInput = Cells(6, 2)
If Not IsNumeric(userInput) Then
Cells(6, 3) = "Please enter a number."
Cells(6, 3).Font.Color = vbRed
End
End If
If userInput < 0 Then
Cells(6, 3) = "Please enter a number greater than zero."
Cells(6, 3).Font.Color = vbRed
End
End If
incomeInvestments = userInput
The code is almost identical. There are only a few differences:
userInput = Cells(6, 2)
If Not IsNumeric(userInput) Then
Cells(6, 3) = "Please enter a number."
Cells(6, 3).Font.Color = vbRed
End
End If
If userInput < 0 Then
Cells(6, 3) = "Please enter a number greater than zero."
Cells(6, 3).Font.Color = vbRed
End
End If
incomeInvestments = userInput
We could just cut-and-paste the code for each input cell. Why would that be a Bad Thing?
Klaus
We'd have to make a few changes to each thing we pasted. It's be easy to make a mistake.
Simon
And hard to find mistakes we did make.
Tara
Good! Anything else?
Lily
What if we wanted to change the validation code? Maybe change the text of the error messages, or change the color. We'd have a lot of code to change.
Tara
Yes, that's right.
Jeremy
Simon talked about finding mistakes. If there are, like, twenty copies of the validation code, but each one changed a little, that's a lot of testing!
Tara
Indeed it is! Good thinking, Jeremy.
One sub to rule them all
Let's look at the validation code again. It's almost identical for every input cell. There are only a few differences:
userInput = Cells(6, 2)
If Not IsNumeric(userInput) Then
Cells(6, 3) = "Please enter a number."
Cells(6, 3).Font.Color = vbRed
End
End If
If userInput < 0 Then
Cells(6, 3) = "Please enter a number greater than zero."
Cells(6, 3).Font.Color = vbRed
End
End If
incomeInvestments = userInput
If we put this in a sub, it'd be something like:
Sub validateInput()
userInput = Cells(ROW, 2)
If Not IsNumeric(userInput) Then
Cells(ROW, 3) = "Please enter a number."
Cells(ROW, 3).Font.Color = vbRed
End
End If
If userInput < 0 Then
Cells(ROW, 3) = "Please enter a number greater than zero."
Cells(ROW, 3).Font.Color = vbRed
End
End If
VARIABLE = userInput
End Sub
Here's part of the worksheet again:
We want the sub to work for row 5, and put the data into, say, incomeWages.
We want the same sub to work for row 6, and put the data into, say, incomeInvestments.
Lily
Oo! Parameters!
Jeremy
Yeah! I was thinking the same!
Parameters are your friend
Sub validateInput(row As Integer, destination As Single)
Dim userInput As String
userInput = Cells(row, 2)
If Not IsNumeric(userInput) Then
Cells(row, 3) = "Please enter a number."
Cells(row, 3).Font.Color = vbRed
End
End If
If userInput < 0 Then
Cells(row, 3) = "Please enter a number greater than zero."
Cells(row, 3).Font.Color = vbRed
End
End If
destination = userInput
End Sub
If we call validateInput with 5 for row, it will check cell (5, 2), and show error messages in cell (5, 3).
If we call validateInput with 6 for row, it will check cell (6, 2), and show error messages in cell (6, 3).
How does validateInput get 5 for row one time, and 6 another? Here's how we would call validateInput:
validateInput 5, incomeWages
validateInput 6, incomeInvestments
validateInput 7, incomeImaginary
validateInput 8, incomeBeauty
The first time validateInput is called, row is 5. The next time, it's 6. The next time, it's 7. The next time, it's 8.
How does validateInput send data back to its caller? Remember how parameters work. Here's the first call.
validateInput 5, incomeWages
Parameters map the sub's memory into the caller's memory. So, when validateInput changes what's in destination, it changes incomeWages in the main program.
Here's the second call.
validateInput 6, incomeInvestments
When validateInput puts data into destination, it's really putting the data into a variable in the caller's memory space. Which variable? Whatever the caller uses as the second parameter.
Jeremy
Wait. When we used parameters earlier, the names were the same. Like, we had a sub signature like this:
Sub getInput(quality As String, size As String, region As String)
The call would be like this:
getInput quality, size, region
The parameter names in the sig and the call were the same.
But they don't have to be?
Tara
That's right. Before, we were using subs to thunk. Each sub was one chunk. We'd only call each sub once.
But now, the whole point is to reuse the sub, call it several times. That's more convenient when the names don't have to match.
What matters is the order of the parameters. So we could have done this:
getInput quality, size, region
...
Sub getInput(goodness As String, bigness As String, where As String)
quality maps to goodness, size to bigness, and region to where.
Exercises
Summary
You can call a sub many times. If you change the parameters in the call, you change the variables the sub affects.