Lab 1: Software Engineering


Introduction

Our problem today is to learn how to design and write a program. Since doing so is creating software, this endeavor is sometimes called software engineering. The particular design technique we will be using in this manual is called object-centered design (OCD), a methodology explicitly designed to help novice programmers get started writing software.

The process of developing a program to solve a problem involves several stages:

  1. Design: Carefully plan your program using OCD...
    1. Behavior: Describe as precisely as possible what the program must do;
    2. Objects: Using the behavioral description, identify the objects needed to solve the problem;
    3. Operations: Using the behavioral description, identify the operations needed to solve the problem;
    4. Algorithm: Organize the objects and operations into an algorithm -- a sequence of steps that solves the problem;
  2. Coding: Translating your algorithm into a programming language like C++;
  3. Testing: Running your program with sample data to check for errors, and debugging those errors until it can be deemed correct; and
  4. Maintanence: Performing any modifications needed to improve the program.
To illustrate these stages, today's exercise will use them to develop a program that, given the lengths of the two legs of a right triangle, will display the length of its hypotenuse.

Design

Yogi Berra once supposedly said,


   If you don't know where you're going,
   you'll end up somewhere else!
This idea is especially important when writing software: you need to "know where you are going" before you sit down to write a C++ program. In order to "know where you are going", you need to spend some time designing your program.

A good design makes a program easy to write.

If you are not a novice programmer, spending the time to design a program may seem like a waste, especially at the beginning when problems are relatively easy. However, the problems will soon become more difficult. If you get in the habit of carefully designing your software now (while doing so is easy), then designing elegant solutions for more difficult problems will also be relatively easy. But if you take a "short cut" now and skip the design stage, you will not master object-centered design, and when the problems get more difficult, you will find that your peers are writing better programs and writing them faster than you.

So be disciplined and get into the habit of carefully designing your software. The remainder of this section will teach you how.

As we saw earlier, object-centered design consists of four sub-stages: describing how the program must behave to solve the problem, identifying its objects, identifying its operations, and organizing those objects and operations into an algorithm.

Behavior

We might describe how our program needs to behave as follows:


 Our program should first display on the screen a greeting, after which 
 it should display a prompt for the lengths of the legs of a right triangle.
 It should then read these two values from the keyboard.  Once it has the 
 two leg lengths, it should compute the hypotenuse length.  It should then 
 display the hypotenuse length (and appropriate labels) on the screen.
This behavioral description gives us all of the information we need to get started designing our program. In particulare, it provides us with the objects and the operations our program requires to solve the problem. We will examine how next.

Objects

If you study our behavioral description and circle all of the nouns (ignoring nouns like "program" and "user"), the result is a list of the objects our program needs to define:

Description Information
Type
Information Kind
(Varying or Constant)
Name
the screen ostream varying cout
a greeting string constant --
a prompt for the legs string constant --
the first leg value double varying leg1
the second leg value double varying leg2
the keyboard istream varying cin
the hypotenuse value double varying hypotenuse
labels for the hypotenuse string constant --

For each noun, we have described some of its attributes:

These attributes are not strictly necessary at this point, but identifying them now saves us from having to do so later on.

Once we know the objects in our problem, it is useful to specify the basic flow of information in our program in terms of them. For example, we can write:

   Specification:
      input(keyboard): leg1 and leg2, two double values.
      output(screen): hypotenuse, a double value.
Such a specification succinctly states what the program does to solve the problem in terms of its inputs and outputs. (Output items like a greeting, prompts and labels are assumed.) It is good programming style to provide such a specification as part of the program's opening comment, for documentation purposes.

Operations

If you study our behavioral description and underline all of the verbs the result is a list of the operations our program needs to perform:

Description Predefined? Name Library?
display a string (the greeting) yes << iostream
display a string (the prompt) yes << iostream
read a double (the first leg value) yes >> iostream
read a double (the second leg value) yes >> iostream
compute the hypotenuse length ?? ?? ??
display a double (the hypotenuse) yes << iostream
display a string (the label) yes << iostream

In addition to listing the basic operations, we also list whether or not the operation is predefined; if so, how it is specified, and where the operation is defined. As with our objects, these extra steps are not necessary, but doing them now will save us time later on.

This table provides us with the basic operations needed to solve our problem; however, there is no predefined operation to compute the length of the hypotenuse of a right triangle. To do so, we must refine this operation by breaking it down into smaller ones. In particular, the Pythagorean Theorem tells us that we need these operations to compute the hypotenuse length:

Description Predefined? Name Library?
find the square of a double value yes pow() cmath
add two double values yes + built-in
find the square root of a double value yes sqrt() cmath
store a double value in a variable yes = built-in

If this seems like a lot of information to digest, don't get bogged down in the details. You will find that when you use a function and/or library frequently, you will naturally begin to remember its details, so all you need is practice. The important thing for now is to understand what steps we are taking to design our software, and why.

Algorithm

