Week1 Procedural Decomposition
Week1 Procedural Decomposition
Week1 Procedural Decomposition
There are often many ways to divide a problem into functions, but some sets
of functions are better than others. Decomposition is a concept that is often
vague and challenging, especially for larger programs with complex
behavior. But the rewards are worth the efort, because a well designed
program is more understandable and more modular. This is important when
programmers work together or when revisiting a past program to add new
behavior or modify existing code. There is no single perfect design, but in
this handout we will discuss several heuristics (guiding principles) for
efectively decomposing large programs into functions.
#include <iostream>
#include "console.h"
#include "simpio.h"
using namespace std;
// A poorly designed version of a program to compute
// a user's body mass index (BMI).
void person(int num);
void readWeight(int num, double height);
void reportStatus(int num, double height, double weight);
int main() {
cout << "This program reads data for two" << endl;
cout << "people and computes their body" << endl;
cout << "mass index and weight status." << endl;
cout << endl;
person(1);
return 0;
}
// process one person
void person(int num) {
cout << "Enter person #" << num << "'s information:" << endl;
double height = getReal("height (in inches)? ");
readWeight(num, height);
}
// read person's weight in pounds
void readWeight(int num, double height) {
double weight;
weight = getReal("weight (in pounds)? ");
reportStatus(num, height, weight);
}
// tell if the person is under/overweight
void reportStatus(int num, double height, double weight) {
double bmi = weight / (height * height) * 703;
cout << "body mass index = " << bmi << endl;
if (bmi < 18.5) {
cout << "underweight" << endl;
} else if (bmi < 25) {
cout << "normal" << endl;
} else if (bmi < 30) {
cout << "overweight" << endl;
} else {
cout << "obese" << endl;
}
if (num == 1) {
person(2); // handle second person
}
}
3
Director
|
+----------------------+----------------------------------+
| | |
Marketing Design Engineering
Administrator Manager Manager
| |
+--------+--------+ +--------+--------+
| | | |
Secretary Architect Engineer Administrator
A good structure gives each group clear tasks to complete, avoids giving any
particular person or group too much work, and provides a balance between
workers and management. This leads to the frst of our procedural design
heuristics.
In the business analogy, each group must have a clear idea of what work it is
to perform. If each group does not have clear responsibilities, it's more
difficult for the company director to keep track of who is working on what
task. When a new job comes in, two departments might both try to claim it,
or a job might go unclaimed by any department.
cohesion
A desirable quality where the responsibilities of a function or process
are closely related to each other.
A good rule of thumb is that you should be able summarize each of your
functions in a single sentence such as, "The purpose of this function is to ..."
Writing a sentence like this is a good way to comment a function's header. A
bad sign is when you have trouble describing the function in a single
sentence, or if the sentence is long and uses the word "and" several times.
This can mean that the function is too large, too small, or does not perform a
cohesive set of tasks.
4
The functions of the bad BMI example have poor cohesion. The person
function's purpose is vague, and readWeight is too trivial and probably
should not be its own function. The reportStatus function would be more
readable if the computation of the BMI were its own function, since the
formula is complex.
A subtler application of this frst heuristic is that not every function must
produce output. Sometimes a function is more reusable if it simply
computes a complex result and returns it, rather than printing the result that
was computed. This leaves the caller free to choose whether to print the
result or to use it to perform further computations. In the bad BMI program,
the reportStatus function both computes and prints the user's BMI. The
program would be more fexible if it had a function to simply compute and
return the BMI value. Such a function might seem trivial because its body is
just one line in length, but it has a clear, cohesive purpose: capturing a
complex expression that is used several times in the program.
coupling
An undesirable state where two functions or processes rigidly depend
on each other.
Functions are coupled if one cannot easily be called without the other. One
way to determine how tightly coupled two functions are is to look at the set
of parameters one passes to the other. A function should accept a
parameter only if that piece of data needs to be provided from outside, and
only if that data is necessary to complete the function's task. In other words,
if a piece of data could be computed or gathered inside the function, or if the
data isn't used by the function, it should not be declared as a parameter to
the function.
An important way to reduce coupling between functions is by using returns
and reference "output" parameters to send information back to the caller. A
function should return a result value if it computes something that may be
useful to later parts of the program. Because it is desirable for functions to
be cohesive and self-contained, returning a result is often more desirable
than calling further functions and passing the result as a parameter to them.
None of the functions in the bad BMI program returns a value. Each function
passes parameters to the next functions, but none of them return. This is a
lost opportunity because several values (such as the user's height, width, or
BMI) would be better handled as return values.
The top person in each major group or department of our company example
reports to the Director. By looking at the groups directly connected to the
Director at the top level of the company diagram, you can see a summary of
the overall work: design, engineering, and marketing. This helps the Director
stay aware of what each group is doing. Looking at the top-level structure
can also be useful if another employee wants a quick overview of the
company's goals.
A program's main function is like the director in that it begins the overall task
and executes the various subtasks. A main function should read as a
summary of the overall program's behavior. Programmers can understand
each other's code by looking at main to get a sense of what the program is
doing as a whole.
6
A common mistake that prevents main from being a good program summary
is when the program contains a "do-everything" function. The main function
will call the do-everything function, which will proceed to do most or all of
the real work.
chaining
An undesirable design where a "chain" of several functions call each
other, without returning the overall fow of control to main.
A program sufers from chaining if the end of each function simply calls the
next function. Chaining often occurs when a new programmer does not fully
understand returns and tries to work around this by passing more and more
parameters down to the rest of the program. The following fgure shows a
hypothetical program with two designs. The fow of calls in a badly chained
program might look like the diagram on the left.
main main
| |
+--- function1 +--- function1
| |
+--- function2 +--- function2
| | |
+--- function3 | +--- function3
| |
+--- function4 +--- function4
| |
+--- function5 +--- function5
The bad BMI program sufers heavily from chaining. Each function does a
small amount of work and then calls the next function, passing more and
more parameters down the chain. The main function calls person, which
calls readWeight, which calls reportStatus. Never does the fow of execution
return to main in the middle of the computation. So by reading main you
don't get a very clear idea what computations will be made.
One function should not call another simply as a way of moving on to the
next task. A more desirable fow of control is to let main manage the overall
execution of tasks in the program, as shown on the right side of the fgure
7
above. This doesn't mean that it is always bad for one function to call
another function; it is okay for one function to call another when the second
is a subtask within the overall task of the frst, such as in BMI3 when the
reportResults function calls reportStatus.
This principle has two applications to programs. The frst is that the main
function should avoid performing low-level tasks as much as possible. For
example, in an interactive program main should not read the majority of the
user input and output lots of println statements.
A sign of poor data ownership is when the same parameter must be passed
down several function calls, such as the height variable in the bad BMI
program. If you are passing the same parameter down several levels of
calls, perhaps that piece of data should instead be read and initialized by one
of the lower-level functions.
After applying all of the heuristics discussed in this handout, we arrive at the
improved version of the BMI program shown on the following page. Notice
that the main function is a better concise summary of the overall execution
of the program, and that the functions have reduced coupling and chaining.
8