Lab 5: Controlling Method Behavior


Introduction

Today's lab explores some Java statements that we can use to write more sophisticated methods. The exercise consists of two parts: In the first part, we examine a problem whose solution requires more complicated behaviors than we have seen thus far, and introduce the new statements to elicit that behavior. In the second part, we introduce a new program-development tool called the debugger and use it to study the behavior of the new statements.

 

Part I: The Tax Computation Problem

Our problem today is to write an interactive tax computation program, that the owner of small online business might use to compute the total cost of items being sold to buyers in states with different sales tax. To make the problem more interesting, we will assume that a national luxury tax has been enacted. The one percent luxury tax will be levied against any purchase over one hundred dollars.

Recall that object-centered design involves several stages:

  1. Identify the behavior we wish the program to exhibit in order to solve the problem.
  2. Identify the objects in that behavior.
  3. Identify the operations in that behavior.
  4. Organize the operations and objects into an algorithm.

Now that we know about methods and classes, we must incorporate them into our design scheme. Methods can be thought of as the means of creating our own operations, so the natural place to incorporate them is in step 3:

  1. Identify the behavior we wish the program to exhibit in order to solve the problem.
  2. Identify the objects in that behavior.
  3. Identify the operations in that behavior.
    If there is no predefined way to perform an operation,
      build a method to perform it, and store it in the class responsible for providing it.
  4. Organize the operations and objects into an algorithm.

Once we have our design, we can encode it in a programming language, test and debug the resulting program, and then perform any maintenance required over its lifetime.

Today's exercise is to use these stages to develop a program that solves our problem.

Preparing Your Workspace

Begin by creating a project directory for this exercise (e.g., lab5) in which to store today's work and then save copies of the files TaxTotals.java, and Tax.java in your project directory.

If you are using an integrated development environment (IDE) like CodeWarrior, create a new project (e.g., TaxTotals) in your project directory, and add TaxTotals.java, Tax.java, and the ann package to it.

Then open TaxTotals.java, add it to the project, and take a moment to personalize its opening documentation.

 

Design

As always, spending a bit of time planning how to attack our problem will result in a better solution. To do so, we follow the steps of object-centered design.

Behavior. We can begin by visualizing and writing down how we want our program to behave. One approach is to have our program behave something like the following:

   This program computes total costs interactively.

   To begin, enter the number of items: 3

   Enter the item name, cost and tax rate for item 1: kleenex 25 5.25
   kleenex     26.3125

   Enter the item name, cost and tax rate for item 2: eggs 1.50 6.15
   eggs         1.59225

   Enter the item name, cost and tax rate for item 3: tires 350 6.15
   tires      374.375

Put into words, our program should

   display on the screen a greeting, followed by a prompt for the 
   number of items, which it should then read from the keyboard.  
   For each item, our program should then display a prompt for 
   the item name, cost and tax rate.  The program should then
   read these values from the keyboard, and compute and display
   the total item cost, along with the name.

Objects. If we identify the nouns in this behavioral description, we get the following list:

Description

Type

Kind

Name

The screen

Screen

varying

theScreen

A greeting

String

constant

--

A prompt for input

String

constant

--

The number of items

int

varying

numberOfItems

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

rate

An item's pay

double

varying

totalCost

From this list, we can build a precise specification of how our program is to behave:

   Input:	The number of items;
		    Each item's name, cost, and tax rate.
   Output:    Each item's name and total cost.

Use this information to complete the specification of TaxTotals.java.

Operations. If we identify the operations in our behavioral description, we get this list:

Description

Predefined?

Name

Library?

display a String (greeting, prompts, labels)

yes

print

ann.easyio

Read an int (numberOfItems) from the keyboard

yes

readInt

ann.easyio

read a String (name) from the keyboard

yes

readWord

ann.easyio

read a double (cost, rate) from the keyboard

yes

readDouble

ann.easyio

compute an item's totalCost, given the cost and rate from the keyboard

no

??

??

display a double (totalCost) on the screen

yes

print

ann.easyio

