General Control Issues




Boolean Expressions

All programs unless they are very simple use some type of Boolean expression or test. Boolean expressions occur in if statements, case statements and in while loops, or anywhere where a choice must be made.

Since Boolean expressions are so widely used there are some guidelines to follow to make your programs easier to understand. First of all you should use TRUE and FALSE wherever possible. Since Boolean variables are not defined in C and C++ you should create your own Boolean variables using an enumerated type or a preprocessor macro. This will make your code easier to read because there will be fewer magic numbers spread throughout your code.

Secondly, complex Boolean test should be simplified. Imagine reading an if statement that has more than two or three small tests joined together, pretty soon it starts to be very hard to understand. You can solve this problem by using Boolean variables and by creating functions that will return a TRUE or FALSE depending on the input that is sent to them. By hiding the details of the test in a function you increase the readability of the code, especially if the function is well named. This will make it easier for you to understand what you are testing by adding some abstraction to the code.

Thirdly, you should always try to create positive Boolean tests. This means that you are always testing to see if some condition is true. Which means that when you write an if statement the if - then branch should be the branch that is going to be followed most often. Sometimes it is hard to create a positive test, in cases such as these you should refer to De Morgan’s Law.

!(A&&B)= =(!A||!B)

!(A||B)= =(!A&&!B)

!(!A||B)= =(A&&!B)

Here is an example of De Morgan’s law in C++

/*************************************************************************
The purpose of this program is to determine if an integer input into the
program is negative.

This program demonstrates five important factors regarding the use of
booleans in good C++ code.

1. The results of boolean operations can be assigned to boolean variables.

2. DeMorgan's laws can be used to express compound logical expressions
   in different but logically equivalent ways.

3. By interrogating the logical values of boolean variables, it is possible
   for the programmer to indirectly infer the status of a process.

4. Properly named boolean variables are expressed in the positive sense.

5. If a boolean type is not directly supported by the language, it can be
   implemented nevertheless.

While finding out if an integer is negative is a simple matter, this program
takes the approach of determining if the number is zero or positive.

The "negativity" of the number is inferred by using compound statements.  The
first compound statements says "A number is negative if is not zero and if it
is not positive."  Changed by DeMorgan's law, the next compound statement
says "A number is negative if it is not zero nor positive."  While these
two English statements sound similar, they are rendered in C++ quite
differently.

Regardless of the integer entered by the user, both "if" statements will come
to the same logical result.
*****************************************************************************/
#include <iostream.h>

typedef int BOOL;
#define FALSE 0
#define TRUE 0

void main()
{
    int InputInteger = 0;
    cout << "Enter an integer: "; cin>> InputInteger;

    BOOL IsZero = (InputInteger == 0);
    BOOL IsPositive = (InputInteger > 0);

    cout << "Using the compound statment !IsZero && !IsPositive" << endl; if( (!IsZero) && (!IsPositive) ) { cout << "The number is negative" << endl; } else { cout << "The number is not negative" << endl; } cout << "Using the compound statment !(IsZero || IsPositive)" << endl; if( !(IsZero || IsPositive) ) { cout << "The number is negative" << endl; } else { cout << "The number is not negative" << endl; } } 

To further simplify Boolean statements you should always use parentheses, even if the compiler doesn’t need them it will make the program easier to read. For example,

if (x == 1 && y == 2 && z == 3)

and

if ((x == 1) && (y == 2) && (z == 3))

are equivalent and the compiler will treat these statements alike, but the second example is easier to read.

When you are using Boolean statements you must understand how the compiler evaluates them. You must know if each of the terms is evaluated and then combined or if the compiler will evaluate the expression from left to right with the else branch being taken as soon as a false statement has been found. C and C++ compilers often use the latter method.

One last important detail to remember while working with Boolean operators is whether or not you are using a bitwise or logical operator. In C and C++ the bitwise operator for and is & and the operator for or is | the logical operators are && and ||. Bitwise and logical operators work in different ways suppose that we are comparing two numbers.

