marc@apollo.uucp (Marc Gibian) (01/09/88)
As promised, here are some examples of decreasing the quality of code while reducing the complexity measure value. The complexity measure used was Raytheon/ED/SSL-s version of the McCabe metric. They made one modification to the algorithm because they judged that they did not want to count each case in a switch as an increment in complexity. The contractual requirement was that every procedure and function have a McCabe value less than or equal to 10. I must admit that since I know longer work for them, the examples I am providing are not being extracted from product libraries. Instead, they are constructed examples describing the things that were actually done to the product I worked on while at Raytheon. Example 1: original fragment: if (a == value1) then y = target1; else if (b == value2) then y = target2; else if (c == value3) then y = target3; else if (d == value4) then y = target4; else if (e == value5) then y = target5; else if (f == value6) then y = target6; end if; transformed fragment: new_func((a == value1), target1); new_func((b == value2), target2); new_func((c == value3), target3); new_func((d == value4), target4); new_func((e == value5), target5); new_func((f == value6), target6); with new_func defined as: new_func(relation_value, target) { if (relation_value) then y = target; end if; } discussion: This example shows an original code fragment which tests a number of similar variables against a number of values. This can not be transformed into a CASE construct, so some way of removing the if else if chain must be found. The resulting code moves the tests into new_func. Note that this particular example uses y as global to new_func, to allow new_func to represent a single iteration of the if else if chain. Some may argue that the transformed code is better. I argue, though, that is hides the logic, while the original code was quite clear, therefore easily maintained. Example 2: original code fragment: if (failing) then if (color_display) then color = red; else inverse = TRUE; end if; else if (mode = offline) then color = color_1; else if (sub_mode = training) then color = color_2; end if; transformed fragment: if (! fail_func) then if (! mode_func) then sub_mode_fun; end if; end if; where fail_func is : BOOLEAN fail_func() { if (failing) then if (color_display) then color = red; else inverse = TRUE; end if; return (TRUE); else return (FALSE); end if; } discussion: The original code fragment handles the problem of selecting a color for a field in a display. The algorithm for this is a hierarchical checking of a number of variables. The fragment mearly moves testings into less obvious locations, while the main code fragment is reduced to calling functions that set the color. I believe the transformed code is more complex and harder to maintain because it is bigger, includes more interfacing, and separates the decisions relating to the color to use. Thats all I have time for now... I would like to repeat my feelings regarding complexity measures, based upon my experience in using them: I agree with the goal of using a metric to measure the characteristics of code, I simply believe that no metric now exists that truly measures the qualities that people (management, customers) want to measure. Marc S. Gibian Software Engineer Apollo Computers email: marc@apollo.UUCP
g-rh@cca.CCA.COM (Richard Harter) (01/09/88)
In article <398e2f3e.fed5@apollo.uucp> marc@apollo.uucp (Marc Gibian) writes: >As promised, here are some examples of decreasing the quality of code >while reducing the complexity measure value... > >original fragment: > > if (a == value1) then > y = target1; > else if (b == value2) then > y = target2; > else if (c == value3) then > y = target3; > else if (d == value4) then > y = target4; > else if (e == value5) then > y = target5; > else if (f == value6) then > y = target6; > end if; > >transformed fragment: > > new_func((a == value1), target1); > new_func((b == value2), target2); > new_func((c == value3), target3); > new_func((d == value4), target4); > new_func((e == value5), target5); > new_func((f == value6), target6); > >with new_func defined as: > >new_func(relation_value, target) >{ > if (relation_value) then > y = target; > end if; >} I would agree that the altered code is less desirable than the original code -- indeed the altered code is not correct in that it is not equivalent to the original code! The altered code goes through all tests whereas the original code stops testing when a match is found. The fundamental problem with the complexity measure in this case is, I believe, twofold: (a) that the language being used lacks the construct being used, and (b) the complexity measure is incorrectly calculated. The language doesn't really capture the parallelism of the construct. The desired construct, in pseudocode, runs something like this: define value list A = {a,b,c,d,e,f} define desired value list B corresponding to A = {value1,value2,value3,value4,value5,value6} define target list C corresponding to A = {target1,target2,target3,target4,target5,target6} loop through list A if item from A equals coresponding desired item from B then set y to target in C corresponding to item from A and exit from loop If the lists are short one prefers to embed them implicitly in the code; if they are long one prefers to be put them in tables. However this solution is not available if the types in the the value list are not all the same. If we borrow the naked select statement from PL/I the code would look like this select { a==value1: y = target1; break; b==value2: y = target2; break; ...... f==value6: y = target6; break; } Whether each case should count as an increment in complexity depends on the cases. In this instance the cases are parallel -- I would feel that the appropriate measure of complexity is the logarithm of the number of cases. If, however, the cases are not parallel, then I would feel that the compexity goes up linearly with the number of case. [I am not prepared to defend this position.] In any case what we see here is a complex structure that uses code repetition rather than data repetition. Neither the language being used, nor the complexity measure being used provide means for dealing with the higher level construct. Each, in its own way, deals with the higher level construct only in terms of lower level constructs. I would agree that the complexity measure being used does not adequately measure the complexity of the code -- I would argue that the fault lies in the lack of adequate means for heirarchical composition of structure in the language and a corresponding lack of measurement in the complexity measurement tool. -- In the fields of Hell where the grass grows high Are the graves of dreams allowed to die. Richard Harter, SMDS Inc.
franka@mmintl.UUCP (Frank Adams) (01/14/88)
In article <398e2f3e.fed5@apollo.uucp> marc@apollo.uucp (Marc Gibian) writes: |As promised, here are some examples of decreasing the quality of code |while reducing the complexity measure value. ... The contractual |requirement was that every procedure and function have a McCabe value less |than or equal to 10. One general comment: these examples suggest that, at least at the current level of development, the complexity measures are better as a tool for evaluating code than as a goal. They make a better servant than a master. |original fragment: | | if (a == value1) then | y = target1; |[...] | else if (f == value6) then | y = target6; | end if; | |transformed fragment: | | new_func((a == value1), target1); |[...] | new_func((f == value6), target6); | |with new_func defined as: | |new_func(relation_value, target) |{ | if (relation_value) then | y = target; | end if; |} Two comments here: It seems to me that a metric should regard an "if ... else if ... ... [else ...]" construct as approximately equivalent in complexity to an "if ... else ..." construct. It seems to me also that use of global variables should be penalized. As a first approximation, one might include a cost of 1 for each global variable referenced by the program. (This still leaves the option of putting several more or less irrelevant globals into a structure, to cut down on the number of globals. Perhaps each component explicitly referenced should cost 1?) |I agree with the goal of using a metric to measure the characteristics of |code, I simply believe that no metric now exists that truly measures the |qualities that people (management, customers) want to measure. This seems like a reasonable summary. -- Frank Adams ihnp4!philabs!pwa-b!mmintl!franka Ashton-Tate 52 Oakland Ave North E. Hartford, CT 06108