Anda di halaman 1dari 15

E ect of OOP on Testing, Debugging, and Maintenance

Bellcore

445 South Street

Morristown, NJ 07962-1910

fhira,jrh,ewk,saulg@bellcore.com

Abstract
Testing, debugging, and maintenance activities account for the bulk of the budget and time
spent developing large, long-lived programs. This paper identi es which aspects of Object-
Oriented Programming (OOP) facilitate or hinder these activities. In particular, we characterize
how the three principle OOP features|data abstraction, inheritance, and dynamic binding|
a ect testing, debugging, and maintenance of large-scale software systems.

1 Introduction
Object-Oriented Programming (OOP) is increasingly being heralded as a solution to the software
crisis. Inheritance, dynamic binding, and polymorphism are the key OOP features that are thought
to yield reusable, extensible, and maintainable software. While it is easy to see intuitively how these
features may make the software developed using them more reusable and extensible, it is not at all
clear that they also make the software more maintainable. In this paper, we examine how these
key OOP features a ect the various perceived bene ts of using OOP. In particular, we examine the
e ect each of the key OO features has on software testing, debugging, and maintenance activities.
It is not uncommon for these activities alone to consume more than half of the total software
development time and budget. Thus it is only prudent to analyze what e ect OOP has on them.
In order to understand the scope of the problem of testing, debugging, and maintaining OO soft-
ware one need only observe the di erences between programming-in-the-small and programming-
in-the-large in the context of these activities. The time scales of small programs are short; their
designs are in the hands of the programmers; and testing, debugging, and maintenance are per-
formed by one or a few knowledgeable people. In contrast, large programs exist over long periods of
time, often \out-living" their creators. The problems of testing, debugging, and maintaining large
and long-lived programs thus require \institutional" rather than individual understanding of the
software. The question, therefore, posed by the innovation of OOP, and the one addressed here,
is: Are large OO programs more or less testable, debugable, and maintainable over their life-cycles
than are their non-OO counterparts? The analysis in this paper also draws from other work in
the literature (see, e.g., [1, 2, 3, 4, 7, 14] for the related work on e ect of OOP on testing and
[5, 6, 12, 8, 9, 10, 11, 13] for that on maintenance).

1
Figure 1: Hierarchy of OO lan

Modules or Inheritance
Objects Ada,
(instance managers) Modula

Extension Restriction
Abstract Data Types or Classes Redefinition
Clu
(type managers)