repeat operations 3-6 once for each item

yes

for

--

As indicated, most of these operations are provided for us by the ann.easyio package. Operations 1-4 and 6 should be familiar by now. There is no predefined Java capability to perform operation 5, and since accounting for luxury tax makes it nontrivial, we will write a method to perform this operation. Since it seems like an operation that might be useful again some day, we will store it in a separate class. Finally, operation 7 involves a new Java statement that we haven't seen before called the for statement, which provides a convenient way to repeat a group of statements a predetermined number of times.

Algorithm. Even without knowing the details of how we will compute the total cost, we can organize our operations into an algorithm for our problem:

  1. Via theScreen, display a greeting on the screen, plus a prompt for the number of items.
  2. From theKeyboard, read an integer, storing it in numberOfItems.
  3. For each value itemNum in the range 1 to numberOfItems:
    1. Via theScreen, display a prompt for the name, cost and tax rate of item itemNum.
    2. From theKeyboard, read a String, a double and a double, storing them in name, cost and rate.
    3. Compute totalCost, using cost and rate.
    4. Via theScreen, display name and totalCost.

    End loop.

Coding, Testing and Debugging

Once we have designed an algorithm to solve our problem, we must encode that algorithm in the a high level programming language (i.e., Java). We can begin this process by constructing a minimal Java program, consisting of

  public class TaxTotals 
  {
       public static void main(String args[]) 
       {
       }
  }

(These lines are already present in TaxTotals.java, after its opening documentation.) To make sure that this much is correct before we add to it, take a moment to compile TaxTotals.java.

Given the minimal Java program, we are ready to encode our algorithm using stepwise translation. We therefore begin with the first step:

1. Via theScreen, display a greeting on the screen,
  plus a prompt for the number of items.

Coding: This step can be performed using a Java output statement, which we have seen before:

   theScreen.print( Value1 + Value2 + ... + ValueN );

so add an output statement to TaxTotals.java that displays the following message:

   This program computes total costs interactively.

   To begin, enter the number of items:

Don't forget to import the package ann.easyio.*!

Before proceeding to the next step, check the correctness of what you just wrote by recompiling your program. The compiler will alert you to any syntax errors in your statement. If an error is listed, you can infer that the error(s) lies in the text you just added, since the program was error-free before that. Find your error(s) within those lines and correct them.