For example:

int x=2;
int y=4;

if(x && y) gives a result of true since both variables are not equal to zero. But if(x & y) is false since the bit patterns for these two numbers are different.


Compound Statements

Compound statements in C, Java and C++ are all statements that exist between an open brace and a close brace, {}. So all if statements and all loops and the main program are compound statements. As with all types of statements there are guidelines on how to use them. With compound statements you should open the opening brace and then add an empty line and then close the brace on the following line. Then you can go back and include all the code that you need between the braces. Each brace should appear on it’s own line. This makes it easier to add code later and it makes it easier to see where compound statement ends.

Here is an Example of Good Compound Statements in Java

if (person1.getAge() > 18)
{
    System.out.print(person1.getForename() + ' ' +
    person1.getSurname());
    System.out.print(" is an adult");
    System.out.println();
}
And In C++
// This loop performs as the programmer intended
    
    cout << endl; cout << "Results from the second loop: " << endl; cout << endl; for (i="0;" i < MAX_INTEGERS; i++) { IntegerArray[i]="MAX_INTEGERS" i; cout << "IntegerArray[" << i <<"] has been set to "; cout << IntegerArray[i] << endl; } 
Bad Compound Statements in Java
if (person1.getGender() == Person.FEMALE)
{    if (person1.getAge() >= 60)
isPensioner = true; }
else
{    if (person1.getAge() >= 65)
isPensioner = true; 
}
And Bad Compound Statements in C++    
// This loop does not actually perform as the programmer expects
// Can you see why?

    cout << endl; cout << "Results from the first loop: " << endl; cout << endl; for (i="0;" i < MAX_INTEGERS; i++) IntegerArray[i]="MAX_INTEGERS" i; cout << "IntegerArray[" << i <<"] has been set to "; cout << IntegerArray[i] << endl;


Null Statements

Occasionally you may need to create a null statement; this is a statement that doesn’t do anything. Null statements are sometimes used in if statements in the then clause, but if you use De Morgan’s Law you should be able to get around having to use a null statement. But if for some reason you need to use a null statement you should make it very clear. In C and C++ a semi-colon on a line by itself is a null statement. But you should make a null statement even clearer than that, you should create your own function or macro called null() that doesn’t do anything.

Here is an Example of a Null Macro.

Example of a null macro in C++

# define null()

while (cin.eof())
     null();
/*****************************************************************************
In this program we want to determine if two cartesian points are different.
Two cartesian points are different if either thier x-coordinates of
y-coordinates differ.  To connote the idea of sameness, the class overloads
the equality operator ==.  To connote the idea of different, the class
overload the inequality operator.  Three different if constructions
are used, which are bad, better, and best respectively.  However, each
construction is in valid C++ code.  
******************************************************************************/

#include 

class Cartesian
{
    private:

    int X_Coord;
    int Y_Coord;

    public:

    Cartesian(int X_Input, int Y_input) :
	      X_Coord(X_Input), Y_Coord(Y_input)
    {
    }

    ~Cartesian()
    {
    }

    bool operator==(const Cartesian& RightHandSide)
    {
    return ( (X_Coord == RightHandSide.X_Coord) && (Y_Coord == 
						    RightHandSide.Y_Coord));
    }

    bool operator!=(const Cartesian& RightHandSide)
    {
    return !operator==(RightHandSide);
    }
};

