You just saw how to take a program and break it into subs. Often, you go further. You break subs into subs.
Pounds to kilos again, again
Here's the structure of the pounds to kilos program again:
- Input
- Get and validate pounds
- Get and validate ounces
- Test if both inputs are zero
- Processing
- Output
We made subs for input, processing, and output. The input sub contained all three steps in the list.
Look at the list again, and you'll see two items that are almost identical:
- Input
- Get and validate pounds
- Get and validate ounces
- Test if both inputs are zero
- Processing
- Output
When you see that, you should think to yourself, "Self, that looks like a candidate for reuse. Maybe I could write one sub to do both things."
Here's the getInput
sub again:
Almost the same
The code for "Get and validate pounds" and "get and validate ounces" is almost identical. Here are the differences:
So the differences are:
- Which row the value comes from (3 or 4).
- The word used in the error messages ("pounds" or "ounces").
- Where the value goes (to
pounds
, or toounces
).
Let's see if we can create one sub we can reuse, first to validate pounds, and then to validate ounces.
Reduce, reuse, recycle
Let's begin by taking one of the two blocks of code, and giving it a name. We'll also replace the things that are different with some markers. I'll leave out the comments for now.
Hmmm. How to get different values for the markers? With our good friends, params! Let's make the code this:
For getPositiveNumber
to work for pounds
, we match up:
row
with 3.errorLabel
with "pounds".value
withpounds
.
For getPositiveNumber
to work for ounces
, we match up:
row
with 4.errorLabel
with "ounces".value
withounces
.
We match things up by the way the sub is called.
Remember that the params in a call to a sub have to match the params in that sub's signature in number and type, but not name. The sig for getPositiveNumber
has three params, so calls to getPositiveNumber
must have three params. The sig for getPositiveNumber
has an Integer
param, then a String
, and then a Single
. So calls to getPositiveNumber
must have an Integer
, then a String
, and then a Single
.
The first call will put 3 into the row
param, "pounds" into the errorLabel
param, and match up pounds
and value
. Whatever getPositiveNumber
puts into value
, it is really putting into pounds
in the caller.
The second call will put 4 into the row
param, "ounces" into the errorLabel
param, and match up ounces
and value
. Whatever getPositiveNumber
puts into value
, it is really putting into ounces
in the caller.
Here's how getInput
ends up:
The blocks of repeated code are gone, moved into the sub getPositiveNumber
. getPositiveNumber
is called twice. The new getInput
is simpler, and more reliable.
getInput
more reliable?
getPositiveNumber 3, "pounds", pounds
… don't have to have the same names as params in the sub's signature…
Sub getPositiveNumber(row As Integer, errorLabel As String, value As Single)
If they had to be the same, it would be harder to reuse code.
The param's data types have to match the data the caller gives. getPositiveNumber()
takes an integer, then a string, then a single. When we call it, we have to give it an integer, a string, and a single. In that order.
Also notice that as long as the caller gives the sub the right type of value, it doesn't have to be a variable. It can be a constant, like 3, or "ounces".
So:
- Call and sub must have the same number of params.
- Call and sub must use the same data types, in the same order.
- Call and sub don't have to use variables at all, or variables with the same name.
It's the data types that must match.
Summary
When you see two blocks of code that are almost identical, you should think to yourself, "Self, that looks like a candidate for reuse. Maybe I could write one sub to do both things."
Taking one of the two blocks of code, and give it a name. Replace the values that are different between the blocks of code with variables. Pass their values as parameters.