C++,
Smalltalk,
Inheritance Eiffel,
C++hierarchies)
(type Smalltalk,
Simula Eiffel
Objects
Classes
Genericity
Polymorphism
Type-checking
Binding

fields
fields
Unconstrained
Statically
Static fields
fields
Constrained
Dynamically
Dynamic
Static Binding Classes)
invisible
invisible
(Parameterized
Resolvable
Type-checking Dynamic
visibleBinding
visible
(Abstract
Resolvable
Type-checking Classes)
to descendents
to clients
(overloading) to descendents
(virtual to Smalltalk,
clients
functions)
Eiffel, Smalltalk
Smalltalk
Ada
C++ Eiffel
Simula
Eiffel
Smalltalk,
Eiffel

C++Eiffel,
Centerline C++
C++
C++
C++C++
Figu

Type-checking

Type-checking
Dynamic
Static

C++
Static Binding ( − virtualfns ) ?

Dynamic Binding Eiffel Smalltalk


in presence of both static and dynamic binding. Figure 9 shows the possible combinations of
type-checking and binding time alternatives.
Another feature of OO languages is polymorphism. There are two types of polymorphism:
statically resolvable and dynamically resolvable. Statically resolvable polymorphism is also referred
to as overloading. Common uses of overloading include operator overloading where an operator may
accept operands of di erent types and the appropriate code to be executed is automatically selected
by the compiler based on the types of the operands. The dynamically resolvable polymorphism
occurs in the presence of inheritance and dynamic binding. If an operation supported by a class
is de ned by one or more of its descendents, and that operation is invoked on an object of that
class, the decision on which function to invoke is made at runtime based on the dynamic class of
the object at that time. Figure 7 depicts the two types of polymorphism.
Some OO languages, e.g., Ei el and C++, also provide features to de ne parameterized classes.
These features are most useful when de ning general-purpose container classes, e.g., lists, sets,
stacks, etc. They enable such data-structures to be de ned irrespective of the types of the objects
they are used to store. Most OO languages also support features to de ne abstract classes that
may not be instantiated but used only to derive subclasses from them. Abstract classes may not
be instantiated because some of their operations may be left unimplemented; they may simply
be speci ed but left unde ned. Concrete classes may then be derived from them by providing
implementations of such operations. Abstract classes are useful in de ning generic classes whose
concrete instantiations must meet certain constraints. The constraints are speci ed in terms of the
unimplemented operations in the abstract class which its concrete descendents must de ne in order
to become instantiable. Figure 8 depicts these two forms of genericity.
From the above discussion, it is apparent that the key features that distinguish OOP from the
traditional procedural programming approach, as supported by C, Pascal, Fortran, or Cobol, are
data abstraction, inheritance, and dynamic binding. Other features mentioned above are either
direct or indirect consequences of these three features or are not central to the use of OOP. For
example, dynamically resolvable polymorphism results directly from the use of dynamic binding
and inheritance when a subclass is allowed to alter the de nitions of one or more of its inherited
operations. Abstract classes too arise due to dynamic binding and inheritance when a class may
simply specify some of its operations but need not de ne them. Parameterized abstract data types
are yet higher level abstractions of abstract data types. Dynamic type-checking is meaningful
only in the presence of dynamic binding. Overloading, on the other hand, is simply an additional
convenience for programmers so they do not have to invent new names for similar operations on
objects of di erent types when it is easy to tell which operation is meant by looking at the context
in which it is invoked.
Note that the term data abstraction above encompasses both encapsulation and information
hiding with respect to the clients of objects. Also note that even though the notion of dynamic
binding is intimately related to that of inheritance in the context of OOP, we treat these two
features separately because inheritance is a powerful mechanism in its own right even in the absence
of dynamic binding. One may develop class hierarchies to successfully exploit the commonality
among various classes and still not use any dynamic binding. Dynamic binding may be used on
top of inheritance to gain added exibility.
In the next two sections we examine the e ect each of the three key OOP features mentioned
above has on debugging and maintenance, and testing activities, respectively.

5
3 E ect of OOP on Debugging and Maintenance
Both debugging and maintenance require that one be able to understand the program|both its
high level design as well as the low level details of the data structures and algorithms used. Both
require that one be able to understand various interdependencies among program components before
one can debug the program or make changes to it. Both making an enhancement to the program
and xing a bug require that one be able to modify the program incrementally without adversely
a ecting the already working sections of the code. Debugging and maintenance, therefore, share
common requirements: i) the ability to understand a program, and ii) the ability to incrementally
modify it. If a language feature makes it easy to debug a program, most likely it will also make it
easy to maintain the program, and vice versa. In this section, we examine the e ect each of the
three distinguishing features of OO languages mentioned above, viz., data-abstraction, inheritance,
and dynamic binding, has on these two activities.
3.1 Data Abstraction
Data abstraction, as mentioned earlier, requires that functions be explicitly associated with the
data-structures they manipulate. Clients may access the data structures only via a speci ed inter-
face. This has obvious advantages from the debugging and maintenance perspectives. As all the
relevant functions are localized together, it becomes easier to read and understand the abstract
data type de ned. It also becomes easier to modify them. As the underlying implementation is
hidden from its clients, it can easily be changed without a ecting them.
Use of data abstraction also makes the software more modular. It reduces the number of
implicit dependencies among software components thus reducing the overall complexity of the
code. Reduced code complexity also leads to improved code comprehension and thus improved
debugging and maintenance.
3.2 Inheritance
One of the greatest bene ts of inheritance is that it promotes reusability and extensibility. It
makes it possible to extend or modify existing software and use it in new applications without
having to write it all from scratch. It makes it possible to create new classes by borrowing most
of their functionality from existing classes and without making separate copies of the common
code that would otherwise have to be maintained separately. In this sense, inheritance promotes
maintainability.
Maintenance often involves adding new functionality to the system. New functionality often
consists of small variations of existing functionality. Inheritance is also useful in these situations.
It enables one to add new functionality to the system by simply rede ning aspects of existing
functionality, and yet preserving the existing functionality. In this sense too, inheritance aids
maintainability.
But, unlike data abstraction, the above power of inheritance comes at an additional cost. Use of
inheritance introduces a new set of dependencies among software components that the programmer
and the maintainer must deal with. In the case of a small and well designed class hierarchy, such
as in most textbook examples used to illustrate the bene ts of inheritance, these dependencies may
be easy to understand. But in the case of large and complex class hierarchies, as is the case for
most real world applications, the network of dependencies is often very complex and dicult to
understand. This has obvious adverse rami cations for both debugging and maintenance.
Inheritance hierarchies are often designed to reuse code. Programmers often design them for
their \convenience," even though they may not conform to general intuition. The resulting class
6
hierarchies often do not correspond to the logical hierarchies one would otherwise expect [12, 11].
This makes it even more dicult for someone other than the original developers of the code to
understand these hierarchies. This further impedes debugging and maintenance.
Many OO languages, as mentioned in the previous section, do not prevent descendents of a
class from directly accessing its data elds. This may compromise the readability of the child
class operations as the data elds they access may be declared several levels away in the class
hierarchy. But more importantly, this may cause serious maintenance problems because making a
change to the parent class may break the child class implementation. In OO languages that provide
mechanisms to prevent subclasses from accessing their superclasses' data elds, on the other hand,
it becomes possible to modify the implementation of a super class without breaking that of its
descendents. This takes away some of the exibility from inheritance but the loss in exibility is
compensated by the increased maintainability.
If an OO language allows a child class to restrict the functionality inherited from its ancestors,
as mentioned earlier, some classes along an inheritance chain may subtract from their inherited
functionality while others may add to it. This may make it dicult for one to predict the func-
tionality of a class even if one understands that of each of its ancestors. The situation may be
further complicated if some classes along the inheritance chain change the de nitions of some of
their inherited operations.
Unlike data-abstraction which localizes relevant functions together, inheritance may distribute
them over several parent and child classes. It may also cause the code that accomplishes a given
operation to be widely dispersed over several classes. In order to understand the functionality of
a class, it becomes necessary to understand the functionality of each of its parents, their parents,
and so on, making it dicult to debug a fault in the class. To make a change to a class it becomes
necessary to consider the e ect it might have on each of its children, their children, and so on,
making it dicult to modify the class.
Use of multiple inheritance makes the scenario even more complex by introducing yet another
set of issues and rules to contend with. Instead of a tree structure the class hierarchy may now
have a more complex graph structure. Unlike single inheritance, multiple inheritance may cause
a child class to inherit properties from its ancestor classes along multiple inheritance chains. This
introduces the problem of repeated inheritance. In the presence of repeated inheritance, changes
made along one inheritance chain may cause undesired e ects to reach a child class along another
inheritance chain. The net e ect is that while this added complexity may allow a programmer to
reuse more existing code, it is possible that the time saved may be more than o set by the increased
cost of debugging and maintaining this complexity.
3.3 Dynamic Binding
Dynamic binding, as mentioned earlier, gives OO languages much of their exibility. Use of dynamic
binding ensures that the code that works on objects of one class also works on objects of other
classes that may be derived from it later on, without requiring any change to the code itself
and without a ecting its existing clients. Thus new systems requirements and changes may be
e ectively handled using dynamic binding in association with inheritance. In this sense, dynamic
binding facilitates maintenance. But it may also make the code more dicult to understand. Thus
it may also aggravate the debugging and maintenance e orts required for object-oriented systems.
In the presence of dynamic binding, it is dicult to determine what code is actually executed
at runtime in response to a message invocation because the type of the object being operated on
may not be determined until runtime. Thus it becomes dicult to predict the runtime program
behavior by static analysis of the program text. This may lead to diculties in debugging and

