CPSC 333 --- Lecture 18 --- Friday, February 16, 1996 More Information about "Functional Independence" Levels of Cohesion Low Cohesion (Highly Undesirable): - Coincidental Cohesion: A module that "only" has *coincidental cohesion* is one supporting tasks that have no meaningful relationship to one another. Page-Jones gives, as an example, a module (*not* necessarily one that can be implemented using software!) supporting the following: a) Fix Car b) Bake Cake c) Walk Dog d) Fill our Astronaut-Application Form e) Have a Beer f) Get out of Bed g) Go the the Movies The legendary (FORTRAN or COBOL) programmer who, after being told to use subroutines and being told of an "ideal" length for these, drew a horizontal line after every twenty lines of code and put each block into a module, was no doubt creating modules with "coincidental cohesion" (and breaking one or two other rules along the way). If you discover that the best name you can find for a module is, "Miscellaneous Functions," then this is *also* a sign that your module has this undesirably low level of cohesion. There is, pretty much, *no excuse* for inclusion of modules whose level of cohesion is this low, in a design for a system that's to be implemented using any high level programming language. - Logical Cohesion: Again, quoting Page-Jones: "A *logically cohesive* module is one whose elements contribute to activities of the same general category in which the activity or activities to be executed are selected from outside the module. Keeping this definition in mind, consider the following example. Someone contemplating a journey might compile the following list: 1. Go by Car 2. Go by Train 3. Go by Boat 4. Go by Plane What relates these activities? They're all means of transport, of course. But a crucial point is that for any journey, a person must choose a specific subset of these modes of transport. It's unlikely anyone would use them *all* on any particular journey. A logically cohesive module contains a number of activities of the same general kind. To use the module, we pick out just the piece(s) we need. Thus, a logically cohesive module is a grab bag of activities. The activities, although different, are forced to share the one and only interface to the module. The meaning of each parameter depends on which activity is being used; for certain activities, some of the parameters will even be left blank (although the calling module still needs to use them and to know their specific types)." As Page-Jones notes later on his description, a module with a name like "Do all System I/O" is probably a module that is only "logically cohesive." Again, a module whose level of cohesion is low as this will be extremely difficult to effectively implement, test, and maintain --- so it should be avoided. - Temporal Cohesion: A *temporally cohesive* module is one supporting tasks that are all related in time. Page-Jones' (somewhat dated) example is a module supporting the tasks 1. Put out Milk Bottles 2. Put out Cat 3. Turn off TV 4. Brush Teeth and argues that these activities are (only) related by the fact that you do them all late at night, just before you go to bed. A module whose name is "Do All Startup Activities," or "Do All Shutdown Activities," might only have "temporal cohesion." On the other hand, it *might* have the next higher level of cohesion... Moderate Cohesion (Acceptable) - Procedural Cohesion: A module with (only) "procedural cohesion" is one supporting different and possibly unrelated activities, in which control passes from one activity to the next. Page-Jones gives an example of a module (whose name might be something like, "Prepare for Holiday Meal:" 1. Clean Utensils from Previous Meal 2. Prepare Turkey for Roasting 3. Make Phone Call 4. Take Shower 5. Chop Vegetables 6. Set Table This is *a bit* better than "temporal cohesion," since we know that there's a fixed "linear ordering" of the activities. However, there's still not much reason for putting all these activities together into one module. - Communicational Cohesion: A module exhibits "communicational cohesion" if all the activities it supports use the same input or output data --- or access and modify the same part of a data structure. Page-Jones' example: A module supporting the activities 1. Find Title of Book 2. Find Price of Book 3. Find Publisher of Book 4. Find Author of Book (again, presumably, with a user to specify one, or a set, of these activities that will occur when the module is called). Another example would be a module providing the entire interface to a stack --- supporting "activities" Push, Pop, an Empty? test, initialization of a new (empty) stack --- and, possibly, operations for initialization by reading the contents from a file, and that perform a "save" by writing contents to a file. (Note that we've now left the kind of "linear ordering", or "containment relationships," that we've had up until now: I can imagine a module that would seem to have "procedural cohesion" but not "communicational cohesion", and *another* module that has "communicational cohesion" but not "procedural cohesion." For most of the kinds of cohesion defined up until this point, if a module had the kind of "cohesion" that had just been defined, then it could be argued that it also had all the other kinds of "cohesion" that had been defined before that.) - Sequential Cohesion: Again, quoting Page-Jones: "A *sequentially cohesive* module is one whose elements are involved in activities such that output data from one activity serves as input data to the next. Page-Jones gives an example of a module supporting the activities 1. Clean Car Body 2. Fill in Holes in Car 3. Sand Car Body 4. Apply Primer ...presumably, with the "car" being the input that is being "passed" as a parameter from task to task. High Cohesion (Desirable) - Functional Cohesion: A module exhibits "functional cohesion" if it supports activities needed for the execution for one and only one problem-related task. Page-Jones gives several examples --- modules with names A. Compute Cosine of Angle B. Verify Alphabetic Syntax C. Read Transaction Record D. Determine Customer Mortgage Repayment E. Compute Point of Impact of Missile F. Calculate Net Employee Salary G. Assign Seat to Airline Customer Good News: You don't generally need to decide precisely *which* kind of cohesion a given module has; it's sufficient, instead, to decide whether you think the level of cohesion is "high" (so that no changes are desirable), "moderate" (so that changes to improve cohesion should be considered --- but rejected if they cause other problems), or "low" (in which changes almost certainly *should* be made). See Chapter 6 of Page-Jones for lots more information. Levels of Coupling Page-Jones cites a set of "principles" that he adapts from Yourdon and Constantine's work on structured design. These principles refer to the kind of connections between modules that are desirable: 1. Create narrow (as opposed to broad) connections. 2. Create direct (as opposed to indirect) connections. 3. Create local (as opposed to remote) connections. 4. Create obvious (as opposed to obscure) connections. 5. Create flexible (as opposed to rigid) connections. Sets (usually, pairs) of modules have connections that follow these principles if their level of coupling is low, and violate these principles if their level of coupling is high. Highest Coupling (Should Not be Allowed): - Content Coupling. Page-Jones also calls this "Pathological Coupling." Two (or more) modules exhibit this if one refers to the "inside" --- the "internal" or "private" part --- of the other in some way. Pages-Jones gives the following examples: - Module A "branches" or "falls through" into Module B (by containing a "GOTO" statement that transfers control somewhere into the middle of Module B) - Module A refers to, or changes, Module B's internal (and, again, "private") data - Module A changes one of the statements in Module B's object code. These could be called "sick practices" with some justification. High level programming languages make these difficult, though you can certainly do these things using assembly languages (or C). "Optimization" is sometimes cited as an excuse for these. This is the *only* plausible excuse for these I can think of --- you might consider resorting to some of these only after every other sensible strategy has failed to produce a program that meets the system's performance requirements (long *after* you've reimplemented critical sections, used hardware components instead of software where possible, etc.) Optimization is often *unnecessary,* and there are less troublesome things you can to do to improve program efficiency. Since the above practices make proper testing difficult, and program maintenance almost impossible, programming practices that introduce "content coupling" should be regarded as a last resort (and ideally, never be used). High Coupling (Undesirable, but possibly Unavoidable; Minimize, and use Interfaces for "Information Hiding"): - Common Coupling: Two or more modules exhibit *common coupling* if they refer to the same global data area --- that is, to something that corresponds to a data store on a DFD or a "register" that must be shared by several processes. - External Coupling: Two or more modules exhibit *external coupling* if they share direct access to the same I/O device or are "tied to the same part of the environment external to software" in some other way. Moderate Coupling (Acceptable where necessary --- but avoid "Tramp Data"): - Control Coupling: Two modules exhibit *control coupling* if one ("module A") passes to the other ("module B") a piece of information that is intended to control the internal logic of the other. This will often be a value used in a test for a "case" statement, "if-then" statement, or "while" loop, in module B's source code. This is perfectly acceptable. However, the program architecture (as shown by the structure chart) should make it clear that module A *does* control module B in this way --- preferably by having module A call module B directly, or vice-versa. Then, when the system is combined together ("integrated") and tested, the two modules will be combined together, and tested as one unit, relatively early in the process --- so that any problems arising from this "control coupling" will be detected early on. Low Coupling (called "Normal Coupling" by Page-Jones) - Stamp Coupling is an extension of "Data Coupling," so you may want to skip down and read about "Data Coupling" first. Two modules ("A" and "B") exhibit *stamp coupling* if one passes directly to the other a "composite" piece of data --- that is, a piece of data with meaningful internal structure --- such as a record (or "structure"), array, or (pointer to) a tree. - Data Coupling: Two modules exhibit data coupling if one calls the other directly and they communicate using "parameter" --- a simple list of inputs and outputs (and inputs that are modified) --- with each parameter being an *elementary* piece of data, such as an integer, floating point number, boolean value, member of an "enumerated set", character, or (maybe) character string. Ideally, this is the usual type of interaction between modules that need to communicate at all: modules with higher level of coupling this are used "when necessary." Lowest Coupling - Modules "A" and "B" have no direct communication and are also not "tied together" by sharing access to the same global data area or external device. This is the ideal situation, because it implies that A and B be implemented, tested, and maintained (almost) completely independently; neither will affect the behaviour of the other Of course, it is necessary to have *some* communication among modules in any nontrivial system. Once again, Page-Jones' book is highly recommended as a reference for more information about coupling.