Lecture
Software quality
Schulmeyer and McManus define software quality as "fitness for use" [10]. The quality of a program must not be a matter of chance. Quality must be guaranteed by the development process. In fact, object-oriented technology does not automatically produce quality: one can write arbitrarily bad programs in any object-oriented programming language.
This is why in the object-oriented development process we attach such importance to the architecture of the software system. Quality is built in thanks to a simple, flexible architecture and is realized through natural and consistent tactical decisions.
Software quality assurance is "the systematic activities providing evidence of the fitness for use of the total software product" [11]. The purpose of quality assurance is to give us quantitative measures of the quality of a software system. Many traditional quantitative measures are directly applicable to object-oriented systems as well.
As described above, walkthroughs and reviews lose none of their significance in object-oriented systems, making it possible to predict the quality of the system and to influence it. Probably the single most important quantitative criterion of quality is the number of defects discovered. During the evolution of the system we record software defects according to their weight and location. The defect discovery rate graph plots the number of defects discovered against time. As Dobbins points out, "the actual number of errors is not as important as the slope of this graph" [12]. For a process under control this graph has the shape of a hump, with its peak roughly in the middle of the testing period, after which the curve falls to zero. An uncontrolled process corresponds to a curve that does not decrease with time, or decreases only slowly.
One of the merits of the macro process in object-oriented development is that it allows continuous collection of data on the number of defects discovered from the very earliest stages of development. For each new release we can carry out system testing and plot the number of defects against time. In a "healthy" project the hump-shaped form of this graph is observed for every release, starting with the earliest ones.
Another quantitative measure is defect density. The number of defects discovered per thousand lines of program text (KSLOC - Kilo Source Lines Of Code) is a traditional metric, applicable in particular to object-oriented systems. In "healthy" projects, defect density "tends to reach a stable value after about 10 KSLOC have been reviewed. As we review further code, we should not observe an increase in this figure" [13].
We believe that in object-oriented systems it is also useful to measure the number of defects per class category or per class. Here the 80/20 rule is considered acceptable: 80% of the defects found in a program are concentrated in 20% of the system's classes [14].
In addition to these more formal approaches to accumulating defect information obtained during testing, we find it useful to organize "bug hunts", in which anyone who wishes may experiment with a release for a limited period of time, after which prizes are awarded to whoever discovered the largest number of defects and to whoever tracked down the most obscure one. The prizes need not be extravagant: a coffee mug, meal vouchers, movie tickets or even a T-shirt will do for rewarding the fearless hunters.
Object-oriented metrics
Probably the worst way for a manager to assess the work that has been done is to count the number of lines of program text written. The number of lines that end up in a fragment of code has absolutely no connection with its completeness or complexity. In addition to the other shortcomings of this Neanderthal approach, it is far too easy to play games with the figures, which leads to productivity estimates that differ from one another by more than two orders of magnitude. For example, what is a line of a program (especially in Smalltalk)? Are physical lines counted, or semicolons? How does one account for several statements on one line, and for statements that occupy more than one line? And how does one measure the amount of labor expended? Should the code be credited to the work of the entire staff, or perhaps only to the programmers who wrote the implementation? Should a working day be counted as eight hours, or should the time spent getting going in the morning be counted too? Traditional measures of complexity are more suited to the first generations of programming languages; they are not indicators of the completeness and complexity of an object-oriented system.
For example, McCabe's cyclomatic complexity metric will not be a particularly useful measure of complexity if it is applied to an object-oriented system as a whole, because it is blind to the system's class structure and to its mechanisms. However, we find it useful to apply the cyclomatic metric to individual classes - it gives some idea of their complexity and can be used to identify the most suspect classes, which probably contain the most defects.
We tend to measure development progress by the number of classes that are finished and working (the logical aspect), or by the number of modules that are functioning (the physical aspect). As was said in the previous chapter, another measure of progress is the stability of the key interfaces (that is, how often they undergo change). At first the interfaces of all the key abstractions change daily, if not hourly. After some time the most important of the key interfaces stabilize, then the second most important ones, and so on. By the end of the development life cycle only a few insignificant interfaces need refinement, since the main emphasis is placed on making the finished classes and modules work together. Sometimes some changes have to be introduced into the key interfaces, but they usually remain upwardly compatible. Moreover, these changes are made only after their impact has been carefully thought through. These changes can be introduced into the system under development when a new release is being prepared.
Chidamber and Kemerer propose several measures that are directly applicable to object-oriented systems [15]:
The weighted methods per class gives a relative measure of a class's complexity; if one assumes that all methods have the same complexity, then this is simply the number of methods in the class. In general, a class that has a greater number of methods than the classes at its own level is more complex; most likely it is specific to the given application and contains the largest number of defects.
The depth of the inheritance tree and the number of children are quantitative characteristics of the shape and size of the class structure. As was discussed in Chapter 3, a well-structured object-oriented system is more often organized as a forest of classes than as a single towering tree. We advise building inheritance structures balanced in depth and breadth: usually no deeper than 7Ѓ2 levels and no wider than 7Ѓ2 branches.
Coupling between objects is a measure of their interdependence. Just as in traditional programming, we strive to design uncoupled (that is, loosely coupled) objects, since they have a better chance of being reused.
Response for a class is the number of methods that can be invoked by instances of the class. Cohesion of methods is a measure of the tightness of an abstraction. A class that can invoke substantially more methods than the classes at its own level is more complex. A class with low cohesion may be suspected of representing an accidental or inappropriate abstraction: such a class should, generally speaking, be re-abstracted into several classes, or its responsibilities should be handed over to other existing classes.
Comments