7
requirements for debugging and
Figure 10: Does OOP help fac
Understandability

data
Base Class
functions
Incremental
Modifiability

inherited data new data


Data Abstraction yes yes
Derived Class
Inheritanceredefined
inherited no newyes
fns fns fns
Dynamic Binding no yes
under intra-class integration te
Figure 12: Inter-operation inte

f1 ff22 ff33
class
class A
A

g1 g2 g3
class B
class B
by their clients. Data abstraction thus makes it possible to create libraries of well-de ned and
well-tested classes for commonly used data-structures and concepts. Other programs may then use
them without having to test them again. Data abstraction, thus, enables one to leverage previous
testing e ort by reusing classes from a class library [1]. Of course, testing an abstract data type
requires that a test driver be created that tests it in various interesting ways that may arise during
its real use. But the added cost of writing a driver program may be easily amortized over all the
later uses of the abstract data type.
4.2 Inheritance
As mentioned earlier, one of the main bene ts of using inheritance is that it enables us to reuse
existing code and extend it or adapt it in desired ways without a ecting the existing clients of
that code. But using inheritance does not, in general, imply that one only needs to test the newly
written code and that the code inherited from the ancestor classes need not be retested [7].
Let us rst consider the case when a child class has direct access over its ancestors' implemen-
tations. In this case, the operations de ned by a child class may alter the data elds inherited
from its ancestors in ways that may cause the operations inherited from the ancestors to function
incorrectly. Thus creating a child class, in such situations, requires that the inherited operations be
re-integration tested with the new operations de ned by the child class. There is, however, no need
to rerun the unit tests for the inherited operations in this case; only the new operations de ned
by the child class need be unit tested. The ability of a child class to directly access its ancestors'
implementation also implies that whenever the implementation of an ancestor class is changed, not
only does that class need to be retested but its descendents may also have to be re-integration
tested with it as the changes may also a ect them.
If, however, a child class has no privileges to access its ancestors' data structures (and in
the absence of dynamic binding; see the next subsection), there is no need to retest the inherited
operations when a child class is created. Also, in this case, if the implementation of a parent class is
changed, there is no need to retest the operations de ned by its descendents. Of course, irrespective
of whether or not a class has access over its ancestors' implementation, whenever changes are made
to a class operation, it must be re-unit tested and re-integration tested with other operations of that
class. Also, whenever changes are made to an operation, new program-based unit- and integration-
tests may have to be developed while the existing speci cation-based unit- and integration-tests
may be reused [4].
It has been noted that the use of OOP, or inheritance in particular, tends to cause programs
to contain a large number of relatively small units or procedures compared to the conventional
systems that consist of relatively fewer but bigger units. Wilde, et al. [12], report that in a C++
and a Smalltalk system they studied about eighty two percent of the methods contained three
statements or less: 1053 of the 1280 methods in the C++ system and 389 of the 477 methods in
the Smalltalk system were less than or equal to three statements long. This suggests that functions
that are performed by individual units in conventional programs require several units to accomplish
the same task in OO programs. This means more inter-procedural analysis needs to be done to
identify all the relevant data- ow elements, e.g., c-uses and p-uses. As inter-procedural data- ow
analysis tends to be relatively more dicult and less accurate compared to intra-procedural data-
ow analysis, it may adversely a ect the data- ow testing of OO programs.

