Professional Documents
Culture Documents
Bellcore
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 identies 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|
aect 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 aect the various perceived benets of using OOP. In particular, we examine the
eect 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 eect 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 dierences 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 eect 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 ) ?
5
3 Eect 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
aecting 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 eect 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 specied 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 dened. It also becomes easier to modify them. As the underlying implementation is
hidden from its clients, it can easily be changed without aecting 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 benets 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 redening 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 benets 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 ramications 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 denitions 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 eect 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 eects to reach a child class along another
inheritance chain. The net eect 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 oset 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 aecting its existing clients. Thus new systems requirements and changes may be
eectively 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 eorts 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
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-dened 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 eort 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 benets of using inheritance is that it enables us to reuse
existing code and extend it or adapt it in desired ways without aecting 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 dened 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 dened 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 dened
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 aect 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 dened 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 specication-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 aect the data-
ow testing of OO programs.
10
Operations
Operations
Operations
Redefined
Inherited
New
5 Conclusions
The most often cited benets of using OOP include improved reusability, extensibility, and main-
tainability. While these benets are yet to be veried empirically, it is at least easy to see intuitively
how OOP may oer 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
benets 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 benet 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 veried.
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 benets 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 Verication (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