Keywords:

# A redo

Let's have a look at this sub business from another angle. Earlier, Klaus and Jeremy wrote a program to convert pounds and ounces in kilos. Here's some output: The program would handle three different types of input error. First, if an input was not a valid number. Second, if the input was negative: Third, if both pounds and ounces were empty, or zero. Here's their code:

1. Private Sub cmdConvert_Click()
2.     'Declare varibles.
3.     Dim pounds As Single
4.     Dim ounces As Single
5.     Dim kilos As Single
6.     Dim userInput As String
7.     'Input
8.     'Validate pounds
9.     userInput = Cells(3, 2)
10.     If Not IsNumeric(userInput) Then
11.         MsgBox "Sorry, pounds must be a number."
12.         End
13.     End If
14.     If userInput < 0 Then
15.         MsgBox "Sorry, pounds cannot be negative."
16.         End
17.     End If
18.     pounds = userInput
19.     'Validate ounces
20.     userInput = Cells(4, 2)
21.     If Not IsNumeric(userInput) Then
22.         MsgBox "Sorry, ounces must be a number."
23.         End
24.     End If
25.     If userInput < 0 Then
26.         MsgBox "Sorry, ounces cannot be negative."
27.         End
28.     End If
29.     ounces = userInput
30.     'Check if both are empty.
31.     If pounds = 0 And ounces = 0 Then
32.         MsgBox "Sorry, please enter pounds, ounces, or both."
33.         End
34.     End If
35.     'Processing
36.     pounds = pounds + ounces / 16
37.     kilos = pounds * 0.453592
38.     'Output
39.     Cells(6, 2) = Round(kilos, 3)
40. End Sub
Lines 2 to 6 declare variables:

1. Private Sub cmdConvert_Click()
2.     'Declare varibles.
3.     Dim pounds As Single
4.     Dim ounces As Single
5.     Dim kilos As Single
6.     Dim userInput As String
Lines 7 to to 29 get and validate the user input. There are three pieces to it. The first one handles the pounds. That's lines 8 to 18:

1.     'Validate pounds
2.     userInput = Cells(3, 2)
3.     If Not IsNumeric(userInput) Then
4.         MsgBox "Sorry, pounds must be a number."
5.         End
6.     End If
7.     If userInput < 0 Then
8.         MsgBox "Sorry, pounds cannot be negative."
9.         End
10.     End If
11.     pounds = userInput

The second part of the input validation handles ounces. That's lines 19 to 29.

1.     'Validate ounces
2.     userInput = Cells(4, 2)
3.     If Not IsNumeric(userInput) Then
4.         MsgBox "Sorry, ounces must be a number."
5.         End
6.     End If
7.     If userInput < 0 Then
8.         MsgBox "Sorry, ounces cannot be negative."
9.         End
10.     End If
11.     ounces = userInput
The third part of the input validation tests whether both inputs are zero:

1.     'Check if both are empty.
2.     If pounds = 0 And ounces = 0 Then
3.         MsgBox "Sorry, please enter pounds, ounces, or both."
4.         End
5.     End If
So, here's the structure of the program so far:

• Get input
• Get and validate pounds
• Get and validate ounces
• Test if both inputs are zero

I've left out the part declaring variables, because it doesn't affect the program's logic.

That's all for input. The next part of the program does the processing.

1.     'Processing
2.     pounds = pounds + ounces / 16
3.     kilos = pounds * 0.453592
Not much to it. Like many programs, most of the code is for validation.

The last part of the program does output:

1.     'Output
2.     Cells(6, 2) = Round(kilos, 3)
The complete structure of the program:

• Input
• Get and validate pounds
• Get and validate ounces
• Test if both inputs are zero
• Processing
• Output

There's that good ol' IPO.

# Sub it

Let's change the program, using subs. The first version will just makes subs for I, P, and O. Here's the original code, with the chunks marked. We'll make a sub for each one.