10
Operations

Operations

Operations
Redefined

Inherited
New

Program-Based run new run new no


runneed
new
(White-Box) Testing tests tests totests
retest

Specification-Based run new rerun old norerun


need
(Black-Box) Testing tests tests old
to retest
tests
4.3 Dynamic Binding
As mentioned earlier, dynamic binding is a powerful concept that makes it easy to extend or
modify existing software without a ecting its clients. But it may also make testing of OO systems
challenging. Use of dynamic binding requires that whenever a child class rede nes an operation
inherited from a parent class, all the other inherited operations be re-integration tested with the
rede ned operation. There is, however, no need to rerun unit tests for those inherited operations.
Only the rede ned operations need to be re-unit tested. Thus the use of dynamic binding also
requires that the parent class operations be retested whenever any of its descendent classes rede nes
an operations inherited from that class. And, as mentioned earlier, if the tests in question are
speci cation based, the existing tests may be rerun, but if they are program based, old tests may
have to be discarded and new ones developed.
The presence of dynamic binding also makes it dicult to statically determine various data- ow
required elements, e.g., c-uses and p-uses, that need to be covered under various data ow testing
criterias. Thus, like inheritance, dynamic binding too may adversely a ect the data- ow testing of
OO programs.
Dynamic binding, as mentioned earlier, also makes it possible to de ne abstract or meta classes
that cannot directly be instantiated but are used only to derive child classes. This implies that
abstract classes may not be tested directly; they may only be tested indirectly by testing their
nearest non-abstract children classes in the class hierarchy.
4.4 Summary
In short, as in the case of debugging and maintenance, use of data-abstraction may also lead to
reduced testing e ort. Use of inheritance and dynamic binding, on the other hand, may not, in
general, facilitate testing. Figure 11 depicts the classi cation of subclass operations into three
groups: those that are newly de ned, those that are inherited but rede ned by the child class,
and those that are inherited as is from its ancestors. Figure 14 summarizes which of these groups
need to be unit-tested upon creation of a subclass. Figure 15 depicts which of them need to be
integration-tested for the general case when a class implementation is visible to its descendents and
dynamic binding is used, and Figure 16 depicts the same when a class implementation is invisible
to its descendents and static binding is used. Figure 17 summarizes if use of OOP may adversely
a ect various coverage testing techniques.

5 Conclusions
The most often cited bene ts of using OOP include improved reusability, extensibility, and main-
tainability. While these bene ts are yet to be veri ed empirically, it is at least easy to see intuitively
how OOP may o er the reusability and extensibility advantages over the non-OOP approaches. But
it is not at all clear whether it makes the software developed using it more maintainable. Of the
three distinguishing features of OOP, only data-abstraction directly aids software maintainability.
The other two, inheritance and dynamic binding, while aiding software reusability and extensibility,
may hinder maintainability. Figure 18 shows a summary of whether OOP features help attain the
bene ts cited. Figure 19 shows a summary of whether these features help improve the various soft-
ware development activities. As before, a `yes' in an entry indicates that arguments weigh in favor
of the feature with respect to the corresponding bene t or activity; a `no' indicates the opposite.
It must, however, be recognized that we have little empirical evidence to support this assessment.