When your source program compiles correctly, execute TaxTotals to test that it displays the intended message. If not, the statements you have added contain logic errors. (i.e., the statements you have added are syntactically correct, but they don't accomplish their task correctly.) Compare your program's statements against the output produced by TaxTotals and modify them as needed. When your program is error-free, proceed to the next step of our algorithm.

2. From theKeyboard, read an integer, storing it in numberOfItems.

Coding: We can encode this step in Java using an input statement:

   Var1 = theKeyboard.readInt();

Add an input statement to your source program to perform step 1. Don't forget to declare a variable to store numberOfItems! Check that what you have added is free of syntax errors before continuing.

Note that we could use an Assertion.check() at this point to check that numberOfItems is nonnegative. However, doing so is unnecessary, thanks to our next step.

3. For each value itemNum in the range 1 to numberOfItems:
  a. Via theScreen, display a prompt for the name, cost and tax rate
      of item itemNum.
  b. From theKeyboard, read a String, a double and a double,
      storing them in name, cost and rate.
  c. Compute totalCost, using cost and rate.
  d. Via theScreen, display name and totalCost.
End loop.

Coding: Let's take this step a piece at a time, starting with the outer part (3) and then doing the inner parts (a-d).

 

3. For each value itemNum in the range 1 to numberOfItems:
  ...
End loop.

The purpose of step 3 is to count from 1 to the number of items, and repeat steps a-d that many times. That is, if there are three items, then steps a-d should be repeated three times.

For situations like this that require repetitive behavior, Java supplies the for statement. The for statement can use its own local variable, called a loop-control variable, to do the counting. A simplified general form of a for statement that counts from firstValue to lastValue is:

   for (Type loopVar = firstValue; loopVar <= lastValue; loopVar++)
   {
      Statements
   }

where loopVar is the loop-control variable, and the Statements between the curley braces are called the body of the loop. The behavior of this statement is as follows:

  1. loopVar is declared and initialized to firstValue.
  2. loopVar is compared against lastValue.
  3. If the comparison evaluates to true:
    1. Statements get executed.
    2. loopVar++ is executed.
    3. Go to 2.

    Otherwise, control proceeds to the next statement.

In our problem, we must count from 1 to the value stored in numberOfItems, using itemNum as the name of the loop-control variable. To do so, we can write:

   for (int itemNum = 1; itemNum <= numberOfItems; itemNum++)
   {
   }

leaving its Statements empty for the moment.

Note that if the user enters a negative value for numberOfItems, the body of the loop will not be executed, because the loop's body is only executed if the condition controlling the loop evaluates to true, and a negative value for numberOfItems will make this condition false.

Add this to TaxTotals.java and then check its syntax. When the compiler generates no errors, proceed to steps 3a-3d.

3a. Via theScreen, display a prompt for the name, cost and tax rate
  of item itemNum.
This is a normal output statement, like those we have seen before, except that in addition to displaying a string, it displays the value of our loop-control variable to generate an "item number." That is, if numberOfItems is 3, then our loop will execute three times, so add an output statement to the body of the loop that will generate the following:

   Enter the name, cost and tax rate for item 1:
   Enter the name, cost and tax rate for item 2:
   Enter the name, cost and tax rate for item 3:

Then check the syntax of what you have written using the compiler. When your program translates correctly, run it and enter 3 for the number of items, to verify that what you have written is free of logic errors.

3b. From theKeyboard, read a String, a double and a double,
  storing them in name, cost and rate.

This step can be encoded by adding a normal input statement to the body of the loop, after the output statement, so take a moment to do so.

However, before such a statement will compile correctly, the objects name, cost and rate must be declared. This raises an interesting question: Where should these objects be declared?

When a variable is declared within a block (i.e., a { and } pair), it does not exist outside of that block. Thus, if our algorithm required us to use name, cost and rate outside of the body of the loop, then we would have to declare them prior to the loop in order to use them both within and following the loop.

However this is not the case; our algorithm uses name, cost and rate only within the body of the loop, so we can declare them just prior to their first use, within the body of the loop.

Add the necessary statements to your program to (i) declare name, cost and rate, and (ii) fill these objects with values entered from the keyboard (inside the loop).

Then check your code's syntax using the compiler, and continue when it is correct.

Anytime we program an input operation, we should consider the possibility of human error: what could the user do to foul up our program? There are several possibilities here:

  1. The user could enter a negative value for cost.
  2. The user could enter a negative value for rate.

The user could also enter a value for rate greater than the maximum tax rate; however our problem does not specify a maximum tax rate, so we will ignore that potential source of error for the moment.

Since either of these errors will cause our program to generate incorrect pay amounts, our code should safeguard against them. To do so, add an Assertion.check() to the program that only allows the program to continue if cost is nonnegative and rate is nonnegative. Use a single Assertion.check() call to do so. (Don't forget its import directive!)

Then check the syntax of what you have written using the compiler, then run your program and test that your Assertion.check() halts the program if either of these two errors occur. When it correctly "catches" these errors, continue.

3c. Compute totalCost, using cost and rate.

This operation is not predefined, and so we will design and build a method to perform it.

Method Analysis. To compute totalCost for an arbitrary item, our method needs the cost and rate. Since these values will differ from item to item, our method should receive these values from its caller.

Method Behavior. Our method should receive the cost and rate from its caller. If cost is less than or equal to the minimum cost for the luxury tax, then our method should compute the total cost as cost plus cost times rate divided by 100 (assume that the rate is given as a percent). Otherwise, it should compute the regular cost as before; the luxury surcharge as one percent of the luxury base (cost minus the minimum for the luxury tax); and then compute the total cost as the sum of the regular cost and the luxury surcharge.

Method Objects. Ignoring nouns like "our method" and "caller", we can identify the following objects in this description:

Description

Type

Kind

Movement

Name

item cost

double

varying

received

cost

tax rate (percent)

double

varying

received

rate

minimum luxury cost

double

constant

local

MIN_LUXURY

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

luxury tax rate

double

constant

local

LUXURY_RATE

For each received object, we must provide a parameter to store that value. We can thus specify the behavior of our method as follows:

   Receive: cost and rate, both doubles.
   Precondition: 0 <= cost and 0 <= rate.
   Return: the total cost, a double.

These observations allow us to create the following stub for our method:

   public static double computeTotalCost(double cost, double rate)
   {
   }

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 will be a local constant double object, defined to have the value 100.0; while regularCost will be a local variable double object.

Since this seems like a method that might be reusable some day, we will store its definition in a class named Tax. Open Tax.java. (If you are using an IDE, add Tax.javato the project.) Create the class Tax and inside that class insert the stub. Since other programs besides TaxTotals.java may be calling this method (and not checking the precondition ahead of time), make the first line in the method an Assertion.check() call that checks the method's precondition. (Copy-and-paste your Assertion.check () from TaxTotals.java.)

Then add the necessary documentation to Tax.java

Method Operations. From our behavioral description, we have these operations:

Description

Defined?

Name

Module?

1

receive cost and rate from caller

yes

method call
mechanism

built-in

2

compare cost and MIN_LUXURY
in less-than-or-equal relationship

yes

<=

built-in

3

compute totalCost normally
(cost plus cost times rate divided by 100)

yes

*

built-in

4

compute totalCost for luxury item

4a

compute regualarCost

yes

*

built-in

4b

compute luxuryBase
(cost minus MIN_LUXURY)

yes

-

built-in

4c

compute luxurySurcharge
(luxuryBase times LUXURY_RATE divided by 100)

yes

*, /

builtin

4d

compute totalCost
(regularCost plus luxurySurcharge)

yes

+

built-in

5

if (2) is true, perform (3), otherwise perform (4)

yes

if

built-in

6

return totalCost

yes

return

built-in

We have seen each of these operations before, except for operation (5), which introduces a new behavior in which we must select one group of statements or another, but not both. As we shall see shortly, the Java if statement provides this behavior.

Method Algorithm. We can organize our objects and operations as follows:

  1. Receive cost and rate from the caller.
  2. If cost <= MIN_LUXURY
      Compute totalCost = cost + cost * rate/100.
    Otherwise
      a. Compute regularCost = cost + cost * rate/100..
      b. Compute luxuryBase = cost - MIN_LUXURY.
      c. Compute luxurySurcharge = luxuryBase * LUXURY_RATE/100.
      d. Compute toalCost = regularCost + luxurySurcharge.
  3. Return totalCost.

Given an algorithm, we are ready to encode our method.

Method Coding. To encode this algorithm in our computeTotalCost() stub, we must know the syntax of the Java if statement. The general pattern is as follows:

   if (BooleanExpression)
   {
      Statements1
   }
   else
   {
      Statements2
   }

Here BooleanExpression is any expression that evaluates to true or false, and Statements1 and Statements2 are 1 or more Java statements. Statements1 is sometimes called the true section of the if, and Statements2 is called its false section. The reason for these names is that when execution reaches an if statement, its BooleanExpression is evaluated. If it is true, then the statements in Statements1 run, and those in Statements2 are skipped. Otherwise, the opposite occurs -- the statements in Statements1 are skipped, and those in Statements2 run.

For our problem, we want BooleanExpression to perform the comparison described in operation (2). In the stub for computeTotalCost(), we can add the following if statement that compares cost and MIN_LUXURY using the less-than-or-equal relationship:

   if (cost <= MIN_LUXURY)
   {
   }
   else
   {
   }

As a parameter, object cost is defined for us, but MIN_LUXURY and LUXURY_RATE are not parameters, and so must be declared. Add statements at the beginning of the method that declare MIN_LUXURY as a constant whose value is 100.0, and declare LUXURY_RATE as a constant whose value is 5.0.

To check this much, compile Tax.java, and you should just get the one error for not providing any return value. Continue if this is the case, otherwise fix the other errors.

Coding operations (3) and (4) is straightforward, so add statements to the if's true section to perform (3), and add statements to its false section to perform (4). Note that each section of the if computes totalCost, however the true section uses the formula appropriate for no luxury tax, while the false section uses the formula appropriate when there is luxury tax. Since each section uses totalCost, it must be declared prior to both sections (i.e., before the if statement).

Finish the method by adding a return statement that returns the value of totalCost. Then recheck the correctness of computeTotalCost(), continuing when it is free of syntax errors.

Now that we have a syntactically correct computeTotalCost() method to perform step 3c of our program's algorithm, we are ready to code that step by calling our method. In the for loop within TaxTotals.java, add an assignment statement with a call to computeTotalCost(). Don't forget to pass it cost and rate as arguments!

To check the correctness of our call, compile TaxTotals.java which will create TaxTotals.class and link it with the class file of Tax.java. When what you have written is free of syntax errors, continue to the final step of our algorithm.

3d. Via theScreen, display name and totalCost.

This step is easily implemented using an output statement. Take a few moments and add an output statement so that if item milk had a total cost of $1.75, the following is displayed:

   milk cost with tax is $150.75

Use whatever symbol is appropriate for your country's currency.

 

Testing

When you are finished, recompile the program. Then test it a few times using sample data, to check that it computes correct results.

 

Maintenance

Recall that 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 rest is consumed by maintenance! One of the goals of object-oriented programming is to reduce this maintenance cost, by writing code that is reusable.

To simulate program maintenance, we can improve TaxTotals by modifying it so that it displays totalCost in monetary format (i.e., with 2 decimal digits instead of the default number). 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. As we have seen before, this can be done with an method in the ann.easyio package

   printFormatted(DoubleExpression, IntegerExpression ) 

Modify your source program so that pay is displayed with precision 2. Then test the correctness of your modification.

 

Part II: Using the Debugger

While the compiler will inform us of any syntax errors our program contains, it cannot detect logic errors. The simplest way to check for logic errors is to examine the values of the variables in our program as the program executes, and verify that they are what they are supposed to be. Most environments provide a special tool called a debugger. A debugger allows you to execute a program statement-by-statement, and examine the values of variables (or expressions) as the program runs.

There are certain common features that most debuggers have.

Breakpoints

When there is a problem with a program, you often have some idea of where the problem lies. You want to run our program until you get to a point close to the suspected trouble spot. By setting a breakpoint, you inform the debugger that it should stop executing the program at a certain point.

Examining Object values

Once you have temporarily halted the execution of our program, you would like to be able to examine the values that are stored in all of your variables and check to see if they match what you expect them to be.

Single Step

Suppose that you haven't found the bug in your program. You would like to be able to execute lines of code one at a time until you find the problem.

Step Into, Over, Out

Because modern methods are composed of many methods, you want to have the ability to decide whether or not to explore a method. If you trust a method, you will want to step over the method call. If you are not sure of a method, you will want to step into the method. Finally, if you are in a method and believe it to be okay, you can step out of the method and return to where it was called from.

   

Depending on which environment you will be using click on the appropriate link for specialized instruction on the use of the associated debugger:

From this exercise, you should now know the basics of using the debugger, as well as have a better understanding of how if statements and for loops work.

 

Phrases you should now understand:

Object-Centered Design, Algorithm, Coding, Testing, Debugging, Stepwise Translation, Selective Execution, If Statement, Repetitive Execution, For Statement.


 

Submit:

Hard copies of your final version of TaxTotals.java and Tax.java; plus a script file showing the execution of TaxTotals.


Back to This Lab's Table of Contents

Back to the Prelab Questions

Forward to the Homework Projects


Back to the Table of Contents

Back to the Introduction


Copyright 2000 by Prentice Hall. All rights reserved.