1. Private Sub cmdConvert_Click()
2.     'Declare varibles.
3.     Dim pounds As Single
4.     Dim ounces As Single
5.     Dim kilos As Single
6.     Dim userInput As String
7.     'Input
8.     'Validate pounds
9.     userInput = Cells(3, 2)
10.     If Not IsNumeric(userInput) Then
11.         MsgBox "Sorry, pounds must be a number."
12.         End
13.     End If
14.     If userInput < 0 Then
15.         MsgBox "Sorry, pounds cannot be negative."
16.         End
17.     End If
18.     pounds = userInput
19.     'Validate ounces
20.     userInput = Cells(4, 2)
21.     If Not IsNumeric(userInput) Then
22.         MsgBox "Sorry, ounces must be a number."
23.         End
24.     End If
25.     If userInput < 0 Then
26.         MsgBox "Sorry, ounces cannot be negative."
27.         End
28.     End If
29.     ounces = userInput
30.     'Check if both are empty.
31.     If pounds = 0 And ounces = 0 Then
32.         MsgBox "Sorry, please enter pounds, ounces, or both."
33.         End
34.     End If
35.     'Processing
36.     pounds = pounds + ounces / 16
37.     kilos = pounds * 0.453592
38.     'Output
39.     Cells(6, 2) = Round(kilos, 3)
40. End Sub

## Processing

1.     'Processing
2.     pounds = pounds + ounces / 16
3.     kilos = pounds * 0.453592
Start by giving the code a name:

1. 'Processing
2. Sub convertToKilos()
3.     pounds = pounds + ounces / 16
4.     kilos = pounds * 0.453592
5. End Sub
The second thing we need to do is add params (parameters). Params are values that go into and out of the sub. The purpose of the sub is to work out kilos. You can see that in the last executable line of code.

1. 'Processing
2. Sub convertToKilos()
3.     pounds = pounds + ounces / 16
4.     kilos = pounds * 0.453592
5. End Sub
So, that's one of the params. We need to know its data type: string, single, integer, whatevs. Look at the declarations in the original code:

1. Private Sub cmdConvert_Click()
2.     'Declare varibles.
3.     Dim pounds As Single
4.     Dim ounces As Single
5.     Dim kilos As Single
6.     Dim userInput As String
kilos is a Single. Let's add that param:

1. 'Processing
2. Sub convertToKilos(kilos as Single)
3.     pounds = pounds + ounces / 16
4.     kilos = pounds * 0.453592
5. End Sub
OK, that's the output of convertToKilos(). When I say "output" here, I'm taking about the output of the sub, not the program. We're just doing convertToKilos() now. We can forget the rest of the program for the moment. That's one of the Good Things about subs.

The code needs values for pounds and ounces. Let's add those as params.

1. 'Processing
2. Sub convertToKilos(pounds as Single, ounces as Single, kilos as Single)
3.     pounds = pounds + ounces / 16
4.     kilos = pounds * 0.453592
5. End Sub
pounds and ounces are inputs to the sub. That is, the sub takes them, and uses them to compute the output value, kilos. Input params are usually listed before output params in a sub's signature.

The last step is to add comments to the sub, explaining what it does, and what the params are. Don't forget this! These comments are what other programmers will use to call your sub.

1. 'Compute kilos.
2. 'Params:
3. '  pounds: pounds (input)
4. '  ounces: ounces (input)
5. '  kilos: result (output)
6. Sub convertToKilos(pounds as Single, ounces as Single, kilos as Single)
7.     pounds = pounds + ounces / 16
8.     kilos = pounds * 0.453592
9. End Sub
A common mistake is to try to declare params in the sub, like this:

1. 'Compute kilos.
2. 'Params:
3. '  pounds: pounds (input)
4. '  ounces: ounces (input)
5. '  kilos: result (output)
6. Sub convertToKilos(pounds as Single, ounces as Single, kilos as Single)
7.     Dim pounds As Single
8.     Dim ounces As Single
9.     Dim kilos As Single
10.     pounds = pounds + ounces / 16
11.     kilos = pounds * 0.453592
12. End Sub
Won't work. VBA will complain. When you use a param in a sub's signature, you're declaring it right there

