|
Softpanorama
(slightly skeptical)
Open Source Software Educational Society |
May the
source be with you,
but remember the KISS principle ;-)
|
A Slightly Skeptical View on the Object-Oriented Programming
The road to Hell is paved with good intentions.
cargo cult programming: n. A style of (incompetent) programming
dominated by ritual inclusion of code or program structures that serve
no real purpose. A cargo cult programmer will usually explain the extra
code as a way of working around some bug encountered in the past, but
usually neither the bug nor the reason the code apparently avoided the
bug was ever fully understood (compare
shotgun debugging,
voodoo programming).
The term `cargo cult' is a reference to aboriginal religions that
grew up in the South Pacific after World War II. The practices of these
cults center on building elaborate mockups of airplanes and military
style landing strips in the hope of bringing the return of the god-like
airplanes that brought such marvelous cargo during the war. Hackish
usage probably derives from Richard Feynman's characterization of certain
practices as "cargo cult science" in his book "Surely You're Joking,
Mr. Feynman!" (W. W. Norton & Co, New York 1985, ISBN 0-393-01921-7).
Jargon 4.2
|
|
|
In the end, productivity and quality are the only true merits a programming methodology
is to be judged upon. As Paul Graham noted the length of the program can serve
as an approximation for how much work it is to write. Not the length in characters,
of course, but the length in distinct syntactic elements-- basically, the size of
the parse tree. It may not be quite true that the shortest program is the least
work to write, but it's close enough. I think that OO has huge problems in this
area: it is one step forward, two steps back.
First of all OO is not a panacea and is not a universally applicable programming
paradigm. Object orientation has limited applicability and should be used when it
brings distinct advantages and never for problems that have other, more compact
and transparent approaches to the solution. As a programming methodology it also
competes with several other (who level design methodologies using scripting
language + low level language; Abstract machine methodology; specialized language
methodology to name a few). That's why most object-oriented algorithms book
are junk, and their authors as close to charlatans as one can get.
When I think about OO I see two distinct trends:
- One is positive and is connected with the refinement of traditional programming
constructs (generic procedures, templates, visibility of variables, etc.). The
hierarchical structuring of namespace and, to a certain extent, hierarchical
namespace inheritance is a very good idea and here OO languages added to the
language design arsenal.
- The second is hugely negative as try to position OO as a new universal software
engineering paradigm. Often OO is oversold as panacea and even as a cult (Bertrand
Meyer).
Actually true OO is very similar to the idea of compiler-compiler and I think
that in many cases combination of a scripting language and a regular compiled programming
language (for example TCL+C) is a better paradigm of software development. Sometimes
defining a specialised language for the problem and writing a specialized compiler
for it is also a viable approach.
For example in his paper
Object Oriented Programming Oversold! B. Jacobs wrote:
OOP became popular primarily because of GUI
interfaces. In fact, many non-programmers think that "Object" in OOP
means a screen object such as a button, icon, or listbox. They often talk about
drag-and-drop "objects". GUI's sold products. Anything
associated with GUI's was sure to get market and sales brochure attention, regardless
of whether this association was accurate or not. I
have even seen salary surveys from respected survey
companies that have a programming classification called "GUI/OOP Programming".
Screen objects can correspond closely with
OOP objects, making them allegedly easier to manipulate in a program. We do
not disagree that OOP works fairly well for GUI's, but
it is now being sold as the solve-all and be-all of programming.
Some argue that OOP is still important even if
not dealing directly with GUI's. In our opinion, much of the hype about
OOP is faddish. OOP in itself does NOT allow programs to do things that they
could not do before. OOP is more of a program organizational
philosophy rather than a set of new external solutions or operations.
He also provided a deep insight that attractiveness of OO is somewhat
similar to the attractiveness of the social doctrine like communism (with its ideas
of central hierarchical planning model and idealistic hopes that that will eliminate
wasteful, redundant procedures):
Why OOP Is Like Communism
Economic communism spread like wildfire in the first half of the 20th century
because it had such appealing ideals. Like OO, these ideals were very seductive
on paper. Intellectuals all over the world were drawn in by it's concepts
in droves. However, the complexities and dynamism of human nature proved
not to favor economic communism as a productive model.
Perhaps it allows for more equality, but mostly
by making everybody equally poor. Further, it forms a kind of "social currency"
based on schmoosing and favoritism that is nearly impossible to objectively
measure and tax. Thus, it is still not really class-less, but simply makes
classes harder to measure.
One of these ideals was central control and
planning of the economy. Duplication and coordination of production would eliminate
wasteful, redundant procedures; thus increasing the overall wealth. A watchful,
central eye would also keep people in line so that they would produce only what
was deemed useful and fair.
OOP has a similar appeal. Data and operations
(methods) are tightly integrated and controlled within the watchful eye of the
OO paradigm.
Unfortunately, OOP and economic communism suffer
similar problems. They both get bogged down in their own bureaucracy and
have a difficult time dealing with change and outside influences which are not
a part of the internal bureaucracy. For example, a process may be stuck
in department X because it may be missing a piece of information that the next
department, Y, or later departments may not even need. Department X may not
know or care that the waiting piece of information is not needed by later
departments. It simply has it's rules and regulations and follows them
like a good little bureaucratic soldier.
In his old Usenix paper
Objecting To Objects Stephen C. Johnson wrote
Object-oriented programming (OOP) is an
ancient (25-year-old) technology, now being pushed as the answer to all the
world's programming ills. While not denying that there are advantages
to OOP, I argue that it is being oversold.
In particular, OOP gives little support to GUI and network support,
some of the biggest software problems we face today.
It is difficult to constrain relationships between objects
(something SmallTalk did better than C++). Fundamentally, object reuse has much
more to do with the underlying models being supported than with the "objectness"
of the programming language. Object-oriented languages
tend to burn CPU cycles, both at compile and execution time, out of proportion
to the benefits they provide. In summary, the goods things
about OOP are often the information hiding and consistent underlying models
which derive from clean thoughts, not linguistic cliches.
In his April 2003 Keynote for PyCon2003) Paul Graham suggested [The
Hundred-Year Language] :
...Somehow the idea of reusability
got attached to object-oriented programming in the 1980s, and no amount of evidence
to the contrary seems to be able to shake it free. But although some object-oriented
software is reusable, what makes it reusable is its bottom-upness, not its object-orientedness.
Consider libraries: they're reusable because they're language,
whether they're written in an object-oriented style or not.
I don't predict the demise of object-oriented programming,
by the way. Though I don't think it has much to offer good programmers, except
in certain specialized domains, it is irresistible to large organizations. Object-oriented
programming offers a sustainable way to write spaghetti code. It lets you accrete
programs as a series of patches.
Large organizations always tend to develop software this way, and I expect
this to be as true in a hundred years as it is today.
...One helpful trick here is to use the
length of the program as
an approximation for how much work it is to write. Not the length in characters,
of course, but the length in distinct syntactic elements-- basically, the size
of the parse tree. It may not be quite true that
the shortest program is the least work to write, but it's close enough that
you're better off aiming for the solid target of brevity than the fuzzy, nearby
one of least work. Then the algorithm for language design becomes:
look at a program and ask, is there any way to write this that's shorter?
Dr. Nikolai Bezroukov
Notes:
- Those pages are written by people for
whom English is not a native language.
Some amount of grammar and spelling errors should be expected.
- This is a Spartan WHYFF (We Help You For
Free) site. It cannot replace the
best teachers and
the best books.
- The site contain some obsolete pages as
it develops like a living tree... Some links on older pages
are broken. Please try to use
Google, Open directory, etc. to find a replacement link (see
HOWTO search the WEB for details). We would appreciate if
you can
mail us a correct link.
|
|
|
|
| Object Oriented Programming (OOP) is
currently being hyped as the best way to do everything from promoting
code reuse to forming lasting relationships with persons of your preferred
sexual orientation. This paper tries to demystify the
benefits of OOP. We point out that, as with so many previous software
engineering fads, the biggest gains in using OOP result from applying
principles that are older than, and largely independent of, OOP. Moreover,
many of the claimed benefits are either not true or true only by chance,
while occasioning some high costs that are rarely discussed. Most seriously,
all the hype is preventing progress in tackling problems that are both
more important and harder: control of parallel and distributed applications,
GUI design and implementation, fault tolerant and real-time programming.
OOP has little to offer these areas. Fundamentally, you get good software
by thinking about it, designing it well, implementing it carefully,
and testing it intelligently, not by mindlessly using an expensive mechanical
process.
-- Abstract to
Objecting to Objects, by Stephen C. Johnson,
Melismatic Software
|
1994 | USENIX
Object Oriented Programming (OOP) is currently being hyped as the best way
to do everything from promoting code reuse to forming lasting relationships
with persons of your preferred sexual orientation. This paper tries to demystify
the benefits of OOP. We point out that, as with so many previous software engineering
fads, the biggest gains in using OOP result from applying principles that are
older than, and largely independent of, OOP. Moreover, many of the claimed benefits
are either not true or true only by chance, while occasioning some high costs
that are rarely discussed. Most seriously, all the hype is preventing progress
in tackling problems that are both more important and harder: control of parallel
and distributed applications, GUI design and implementation, fault tolerant
and real-time programming. OOP has little to offer these areas. Fundamentally,
you get good software by thinking about it, designing it well, implementing
it carefully, and testing it intelligently, not by mindlessly using an expensive
mechanical process.
Define Your Terms
Object Oriented Programming (OOP) is a term largely borrowed from the SmallTalk
community, who were espousing many of these techniques in the mid 1970's. In
turn, many of their ideas derive from Simula 67, as do most of the core ideas
in C++. Key notions such as encapsulation and reuse have been discussed as far
back as the 60's, and received a lot of discussion during the rounds of the
Ada definition. Although there have been, and will
always be, religious fanatics who think their language is the only way to code,
the really organized OOP hype started in the late 1980's. By
the early 1990's, both Next and Microsoft were directing their marketing muscle
into persuading us to give up C and adopt C++, while SmallTalk and Eiffel both
were making a respectable showing, and object oriented operating systems and
facilities (DOE, PenPoint, COBRA) were getting a huge play in the trade press--the
hype wars were joined.
It is said that countries get the governments
they deserve, and perhaps that is true of professions as well--a lot of the
energy fueling this hype derives from the truly poor state of software development.
While hardware developers have provided a succession of products with radically
increasing power and lower cost, the software world has seen very little productivity
improvement. Major, highly visible products from industry leaders continue to
be years late (Windows NT), extremely buggy (Solaris) or both, costs skyrocket,
and, most seriously, people are very reluctant to pay 1970's software costs
when they are running cheap 1990's hardware. I believe a lot of non-specialists
look at software development and see it as so completely screwed up that the
cause cannot be profound--it must be something simple, something a quick fix
could fix. Maybe if they just used objects...
To be more precise, most of what I say will apply to C++, viewed as a poor
stepchild by most of the OOP elite. Actually, the few comments I will make about
more dynamically typed languages like SmallTalk make C++ look good by comparison.
I will also focus my concern fairly narrowly. I
am interested in tools, including languages, that make it easier and more productive
to generate large serious high quality software products. So focusing rules
out a bunch of sometimes entertaining philosophical and aesthetic arguments
best entertained over beer.
... ... ...
What Works in OOP
Those who report big benefits from using OOP are not lying. Many of the reported
benefits come from focusing on designing the software models, including the
roles and interactions of the modules, enabling the modules to encapsulate expertise,
and carefully designing the interfaces between these modules. While most OOP
systems allow you, and even encourage you, to do these things, most older programming
systems allow these techniques as well. These are good, old ideas that have
proved their worth in the trenches for decades, whether they were called OOP,
structured programming, or just common sense. I have seen excellent programs
written in assembler that used these principles, and terrible programs in C++
that did not. The use of objects and inheritance is not what makes these programs
good.
What works in all these cases is that the programs were well thought out
and the design was done intelligently, based on a clear and well communicated
set of organizing principles. The language and the operating system just don't
matter. In many cases, the same organizing principles used to guide the design
can be used to guide the construction and testing of the product as well. What
makes a piece of software good has a lot to do with the application of thought
to the problem being addressed, and not much to do with what language or methodology
you used. To the extent that the OOP methodology makes you think problems through
and forces you to make hidden assumptions explicit, it leads to better code.
OOP Claims Unmasked
The hype for OOP usually claims benefits such as faster development time,
better code reuse, and higher quality and reliability of the final code. As
the last section shows, these are not totally empty claims, but when true they
don't have much to do with OOP methodology. This section examines these claims
in more detail.
OOP is supposed to allow code to be developed faster; the question is, "faster
than what?". Will OOP let you write a parser faster than Yacc, or write a GUI
faster than using a GUI-builder? Will your favorite OOP replace awk
or Perl or csh within a few years? I think not.
Well, maybe faster than C, and I suppose if we consider only raw C this claim
has some validity. But a large part of most OOP environments is a rich set of
classes that allow the user to manipulate the environment--build windows, send
messages across a network, receive keystrokes, etc. C, by design, has a much
thinner package of such utilities, since it is used in so many different environments.
There were some spectacularly productive environments based on LISP a few years
back (and not even the most diehard LISP fanatic would say that LISP is object
oriented). A lot of what made these environments productive was a rich, well
designed set of existing functions that could be accessed by the user. An that
is a lot of what makes OOP environments productive compared to raw C. Another
way of saying this is that a lot of the productivity improvement comes from
code reuse.
There is probably no place where the OOP claims are more misleading than
the claims of code reuse. In fact, code reuse is a complex and difficult problem--it
has been recognized as desirable for decades, and the issues that make it hard
are not materially facilitated by OOP.
In order for me to reuse your code, your code needs to do something that
I want done (that's the easy part), and your code needs to operate within the
same model of the program and environment as my code (that's the hard part).
OOP addresses some of the gratuitous problems that occasionally plagued code
reuse attempts (for example, issues of data layout), but the fundamental problems
are, and remain, hard.
An example should make this clearer. One of the most common examples of a
reused program is a string package (this is particularly compelling in C++,
since C has such limited string handling facilities). Suppose you have written
a string package in C++, and I want to use it in my compiler symbol table. As
it happens, many of the strings that a compiler uses while compiling a function
do not need to be referenced after that function has been compiled. This is
commonly dealt with by providing an arena-based allocator, where storage can
be allocated out of an arena associated with a function, and then the whole
arena can be discarded when the function has been processed. This minimizes
the chance of memory leaks and makes the deallocation of storage essentially
free (Similar techniques are used to handle transaction-based storage in a transaction
processing system, etc.).
So, I want to use your string package, but I want your string package to
use my arena-based allocator. But, almost certainly, you have encapsulated knowledge
of storage allocation so that I can't have any contact with it (that is a
feature of OOP, after all), so I can't use your package with my storage
allocator. Actually, I would probably have more luck reusing your package had
it been in C, since I could supply my own malloc and free
routines (although that has its own set of problems).
If you had designed your string package to allow me to specify the storage
allocator, then I could use it. But this just makes the point all the more strongly.
The reason we do not reuse code is that most code is not designed to be reused
(notice I said nothing about implementation). When code is designed
to be reused (the C standard library comes to mind) it doesn't need object oriented
techniques to be effective. I will have more to say about reuse by inheritance
below.
One of the major long-term advantages of object-oriented techniques may be
that it can support broad algorithmic reuse, of a style similar to the Standard
Template Library of C++. However, the underlying
language is enormously overbuilt for such support, allowing all sorts of false
traps and dead-ends for the unwary. The Standard Template Library
took several generations and a dozen of the best minds in the C++ community
to reach its current state, and it's no mistake that several of the early generations
were coded in Ada and SCHEME--its power is not in the language, but in the ideas.
The final advantage claimed for OOP is higher
quality code. Here again, there is a germ of truth to this claim, since some
problems with older methods (such as name clashes in libraries) are harder to
make and easier to detect using OOP. To the extent that we can reuse "known
good" code, our quality will increase--this doesn't depend on OOP. However,
basically code quality depends on intelligent design, an effective implementation
process, and aggressive testing. OOP does not address the first or last step
at all, and falls short in the implementation step.
For example, we might wish to enforce some simple style rules on our object
implementations, such as requiring that every object have a print
method or a serialize method for dumping the object to disc.
The best that many object- oriented systems can do is provide you (or, rather,
your customer) with a run-time error when you try to dump an object to disc
that has not defined such a method (C++ actually does a bit better than that).
Many of the more dynamically typed systems, such as SmallTalk or PenPoint, do
not provide any typing of arguments of messages, or enforce any conventions
as to which messages can be sent to which objects. This makes messages as unstructured
as GOTO's were in the 1970's, with a similar impact on correctness and quality.
One of the most unfortunate effects of the OOP bandwagon is that it encourages
the belief that how you speak is more important than what you say. It is rather
like suggesting that if someone uses perfect English grammar they must be truthful.
It is what you say, and not how you say it.
... ... ...
He said that She said that He had Halitosis
Using a computer language is a social, and even
political act, akin to voting for a candidate or buying a certain brand of car.
As such, our choices are open to manipulation by marketeers, influence by fads,
and various forms of rationalization by those who were burned and have trouble
admitting it. In particular, much of what is "known" about a
language is something that was true, or at widely believed, at one point in
the language's history, but may not be true currently. At one point, "everybody"
knew that PL/I had no recursive functions, ALGOL 68 was too big a language to
be useful, Ada was too slow, and C could not be used for numerical problems.
Some of these beliefs were never true, and none of them are true now, but they
are still widely held. It is worth looking at OOP in this light.
Some of the image manipulators target nontechnical people such as our bosses
and customers, and may try to persuade them that OOP would solve their problems.
As we have seen, however, many of the things that are "true" of OOP (for example,
that it makes reuse easy) are difficult to justify when you look more carefully.
As professionals, it is our responsibility to ask whether moving to OOP is in
the best interests of ourselves, our company, or our profession. We must also
have the courage to reject the fad when it is a diversion or will not meet our
needs. We must also make this decision anew for each project, considering all
the potential factors. Realistically, the answer will probably be that some
projects should use OOP, others should not, and for a fair number in the middle
it doesn't matter very much.
Summary
The only way to construct good software is to think about it. Since the scope
of problems that software attempts to address is so vast, the kinds of solutions
that that we need is also vast. OOP is a good tool to have in our toolbox, and
there are places that it is my tool of choice. But there are also places where
I would avoid it like the plague. It is important to all of us that we continue
to have that option.
OO is definitely overkill for a lot of web projects.
It seems to me that so many people use OO frameworks like Ruby and Zope because
“it’s enterprise level”. But using an ‘enterprise’
framework for small to medium sized web applications just adds so much overhead
and frustration at having to learn the framework that it just doesnt seem worth
it to me. Having said all this I must point out that
I’m distrustful of large corporations and hate their
dehumanising heirarchical structure. Therefore i am naturally
drawn towards open source and away from the whole OO/enterprise/heirarchy paradigm.
Maybe people want to push open source to the enterprise level in the hope that
they will adopt the technology and therefore they will have more job security.
Get over it - go and learn Java and .NET if you want job security and preserve
open source software as an oasis of freedom away from the corporate world. Just
my 2c
===
OOP has its place, but the diversity of frameworks is just as challenging
to figure out as a new class you didn’t write, if not more. None of them work
the same or keep a standard convention between them that makes learning them
easier. Frameworks are great, but sometimes I think maybe they don’t all have
to be OO. I keep a small personal library of functions I’ve (and others have)
written procedurally and include them just like I would a class. Beyond the
overhead issues is complexity. OOP has you chasing declarations over many files
to figure out what’s happening. If you’re trying to learn how that unique class
you need works, it can be time consuming to read through it and see how the
class is structured. By the time you’re done you may as well have written the
class yourself, at least by then you’d have a solid understanding. Encapsulation
and polymorphism have their advantages, but the cost is complexity which can
equal time. And for smaller projects that will likely never expand, that time
and energy can be a waste.
Not trying to bash OOP, just to defend procedural style. They each have their
place.
===
Sorry, but I don’t like your text, because you mix Ruby and Ruby on Rails
alot. Ruby is in my opinion easier to use then PHP, because PHP has no design-principle
beside “make it work, somehow easy to use”. Ruby has some really cool stuff
I miss quite often, when I have to program in PHP again (blocks for example),
but has a more clear and logical syntax.
Ruby on Rails is of course not that easy to use, at least when speaking about
small-scale projects. This is, because it does alot more than PHP does. Of course,
there are other good reasons to prefere PHP over Rails (like the better support
by providers, more modules, more documentation), but from my opinion, most projects
done in PHP from the complexity of a blog could profit from being programmed
in Rails, from the pure technical point of view. At least I won’t program in
PHP again unless a customer asks me.
===
I have a reasonable level of experience with PHP and Python but unfortunately
haven’t touched Ruby yet. They both seem to be a good choice for low complexity
projects. I can even say that I like Python a lot. But I would never consider
it again for projects where design is an issue. They also say it is for (rapid)
prototyping. My experience is that as long as you can’t afford a proper IDE
Python is maybe the best place to go to. But a properly
“equipped” environment can formidably boost your productivity with a statically
typed language like Java. In that case Python’s advantage shrinks
to the benefits of quick tests accesible through its command line.
Another problem of Python is that it wants to be
everything: simple and complete, flexible and structured, high-level while allowing
for low-level programming. The result is a series of obscure features
Having said all that I must give Python all the credits of a good language.
It’s just not perfect. Maybe it’s Ruby

My apologies for not sticking too closely to the subject of the article.
===
The one thing I hate is OOP geeks trying to prove that they can write code
that does nothing usefull and nobody understands.
“You don't have to use OOP in ruby! You can do
it PHP way! So you better do your homework before making such statements!”
Then why use ruby in the first place?
“What is really OVERKILL to me, is to know the hundreds of functions, PHP
provides out of the box, and available in ANY scope! So I have to be extra carefull
wheter I can use some name. And the more functions - the bigger the MESS.”
On the other hand, in ruby you use only functions avaliable for particullar
object you use.
I would rather say: “some text”.length than strlen(”some text”); which is
much more meaningful! Ruby language itself much more descriptive. I remember
myself, from my old PHP days, heaving alwayse to look up the php.net for appropriate
function, but now I can just guess!”
Yeah you must have weak memory and can`t remember wheter strlen() is for
strings or for numbers….
Doesn`t ruby have the same number of functions just stored in objects?
Look if you can`t remember strlen than invent your own classes you can make
a whole useless OOP framework for PHP in a day……
I'm not a fan of object orientation for the sake of object orientation. Often
the proper OO way of doing things ends up being
a productivity
tax. Sure, objects are the backbone of any modern programming language,
but sometimes I can't help feeling that
slavish adherence
to objects is making my life a lot more difficult. I've always found
inheritance
hierarchies to be brittle and unstable, and then there's the massive
object-relational
divide to contend with. OO seems to bring at least as many problems to the
table as it solves.
Perhaps Paul Graham summarized
it best:
Object-oriented programming generates a lot of what looks like work. Back
in the days of fanfold, there was a type of programmer who would only put
five or ten lines of code on a page, preceded by twenty lines of elaborately
formatted comments. Object-oriented programming is like crack for these
people: it lets you incorporate all this scaffolding right into your source
code. Something that a Lisp hacker might handle by pushing a symbol onto
a list becomes a whole file of classes and methods. So it is a good tool
if you want to convince yourself, or someone else, that you are doing a
lot of work.
Eric Lippert observed a similar occupational hazard among developers. It's
something he calls
object
happiness.
What I sometimes see when I interview people and review code is symptoms
of a disease I call Object Happiness. Object Happy people feel the need
to apply principles of OO design to small, trivial, throwaway projects.
They invest lots of unnecessary time making pure virtual abstract base classes
-- writing programs where IFoos talk to IBars but there is only one implementation
of each interface! I suspect that early exposure to OO design principles
divorced from any practical context that motivates those principles leads
to object happiness. People come away as OO True Believers rather than OO
pragmatists.
I've seen so many problems caused by excessive, slavish adherence to OOP
in production applications. Not that object oriented programming is inherently
bad, mind you, but a little OOP goes a very long way. Adding objects
to your code is like adding salt to a dish: use a little, and it's a savory
seasoning; add too much and it utterly ruins the meal. Sometimes it's better
to err on the side of simplicity, and I tend to favor the approach that results
in less code, not more.
Given my ambivalence about all things OO, I was amused when
Jon Galloway forwarded me a
link to Patrick Smacchia's
web page. Patrick is a French software developer. Evidently the acronym
for object oriented programming is spelled a little differently in French than
it is in English: POO.
April 2003 (Keynote from PyCon2003)
...I have a hunch that the main branches of the evolutionary
tree pass through the languages that have the smallest, cleanest cores. The
more of a language you can write in itself, the better.
...Languages evolve slowly because they're not really technologies. Languages
are notation. A program is a formal description of the problem you want a computer
to solve for you. So the rate of evolution in programming languages is more
like the rate of evolution in mathematical notation than, say, transportation
or communications. Mathematical notation does evolve, but not with the giant
leaps you see in technology.
...I learned to program when computer power was scarce. I can remember taking
all the spaces out of my Basic programs so they would fit into the memory of
a 4K TRS-80. The thought of all this stupendously inefficient software burning
up cycles doing the same thing over and over seems kind of gross to me. But
I think my intuitions here are wrong. I'm like someone who grew up poor, and
can't bear to spend money even for something important, like going to the doctor.
Some kinds of waste really are disgusting. SUVs, for example, would arguably
be gross even if they ran on a fuel which would never run out and generated
no pollution. SUVs are gross because they're the solution to a gross problem.
(How to make minivans look more masculine.) But not all waste is bad. Now that
we have the infrastructure to support it, counting the minutes of your long-distance
calls starts to seem niggling. If you have the resources, it's more elegant
to think of all phone calls as one kind of thing, no matter where the other
person is.
There's good waste, and bad waste. I'm interested in good waste-- the kind where,
by spending more, we can get simpler designs. How will we take advantage of
the opportunities to waste cycles that we'll get from new, faster hardware?
The desire for speed is so deeply engrained in us, with our puny computers,
that it will take a conscious effort to overcome it. In language design, we
should be consciously seeking out situations where we can trade efficiency for
even the smallest increase in convenience.
Most data structures exist because of speed. For
example, many languages today have both strings and lists. Semantically, strings
are more or less a subset of lists in which the elements are characters. So
why do you need a separate data type? You don't, really. Strings only exist
for efficiency. But it's lame to clutter up the semantics of the language with
hacks to make programs run faster. Having strings in a language seems to be
a case of premature optimization.
... Inefficient software isn't gross. What's gross is a language
that makes programmers do needless work. Wasting programmer time is the true
inefficiency, not wasting machine time. This will become ever more clear as
computers get faster
...Somehow the idea of reusability got attached to object-oriented
programming in the 1980s, and no amount of evidence to the contrary seems to
be able to shake it free. But although some object-oriented software is reusable,
what makes it reusable is its bottom-upness, not its object-orientedness. Consider
libraries: they're reusable because they're language, whether they're written
in an object-oriented style or not.
I don't predict the demise of object-oriented programming, by the way.
Though I don't think it has much to offer good programmers, except in certain
specialized domains, it is irresistible to large organizations. Object-oriented
programming offers a sustainable way to write spaghetti code. It lets you accrete
programs as a series of patches.
Large organizations always tend to develop
software this way, and I expect this to be as true in a hundred years as it
is today.
...As this gap widens,
profilers will become increasingly important. Little attention is paid to profiling
now. Many people still seem to believe that the way to get fast applications
is to write compilers that generate fast code. As the gap between acceptable
and maximal performance widens, it will become increasingly clear that the way
to get fast applications is to have a good guide from one to the other.
...One of the most exciting trends in the last ten years has been the rise of
open-source languages like Perl, Python, and Ruby. Language design is being
taken over by hackers. The results so far are messy, but encouraging.
There are some stunningly novel ideas in Perl, for
example. Many are stunningly bad, but that's always true of ambitious
efforts. At its current rate of mutation, God knows what Perl might evolve into
in a hundred years.
...One helpful trick here is to use the
length of the program as
an approximation for how much work it is to write. Not the length in characters,
of course, but the length in distinct syntactic elements-- basically, the size
of the parse tree. It may not be quite true that
the shortest program is the least work to write, but it's close enough that
you're better off aiming for the solid target of brevity than the fuzzy, nearby
one of least work. Then the algorithm for language design becomes:
look at a program and ask, is there any way to write this that's shorter?
An extensive discussion of subtyping, insidious problems with subclassing,
and practical rules to avoid them.
- Does OOP really separate interface from implementation?
- The manifestation of a problem: an example of how an implementation
inheritance prevents separation of interface and implementation
- Subtyping vs. Subclassing
- Explanation why the problem above happened
-
Subclassing errors, OOP style and practically checkable to prevent them
- Demonstration how statically checkable rules can prevent the problem
from occurring [a separate document]
A more formal and general presentation of this topic is given in a paper
and a talk at a Monterey 2001 workshop (June 19-21, 2001, Monterey, CA):
Subtyping-OOP.ps.gz
[35K] and
MTR2001-Subtyping-talk.ps.gz
[67K]
Does OOP really separate interface from implementation?
Decoupling of abstraction from implementation is one of the holy grails of
good design. Object-oriented programming in general and encapsulation in particular
are claimed to be conducive to such separation, and therefore to more reliable
code. In the end, productivity and quality are the only true merits a programming
methodology is to be judged upon. This article is to show a very simple example
that questions if OOP indeed helps separate interface from implementation. The
example is a very familiar one, illustrating the difference between subclassing
and subtyping. The article carries this example of Bags and Sets one step further,
to a rather unsettling result. The article set out to follow good software engineering;
this makes the resulting failure even more ominous.
The article aims to give a more-or-less "real" example, which one can run
and see the result for himself. By necessity the example had to be implemented
in some language. The present article uses C++. It appears however that similar
code (with similar conclusions) can be carried on in many other OO languages
(e.g., Java, Python, etc).
Suppose I was given a task to implement a Bag -- an unordered collection
of possibly duplicate items (integers in this example). I chose the following
interface:
typedef int const * CollIterator; // Primitive but will do
class CBag {
public:
int size(void) const; // The number of elements in the bag
virtual void put(const int elem); // Put an element into the bag
int count(const int elem) const; // Count the number of occurrences
// of a particular element in the bag
virtual bool del(const int elem); // Remove an element from the bag
// Return false if the element
// didn't exist
CollIterator begin(void) const; // Standard enumerator interface
CollIterator end(void) const;
CBag(void);
virtual CBag * clone(void) const; // Make a copy of the bag
private:
// implementation details elided
};
|
Other useful operations of the CBag package are implemented without the knowledge
of CBag's internals. The functions below use only the public interface of the
CBag class:
// Standard "print-on" operator
ostream& operator << (ostream& os, const CBag& bag);
// Union (merge) of the two bags
// The return type is void to avoid complications with subclassing
// (which incidental to the current example)
void operator += (CBag& to, const CBag& from);
// Determine if CBag a is subbag of CBag b
bool operator <= (const CBag& a, const CBag& b);
inline bool operator >= (const CBag& a, const CBag& b)
{ return b <= a; }
// Structural equivalence of the bags
// Two bags are equal if they contain the same number of the same elements
inline bool operator == (const CBag& a, const CBag& b)
{ return a <= b && a >= b; }
|
It has to be stressed that the package was designed to minimize the number
of functions that need to know details of CBag's implementation. Following good
practice, I wrote validation code (file vCBag.cc
[Code]) that tests all the functions and methods of the CBag package and
verifies common invariants.
Suppose you are tasked with implementing a Set package. Your boss defined
a set as an unordered collection where each element has a single occurrence.
In fact, your boss even said that a set is a bag with no duplicates.
You have found my CBag package and realized that it can be used with few additional
changes. The definition of a Set as a Bag, with some constraints, made the decision
to reuse the CBag code even easier.
class CSet : public CBag {
public:
bool memberof(const int elem) const { return count(elem) > 0; }
// Overriding of CBag::put
void put(const int elem)
{ if(!memberof(elem)) CBag::put(elem); }
CSet * clone(void) const
{ CSet * new_set = new CSet(); *new_set += *this; return new_set; }
CSet(void) {}
};
|
The definition of a CSet makes it possible to mix CSets and CBags, as in
set += bag; or bag += set; These operations are well-defined,
keeping in mind that a set is a bag that happens to have the count of all members
exactly one. For example, set += bag; adds all elements from a
bag to a set, unless they are already present. bag += set; is no
different than merging a bag with any other bag.
You too wrote a validation suite to test all CSet methods (newly defined
and inherited from a bag) and to verify common expected properties, e.g.,
a+=a is a.
In my package, I have defined and implemented a function:
// A sample function. Given three bags a, b, and c, it decides
// if a+b is a subbag of c
bool foo(const CBag& a, const CBag& b, const CBag& c)
{
CBag & ab = *(a.clone()); // Clone a to avoid clobbering it
ab += b; // ab is now the union of a and b
bool result = ab <= c;
delete &ab;
return result;
}
|
It was verified in the regression test suite. You have tried this function on
sets, and found it satisfactory.
Later on, I revisited my code and found my implementation of foo() inefficient.
Memory for the ab object is unnecessarily allocated on heap. I
rewrote the function as
bool foo(const CBag& a, const CBag& b, const CBag& c)
{
CBag ab;
ab += a; // Clone a to avoid clobbering it
ab += b; // ab is now the union of a and b
bool result = ab <= c;
return result;
}
|
It has exactly the same interface as the original foo(). The code hardly changed.
The behavior of the new implementation is also the same -- as far as I and the
package CBag are concerned. Remember, I have no idea that you're re-using my
package. I re-ran the regression test suite with the new foo(): everything tested
fine.
However, when you run your code with the new implementation of foo(), you
notice that something has changed! You can see this for yourself: download
the complete code from
[Code]. make vCBag1 and make vCBag2 run validation
tests with the first and the second implementations of foo(). Both tests complete
successfully, with the identical results. make vCSet1 and
make vCSet2 test the CSet package. The tests -- other than those of foo()
-- all succeed. Function foo() however yields markedly different results. It
is debatable which implementation of foo() gives truer results for CSets. In
any case, changing internal algorithms of a pure function foo() while
keeping the same interfaces is not supposed to break your code. What happened?
What makes this problem more unsettling is that both you and I tried to do
everything by the book. We wrote a safe, typechecked code. We eschewed casts.
g++ (2.95.2) compiler with flags -W and -Wall issued not a single warning. Normally
these flags cause g++ to become very annoying. You didn't try to override methods
of CBag to deliberately break the CBag package. You attempted to preserve CBag's
invariants (weakening a few as needed). Real-life classes usually have far more
obscure algebraic properties. We both wrote regression tests for our implementations
of a CBag and a CSet, and they passed. And yet, despite all my efforts to separate
interface and implementation, I failed. Should a programming language or the
methodology take at least a part of the blame?
[OOP-problems]
Subtyping vs. Subclassing
The problem with CSet is caused by CSet design's breaking of the Liskov Substitution
Principle (LSP)
[LSP]. CSet has been declared as a subclass of CBag. Therefore, C++
compiler's typechecker permits passing a CSet object or a CSet reference to
a function that expects a CBag object or reference. However, it is well known
[Subtyping-Subclassing] that a CSet is not a subtype of a CBag.
The next few paragraphs give a simple proof of this fact, for the sake of reference.
One approach is to consider Bags and Sets as pure values, without
any state or intrinsic behavior -- just like integers are. This approach is
taken in the next article,
Preventing-Trouble.html. The other point of view -- the one used in this
article -- is Object-Oriented Programming, of objects that encapsulate state
and behavior. Behavior means an object can accept a message, send a reply and
possibly change its state. Let us consider a Bag and a Set separately, without
regard to their possible relationship. Throughout this section we use a different,
concise notation to emphasize the general nature of the argument.
We will define a Bag as an object that accepts two messages:
(send a-Bag 'put x)
- puts an element x into the Bag, and
(send a-Bag 'count x)
- gives the count of occurrences of x in the Bag (without changing a-Bag's
state).
Likewise, a Set is defined as an object that accepts two messages:
(send a-Set 'put x)
- puts an element x into a-Set unless it was already there,
(send a-Set 'count x)
- gives the count of occurrences of x in a-Set (which is always either
0 or 1).
Let's consider a function
(define (fnb bag)
(send bag 'put 5)
(send bag 'put 5)
(send bag 'count 5))
|
The behavior of this function can be summed as follows: given a Bag, the function
adds two elements into it and returns
(+ 2 (send orig-bag 'count 5))
Technically you can pass to fnb a Set object as well. Just as
a Bag, a Set object accepts messages put and count.
However applying fnb to a Set object will break the function's
post-condition, which stated above. Therefore, passing a set object where a
bag was expected changes behavior of some program. According to the Liskov Substitution
Principle (LSP), a Set is not substitutable for a Bag -- a Set cannot be a
subtype of a Bag.
Let's consider a function
(define (fns set)
(send set 'put 5)
(send set 'count 5))
|
The behavior of this function is: given a Set, the function adds an element
into it and returns 1. If you pass to this function a bag (which -- just as
a set -- replies to messages put and count), the function
fns may return a number greater than 1. This will break fns's
contract, which promised always to return 1.
Therefore, from the OO point of view, neither a Bag nor a Set are a subtype
of the other. This is the crux of the problem. Bag and Set only appear
similar. The interface or an implementation of a Bag and a Set appear to invite
subclassingof a Set from a Bag (or vice versa). Doing so however will
violate the LSP -- and you have to brace for very subtle errors. The previous
section intentionally broke the LSP to demonstrate how insidious the errors
are and how difficult it may be to find them. Sets and Bags are very simple
types, far simpler than the ones you deal with in a production code. Alas, LSP
when considered from an OOP point of view is undecidable. You cannot
count on a compiler for help in pointing out an error. You cannot rely on regression
tests either. It's manual work -- you have to see the problem
[OOP-problems].
Subtyping and Immutability
One may claim that "A Set *is not a* Bag, but an ImmutableSet *is an* ImmutableBag."
That is not correct. An immutability per se does not confer subtyping to "derived"
classes of data. As an example, consider a variation of the previous argument.
We will use a C++ syntax for a change. The examples will hold if re-written
in Java, Haskell, Self or any other language with a native or emulated OO system.
class BagV {
virtual BagV put(const int) const;
int count(const int) const;
... // other similar const members
};
class SetV {
virtual SetV put(const int) const;
int count(const int) const;
... // other similar const members
};
|
Instances of BagV and SetV classes are immutable, yet the classes are not
subtypes of each other. To see that, let us consider a polymorphic function
template <typename T> int f(const T& t)
{ return t.put(1).count(1); }
|
Over a set of BagV instances, the behavior of this function can be represented
by an invariant
f(bag) == 1 + bag.count(1)
If we take an object asetv = SetV().put(1) and pass it to
f(), the invariant above will be broken. Therefore, by LSP, a SetV
is not substitutable for BagV: a SetV is not a BagV.
In other words, if one defines
int fb(const BagV& bag) { return bag.put(1).count(1); }
|
he can potentially pass a SetV instance to it: e.g., either by making SetV a
subclass of BagV, or by reinterpret_cast<const BagV&>(aSetV). Doing
so will generate no overt error; yet this will break fb()'s invariant and alter
program's behavior in unpredictable ways. A similar argument will show that
BagV is not a subtype of SetV.
C++ objects are record-based. Subclassing is a way of extending records,
with possibly altering some slots in the parent record. Those slots must be
designated as modifiable by a keyword virtual. In this context, prohibiting
mutation and overriding makes subclassing imply subtyping. This was the reasoning
behind BRules [Preventing-Trouble.html].
However merely declaring the state of an object immutable is not enough to
guarantee that derivation leads to subtyping: An object can override parent's
behavior without altering the parent. This is easy to do when an object is implemented
as a functional closure, when a handler for an incoming message is located with
the help of some kind of reflexive facilities, or in prototype-based OO systems.
Incidently, if we do permit a derived object to alter its base object, we implicitly
allow behavior overriding. For example, an object A can react to
a message M by forwarding the message to an object B
stored in A's slot. If an object C derived from
A alters that slot it hence overrides A's behavior
with respect to M.
For example,
http://pobox.com/~oleg/ftp/Scheme/index.html#pure-oo
implements a purely functional OO system. It supports objects with an identity,
state and behavior, inheritance and polymorphism. Everything in that
system is immutable. And yet it is possible to define something like a BagV,
and derive SetV from it by overriding a put message handler. Acting
this way is bad and invites trouble as this breaks the LSP as shown earlier.
Yet it is possible. This example shows that immutability per se does not turn
object derivation into subtyping.
The present page is a compilation and extension of two articles posted on
comp.object, comp.lang.functional, comp.lang.c++.moderated newsgroups on Jun
18 and Jul 14, 2000.
Discussion thread:
http://www.deja.com/viewthread.xp?AN=644379349.1&search=thread&recnum=%3c8katsh$fmf$1@nnrp1.deja.com%3e%231/5&group=comp.object&frpage=viewthread.xp
Acknowledgment
Andy Gaynor has asked the right questions. This article is merely an answer.
Actually Spolsky does not understand the role of scripting languages.
But hi is right of target with his critique of OO. Object oriented programming is
no silver bullet.
Dec
14, 2006
(InfoWorld) Joel Spolsky
is one of our most celebrated pundits
on the practice of software development,
and he's full of terrific insight. In
a recent blog post, he decries the fallacy
of
"Lego programming" -- the all-too-common
assumption that sophisticated new tools
will make writing applications as easy
as snapping together children's toys.
It simply isn't so, he says -- despite
the fact that people have been claiming
it for decades -- because the most important
work in software development happens
before a single line of code is written.
By way of support,
Spolsky reminds us of a quote from the
most celebrated pundit of an earlier
generation of developers. In his 1987
essay
"No Silver Bullet," Frederick P.
Brooks wrote,
"The essence of a software entity
is a construct of interlocking concepts
... I believe the hard part of building
software to be the specification, design,
and testing of this conceptual construct,
not the labor of representing it and
testing the fidelity of the representation
... If this is true, building software
will always be hard. There is inherently
no silver bullet."
As Spolsky points
out, in the 20 years since Brooks wrote
"No Silver Bullet," countless products
have reached the market heralded as
the silver bullet for effortless software
development. Similarly, in the 30 years
since Brooks published "
The Mythical Man-Month" -- in which,
among other things, he debunks the fallacy
that if one programmer can do a job
in ten months, ten programmers can do
the same job in one month -- product
managers have continued to buy into
various methodologies and tricks that
claim to make running software projects
as easy as stacking Lego bricks.
Don't you believe
it. If, as Brooks wrote, the hard part
of software development is the initial
design, then no amount of radical workflows
or agile development methods will get
a struggling project out the door, any
more than the latest GUI rapid-development
toolkit will.
And neither will
open source. Too often, commercial software
companies decide to turn over their
orphaned software to "the community"
--
if such a thing exists -- in the
naive belief that open source will be
a miracle cure to get a flagging project
back on track. This is just another
fallacy, as history demonstrates.
In 1998, Netscape
released the source code to its Mozilla
browser to the public to much fanfare,
but only lukewarm response from developers.
As it turned out, the Mozilla source
was much too complex and of too poor
quality for developers outside Netscape
to understand it. As Jamie Zawinski
recounts, the resulting decision
to rewrite the browser's rendering engine
from scratch derailed the project anywhere
from six to ten months.
This is a classic
example of the fallacy of the mythical
man-month. The problem with the Mozilla
code was poor design, not lack of an
able workforce. Throwing more bodies
at the project didn't necessarily help;
it may have even hindered it. And while
implementing a community development
process may have allowed Netscape to
sidestep its own internal management
problems, it was certainly no silver
bullet for success.
The key to developing
good software the first time around
is doing the hard work at the beginning:
good design, and rigorous testing of
that design. Fail that, and you've got
no choice but to take the hard road.
As Brooks observed all those years ago,
successful software will never be easy.
No amount of open source process will
change that, and to think otherwise
is just more Lego-programming nonsense.
Resolved: Objects Have Failed
I participated in a debate on the question "Objects Have Failed" at OOPSLA
2002 in Seattle, Washington. My teammate was Brian Foote, and our opponents
were Guy L. Steele Jr. and James Noble. My opening remarks were scripted, as
were Guy Steele's, and my rebuttals were drawn from an extensive set of notes.
- rpg's Opening Remarks [html]
- gls's Opening Remarks [html]
- Discursive Notes [1.1 mb
pdf ]
- Notes as Slides [7.1 mb
pdf ]
(Opening remarks by Richard P. Gabriel, November 6, 2002)
What can it mean for a programming paradigm to fail? A paradigm fails when
the narrative it embodies fails to speak truth or when its proponents embrace
it beyond reason. The failure to speak truth centers around the changing needs
of software in the 21st century and around the so-called improvements on OO
that have obliterated its original benefits. Obsessive
embrace has spawned a search for purity that has become an ideological weapon,
promoting an incremental advance as the ultimate solution to our software problems.
The effect has been to brainwash people on the street. The statement
"everything is an object" says that OO is universal, and the statement "objects
model the real world" says that OO has a privileged position. These are very
seductive invitations to a totalizing viewpoint. The result is to starve research
and development on alternative paradigms.
Someday, the software we have already written will be a set of measure 0.
We have lived through three ages of computing—the first was machine coding;
the second was symbolic assemblers, interpreter routines, and early compilers;
and the third was imperative, procedural, and functional programming, and compiler-based
languages. Now we are in the fourth: object-oriented programming. These first
four ages featured single-machine applications. Even though such systems will
remain important, increasingly our systems will be made up of dozens, hundreds,
thousands, or millions of disparate components, partial applications, services,
sensors, and actuators on a variety of hardware, written by a variegated set
of developers, and it won’t be incorrect to say that no one knows how it all
works. In the old world, we focussed on efficiency, resource limitations, performance,
monolithic programs, standalone systems, single author programs, and mathematical
approaches. In the new world we will foreground robustness, flexibility, adaptation,
distributed systems, multiple-author programs, and biological metaphors for
computing.
Needless to say, object-orientation provides
an important lens through which to understand and fashion systems in the new
world, but it simply cannot be the only lens. In future systems,
unreliability will be common, complexity will be out of sight, and anything
like carefully crafted precision code will be unrealistic. It’s like a city:
Bricks are important for building part of some buildings, but the complexity
and complicated way a variety of building materials and components come together
under the control of a multitude of actors with different cultures and goals,
talents and proclivities means that the kind of thinking that goes into bricks
will not work at the scale of the city. Bricks are just too limited, and the
circumstances where they make sense are too constrained to serve as a model
for building something as diverse and unpredictable as a city. And further,
the city itself is not the end goal, because the city must also—in the best
case—be a humane structure for human activity, which requires a second set of
levels of complexity and concerns. Using this metaphor to talk about future
computing systems, it’s fair to say that OO addresses concerns at the level
of bricks.
The modernist tendency in computing is to engage in totalizing discourse
in which one paradigm or one story is expected to supply all in every situation.
Try as they might, OO’s promoters cannot provide a believable modernist grand
narrative to the exclusion of all others. OO holds no privileged position. So
instead of Java for example embracing all the components developed elsewhere,
its proponents decided to develop their own versions so that all computing would
be embraced within the Java narrative.
Objects, as envisioned by the designers of languages like Smalltalk and Actors—long
before C++ and Java came around— were for modeling and building complex, dynamic
worlds. Programming environments for languages like Smalltalk were written in
those languages and were extensible by developers. Because the philosophy of
dynamic change was part of the post-Simula OO worldview, languages and environments
of that era were highly dynamic.
But with C++ and Java, the dynamic thinking fostered by object-oriented languages
was nearly fatally assaulted by the theology of static thinking inherited from
our mathematical heritage and the assumptions built into our views of computing
by Charles Babbage whose factory-building worldview was dominated by omniscience
and omnipotence.
And as a result we find that object-oriented
languages have succumbed to static thinkers who worship perfect planning over
runtime adaptability, early decisions over late ones, and the wisdom of compilers
over the cleverness of failure detection and repair.
Beyond static types, precise interfaces, and mathematical reasoning, we need
self-healing and self-organizing mechanisms, checking for and responding to
failures, and managing systems whose overall complexity is beyond the ken of
any single person.
One might think that such a postmodern move would have good consequences,
but unlike Perl, the combination was not additive but subtractive—as if by undercutting
what OO was, OO could be made more powerful. This may work as a literary or
artistic device, but the idea in programming is
not to teach but to build.
The apparent commercial success of objects and our love affair with business
during the past decade have combined to stifle research and exploration of alternative
language approaches and paradigms of computing. University and industrial research
communities retreated from innovating in programming languages in order to harvest
the easy pickings from the OO tree. The business frenzy at the end of the last
century blinded researchers to diversity of ideas, and they were into going
with what was hot, what was uncontroversial. If ever there was a time when Kuhn’s
normal science dominated computing, it was during this period.
My own experience bears this out. Until 1995, when I went back to school
to study poetry, my research career centered on the programming language, Lisp.
When I returned in 1998, I found that my research area had been eliminated.
I was forced to find new ways to earn a living within the ecology created by
Java, which was busily recreating the computing world in its own image.
Smalltalk, Lisp, Haskell, ML, and other languages languish while C++, Java,
and their near-clone C# are the only languages getting attention. Small languages
like Tcl, Perl, and Python are gathering adherents, but are making no progress
in language and system design at all.
Our arguments come in several flavors:
- The object-oriented approach does not adequately address the computing
requirements of the future.
- Object-oriented languages have lost the simplicity—some would say purity—that
made them special and which were the source of their expressive and development
power.
- Powerful concepts like encapsulation were supposed to save people from
themselves while developing software, but encapsulation fails for global
properties or when software evolution and wholesale changes are needed.
Open Source handles this better. It’s likely that modularity—keeping things
local so people can understand them—is what’s really important about encapsulation.
- Objects promised reuse, and we have not seen much success.
- Despite the early clear understanding of the nature of software development
by OO pioneers, the current caretakers of the ideas have reverted to the
incumbent philosophy of perfect planning, grand design, and omniscience
inherited from Babbage’s theology.
- The over-optimism spawned by objects in the late 1990s led businesses
to expect miracles that might have been possible with objects unpolluted
by static thinking , and when software developers could not deliver, the
outrageous business plans of those businesses fell apart, and the result
was our current recession.
- Objects require programming by creating communicating entities, which
means that programming is accomplished by building structures rather than
by linguistic expression and description through form, and this often leads
to a mismatch of language to problem domain.
- Object design is like creating a story in which objects talk and interact
with each other, leading people to expect that learning object-oriented
programming is easy, when in fact it is as hard as ever. Again, business
was misled.
- People enthused by objects hogged the road, would not get out of the
way, would not allow alternatives to be explored—not through malice but
through exuberance—and now resources that could be used to move ahead are
drying up. But sometimes this exuberance was out-and-out lying to push others
out of the way.
But in the end, we don’t advocate changing the way we work on and with objects
and object-oriented languages. Instead, we argue for diversity, for work on
new paradigms, for letting a thousand flowers bloom. Self-healing, self-repair,
massive and complex systems, self-organization, adaptation, flexibility, piecemeal
growth, statistical behavior, evolution, emergence, and maybe dozens of other
ideas and approaches we haven’t thought of—including new physical manifestations
of non-physical action—should be allowed and encouraged to move ahead.
This is a time for paradigm definition and shifting. It won’t always look
like science, won’t always even appear to be rational; papers and talks explaining
and advocating new ideas might sound like propaganda or fiction or even poetry;
narrative will play a larger role than theorems and hard results. This will
not be normal science.
In the face of all this, it’s fair to say that
objects have failed.
[Feb 14, 2006]
OOP Criticism Object Oriented Programming Oversold by B. Jacobs.
OOP criticism and OOP problems. The emperor has no clothes! Reality Check
101. Snake OOil. Updated: 5/14/2005
OOP Myths Debunked:
- Myth: OOP is a proven general-purpose technique
- Myth: OOP models the real world better
- Myth: OOP makes programming more visual
- Myth: OOP makes programming easier and faster
- Myth: OOP eliminates the "complexity" of "case" or "switch" statements
- Myth: OOP reduces the number of places that require changing
- Myth: OOP increases reuse (recycling of code)
- Myth: Most things fit nicely into hierarchical taxonomies
- Myth: Sub-typing is a stable way to model differences
- Myth: Self-handling nouns are more useful than self-handling verbs
- Myth: Most operations have one natural "primary noun"
- Myth: OOP does automatic garbage-collection better
- Myth: Procedural cannot do components well
- Myth: OO databases can better store large, multimedia data
- Myth: OODBMS are overall faster than RDBMS
- Myth: OOP better hides persistence mechanisms
- Myth: C and Pascal are the best procedural can get
- Myth: SQL is the best relational language
- Myth: OOP would have prevented more Y2K problems
- Myth: OOP "does patterns" better
- Myth: Only OOP can "protect data"
- Myth: Implementation changes significantly more often than interfaces
- Myth: Procedural/Relational ties field types and sizes to the code more
- Myth: Procedural cannot extend compiled portions very well
- Myth: No procedural language can re-compile at the routine level
- Myth: Procedural/Relational programs cannot "factor" as well
- Myth: OOP models human thought better (Which human?)
- Myth: OOP is more "modular"
- Myth: OOP divides up work better
- Myth: OOP "hides complexity" better
- Myth: OOP better models spoken language
- Myth: OOP is "better abstraction"
- Myth: OOP reduces "coupling"
- Myth: OOP does multi-tasking better
- Myth: OOP scales better
- Myth: OOP is more "event driven"
- Myth: Most programmers prefer OOP
- Myth: OOP manages behavior better
Software-engineering has two kinds of cargo-cult; slavish adherence to process
without regard to the effect on product, and reliance on personal heroics, again
without regard to product. In both cases, organizations try to mimic programming
style/paradigm, but only mimic the external appearance without understanding
real programming techniques and ideas behind the technology.
The real difference is not which style is chosen, but what
education, training, and understanding is brought to bear on the project. Rather
than debating process vs. commitment, we should be looking for ways to raise
the average level of developer and manager competence. That will improve our
chances of success regardless of which development style we choose.
I'm fairly sure you could accurately gauge the
maturity of a programming team by the amount of superstition in the source code
they produce. Code superstitions are a milder form of
cargo cult software development, in which you find people writing code constructs
that have no conceivable value with respect to the functions that the code is
meant to fulfill.
A recent conversation reminded me of an example
I find particularly disturbing. Sample code for dealing with JDBC is particularly
prone to being littered with this particular error, as shown below. (I suspect
that is not coincidental; I'll be coming back to that.) I have elided most braces
out for clarity and terseness - imagine that this is a cross between Java and
Python:
import java.sql.*;
public class JdbcSample {
public static void main(String[] args) {
Connection conn = null;
try
conn = DriverManager.getConnection("jdbc:someUrl");
// ...more JDBC stuff...
catch (SQLException ex)
// Too often that is silently ignored, but that's another blog entry
finally
if (conn != null)
try
conn.close();
catch (SQLException sqlEx)
conn = null;
}
The "superstition" part is that setting the connection
to null can have absolutely no useful effect; being a local variable, "conn"
will become eligible for garbage collection as soon as it goes out of scope
anyway, which the most rudimentary analysis of flow control reveals it will
immediately after being set to null.
I am always particularly interested in finding
out what goes on in the minds of programmers who write this kind of thing, because
that will sometimes reveal the roots of the superstition. Most of the time,
though, if you raise question in a design review the programmer will say something
like "I copied and pasted it from sample code". This is how the superstitions
spread - and it's also a red flag with respect to the team's practice maturity
- but rarely an occasion to gain insight into why the superstition took hold,
which is what you'll need to know in "remedial" training.
Now, the "null" concept, obvious as it seems,
is a likely place for superstitions to accrete around. If you look closely,
"null" is nothing but obvious. Comparing Java and Smalltalk, for instance, we
find that they differ radically with respect to calling instance methods on
null, or "nil" as it's called in Smalltalk; "nil" does have some instance methods
you can call. Also, what is the type of the "null" value in Java ? It is a special
type called "the null type", which looks like a sensible answer but incidentally
breaks the consistency of the type system; the only types which are assignable
to variables are the type of the variable or subtypes of that type, so "null
type" should be a subclass of every Java class. (It actually works that way
in Eiffel, as Nat Pryce reminds me - see comments.)
See also
here for another example of a null-related Java superstition, also surprisingly
common, as you can verify by Googling for "equals null".
In the case of JDBC, I would bet that idioms
of resource allocation and deallocation inherited from non-garbage collected
languages, like C, were the main force in establishing the superstition. Even
people new to Java get used to not calling "dispose" or "delete" to deallocate
objects, but unfortunately the design of the JDBC "bridges" between the object
and relational worlds suffer from a throwback to idioms of explicit resource
allocation/deallocation.
Owing to what many see as a major design flaw
in Java, "going out of scope" cannot be relied on as an indicator that a resource
is no longer in use, either, so whenever they deal with JDBC Java programmers
are suddenly thrown back into a different world, one where deallocation is something
to think about, like not forgetting your keys at home. And so, in precisely
the same way as I occasionally found myself patting my pockets to check for
home keys when I left the office, our fingers reflexively type in the
closest equivalent we find in Java to an explicit deallocation - setting to
null.
You may object that the setting-to-null superstition
is totally harmless. So is throwing salt over your shoulder. While this may
be true of one particular superstition, I would be particularly concerned
about a team which had many such habits, just like you wouldn't want to trust
much of importance your batty old aunt who avoids stepping on cracks, stays
home on Fridays, crosses herself on seeing a black cat, but always sends you
candy for Christmas.
Posted by Morendil at November
15, 2004 04:57 PM
Laurent Bossavit explains the notion
of "Cargo Cult" programming - the example being setting a temporary variable
to null (i.e., one that is going out of scope)
You may object that the setting-to-null superstition
is totally harmless. So is throwing salt over your shoulder. While this may
be true of one particular superstition, I would be particularly concerned about
a team which had many such habits, just like you wouldn't want to trust much
of importance your batty old aunt who avoids stepping on cracks, stays home
on Fridays, crosses herself on seeing a black cat, but always sends you candy
for Christmas.
What superstitious coding practices does your
group have?
Comments
null helps GC yes?
no? [john mcintosh] November 15, 2004 19:23:32 EST
I once had a fellow phone me from Hong Kong who
explained a performance problem they were having. Seems they at the end of each
method, and in each "destroy" method for a class (used to to destroy instances),
they would set all the variables to NULL. The best was of course iterating over
thousands of array elements, setting them to NULL since they felt this was helping
the GC find NULL (garbaged) variables faster. Once they stopped doing this why
windows just snapped closed....
[PDF]
Cargo
Cults in Java by Gordon Fletcher University of Salford
This paper is a personal account of my experience
of teaching Java programming to undergraduate and postgraduate students. These
students enter their respective subjects with no previous Java programming knowledge.
However, the undergraduate students have previous experience with Visual Basic
programming. In contrast, the postgraduate students are enrolled in a “conversion”
course which, in most cases, means that they were unfamiliar with any form of
programming language or, in some cases, some core information technology skills.
Irrespective of these differences, I have witnessed how both groups independently
develop, what can be described as, a trade based culture with similarities to
‘cargo cults’ around the Java language. This anthropological term provides
a useful terms of reference as the focus of programming activity for many students
increasingly centres upon the imitation of code gathered from the lecturer or,
in some cases, each other. This is particularly evident as project deadlines
approach. In extreme examples of this cargo cult fever, students will discard
potentially strong project developments that incorporate many features of good
software design in favour of inelegant and cobbled together code on the single
criteria of better functionality.
In this paper I use the concept of the cargo
cult to frame the differing expectations surrounding “learning Java” that are
held by students and their lecturer. I draw upon my own observations and experiences
as the teacher in these learning environments and upon feedback from my most
recent cohort of undergraduate students undertaking an BSc(Hons) programme within
a UK university. The student feedback is drawn from a questionnaire containing
six questions relating to their experiences and expectations regarding a Java
programming subject. The definition and description of the cargo cult is also
used to consider how this relationship can be established in a way that encourages
positive learning outcomes through the obligations and reciprocation associated
with gifts – in this case, clearly labeled gifts of code. The cargo cult
and the erroneous form of thinking associated with it provide a useful framework
for understanding the teaching and learning environment in which I taught Java.
In this way the interactions and motivation of students and the lecturers who
ultimately share the common goal of obtaining their academic success can be
scrutinized with the aim of improving this experience for all those involved.
The cargo cult is not, however, ‘simply’ an anachronistic analogy drawn
from social anthropology. Cargo cult thinking has been identified within contemporary
culture as readily as tribal cultures and with equal significance (Hirsch
2002; Cringely 2001, Fitzgerald 1999; Feynman 1974).
2. Cargo Cult Thinking
It is important to acknowledge that cargo cult thinking is not necessarily the
‘wrong’ way of thinking or that this paper seeks to castigate students’ study
practices. Cargo cult thinking is based, in part, on conclusions drawn from
only partially observed phenomena. In many respects this paper is a reflexive
exercise regarding my own teaching practices and an examination of the ways
in which cargo cult thinking can be employed to achieve positive learning outcomes.
Nonetheless, despite this acknowledgement, the actions of cargo cult followers
are based upon a “fallacious reasoning” of cause and effect. This could be summarized
in the context of Java programming as the assumption that if I, as a student,
write my code like you, the lecturer, do, or use your code as much as possible,
I will be a programmer like you and this is what is required for me to do well
- or at least pass - this subject. However, as teachers of Java it is
necessary to acknowledge the – perhaps dormant – presence of this attitude and
to consequently offer offhand code examples with extreme caution. I have repeatedly
spotted examples of my own code embedded within students’ projects. Although
the code may originally have been offered as a quick and incomplete example
of a concept or a particular line of thinking it can too readily become the
cornerstone of larger scale classes without modification. It is perhaps, then
unsurprising, that the cargo cult attitude does, develop among students when
they are first learning a programming language and the concepts of programming.
The consequence of pursuing this belief unchecked parallels the effects
of learning in a “Java for Dummies” manner. Deeper, conceptual understanding
and problem-solving techniques remain undeveloped and students are left able
only to imitate the step-by-step procedures outlined by the textbook.
This step-by-step form of explicit instruction discourages exploration and
discourages students from appreciating the learning that is occurring when they
disentangle java compiler errors. This is perhaps one of the most revealing
differences between students and lecturers. While experienced programmers
use compiler errors, new programmers will see the errors as “just one more thing”
getting in the way of a successfully executing application. This suggests a
lack of awareness that programming is not synonymous with writing code.
The consuming focus in the majority of undergraduate and postgraduate assessment
projects is upon pursuing and obtaining functionality in their code to the detriment
of the user interface, clear documentation, class structures, code reusability,
extensibility or reliability.
When code is reused, and especially when code is
acquired from outsourced teams or incorporated via Web services technologies,
there's a real opportunity for cargo cult practices to take hold. Source code
may follow unfamiliar naming conventions, and design documents and internal
memos may be written in unfamiliar languages or in a language that we know by
people who don't speak that language very well. We may not even have the source—we
may have only WSDL (Web Services Description Language) or some other interface
definition to guide us.The wooden headphones
may bear fancy names like "design patterns," but they're still an indicator
that we may be building systems that look like those that have worked before—instead
of designing from deep understanding toward solutions that meet new needs.
"The first principle is that you must not fool
yourself," said the late physicist Richard Feynman in the 1974 Caltech commencement
address that's often considered the origin of the "cargo cult" phrase, at least
as used by coders. That's a good principle. Reusing code that we don't understand
or reusing familiar methods merely because we do understand them are behaviors
for which we should be on guard.
In the not-so-recent past, headlines proclaimed, "Software
ICs Will Revolutionize Computer Programming." ind development would reduce programming
to assembling standardized "objects," and that the need for programmers would
decline as software "technicians" with minimal training would develop the software
of the future.
Ten years have passed, and this clearly hasn't happened. Skilled
programmers are in greater demand, the skill levels required are higher, and
software is harder to develop. The business press says that nirvana is now just
around the corner; companies that have the words "object-oriented" in their
business plan are in demand among venture firms. Yet object-oriented methodologies
are over 20 years old. Are today's technological forecasts any more accurate
than those of 10 years ago?
This is not to say that OO can't work. There are examples
of successful OO projects; usually these are showcase projects staffed with
top developers. For the most part, however, object-oriented technology has not
been the "magic bullet." In this article, I'll briefly discuss some reasons
that OO has thus far failed to deliver. More importantly, I'll address some
ways that organizations with average programmers can achieve high levels of
reuse and shorten development cycles.
Let me count the way
The principal benefit cited for object-oriented methodologies
is "reuse." This sounds like a valuable benefit; if we improve reuse, we write
less code. Less code means faster development and easier maintenance in the
future. Less code also means fewer chances for bugs, so it indirectly affects
product quality. However, industry watchers report that there is only 15 percent
average reuse in today's object-based projects. That's a pretty damning statistic,
if true; we did better 20 years ago with COBOL subroutines! Others have cited
different statistics; one major consulting firm reports 25 percent reuse across
clients, and some academic centers report 80 percent reuse. So what's the real
story?
All of these figures beg the question: "How do you measure
reuse?" Is reuse a measure of code that is referenced in more than one place?
(Subroutines could do that before OO.) Is code referenced in 50 places counted
differently from code referenced in two places? One measure of reuse might be
the size of an application developed using OO technology versus one developed
using a different technology. This measurement, however, is impossible to perform,
as such systems don't exist. Further, a search of the literature turns up no
widely-used standards for measuring reuse.
Yet another complication is the granularity involved in measuring
reuse. The usual unit is the object itself. But no one looks inside the object.
One can create a simple object that can be used for only one specific function.
This object can be made to serve more functions (thus improving its reuse) by
adding methods to it. Perhaps, however, the same programming benefit could have
been achieved by creating a new object for the additional functions rather than
enhancing the first object with additional methods. The amount of programming
work is the same in both cases, but the bulkier single object with additional
methods counts for a higher level of reuse to most people, even though this
object is carrying around a lot of unused "baggage" in any one instantiation.
The bottom line is that there is no practical objective way
to measure reuse. Anyone out to make a point (positive or negative) about reuse
can find a metric to prove that point. This creates a new problem. If you can't
measure something, how can you improve it? For the time being, we will have
to assume that we know good reuse when we see it, even if we can't measure it.
We can do this by observing how long it takes to develop an application or how
much code it takes to develop the application (assuming experienced, competent
programmers). By using this subjective approach, it is apparent to most developers
that we are still losing ground.
Objects and Components
Agreeing on what constitutes an "object" is a fundamental
problem with object-oriented technology. In theory, an object represents a real-world
entity, such as a person, vehicle, merchandise, etc. Yet most programmers think
of objects as processing entities -- listboxes, text widgets, windows, etc.
While it would be possible to start with widgets and, through encapsulation
and inheritance, end up with, say, vehicles, developers just don't do this when
building real systems. So one problem is that most OO development is not truly
object oriented, but rather programming with predefined widgets. Just because
you are programming in C++ does not mean that you are doing object-oriented
development. As we used to say, "Real FORTRAN programmers can write FORTRAN
in any language" -- and real procedural programmers can write procedural code
in C++.
There is a well-established, theoretical basis for object-oriented
methodology. Even if some developers don't understand it, don't use it correctly,
or disagree with it, there is a body of reference material that precisely defines
objects and regulates their use.
The computer industry has recently begun to shift focus from
"objects" to "components" as the answer to our dreams. But what is a component?
Some simply use the term "component" as another name for a widget. I have a
catalog in front of me that purports to offer "components." It includes charting
tools, a cryptographic package, a Text Edit control developer's kit, a collection
of widgets (grids, trees, notebooks, meters, etc.), communications drivers,
and similar entities. This definition of "component" is not the answer we are
seeking, however.
A search of the literature doesn't help, either. There are
many articles that discuss components, but few that actually define a component.
Industry expert Judith Hurwitz says, "Components are made up of business rules,
application functionality, data, or resources that are encapsulated to allow
reuse in multiple applications." Alan Radding, who writes about multi-tier development,
responds, "In [Judith] Hurwitz Consulting's hypertier scheme, everything in
effect ends up as a component." Don Kiely, writing about components for IEEE's
Computer magazine never actually defines components, but he does define "framework
assemblies" as groups of components "that could be plugged into an application
as easily as individual components." This is a significant statement because
it shows that Kiely, Hurwitz, and Radding are thinking along the same lines,
even if they use different words. Kiely also makes the useful observation that,
"to be truly effective, components should be portable and inter-operable across
applications," something that I will come back to later.
Slashdot
The Object Oriented Hype discussion of B. Jacobs paper Object Oriented
Programming Oversold!
These problems form obstacles to the further development of
object-oriented software engineering, and in some situations are beginning to
cause its outright rejection. Such problems can be solved either by a variety
of ad hoc tools and methodologies, or by progress in language technology (both
design and implementation). Here are some things that could or should be done
in the various areas.
- Economy of execution. Much can be done to improve
the efficiency of method invocation by clever program analysis, as well
as by language features (e.g. by "final" methods and classes); this is the
topic of a large and promising body of current work. We also need to design
type systems that can statically check many of the conditions that now require
dynamic subclass checks.
- Economy of compilation. We need to adopt languages
and type systems that allow the separate compilation of (sub)classes, without
resorting to recompilation of superclasses and without relying on "private"
information in interfaces.
- Economy of small-scale development. Improvements
in type systems for object-oriented languages will improve error detection
and the expressiveness of interfaces. Much promising work has been done
already and needs to be applied or further deployed
[1] [5].
- Economy of large-scale development. Major progress
should be achieved by formulating and enforcing inheritance interfaces:
the contract between a class and its subclasses (as opposed to the instantiation
interface which is essentially an object type). This recommendation requires
the development of adequate language support. Parametric polymorphism is
beginning to appear in many object-oriented languages, and its interactions
with object-oriented features need to be better understood. Subtyping and
subclassing must be separated. Similarly, classes and interfaces must be
separated.
- Economy of language features. Prototype-based
languages have already tried to reduce the complexity of class-based languages
by providing simpler, more composable features. Even within class-based
languages, we now have a better understanding of how to achieve simplicity
and orthogonality, but much remains to be done. How can we design an object-oriented
language that is powerful and simple; one that allows powerful engineering
but also simple and reliable engineering?
OOP Criticism -- good OOP criticism and OOP problems (The emperor has
no clothes!). Contains a very good collection of links
Contents
External Links
This research study, investigates some of the
problems and unresolved issues in the OOPar. Contrary to adopting a WHAT (the
problem) and HOW (the solution) approach it uniquely asks WHY these
problem and issues exist. We argue that the WHAT & HOW approach, although useful
in the short term, does not provide a long term solution to the problems in
data modelling (DM). As a result of adopting such an approach and the empirical
and wide ranging nature of chapter 3, four aspects are proposed.
2.0 Concepts of the OO model
The main concepts that underline the OOPar, are
outlined in the following sections.
2.1 Object Classes & Objects
In the OOPar, the problem domain is modeled using
object-classes, and there instances objects (Booch, 1994). An object is any
abstract or real world item that is relevant to the system. An object class
is a grouping of these objects. For example, in a library information system
an object-classes would be such things as members, books, etc. Objects would
be instances of these classes, e.g. Joe Bloggs, Object-oriented analysis by
Martin, etc.
2.2 Methods
Methods are predefined operations, and are associated
with an object-class. "Methods specify the way in which an object's data are
manipulated" (Martin & Odell, (1992), p.17). T
Empty Java constructor [Jason Dufair] November 16, 2004 10:08:33 EST
I'm on a team doing Java right now. I see a lot of empty Java constructors. Being a Smalltalker making a living doing Java, I figured they must be there for a reason. Come to find out an empty constructor just calls the super's constructor. As if it weren't there in the first place. Whee!