Chapter 8: Loops | Chapter 10: Functions |
In this chapter we’ll follow a very simple example that uses the concepts we’ve learned so far. We’re going to write a program that calculates the tip at a restaurant.
There are many ways to go about designing and writing a program. One way that often works well is to imagine someone using the program, and describe what that would be like. This has the advantage of making sure the program will be useful once written. (This may seem like an obvious thing to want from a program, but you’d be surprised how many programs end up useless because their designers didn’t really think about what they would be like to use.)
So we’re imagining our user, call him Tom, at a restaurant. The bill comes and he needs to write down the tip, and wants to use the program to calculate the tip for him. Therefore, at the very least, the program must display the tip for Tom to write down. But the tip depends on the value of the bill, so at some point the program must be given this information. This leads us to the following interaction:
That’s called a use case—a fictitious but realistic interaction between the user and the program. They become especially useful when designing larger programs. A use case for Microsoft Word might be, “The user wants to add a table of contents to their document.” Sometimes after writing a use case it becomes apparent that the user needs to do far too much work, or understand too many abstract concepts, or perform too many steps to do achieve something simple. They are a great way to see the world from the user’s point of view.
Now we want to see the program from the computer’s point of view. Let’s rephase the above steps from the point of view of the program:
Let’s translate that into code:
print "What was the value of the bill?" bill = input() print tip
The first line will prompt the user, the second will wait until the user enters the value, and the third will display the tip. But remember from the chapter on variables that a variable must be set before it can be used. In the third line above, the variable tip is used, but it’s never set anywhere in the program. We must calculate the tip somewhere. Let’s add that to our steps:
How should we turn that new third step into code? We know that we want to set the tip variable. Therefore we know we’ll be using an assignment statement, so we’ve got at least this:
tip =
Now we must figure out what to put on the right side of the equal sign. Let’s say that we want the tip to be 15% of the bill. Calculating 15% of something is like multiplying it by 0.15. And we want 15% of the bill. So the math for that is:
bill * 0.15
That gives us this line:
tip = bill * 0.15
Remember what this does: it retrieves the value of the bill variable from memory, multiplies that value by 0.15, and stores the product in memory, associating it with the new variable tip. And the whole program looks like this:
print "What was the value of the bill?" bill = input() tip = bill * 0.15 print tip
Let’s try it:
What was the value of the bill?
12.50
1.875
Note that the user doesn’t enter a dollar sign (or any currency), and the output doesn’t have it either. The computer just deals with numbers, and you have to go (quite far) out of your way to make it understand that you’re dealing with money. (Microsoft Excel makes this look easy, but when you’re writing your own programs, it’s not.)
Notice also that although $1.87 is right, the computer prints out an extra “5” afterwards. That’s the precise value of 15% of $12.50. We haven’t told the computer to round to the nearest penny, so it didn’t. That’s okay, the user can do it in his head. (It’s not very hard to do that, but we won’t get distracted with that here.) Let’s try another value for the bill:
What was the value of the bill?
12.92
1.9379999999999999
Whoa! The $1.93 looks right, but what’s all the junk after that? What the computer means here is “1.938”, but instead prints out a value that’s very very close to that. The reason is quite complicated, but basically the computer doesn’t work in base 10 (decimal) like us humans, but instead works in base 2 (binary). In base 2 some numbers (like 12.92) are difficult to represent, just like in base 10 some numbers (like 3.333 repeating) are difficult to represent. So numbers we think should be easy, like the tip 1.938 in this case, look wrong when displayed. We can fix that by rounding to the nearest penny, but we’ll leave that to another time.
Let’s instead add some features to our program. Tom, our imaginary user, sometimes gets good service and sometimes gets bad service. For good service he’d like to tip 20%, and for bad service he’d like to tip 15%. We now have two use cases:
and:
From the computer’s point of view:
Let’s get more specific in that second-to-last step:
The first four lines of code are pretty easy:
print "What was the value of the bill?" bill = input() print "How was the service?" service = input()
Now we want to calculate a different tip depending on the quality of the service. We can use an if statement:
if service == "good": tip = bill * 0.20 else: tip = bill * 0.15
If that didn’t make sense, review the chapter on conditionals. The last line is just as before:
print tip
Let’s try it with a round number for the bill, like $10:
What was the value of the bill? 10 How was the service? good 2.0
and:
What was the value of the bill? 10 How was the service? bad 1.5
Note that the value isn’t displayed like it would be for currency (2.00 and 1.50). Again that’s because the computer is just dealing with numbers, not with money.
Looks like our program is working and it’s useful. There’s something about the program that’s not ideal, though. In this if statement:
if service == "good": tip = bill * 0.20 else: tip = bill * 0.15
the calculation is done in two separate places, with one number (the percentage) different. That’s what we wanted, but any time you see repeated code like this you should wonder whether you shouldn’t find a better way to write the program. Repeated code means that if you change one line then you have to change the other. The calculation might get more complicated, like removing the tax from the bill before calculating the tip. (Some people calculate the 15% from the pre-tax subtotal.) You’d then end up with something like this if your local tax is 8%:
if service == "good": tip = bill / 1.08 * 0.20 else: tip = bill / 1.08 * 0.15
In this simple example it’s not too bad, but you can imagine a more complex program or calculation where having to update both identically would risk bugs. You may change one and not the other. It would be better to only do this calculation in one place. The next chapter will show you one way to do this (with functions), but here we’ll use another. Let’s imagine that you’re forced to only have the above calculation in one place. Well the calculation takes into account the percentage, which is different depending on the service, so clearly the exact percentage can’t be a part of that calculation. Looks like a good opportunity for a variable:
tip = bill * percentage
Now we have to set the percentage variable first. Here is the new tip-calculating code:
if service == "good": percentage = 0.20 else: percentage = 0.15 tip = bill * percentage
Let’s review step by step what’s going on with this new version. The value of the service variable is retrieved from memory and compared to the string “good”. If they are equal (i.e., if the user entered “good”), then the variable percentage in memory will be set to the value 0.20. Otherwise (if the user enters anything else, such as “bad”) the variable percentage will be set to the value 0.15. Then, after that’s done, the tip variable will be set to the product of the bill variable (given to us by the user) and the percentage variable (which was just set).
Now if you want to change your program to take into account tax, there’s only one line to change:
if service == "good":
percentage = 0.20
else:
percentage = 0.15
tip = bill / 1.08 * percentage
That’s fewer opportunities for errors, and when your program gets big, you want as few of those opportunities as possible. The process we just went through is called refactoring. That’s rearranging the code so that it functions identically, but gives us some advantage, in this case reducing the number of times the tip-calculating equation is in our program. Here’s our entire program:
print "What was the value of the bill?" bill = input() print "How was the service?" service = input() if service == "good": percentage = 0.20 else: percentage = 0.15 tip = bill * percentage print tip
Our program is getting pretty complicated. If we were to
come back to it three months from now, we may have a hard
time understanding what it’s doing. Let’s write down,
within the program itself, some notes to explain to future
readers of the code (perhaps ourselves) what’s going on. We
can do that with comments. Comments
are notes in the code that are purely for the benefit of the
programmer. The computer ignores them completely. In our
language, comments start with a #
character, sometimes pronounced “pound”
or “hash”. This is
our entire program, with comments:
# Get the value of the bill from the user. This # is the total amount, including tax, but without # any currency symbol. print "What was the value of the bill?" bill = input() # Find out how the service was. This should be # the word "good" or "bad". Any word other than # "good" will be considered the same as "bad". print "How was the service?" service = input() # Good service gets a 20% tip. Bad service gets 15%. if service == "good": percentage = 0.20 else: percentage = 0.15 # Calculate and display the tip. The tip here is based # on the whole bill, including tax. XXX We could # remove the tax first. Perhaps ask the user whether they # want the tax included in the calculation? tip = bill * percentage print tip
Notice that the comments not only explain what’s going on (“Calculate and display the tip”), but also point out various requirements (“but without any currency symbol”) and subtleties (“Any word other than ‘good’ will be considered the same as ‘bad’”). It’s nice to break the program into logical blocks, like we did above. The two lines that ask for and get the value of the bill go together. They should be visually grouped, with a blank line between them and the next block. (Blank lines are ignored by the computer.)
Note the comment that starts with “XXX”. That’s a common thing to write in comments when there’s a potential problem or open question. Here the comment points out that the program could be improved by asking the user whether they’d like to remove the tax before calculating the tip. If you’re ever looking for ways to improve your program, you can easily search for “XXX” in your text editor.
Use comments aggressively. It’s even useful to write comments before you write the code, or as you’re writing it, rather than afterwards as we did here. My friend Drew Olbrich invented a programming style called Extreme Commenting. His comments are very verbose and detailed. It seems like a lot of work when initially writing them, but you sure are glad six months later when you have to understand and modify that code.
Chapter 8: Loops | Chapter 10: Functions |