Once we have identified all of the objects and operations needed to solve our problem, we can organize them into a sequence of steps that solves our problem, which is called an algorithm for the problem. An algorithm should specify what a program does in fair detail, but it need not worry about the syntax details of a particular language like C++.

For our problem, we can organize the objects and operations as follows:

  1. Display via cout a greeting for the user.
  2. Display via cout prompts for the two leg lengths.
  3. Read from cin two double values, storing them in leg1 and leg2.
  4. Compute the hypotenuse length using the Pythagorean Theorem:
          hypotenuse = sqrt(pow(leg1, 2.0) + pow(leg2, 2.0))
    	
  5. Display via cout the value of hypotenuse. along with an appropriate label.
This algorithm provides the blueprint for our C++ program. Once we have it, we are done designing our program, and are ready to begin implementing our design.

Coding

Begin by creating a new project for this exercise (e.g., hypot) and then save a copy of hypot.cpp in the project directory. (Your instructor may have a local version for you to copy.) hypot.cpp contains just a fragmentary opening comment (documentation that explains what the program does, who wrote it, and so on). Start by using Project -> Add To Project -> Files... to add hypot.cpp to your project. Then open hypot.cpp and customize its opening comment with your name, the date, the name of your school, your course number, the problem specification, and any other information your instructor wishes it to contain.

Coding is the task of taking an algorithm and translating it into a high-level language like C++. We can begin this task by adding a minimal C++ program to hypot.cpp after the opening comment:

   int main()
   {
   }

Every C++ program must have a main function where execution begins and ends. The lines above make up a minimal main function, and thus provide a framework to which we can add other statements that perform the steps of our algorithm.