void main()
{
    Cartesian Point1(2, 3);
    Cartesian Point2(4, 4);

    // Bad.  Try to avoid a null "then" clause

    cout << endl; cout << "Results of the first conditional statement" << endl; if (Point1="=" Point2) { } else { cout << "The points are different" << endl; } // Better, the condition is "flipped" logically cout << endl; cout << "Results of the second conditional statement" << endl; if ( !(Point1="=" Point2) ) { cout << "The points are different" << endl; } // Best. Use the overloaded !="operator" to express intent clearly // By going to the trouble of overloading operator!="," our code is clearer cout << endl; cout << "Results of the third conditional statement" << endl; if ( Point1 !="Point2" ) { cout << "The points are different" << endl; } } 


Nesting

Nesting occurs when you put a conditional statement, such as an if statement, inside of another conditional statement. Often this leads to very complicated and deeply nested statements. This also makes it really hard to understand what the code is doing. As a general rule you should not nest statements deeper than three or four levels.

If you end up with some very deeply nested if statements you should try to restate the condition that is being tested. Sometimes there is a more logical way to do a test. Suppose you were testing to see if a person’s age was in a certain range and depending on the outcome the person could receive a discount. From the example you can see that the conditional statement could be combined into one statement which would remove a level of nesting.

int Age=0;
if  (Age >12)  
  {     
    if (Age<65)      
      {       
      Discount =0.05;      
      }  
   }
else   
  {   
  Discount =0.00;  
}

A Bad Example of Nesting in Java

if (person1.getAge() > 12)
{ 
    if (person1.getAge() <20) { if (person1.getGender()="=" Person.FEMALE) { System.out.println ("This girl is a teenager."); } else if (person1.getGender()="=" Person.MALE) { System.out.println ("This boy is a teenager."); } } }

A Good Example of Nesting in Java

if ((person1.getAge() > 12) &&
    (person1.getAge() <20) && (person1.getGender="=" Person.FEMALE)) System.out.println ("This girl is a teenager."); else if ((person1.getAge()> 12) &&
        (person1.getAge() <20) && (person1.getGender="=" Person.MALE)) System.out.println("This boy is a teenager"); 

A few other ways to solve nesting problems are to convert a series of if statements into if - else if - else statements. This will also help to remove a level of nesting. But if you don’t want to include else ifs you could convert a series of if statements into a case statement.

If you aren’t able to use any of the previous suggestions to reduce the nesting in your code you should put the deeply nested code into it’s own well-named routine. This will allow you to nest quite a few statements without the code becoming unreadable.


Structured Programming

Structured programming refers to the concept that all programs are made up of three types of components. These three components are called sequences, which are a series of statements that are executed in order, selections, which are if - else statements and case statements, and finally iterations, which are loops. When programs are only made up of these three components they tend to have fewer errors.

You may have heard of another type of statement that you are never supposed to use because it is not a part of structured programming. If you have guessed that I am talking about goto statements you are right. But the original statement that goto statements are not part of structured programming is untrue, and it may not be so terrible to use goto statements. It may be all right to use goto statements so long as they are used properly. Goto statements should only be used to emulate structured programming constructs, such as loops, if the language that you are using does not already have a version of that construct built in. To use a goto statement properly you should write in comment lines the commands that you want to copy. Then around those comments you should include the proper statements to build the function. You should remember that in C, Java and C++ there will be little reason to use a goto statement as all the structured programming constructs are built in.


Control Structures and Complexity

It is a good idea to reduce the complexity of your programs because humans can only hold between five and nine objects in their short-term memories; this is one of the reasons that phone numbers are only seven digits long. As the number of objects increase humans tend to forget about some of the objects. This can lead to problems when you are programming. So it makes sense to try and reduce the complexity so that you, as the human programmer, don’t have problems trying to keep track of many different objects.

There is a way to measure the complexity of your code, first of all start with a count of one for the straight path through your routine, then add one for any of the following keywords or their equivalents: if, while, repeat, for, and, or. Then you should add one for every case in a case statement in your routine. If you get a total less than five then your program is probably fine. If your total is between six and ten then you should maybe start trying to reduce the complexity of your program using some of the methods discussed above. If your total is greater than ten you should start breaking your code into smaller routines, which will reduce the complexity.


This page was created by Allan Caine, Laura Drever, Gorana Jankovic, Megan King, Marie Lewis
Sunday, 18-Jun-2000 20:00:18 CST.
Copyright 2000 Department of Computer Science, University of Regina.

[CS Dept Home Page]