12
Maintainability

Maintenance
Statement

Debugging
Extensibility
Coverage

Coverage

Coverage

Coverage
Data-flow
Reusability

Branch
Coding

Testing
Design

Path

Data Abstraction
Abstraction no no yesyes yesyesyes
Data
Data Abstraction yes
yes yes
no yes
Inheritance no no yes yes no
Inheritance
Inheritance yes
yes yes
yes no no no
Dynamic Binding no no yes yes
Dynamic Binding
Dynamic Binding yes
yes yes
yes no no no no
It is essential that careful case studies over the life-cycle of OO systems be carried out. Only then
can the above conjectures be veri ed.
The issue we have addressed here is not whether an OO software is testable, maintainable,
and debugable, but whether it is more or less testable, maintainable, and debugable than the
corresponding non-OO software in the context of large-scale software projects. Some evidence is
beginning to appear that it may not be [5, 7, 9, 10, 11, 13].
This, obviously, does not mean that OOP should not be pursued. It does mean that instead of
simply focusing on the advantages of using OOP, we must also understand its pitfalls. It also means
that more research must be done and tools and techniques that explicitly address the problems of
OO systems outlined above must be developed. Only then the bene ts OOP promises may be fully
attained.

References
[1] T. J. Cheatham and L. Mellinger. Testing object-oriented software systems. In Proceedings of
the ACM Annual Computer Science Conference, pages 161{165. ACM, 1991.
[2] Roong-Ko Doong and Phyllis G. Frankl. Case studies on testing object-oriented programs. In
Proceedings of the Symposium on testing, Analysis, and Veri cation (TAV4), pages 165{177.
ACM, October 1991.
[3] S. P. Fiedler. Object-oriented unit testing. HP Journal, 36(4):69{74, April 1989.
[4] Mary Jean Harrold, John D. McGregor, and Kevin J. Fitzpatrick. Incremental testing of object-
oriented class structures. In Proceedings of the 14th International Conference on Software
Engineering, pages 68{80. The IEEE Computer Society Press, 1992.
[5] Moises Lejter, Scott Meyers, and Steven P. Reiss. Support for maintenaning object-oriented
programs. In Proceedings of the 1991 IEEE Conference on Software Maintenance, pages 171{
178. IEEE, 1991.
[6] Dennis Manel and William Havanas. A study of the impact of C++ on software maintenance.
In Proceedings of the 1990 IEEE Conference on Software Maintenance, pages 63{69. IEEE,
November 1990.
[7] Dewayne E. Perry and Gail E. Kaiser. Adequate testing and object-oriented programming.
Journal Of Object-Oriented Programming, 2(5):13{19, Jan/Feb 1990.
[8] Jack H. Schwartz. Object oriented extensions to Ada: A dissenting opinion; a position paper for
object-oriented Ada panel. In TRI-Ada'90 Proceedings, pages 92{94. ACM SIGAda, December
1990.
[9] M. D. Smith and D. J. Robson. Object-oriented programming : The problems of validation.
In Proceedings of the 1990 IEEE Conference on Software Maintenance, pages 272{281. IEEE,
November 1990.
[10] David Taenzer, Murthy Ganti, and Sunil Podar. Object-oriented software reuse: The yoyo
problem. Journal Of Object-Oriented Programming, pages 30{35, Sept/Oct 1989.
[11] Frederic H. Wild. A comparison of experiences with the maintenance of object-oriented sys-
tems: Ada vs. C++. In TRI-Ada'90 Proceedings, pages 66{73. ACM SIGAda, December
1990.
14
[12] Norman Wilde, Allen Chapman, Paul Matthews, and Ross Huitt. Describing object oriented
software: What maintainers need to know. Technical Memo STS-019926, Bellcore, September
1991.
[13] Norman Wilde and Ross Huitt. Maintenance support for object oriented programs. In Pro-
ceedings of the 1991 IEEE Conference on Software Maintenance, pages 162{170. IEEE, 1991.
[14] Jianhua Zhu. Automated support for class testing. In Proceedings of the Tenth IEEE Annual
Software Reliability Symposium, Denver, Colorado. IEEE Computer Society Press, June 1992.

15