When you have added these lines to hypot.cpp, translate your program into machine language, as we did in Lab 0. If your compiler is ANSI-compliant, no errors or warnings should appear. (When you run this program, nothing should happen because it doesn't do anything yet.)

If error messages appear, then what you have entered contains syntax errors (i.e. your program is not a "grammatically correct C++ sentence"). Errors in a program are frequently called bugs. In order for your program to compile, you must return to the editor and correct these errors, which is called debugging your program.

Note:If a warning message like:


   Warning: No return statement at end of function
or something similar is displayed, then your compiler is not ANSI-compliant. If this happens, add the line
   return 0;
after the { and before the } in the main function. This causes your main function to terminate and return the integer zero, which signals "normal termination" to many operating systems.

Once we have the minimal C++ program constructed and can translate it, we are ready to encode our algorithm. To do so, we want to take each step of the algorithm and write one or more C++ statements that perform that step. Step-wise translation is the process of translating an algorithm into a program, one step at a time. We therefore begin with the first step.

1. Display via cout a greeting for the user.

This step can be performed using a C++ output statement. In general, to display a sequence of values Value1... ValueN on the screen, we can use the following output statement pattern:
   cout << Value1 <<  Value2 << ... <<  ValueN;
Add an output statement to your source program following the { that displays the following message, using \n to print blank lines before and after the message, so that it is easy to read:
   Given two right triangle leg lengths,
    this program computes the hypotenuse length.
When you have added the commands to your source program, re-compile it. The compiler should generate an error message like the following:

	hypot.cpp: In function int main()
	hypot.cpp: 'cout' undeclared...
The problem is that the meanings of the symbols cout and << are declared in a system file named iostream, not in your program. To access these declarations from that file, we can instruct the compiler to insert the contents of iostream into hypot.cpp, by adding an #include directive just before the int main() line:
   #include<iostream>
   using namespace std;
This #include directive must appear in any program that performs output using cout and <<. The # symbol should be the first column, in order for it be processed correctly. The <> symbols around the file name tell the compiler to look for iostream in a special system "include" directory, rather than in your current directory. Try to remember the error message (or make a note of it) so that if you see that message again, you will know how to "debug" that error.

The using namespace std; directive tells the C++ compiler that our program will only use the standard definitions of names like cin, cout, sqrt() and pow(). In this lab manual, we will always use the standard definitions for such names, so our programs will always include this directive.

Now, retranslate your program to check that what you have written is free of syntax errors. If not, you can infer that the error lies in the text you have added since your last correct compilation. Find your error(s) within those lines and use the editor to correct them.

When your source program compiles correctly, run hypot.exe to test that it displays the intended message. If it does not, the statements you have added contain logic errors (i.e., your program is a valid C++ 'sentence', but it 'says' something different than you intended.) Compare your source program statements against the output produced by hypot.exe, and modify them as needed to correct the error.

When your program is error-free, proceed to the next step of our algorithm.

2. Display via cout prompts for the two leg lengths.

Like our first step, this step can also be accomplished using an output statement. Add to hypot.cpp an output statement that displays the following prompt for input:

   Enter the two leg lengths: 
Use \n to leave a blank line between the greeting and this prompt, and leave a space after the colon. Check the correctness of what you have written by translating your program, and continue to the next step when it is correct.

3. Read from cin two double values, storing them in leg1 and leg2.

This step can be performed using a C++ input statement. In general, to read a sequence of values from the keyboard and store them in Var1...VarN, we can use the following input statement pattern:

   cin >> Var1 >>  Var2 >> ... >>  VarN;
Using this pattern, add an input statement to hypot.cpp to perform step 3 of our algorithm. Then check its correctness by retranslating your program. Similar (but different) errors should be generated, but this time it is our objects that are undefined. C++ requires that an object be declared before it can be used, using a declaration statement whose pattern is:

   Type Var1,
        Var2,
        ...
        VarN;
where Type is the types of the objects being declared, and Var1, ..., VarN are the names of those objects, as specified back in our Design stage.

In C++, the type double is used to define objects capable of storing real numeric values. One of the nice features of C++ is that declarations are statements, and thus can appear anywhere a C++ statement is permitted. Insert a declaration for leg1 and leg2 just before your input statement. Then re-translate your program, and continue when it is free of syntax errors.

4. Compute the hypotenuse length using the Pythagorean Theorem.

As we have seen, this step can be performed using the sqrt() and pow() functions that perform square root and exponentiation, respectively.

   hypotenuse = sqrt(pow(leg1, 2.0) + pow(leg2, 2.0))
As noted previously, the sqrt() and pow() functions are declared in the library file cmath, so add a #include directive above your main function (between the #include directive and the using namespace std; directive) to insert the contents of cmath into hypot.cpp. (If your compiler is not fully ANSI-compliant, try using math.h instead of cmath

To perform this step, we can use a variant on the declaration statement that allows us to declare and initialize an object. Since hypotenuse is to store a double value, we write:

   double hypotenuse = sqrt(pow(leg1, 2.0) + pow(leg2, 2.0));
Such a statement declares hypotenuse as a double object, and initializes it with the value of the expression to the right of the = symbol. Add this statement after your input statement (since it depends upon the values of leg1 and leg2). Then retranslate your program and continue to the final step when it is free of syntax errors.

5. Display via cout the value of hypotenuse, along with an appropriate label.

This final step of our algorithm is another output statement. Since the pattern for the output statement allows us to output multiple values:

   cout << Value1 <<  Value2 << ... <<  ValueN;
a single output statement is sufficient to accomplish this step. Add an output statement to hypot.cpp, such that if the value of hypotenuse is 1.414214, then the message

   --> The hypotenuse length is: 1.414214
is displayed with blank lines above and below it (to make the result stand out). Retranslate your program, and when it is free of syntax errors, continue to the next stage of program development.

Testing

The third stage of program development is a thorough testing. The basic idea is to execute the program using sample data values that "exercise" the program, to see if it contains any logic errors. Execute hypot with the following easy-to-check values, to see if you get the correct results.

leg1 leg2 predicted
hypotenuse
observed
hypotenuse
1.0 1.0 1.414214 ________________
3.0 4.0 5.0 ________________
5.0 12.0 13.0 ________________

If you do not get the predicted results, then the error most likely lies in the formula by which you are calculating the hypotenuse value, so double-check it for accuracy and fix any mistakes it contains. Then retranslate and retest hypot.exe until you have a reasonable degree of confidence that it is correct.

Maintenance

Unlike programs that are written by students, "real world" programs may be used for many years. It is often the case that such programs must be modified several times over the course of their lifetimes, a task which is called program maintenance. Maintenance is thus the final (and usually the longest) stage of program development. Some studies have shown that the cost of maintaining a program can account for as much as 80% of its total cost! One of the goals of object-oriented programming is to try to reduce this maintenance cost, by writing code that is reusable.

To simulate program maintenance, suppose that hypot should display only three decimal digits instead of the default number. To do so, its source program must be altered, so return to your text editor.

The number of decimal digits displayed for a real number is called the precision of that number. To show only three decimal digits, we must alter the default precision. This can be done with an I/O manipulator called setprecision() (declared in the file iomanip) whose general form is:

   setprecision( IntegerExpression )
A manipulator can be inserted into cout just like a value. That is, if setprecision() is placed in an output statement:
   cout << Val1 << setprecision(i) << Val2 <<  ... << ValN;
then real values before it (i.e., Val1 ) will be displayed with the default precision, but real values following it (i.e., Val2 << ... << ValN) will be displayed with precision i. Modify your source program so that hypotenuse is displayed with precision 3. Don't forget to #include the file iomanip before the using namespace std; line! Then test the correctness of your modification, continuing when hypot works as intended.

Phrases you should now understand:

Design, Behavior, Object, Operation, Algorithm, Coding, Testing, Debugging, Maintenance, Specification, Stepwise Translation, Source Program, Executable Program, Input Statement, Output Statement, I/O Manipulator.


Submit:

Turn in to your instructor a hard copy of your final version of hypot.cpp and a hard copy of its output given the input values 1 and 1.

Clean Up

Don't forget to clean up by removing the object code from your project, and then discarding the Debug folder. Then do any final cleanup (i.e., save your work to a floppy, remove your originals from the hard drive, etc.)


Back to This Lab's Home Page

Back to the Prelab Questions

Forward to the Homework Projects


Copyright 1998 by Joel C. Adams. All rights reserved.