CS 106B: Procedural Decomposition Handout written by Marty Stepp
Procedural Design Heuristics There are often many ways to divide a problem into functions, but some sets of functions are better than others. Decomposition is a concept concept that is often vague and challenging, especially for larger programs with complex behavior. But the rewards rewards are worth the eort, because a well designed program is more more understandable and more modular. This is important when programmers wor! together or when revisiting a past program to add new behavior or modify existing existing code. There is no single perfect design, design, but in this handout we will discuss several heuristics "guiding "guiding principles# for eectively decomposing large programs into functions. $onsider the following poorly structured implementation of a program to compute a person%s body mass index, or BM& . 'e%ll use this this program as a counterexample, highlighting places where it violates our heuristics and reasons why it is worse than the previous complete version of the BM& program. "see next page#
(
#include #include "console.h" #include "simpio.h" using namespace std; // A poorly designed version of a program to compute // a user's body mass index !$. void personint num$; void read%eightint num& double height$; void reporttatusint num& double height& double (eight$; int main$ ) cout << "*his program reads data for t(o" << endl; cout << "people and computes their body" << endl; cout << "mass index and (eight status." << endl; cout << endl; -
person+$; return ,;
// process one person void personint num$ ) cout << "nter person #" << num << "'s information" << endl; double height 0 get1eal"height in inches$2 "$; read%eightnum& height$; // read person's (eight in pounds void readWeightint num& double height$ ) double (eight; (eight 0 get1eal"(eight in pounds$2 "$; reporttatusnum& height& (eight$; // tell if the person is under/over(eight void reportStatusint num& double height& double (eight$ ) double bmi 0 (eight / height 3 height$ 3 4,5; cout << "body mass index 0 " << bmi << endl; if bmi < +6.7$ ) cout << "under(eight" << endl; - else if bmi < 87$ ) cout << "normal" << endl; - else if bmi < 5,$ ) cout << "over(eight" << endl; - else ) cout << "obese" << endl; -
-
if num 00 +$ ) person8$; -
// handle second person
) The functions of a program are li!e wor!ers in a company. The author of a program acts li!e the director of a company, deciding what employee positions to create, what employees should be grouped together into groups, which wor! to tas! to which group, and how groups will interact. Suppose a company director were to divide wor! into three ma*or departments, two being overseen by middle managers+
9irector : : : : ar=eting 9esign ngineering Administrator anager anager : : : : : : ecretary Architect ngineer Administrator
good structure gives each group clear tas!s to complete, avoids giving any particular person or group too much wor!, and provides a balance between wor!ers and management. This leads to the -rst of our procedural design heuristics.
1. Each function should have a coherent set of responsiilities. &n the business analogy, each group must have a clear idea of what wor! it is to perform. &f each group does not have clear responsibilities, it%s more dicult for the company director to !eep trac! of who is wor!ing on what tas!. 'hen a new *ob comes in, two departments might both try to claim it, or a *ob might go unclaimed by any department. The analogous concept in programming is that each function should have a clear purpose and set of responsibilities. This is called cohesion .
cohesion desirable /uality where the responsibilities of a function or process are closely related to each other. good rule of thumb is that you should be able summari0e each of your functions in a single sentence such as, 1The purpose of this function is to ...1 'riting a sentence li!e this is a good way to comment a function%s header. bad sign is when you have trouble describing the function in a single sentence, or if the sentence is long and uses the word 1and1 several times. This can mean that the function is too large, too small, or does not perform a cohesive set of tas!s.
2
The functions of the bad BM& example have poor cohesion. The person function%s purpose is vague, and read'eight is too trivial and probably should not be its own function. The reportStatus function would be more readable if the computation of the BM& were its own function, since the formula is complex. subtler application of this -rst 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. &n the bad BM& program, the reportStatus function both computes and prints the user%s BM&. The program would be more 3exible if it had a function to simply compute and return the BM& value. Such a function might seem trivial because its body is *ust one line in length, but it has a clear, cohesive purpose+ capturing a complex expression that is used several times in the program.
!. "o one function should do too large a share of the overall tas#. 4ne subdivision of a company cannot be expected to design and build the entire product line for the year. This would overwor! that subdivision and would leave the other divisions without enough wor! to do. lso this would ma!e it dicult for the subdivisions to communicate eectively, since so much important information and responsibility would be concentrated among so few people. Similarly, one function should not be expected to comprise the bul! of a program. This follows naturally from Heuristic 5, because a function that does too much cannot be cohesive. 'e sometimes refer to functions li!e these as 1do6everything1 functions because they do nearly everything involved in solving the problem. 7ou may have written a 1do6everything1 if one function that is much longer than the others, hoards most of the variables and data, or contains the ma*ority of the logic and loops. &n the bad BM& program, the person function is an example of a do6 everything function. This may seem surprising, since the function is not very many lines long. But a single call to person leads to several other calls that collectively end up doing all of the wor! for the program.
$. Coupling and dependencies et%een functions should e minimi&ed. company is more productive if each of its subdivisions can largely operate independently when completing small wor! tas!s. Subdivisions of the company do need to communicate and depend on each other, but such
8 communication comes at a cost. &nter6departmental interactions are often minimi0ed and !ept to meetings at speci-c times and places. &n programming, we try to avoid functions that have tight coupling.
coupling n undesirable state where two functions or processes rigidly depend on each other. 9unctions are coupled if one cannot easily be called without the other. 4ne way to determine how tightly coupled two functions are is to loo! at the set of parameters one passes to the other. 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 tas!. &n 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. n important way to reduce coupling between functions is by using returns and reference 1output1 parameters to send information bac! to the caller. 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 self6contained, returning a result is often more desirable than calling further functions and passing the result as a parameter to them. :one of the functions in the bad BM& program returns a value. ;ach 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 BM would be better handled as return values.
'. (he main function should e a concise summar) of the overall program. The top person in each ma*or group or department of our company example reports to the Director. By loo!ing at the groups directly connected to the Director at the top level of the company diagram, you can see a summary of the overall wor!+ design, engineering, and mar!eting. This helps the Director stay aware of what each group is doing.
> common mista!e that prevents main from being a good program summary is when the program contains a 1do6everything1 function. The main function will call the do6everything function, which will proceed to do most or all of the real wor!. nother mista!e is when a program suers from a property that we call chaining.
chaining n undesirable design where a 1chain1 of several functions call each other, without returning the overall 3ow of control to main. program suers from chaining if the end of each function simply calls the next function. $haining often occurs when a new programmer does not fully understand returns and tries to wor! around this by passing more and more parameters down to the rest of the program. The following -gure shows a hypothetical program with two designs. The 3ow of calls in a badly chained program might loo! li!e the diagram on the left.
main : function+ : function8 : function5 : function : function7
main : : : : :
function+ function8 : function5 function : function7
The bad BM& program suers heavily from chaining. ;ach function does a small amount of wor! and then calls the next function, passing more and more parameters down the chain. The main function calls person, which calls read'eight, which calls reportStatus. :ever does the 3ow 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. 4ne function should not call another simply as a way of moving on to the next tas!. more desirable 3ow of control is to let main manage the overall execution of tas!s in the program, as shown on the right side of the -gure
? above. This doesn%t mean that it is always bad for one function to call another function@ it is o!ay for one function to call another when the second is a subtas! within the overall tas! of the -rst, such as in BM&) when the reportAesults function calls reportStatus.
*. Data should e +o%ned+ at the lo%est level possile. Decisions in a company should be made at the lowest possible level in the organi0ational hierarchy. 9or example, a low6level administrator can decide how to perform hisher own wor! without needing to constantly consult a manager for approval. But the administrator does not have enough information or expertise to design the entire fall product line@ this goes to a higher authority such as the manager. The !ey principle is that each wor! tas! should be given to the lowest person in the hierarchy who can correctly handle it. This principle has two applications to programs. The -rst is that the main function should avoid performing low6level tas!s as much as possible. 9or example, in an interactive program main should not read the ma*ority of the user input and output lots of println statements. The second application is that variables should be declared and initiali0ed in the narrowest possible scope. bad design is to have main "or another high6 level function# read input and perform computations, and then pass this data as parameters to the various low6level functions. better design is to have the low6level functions read and process the data, and return it to main only if it is needed by a later subtas! in the program. 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 BM& program. &f you are passing the same parameter down several levels of calls, perhaps that piece of data should instead be read and initiali0ed by one of the lower6level functions.
,mproved version of B-, program fter applying all of the heuristics discussed in this handout, we arrive at the improved version of the BM& program shown on the following page. :otice 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.
C
// A better designed version of a program to compute // t(o users' body mass index !$ values. void intro$; void personint num$; double compute!double height& double (eight$; void reporttatusdouble bmi$; int -
main$ )
intro$; person+$; person8$; return ,;
// A (elcome message to introduce the program to the user. void intro$ ) cout << "*his program reads data for t(o" << endl; cout << "people and computes their body" << endl; cout << "mass index and (eight status." << endl; cout << endl; // 1ead information about one person and compute/display !. void personint num$ ) cout << "nter person #" << num << "'s information" << endl; double height 0 get1eal"height in inches$2 "$; double (eight 0 get1eal"(eight in pounds$2 "$; double bmi 0 compute!height& (eight$; reporttatusbmi$; // ?omputes/returns one person's ! value based on height and (eight. double computeBMIdouble height& double (eight$ ) return (eight / height 3 height$ 3 4,5; // @rints the person's ! and over/under (eight status. void reportStatusdouble bmi$ ) cout << "body mass index 0 " << bmi << endl; if bmi < +6.7$ ) cout << "under(eight" << endl; - else if bmi < 87$ ) cout << "normal" << endl; - else if bmi < 5,$ ) cout << "over(eight" << endl; - else ) cout << "obese" << endl; -