How is convertToKilos() used? Here's the main program:

1. Private Sub cmdConvert_Click()
2.     Dim pounds As Single
3.     Dim ounces As Single
4.     Dim kilos As Single
5.     'Input
6.     getInput pounds, ounces
7.     'Processing
8.     convertToKilos pounds, ounces, kilos
9.     'Output
10.     outputKilos kilos
11. End Sub
(Notice how simple the program is!)

pounds, ounces, and kilos are declared in the main program, and used when calling the subs. So, you Dim variables in the caller. Subs get them as params.

Let's summarize what we've done. Here's the program's structure:

• Input
• Get and validate pounds
• Get and validate ounces
• Test if both inputs are zero
• Processing
• Output

A bunch of steps that are easy to understand. We've taken the processing step…

• Input
• Get and validate pounds
• Get and validate ounces
• Test if both inputs are zero
• Processing
• Output

… made a sub for it. That's the goal. A sub for each chunk of the program.

## The output sub

This one is easy, too. Here's the original code for it.

1.     'Output
2.     Cells(6, 2) = Round(kilos, 3)
We need to:

• Give the sub a name.

Here's the name part:

1. Sub outputKilos()
2.     Cells(6, 2) = Round(kilos, 3)
3. End Sub
A good name, that says exactly what the code does.

Now let's do the params. The code only needs one value:

1. Sub outputKilos()
2.     Cells(6, 2) = Round(kilos, 3)
3. End Sub
So:

1. Sub outputKilos(kilos as Single)
2.     Cells(6, 2) = Round(kilos, 3)
3. End Sub

1. 'Output kilos.
2. 'Params:
3. '  kilos: kilos (input)
4. Sub outputKilos(kilos As Single)
5.     Cells(6, 2) = Round(kilos, 3)
6. End Sub
Line 3 says that kilos is an input. That's correct. kilos is an input to this sub. Even though kilos is an output from the entire program, it's an input for this chunk of code.

Here's the main program again:

1. Private Sub cmdConvert_Click()
2.     Dim pounds As Single
3.     Dim ounces As Single
4.     Dim kilos As Single
5.     'Input
6.     getInput pounds, ounces
7.     'Processing
8.     convertToKilos pounds, ounces, kilos
9.     'Output
10.     outputKilos kilos
11. End Sub
You can see the call the outputKilos at the bottom.

# The input

OK, this is the complicated one. Here's the original code:

1.     'Input
2.     'Validate pounds
3.     userInput = Cells(3, 2)
4.     If Not IsNumeric(userInput) Then
5.         MsgBox "Sorry, pounds must be a number."
6.         End
7.     End If
8.     If userInput < 0 Then
9.         MsgBox "Sorry, pounds cannot be negative."
10.         End
11.     End If
12.     pounds = userInput
13.     'Validate ounces
14.     userInput = Cells(4, 2)
15.     If Not IsNumeric(userInput) Then
16.         MsgBox "Sorry, ounces must be a number."
17.         End
18.     End If
19.     If userInput < 0 Then
20.         MsgBox "Sorry, ounces cannot be negative."
21.         End
22.     End If
23.     ounces = userInput
24.     'Check if both are empty.
25.     If pounds = 0 And ounces = 0 Then
26.         MsgBox "Sorry, please enter pounds, ounces, or both."
27.         End
28.     End If
Let's make it into a sub.

The two steps to making a sub from existing code are:

• The name.
• The params.

The name is easier.

1. Sub getInput()
2.     'Input
3.     'Validate pounds
4.     userInput = Cells(3, 2)
5.     ...
6. End Sub
OK, now the params. This code gets input from the worksheet:

bc. userInput = Cells(3, 2)

