Today's lab explores some Java statements that you can use to write more sophisticated methods. You'll work on a problem whose solution requires more complicated behaviors than we have seen thus far, and you'll see the built-in statements of Java that provide these behaviors.
First, a word about naming convensions in this manual: often I
will refer to a method by its name using parentheses, but
ignoring its parameters. For example, I'll often
refer to the method main()
even though this method
does have a String[]
parameter named
args
. If there's an ambiguity or if I need to
highlight the parameter, then you may find a name or data type in
the method name; otherwise, the parenthese are there just to make
it clear that I'm talking about a method.
Our problem in this lab exercise is to write an interactive tax computation program that the owner of small online business might use to compute the cost of items being sold to buyers in states with different sales tax. To make the problem more interesting, we will assume that an additional national 1% luxury tax has been enacted for any purchase over one hundred dollars.
Recall that object-centered design involves several steps, including instructions on how and when to create new methods:
Each method (including the main program) gets its own OCD.
Do this...
edu.institution
.username
.hotj.lab05
Tax
.TaxTest
.TaxDriver
.lab05.txt
in your Exercise
Questions
folder.As always, spending a bit of time planning how to attack our problem will result in a better solution. Let's begin by visualizing and writing down how the driver program might behave. Here's one option:
This program computes the cost of individual items given a price and a sales tax. Enter the number of items: 3 Enter the item name, cost and tax rate for item 1: kleenex 25 5.25 The cost of kleenex is $26.31 with tax. Enter the item name, cost and tax rate for item 2: eggs 1.50 6.15 The cost of eggs is $1.59 with tax. Enter the item name, cost and tax rate for item 3: tires 350 6.15 The cost of tires is $374.03 with tax.
Consider the first item: "kleenex" costs $25 (must be a really big box), and it's sold with a 5.25% sales tax. Its total cost will be its original cost (25) plus the sales tax (25*0.0525), which comes to a total of 26.3125 (i.e., 25 + 25*0.0525).
The cost of the "eggs" is computed the same way.
Since the "tires" cost more than $100, a 1% luxury tax on the amount above $100 is added in addition to the normal sales tax.
Put into words, the main program (i.e., the driver) should do the following:
Behavior of driver The program should display on the screen a greeting, and then prompt for and read in the number of items to process. Then our program should read in and process each item.
The responsibility of this driver is to handle the top-most details: discover how many items, and process each one. The details of the processing will be left to another method.
If we identify the nouns in this behavioral description, we get the following list:
Objects Description Type Kind Name the screen Screen
varying theScreen
A greeting String
literal -- A prompt for input String
literal -- The number of items int
varying numberOfItems
The number of the current item int
varying (loop index) itemNum
The keyboard Keyboard
varying theKeyboard
From this list, we can build a precise specification of how our program is to behave:
Specification:
input (keyboard): The number of items.
output (screen): The result of processing each item.
For a computation method, the specification revolves around the parameters and return value. For a driver (which is what you have here), the specification centers on the input and output of the program.
Do this...
Write a main()
driver method in the
TaxDriver
class, and write the Javadoc for main()
using the information in this design.
When you identify the operations in our behavioral description, you get this list:
Operations Description Predefined? Name Library Display a String
(greeting, prompts, labels)yes ann.easyio
Read an int
from the keyboardyes readInt
ann.easyio
Process an item no processItem(num)
same class repeat processing for each item yes for
loopbuilt-in
The I/O operations are provided for us by the
ann.easyio
package; readInt()
is fairly
straightforward. You should note that really bad things happen when
your program tries to read in an int
(or any
particular data type) but finds a word (or otherwise mismatching
data type) instead. For now, you don't have to worry about errors
in the user's input.
There is no predefined Java built-in or method to process an item.
Do this...
Look at the operations entry for the "process an item"
operation.
Presently it's undefined, but even while writing this driver
method, you can choose a name and a library. Since this operation
is so closely tied with this driver, you might as well put it in
the same class (i.e., TaxDriver
). And you can give it
a name, Since the message printed when processing an item contains
the number of the item (e.g., "item 1
"), the
processItem()
method will need the number of the
current item; so we even know that it takes one argument.
The last operation is a new Java statement called the
for
statement which repeats a group
of statements a specified number of times. We'll deal with this
statement when we write the code; for now, think of it as counting
on your fingers.
Even without knowing the details of how you will process an item, an algorithm can be formed from the operations.
Algorithm
- Display a greeting on
theScreen
plus a prompt for the number of items.- From
theKeyboard
, read an integer, storing it innumberOfItems
.- For each value
itemNum
in the range 1 tonumberOfItems
:End loop.
- Process item #
itemNum
.
Testing a driver with JUnit is tricky at best. Let's settle on testing the computation method with a JUnit test case, but the driver you'll have to test by hand. It's not a tricky driver, so this should be okay.
You should have this code already in your editor (and some Javadoc comments):
public class TaxDriver { public static void main(String[] args) { } }
Do this...
To make sure that this much is correct before you add to it, take
a moment to compile your
code.
Taking a quick look at the list of objects, you will notice that
we need theKeyboard
and theScreen
. We can
define them when we need them (e.g., theKeyboard
isn't
needed until Step #2, but it's often helpful to declare I/O
variables as early as possible. Generally we will not include
declarations for our I/O variables in our algorithms, but they're
very necessary in the code.
Do this...
Declare and initialize theKeyboard
and
theScreen
at the beginning of main()
.
This will mean importing the proper libraries at the top of the
file.
Let's got through the algorithm for the driver step by step. Have the algorithm handy.
Step #1. This step can be performed using a Java output statement, which we have seen before:
theScreen.print(Value1
+Value2
+ ... +ValueN
); theScreen.println(Value1
+Value2
+ ... +ValueN
);Each
ValueI
is some Java object (mostlyString
literals and some variables). You can use eitherprint()
orprintln()
; the latter adds a newline automatically to the output.Do this...
Add an output statement tomain()
that displays the following message:This program computes the cost of individual items given a price and a sales tax. Enter the number of items:Then compile your program, and run it. You should see this output, and the driver ends.
Step 2. The second step is an input expression:
theKeyboard.readInt()This is an expression that reads in an integer from the keyboard. Remember that expressions are incomplete thoughts (the missing semi-colon is deliberate, not a mistake!). Step #2 of the algorithm says to read in
numberOfItems
from the keyboard. So the complete statement is a declaration fornumberOfItems
which uses the input expression above for initialization.Do this...
Write the resulting declaration statement. Again, compile and run your program to make sure it's okay so far.As long as it executes without errors, you're fine; the following statement will actually do something with the value.
Step 3. The primary purpose of Step 3 is to count from 1 to
numberOfItems
, and repeat another statement (i.e., process an item) each time.For situations like this that require repetitive behavior based on counting, Java supplies the
for
statement. Thefor
statement can use its own local variable, called a loop-control variable, to do the counting.A simplified general form of a counting
for
loop that counts fromfirstValue
tolastValue
looks like this:
counting-for-loop pattern for (intloopVar
=firstValue
;loopVar
<=lastValue
;loopVar
++) {bodyStatements
}
loopVar
is the loop-control variable.firstValue
andlastValue
are the beginning and ending values forloopVar
, respectively and inclusively.bodyStatements
is the list of statements that are executed repeatedly.- Execution:
bodyStatements
is repeatedly executed withloopVar
set to different values betweenfirstValue
andlastValue
, inclusive and in increasing order.- Warning: there's no semi-colon before the opening curly brace!
The
bodyStatements
are usually just called the body of the loop.The behavior of this statement is as follows:
Algorithm
loopVar
is declared and initialized tofirstValue
.- If
loopVar
is less than or equal tolastValue
.Otherwise
Statements
get executed.is executed.
loopVar
++- Restart this step.
- The loop stops, and control goes to the statement after the loop.
So how does the pattern match up with our algorithm?
numberOfItems
indicates how many items we have, assuming we start counting from 1. So,firstValue
is 1, andlastValue
isnumberOfItems
. ThebodyStatements
will take care of themselves in just a bit. So the only choice now isloopVar
, but even that is covered in the object list byitemNum
.Do this...
Using the pattern above, write the countingfor
loop for Step #3. Compile and run the program; you won't see any visible evidence yet that the loop is running, but you shouldn't get any errors.If the user enters a negative value for
numberOfItems
, the body of the loop will not be executed.itemNum
starts at 1, which is already greater than any negative number. So the test will be false immediately, making any explicit checking for a negative number unnecessary (unless you wanted to print an error message).If your program never quits, you're in an infinite loop---a loop that runs forever and ever. Generally this is due to a syntax error or because the initialization or test are done wrong. You'll have to forcibly stop the program.
As for the body of the loop, it's just a single statement that invokes
processItem()
. The method does require one arugment: the number of the current item; the object list tells you where this number is stored. This method is the next method that you work on, so as long as everything has compiled and run fine up to this point, adding in a call to this method is fine. The compiler will complain, and fixing that is your next step.Do this...
Add a call toprocessItem()
as the body of the loop.Incidentally,
processItem()
, it was decided above, will be declared in the same class as the driver,TaxDriver
. Be sure to pass initemNum
as an argument.
That finishes up your work on the main()
method.
Now it's time to turn to the processItem()
method
(which is the one compilation error you should have right now).
To process an item involves this behavior:
Behavior of TaxDriver#processItem()
The method first receives the number of the current item. For this item, the method displays a prompt for the item name, the item cost, and the tax rate. Then, these three pieces of information are read in from the keyboard. The total cost of the item is computed, and a friendly message is printed reporting the total cost.
Objects of TaxDriver#processItem()
Description Type Kind Name the number of the current item int
paramater itemNum
the screen Screen
varying theScreen
A prompt for input String
literal -- The keyboard Keyboard
varying theKeyboard
An item's name String
varying name
An item's cost double
varying cost
An item's tax rate double
varying taxRate
An item's cost double
varying totalCost
From these objects, you get this specification:
Specification of
TaxDriver#processItem()
:receive:
itemNum
, the number of the current item.
input (keyboard): The item's name, cost, and tax rate.
output (screen): The item's name and total cost.
Technically, this method is an extension of the driver (as opposed to being a computation method). So the specification is mostly in terms of input and output. The method does receive one value which the driver itself sets up.
Do this...
Write a processItem()
stub in the
TaxDriver
class, and write the Javadoc for the method. You'll need
to remember how to specifiy a "no return type".
If we identify the operations in our behavioral description, we get this list:
Operations of TaxDriver#processItem()
Description Predefined? Name Library Display anything yes ann.easyio
read a name (i.e., a "word") from the keyboard yes readWord
ann.easyio
read a double
from the keyboardyes readDouble
ann.easyio
compute an item's totalCost
no computeTotalCost() Tax
Do this...
Make careful note how you're going to read in an item's name.
The following distinctions matter only during input. No matter
how you read in the name, it will always be a
String
.
Keyboard
provides no way to read in a mere
"string".Keyboard
provides a method for this.Keyboard
provides a
method for this as well.No matter how you try to solve this problem, it will be disappointing. Input is very difficult, especially from the keyboard on a console. It is easiest to just require your users to enter one single word for the name of an item, and so consider the name of an item to be a "word".
There is no predefined Java capability to compute
totalCost
, so this will lead us to another
OCD and another method. Since the responsibility of this other
method will be computation (not input or output), you'll
put it in another class (the current class's responsibility is
input and output).
Algorithm of TaxDriver#processItem()
- Receive
itemNum
.- Display a prompt for the name, cost and tax rate of item #
itemNum
.- From
theKeyboard
, read inname
,cost
, andtaxRate
.- Compute
totalCost
, usingcost
andtaxRate
.- Display
name
andtotalCost
.
Do this...
Make sure you have a stub method for processItem()
,
enough to make the compiler happy.
You will notice that you need theKeyboard
and
theScreen
again. Because of a variable's
scope, the previous declarations in
main()
do not work in this new method. As soon as the
compiler reaches the end of main()
, those declarations
are inactive. So these variables must be redeclared in
this new method; using the same names is perfectly fine since you
have a brand new scope.
Do this...
Declare and initialize theKeyboard
and
theScreen
at the beginning of
processItem()
.
And now for coding up the algorithm of
processItem()
:
Step #1. As with any method, receiving a value is already done when you declare your parameters. This method receives
itemNum
; you should have a parameteritemNum
.Step #2. This is a normal output statement, like those we have seen before. One trick to the output is that we want to include the number of the item, like so:
Enter the name, cost and tax rate for item 22:This is the output you should get when
itemNum
is 22.Do this...
Add an output statement that prints out the appropriate statement. Compile and run your program; you should see some visible evidence that the loop is executing. Run your driver several times; even try a negative number.Step #3. Once the output statement works, you need to read in some values as this next step. Since we need to read in three values, this step will actually take three statements.
The statements themselves are similar to the input statement in
main()
, except for the method you invoke ontheKeyboard
. It'll go easiest for you if you heeded the hint just after the operations list.Do this...
Add three statements that declare and initialize the appropriate variables with values read in from the keyboard. Compile and run your program.Anytime you code up an input operation, you should consider the possibility of human error: what could the user do to foul up your program? There are several possibilities here:
- The user could enter a non-numeric value for
cost
.- The user could enter a negative value for
cost
.- The user could enter a non-numeric value for
taxRate
.- The user could enter a negative value for
taxRate
.These are tricky problems. The non-numeric problems are handled somewhat by the
read
methods of theWhatever
()Keyboard
object; eventually we will look at how you can process the complaints from these methods. For now, do not worry about non-numeric input for numeric variables.This leaves the problem of negative values. You could test for negative values, and there are good reasons to test for them here. However, this is a responsibility we'll save for the computation method.
Step #4. As noted before, the total-cost operation is not predefined, and so we will design and build a method to perform it. At this point we can at least note that we could call the method
computeTotalCost()
. It'll need the cost of item and it's tax rate, so you'll have to pass them in as arguments; assume that the cost is listed before the rate. Since this method will live in theTax
class, you have all you need to write the method invocation.Do this...
Write a declaration statement fortotalCost
that initializes it to be the result of callingcomputeTotalCost()
of theTax
class. You'll have to pass the cost and the tax as arguments to this method. Wait on the resulting compiler error.Step #5. The last step is easily implemented using an output statement.
Do this...
Add an output statement that would print this given the proper inputs:The cost of milk is $1.784332 with tax.
This completes the coding of the driver method. You now have to write the code for the computation method.
To compute totalCost
for an arbitrary
item, our method needs the cost
and
taxRate
. Since these values will differ from
item to item, our method should receive these values from its
caller. Remember to keep the responsibilities straight: the methods
in TaxDriver
do the input and output. You're now
completely done with input and output! Everything else is done
through paramaters and returned values.
Behavior of computeTotalCost()
Our method should receive the cost and tax rate from its caller.
- If the cost or tax rate is negative, the method should generate an error.
- If the cost is less than or equal to the minimum cost for the luxury tax:
- Then let the total cost be the cost plus the cost times the tax rate divided by 100.
- Otherwise,
- Let the regular cost be the cost plus the cost times the tax rate divided by 100.
- Let the luxury base be the cost minus the luxury-tax minimum.
- Let the luxury surcharge be luxury-tax rate times the luxury base divided by 100.
- Let the total cost be the sum of the regular cost and the luxury surcharge.
It assumed that tax rates are given in percentage form (e.g.,
5
for 5%); hence, all computations with tax rates must be divided by 100.
That's a lot of English prose to describe mathematical formulas; when writing your own behavior paragraphs, feel free to write mathematical formulas.
We can identify the following objects in this description:
Objects Description Type Kind Movement Name item cost double
varying received cost
tax rate (percent) double
varying received taxRate
negative parameter error IllegalArgumentException
-- thrown -- minimum luxury cost double
constant local MIN_LUXURY_COST = 100.00
total cost double
varying returned totalCost
regular cost double
varying local regularCost
luxury surcharge double
varying local luxurySurcharge
luxury base double
varying local cost - MIN_LUXURY_COST
luxury tax rate double
constant local LUXURY_TAX_RATE = 1.0
We can thus specify the behavior of our method as follows:
Specification:
receive:
cost
andtaxRate
.
precondition:cost
>= 0 andtaxRate
>= 0.
return: the total cost.
This specification introduces a precondition specification. A precondition is a condition that must be true before the method executes. Sometimes we'll write code that generates an error for the precondition; other times we'll just let the method return a junk value. In either case, we must warn any programmer who might call this method, so it's important that the precondition be noted in the documentation of the method. In our behavior paragraph and object list, we've added the error that will be generated. We'll talk about how we do this and what a "thrown" movement means when we code up the method.
Do this...
Using the specification for Tax#computeTotalCost()
,
write its stub as well as its Javadoc comment. Use
Double.NaN
as the dummy value that you return. Compile and run the driver.
Each object that is neither received nor returned must be
defined as a local object, within the body of our method. For
example, MIN_LUXURY_COST
will be a local
double
constant, defined to have the value 100.0;
while regularCost
will be a local
double
variable.
From our behavioral description, we have these operations:
Operations Description Predefined? Name Library receive cost
andtaxRate
from calleryes parameters built-in test if cost
ortaxRate
is negativeyes < 0
and||
built-in complain with an error yes throw
built-in compare cost
andMIN_LUXURY_COST
in less-than-or-equal relationshipyes <=
built-in compute totalCost
without luxury taxyes +
and*
built-in compute regularCost
yes *
built-in compute luxuryBase
yes -
built-in compute luxurySurcharge
yes *
and/
builtin compute totalCost
yes +
built-in if no luxury tax applies, compute normally, otherwise apply the tax yes if
built-in return totalCost
yes return
built-in
We have seen most of these operations before except for making decisions and complaining about errors. The table already suggests solutions for these operations.
We can organize our objects and operations as follows:
Algorithm
- Receive
cost
andtaxRate
from the caller.- If
cost
ortaxRate
is negative,
- Generate an error.
- If
cost
<=MIN_LUXURY_COST
Otherwise
- Compute
totalCost
=cost
+cost
*taxRate
/100.
- Compute
regularCost
=cost
+cost
*taxRate
/100.- Compute
luxuryBase
=cost
-MIN_LUXURY_COST
.- Compute
luxurySurcharge
=luxuryBase
*LUXURY_TAX_RATE
/100.- Compute
totalCost
=regularCost
+luxurySurcharge
.- Return
totalCost
.
Now that we have an algorithm, we can turn to testing this computation method.
Do this...
Add a test method to the TaxTest
class named
testComputeTotalCost()
. Add a Javadoc comment before the method
to indicate what method you're testing.
Coming up with data for testing this method requires plug-and-chug work that we'll skip over. It's a matter of coming up with some numbers and pushing them through the algorithm above. So it takes some time, not a whole lot of thought.
A good place to start when testing is to test zero values. What
if cost
is 0? What if
taxRate
is 0? What if they're both 0? There's
three tests already!
Do this...
Then add three calls to assertEquals()
to test the
three zero cases described above. Pick your own values for the
non-zero values in these three cases. Compile and run the test-case class for a
red bar. (The computation method should still be returning
Double.NaN
, so there's no way to get a green bar
yet.)
In all of the assertEquals()
tests, since we really
only have to be accurate to the nearest cent, use 1e-2
(i.e., .01
) as the fudge factor.
Zero tests only get you so far because zero actually quite a strange number; let's look at two other interesting computations.
First, what if the cost is less than the luxury base? Our test method will assume that the luxury base is 100. We don't want our numbers to be too simple, so let's try a cost of $59 and a tax of 4.35%. The result then should be 59 + 59*4.35/100 or 61.566 (as I discover from my handy calculator). There's our fourth test.
Second, what if the cost is greater than the luxury base? Again, we assume that the luxury base is 100. Let's try a cost of $112, a tax of 4.35%, and a luxury tax of 1.0%. Keep in mind that the normal sales tax applies to the whole cost while the luxury tax applies only to the cost above the luxury base. The computation then is
112 + 112*4.35/100 + (112-100)*1.0/100 = 116.99
There's a fifth test.
You want to make sure you're getting that red bar because it reassures you that the tests are being executed now. Later when you get a green bar, you'll know that the tests are being executed with good code.
You should also test the preconditions. This is a bit tricky due to the way that you're going to handle a failed precondition. First you have to try to violate a precondition:
Tax.computeTotalCost(-3, 12.3);
The 12.3
doesn't matter here since the
-3
cost is enough to trigger a failed precondition.
However, since this method call results in a failed precondition,
the test this is in will always fail unless we add some more
code
test-failure pattern |
try {
|
The idea with this code is that you try to execute some
code that you expect will fail. If the method call does
not fail, then the fail()
method is invoked.
The method fail()
comes to us from the JUnit framework
(just like assertEquals()
), and if it's executed, the
test fails.
But if, as we hope, the method call to method()
does fail, then an exception is thrown.
An exception is a special object in Java indicating that something
is wrong---very, very wrong. The catch
clause catches
the exception, and allows the code to keep executing normally. So
the exception is never seen by the JUnit code, and so the test
doesn't fail. It's basic "two wrongs make a right"!
You also need to identify what type of exception has been
thrown. For now, you have really just one choice:
IllegalArgumentException
.
We'll explore the try
-catch
statement
in more detail in a latter lab. For now, it's enough just to use
the pattern above.
Some programmers prefer separating out their tests-for-failure from their tests-for-successful-computation.
Do this...
Write a new test method in TaxTest
named
testComputeTotalCostFailures()
.
Then you can add failure tests to it.
Do this...
Using the precondition-violating method call and the failure-test
pattern, add two failure tests to
testComputeTotalCostFailure()
. One should test a
negative cost, the other should test a negative tax rate. The
messages in the call to fail()
should indicate which
argument is bad.
This means you'll have two
try
-catch
statements.
Constants should be declared first in the method.
Do this...
Declare any constants in the object list at the beginning of
computeTotalCost()
.
To write the code for the computeTotalCost()
method, you must use the if
statement. Here's the
general pattern:
two-branch-if-statement pattern |
if (
|
As the pattern mentions, the condition of the
if
statement determines the control flow. If
condition
is
true
, then the thenStatements
(a.k.a. then clause) are executed; otherwise
(i.e., condition
was false
), the elseStatements
(a.k.a. else clause) are executed.
The choice here is exclusively either-or: for one execution of a
simple if
statement, either the then
clause is executed, or the else clause is
executed. But never both.
You need the if
statement for two things: first,
for handling the error case (when the cost or tax rate is
negative); second, for doing the basic computation.
Java uses exceptions to handle error situations. In the test method you tried some code and then caught the exception that was expected. Any good programming language carries through with its metaphors, and Java is a good programming language. Just as in the rest of life, we catch things that are thrown at us. Here's a pattern for checking parameters which throws an exception:
parameter-check pattern |
if (
|
It's important to note that throw
works a lot like
the return
statement---the current method stops
immediately. Unlike the return
statement, a
throw
statement cascades through all of the methods
that have been called. For now, it cascades all the way to the Java
Virtual Machine, and you're program will crash! The test methods
use a try
-catch
to handle the throwing
more gracefully, but we'll wait until later to explore this in more
details. For now, let the program crash.
The parameter-check pattern uses a single-branch
if
statement. The else clause is officially
optional, and in some situations, it's not necessary. The throw
statement gets the program out of the method if there's something
wrong; and if there's nothing wrong, execution just goes to the
statement after the parameter check.
To fit the parameter-check pattern: the errorCondition
is whenever either parameter is negative; messageString
should be something that says that the parameters should be
positive.
As for the exceptionClass
,
Java provides us with an IllegalArgumentException
class. That's exactly the situation we're in: if
cost
or taxRate
is
negative, then we have at least one illegal argument. You already
catch this exception in a test method because you could anticipate
that you would throw it from computeTotalCost()
when
you wrote the real computation for it.
Do this...
Add a parameter check to the beginning of the
computeTotalCost()
method. Compile your code, and run the test case class; the
computation tests should still fail, but the failure test
should not fail anymore.
The second if
statement should compare
cost
against
MIN_LUXURY_COST
.
Do this...
After the parameter check, add a two-branch if
statement that tests to see if cost
is less
than or equal to MIN_LUXURY_COST
. Leave the
then- and else-clauses empty; leave the original
return
statement at the end of the method. Compile your code to make sure it's
good.
MIN_LUXURY_COST
should have been declared
earlier as a constant.
Both sections of the if
computes
totalCost
which will be returned after the
whole if
statement. Since this variable is used in
both sections, it must be declared before the
if
statement. Declare the variable
once before the if
statement.
You don't have to initialize it to anything there; just be sure to
assign the appropriate value to it in both sections of the
if
statement.
Do this...
Coding the statements for the then and else clauses of this
if
statement is straightforward, so go ahead and add
them. Be sure to assign a value to
totalCost
in each clause; do not
redeclare totalCost
!
Do this...
Finish the method by changing the return
statement so
that it returns the value of totalCost
. Compile and run the test-case class for a green
bar---finally!
Do this...
When the test-case class runs for a green bar, run the driver again.
When you test bad data with a driver, the program will quit with a exception report. This is normal for a driver that doesn't catch its exceptions. Also be sure to test the driver for a negative number of items.
Program maintenance is the final stage of program development, in which modifications and improvements are added during its lifetime. Some studies have shown that the cost to write a program is only 20% of its total cost---the other 80% accumulates during maintenance! One of the goals of object-oriented programming is to reduce this maintenance cost by writing code that is reusable.
You can improve the driver by modifying it so that it displays
totalCost
in monetary format (i.e., with 2
decimal digits). The number of decimal digits in a real number is
called the precision of that number. To show only
two decimal digits, we must alter the default precision. This can
be done with a method in the ann.easyio
package for
Screen
objects:
theScreen.printFormatted(doubleExpression
,integerExpression
)
Do this...
Modify your source program so that totalCost
is displayed with precision 2. Then test the correctness of your
modification.
Very important note: you are changing the output of the driver. This does not in any way, shape, or form change the tests in the test method. The computation is the same as it always was, the driver alone has changed the way it displays the information. So your test-case class should still run for a green bar!
Turn in your code (three files) plus a sample execution of your test case and six executions of your driver. Be sure to run the driver on some bad data (negative number of items, negative cost, and negative tax rate); it's also useful to duplicate the tests in the test method since you already know what output you should get.
Also make sure that all of your code is well documented with Javadoc comments.
body of a loop, catch an exception, condition, counting
for
loop, else clause, exception, exception report,
for
statement, for
statement, human
error, infinite loop, loop-control variable, precision,
precondition, program maintenance, repetitive behavior, scope,
single-branch if
statement, then clause, throw an
exception