There are no input params; the sub grabs data from the worksheet, and sends it out. Some input subs have input params, e.g., the names of files to open, or the numbers of cells to read from. We'll leave this one without input params.

What about output params? Here they are:

1. Sub getInput()
2.     'Input
3.     'Validate pounds
4.     userInput = Cells(3, 2)
5.     If Not IsNumeric(userInput) Then
6.         MsgBox "Sorry, pounds must be a number."
7.         End
8.     End If
9.     If userInput < 0 Then
10.         MsgBox "Sorry, pounds cannot be negative."
11.         End
12.     End If
13.     pounds = userInput
14.     'Validate ounces
15.     userInput = Cells(4, 2)
16.     If Not IsNumeric(userInput) Then
17.         MsgBox "Sorry, ounces must be a number."
18.         End
19.     End If
20.     If userInput < 0 Then
21.         MsgBox "Sorry, ounces cannot be negative."
22.         End
23.     End If
24.     ounces = userInput
25.     'Check if both are empty.
26.     If pounds = 0 And ounces = 0 Then
27.         MsgBox "Sorry, please enter pounds, ounces, or both."
28.         End
29.     End If
30. End Sub
All that code, just to get values for two params. That's normal in business programming.

Let's add them to the sig, and document them:

1. 'Input and validate pounds and ounces
2. 'Params:
3. '  pounds: value user entered (output).
4. '  ounces: value user entered (output).
5. Sub getInput(pounds As Single, ounces As Single)
6. ...
pounds and ounces are labeled as output. Why? Klaus
Because they're output from the sub. The sub hands them back to the main program. Tara
Right!

## A local variable

If we ran the program right now, we'd get an error: Every variable has to be defined somewhere. For a param, it can be defined in the caller, like this:

1. Private Sub cmdConvert_Click()
2.     Dim pounds As Single
3.     Dim ounces As Single
4.     Dim kilos As Single
5.     'Input
6.     getInput pounds, ounces
7.     'Processing
8.     convertToKilos pounds, ounces, kilos
9.     'Output
10.     outputKilos kilos
11. End Sub
12. Sub getInput(pounds As Single, ounces As Single)
13. ...
14. End Sub
15. Sub convertToKilos(pounds As Single, ounces As Single, kilos As Single)
16. ...
17. End Sub
18. Sub outputKilos(kilos As Single)
19. ...
20. End Sub
pounds, ounces, and kilos are all defined in the main program, then passed to the subs as params. That works fine.

Here's code from the input sub:

1. Sub getInput(pounds As Single, ounces As Single)
2.     'Validate pounds
3.     userInput = Cells(3, 2)
4.     If Not IsNumeric(userInput) Then
Line 3 has a variable that isn't from a param:

1. Sub getInput(pounds As Single, ounces As Single)
2.     'Validate pounds
3.     userInput = Cells(3, 2)
4.     If Not IsNumeric(userInput) Then
We could add userInput as a param, and define it in the main program.

1. Private Sub cmdConvert_Click()
2.     Dim pounds As Single
3.     Dim ounces As Single
4.     Dim kilos As Single
5.     Dim userInput As String
6.     'Input
7.     getInput pounds, ounces, userInput
8.     'Processing
9.     convertToKilos pounds, ounces, kilos
10.     'Output
11.     outputKilos kilos
12. End Sub
13. Sub getInput(pounds As Single, ounces As Single, userInput as String)
14.     'Validate pounds
15.     userInput = Cells(3, 2)
16.     If Not IsNumeric(userInput) Then
17.     ...
Is that a good idea? Hmm… userInput is not used by any other sub. Declaring it in the main program lets the program run, but it makes the code untidy, harder to understand, and more prone to bugs. And screaming.

userInput is a temporary variable, only used in getInput. So, we should define it in getInput:

1. Private Sub cmdConvert_Click()
2.     Dim pounds As Single
3.     Dim ounces As Single
4.     Dim kilos As Single
5.     'Input
6.     getInput pounds, ounces
7.     'Processing
8.     convertToKilos pounds, ounces, kilos
9.     'Output
10.     outputKilos kilos
11. End Sub
12. Sub getInput(pounds As Single, ounces As Single)
13.     Dim userInput As String
14.     'Validate pounds
15.     userInput = Cells(3, 2)
16.     If Not IsNumeric(userInput) Then
17.     ...
Better! The main program stays clean and tidy. userInput is declared just where it is needed. It doesn't show up anywhere else.

userInput is a local variable. The CPU allocates memory for it when it runs getInput. When the CPU exits the sub, it releases the memory, and userInput ceases to exist.

Use locals when you can. Reserve params for data that needs to pass between subs.

# The whole shebang

Here's all the code:

1. Private Sub cmdConvert_Click()
2.     Dim pounds As Single
3.     Dim ounces As Single
4.     Dim kilos As Single
5.     'Input
6.     getInput pounds, ounces
7.     'Processing
8.     convertToKilos pounds, ounces, kilos
9.     'Output
10.     outputKilos kilos
11. End Sub
12. 'Input and validate pounds and ounces
13. 'Params:
14. '  pounds: value user entered (output).
15. '  ounces: value user entered (output).
16. Sub getInput(pounds As Single, ounces As Single)
17.     Dim userInput As String
18.     'Validate pounds
19.     userInput = Cells(3, 2)
20.     If Not IsNumeric(userInput) Then
21.         MsgBox "Sorry, pounds must be a number."
22.         End
23.     End If
24.     If userInput < 0 Then
25.         MsgBox "Sorry, pounds cannot be negative."
26.         End
27.     End If
28.     pounds = userInput
29.     'Validate ounces
30.     userInput = Cells(4, 2)
31.     If Not IsNumeric(userInput) Then
32.         MsgBox "Sorry, ounces must be a number."
33.         End
34.     End If
35.     If userInput < 0 Then
36.         MsgBox "Sorry, ounces cannot be negative."
37.         End
38.     End If
39.     ounces = userInput
40.     'Check if both are zero.
41.     If pounds = 0 And ounces = 0 Then
42.         MsgBox "Sorry, please enter pounds, ounces, or both."
43.         End
44.     End If
45. End Sub
46. 'Compute kilos.
47. 'Params:
48. '  pounds: pounds (input)
49. '  ounces: ounces (input)
50. '  kilos: result (output)
51. Sub convertToKilos(pounds As Single, ounces As Single, kilos As Single)
52.     pounds = pounds + ounces / 16
53.     kilos = pounds / 2.20462
54. End Sub
55. 'Output kilos.
56. 'Params:
57. '  kilos: kilos (input)
58. Sub outputKilos(kilos As Single)
59.     Cells(6, 2) = Round(kilos, 3)
60. End Sub
Ah!

# Top-down versus bottom-up Lily
OK, we started this lesson with code that the dudes had written. Then we made some subs, and then the main program.

Is that how you work when you don't have existing code? Tara
No. What we did here is called bottom-up. We made a sub – computeKilos – and then worked out how to call it.

When I write a program, I don't start with code from the dudes. I start from scratch. I work top-down. I work out the structure, often as bullet points. Then I write the main program, and then the subs.

That's what you should do, too. We did this one bottom up, so you could better understand how subs work. But top-down is the way to go.

The process of taking a chunk of functionality (like getting input, doing processing, or showing output) and making a subroutine out of it is called encapsulation. It's one of the Big Ideas of programming. Encapsulation is key to writing complex software. Take a task, break it into chunks, with a sub for each one. If those chunks are complex, break them up into smaller chunks. And so on.

Big Idea

Encapsulation

Take a chunk of functionality, and make it into a subroutine.

# Summary

Earlier, Klaus and Jeremy wrote a program to convert pounds and ounces in kilos. We broke out the I, P, and O code into their own subroutines.