Did OOP do this to us?
sob.apotheon.org
sob.apotheon.org
I support the article's claim that programmers are responsible for spaghetti code, but I disagree that OOP in general is responsible for facilitating this behavior. Compare Java's OO to smalltalk's OO.
On the one hand, you have "a halfway point between C++ and Lisp" which inherits a bytecode on virtual machine implementation from lisp and most everything else from C++. [http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/m...]
On the other hand you have an open source ecosystem built on top of a VM, where all code is available for modification and extension by the user. [http://www.squeak.org/About/]
Java programs are notorious for being resource hogs and unstable, and smalltalk programs are notorious for the opposite.
This dichotomy suggests OOP is not the evil villain it's made out to be. Rather, I would suggest that the average quality of code produced in a particular language is proportional to how easy it is to read other people's code in that language.
Java makes it very easy to hide your code. Often while working on a large Java project at school, each of my team mates and I divide the project into classes and we are each responsible for a particular set of classes. We don't ever need to look at one another's code to complete the assignment, and thus bugs can creep in.
On the other hand, I work on a mid-size PHP code base which is my responsibility alone. Thankfully, one of my co-workers is an excellent PHP coder. Whenever I make revisions to the code, I ask him to review my code. The mere knowledge that someone else is going to read the code I write is enough impetus for me to write better code in the hope that someday he won't find something I'll need to revise.
My experience is not an isolated case, as can be seen in this article by Jeff Atwood who quotes Code Complete: [http://www.codinghorror.com/blog/archives/000495.html].
This is supported by the generally higher quality of code in very shared languages like C, Smalltalk and Python, than in seldom shared languages like Java, C++ and Cobol.
Clearly, C has a high potential for code hiding, but a community of code sharing (Unix) was built around the language to address this issue.
1. Thank you for the input.
2. I think you're imagining a greater amount of blame being heaped on OOP than I actually intended. That may be the fault of unclear presentation on my part, though.
3. The second half of your comment is most excellent, and quite worth the read. Thanks for making these points so well.
Your point is that OOP "creates a means of loosely coupling code in a given complex system," which is considerably more scalable than tightly coupled code, which in turn leads to ever larger programs.
My comparison of Java and Smalltalk was meant to show that there exists versions of OOP which completely decouple code. In a Smalltalk VM (or at least in Squeak), each object you create is immediately available to you as though it were a command line utility.
This is very much in the spirit of the unix philosophy which you cite in both entries:
"...certain older development philosophies (like "the Unix philosophy") that provided significantly better modularity and significantly reduced complexity." [http://sob.apotheon.org/?p=935]
"Unix is the tradition of decoupled code, whence the very notion of a user environment composed entirely of programs that "do one thing well" comes to mind." [http://sob.apotheon.org/?p=245]
I haven't experienced a smalltalk app that couldn't be described similarly - but maybe things are improved in recent times, I should look again.
Note that Java didn't make enterprise software into a bloated, confusing architectural spaghetti. It just found its niche there. Remember that Java was originally intended for writing embedded software for set-top boxes; then it got repurposed for writing "write once, run anywhere" desktop applets. It is only because the language was a nigh-complete failure in those niches that it became primarily known as a language for server-side enterprise software, which has influenced its development ever since.
So it might be that the enterprise development "pattern", memorably characterized by PG as "maintainable spaghetti", did this to (Java's) OOP, not the other way around. But I would suggest that it's a little of both: Java and the "enterprise pattern" are coevolved, like the chicken and its egg. At this point it's hard to sort out which came first.
UPDATE: I should point out that the author says more or less the same thing:
OOP didn't do this to us; we did it to ourselves. OOP just held our hats.
His title is a rhetorical question, so the fact that I think it's subtly misleading is actually a sign that it's doing its job. ;)
Frameworks done by committee are even worse. God save us from W3C and the Apache Foundation.
Counterpoint: software from dictators: Python and Django.
News at 11.
The second phase was more closely related to patterns and agile methologies. Here, the illusion of OOP creating generic reuse was abandoned, the idea of "containment over inheritance" was broached, and the paradigm became closer to "the right tool for the job".
I am actually amazed that methodologies were able to improve but I can't argue with what I've seen.
Most importantly, I think it might be some sort of "it's so expensive, it's GOT to work" effect. The complexity of OOP patterns can be pretty ridiculous, so perhaps people dilligently code away using them because they feel like there's nothing better, or they feel like the complexity has to be good for something.
Exactly.
“Simplicity is hard to build, easy to use, and hard to charge for. Complexity is easy to build, hard to use, and easy to charge for.”
Java is like a variant of the game of Tetris in which none of the pieces can fill gaps created by the other pieces, so all you can do is pile them up endlessly.
-- Steve Yegge (2007, Codes Worst Enemy)
The first culprit is Java itself. The language is missing critical builtin data structures, ArrayList and Hashmap with corresponding literals. If they had been part of the language and not just libraries, Java might have taken a completely different direction. Throw in lack of overloadable operators, first class functions, and descent introspection and you get a language that in not very expressive.
Lack of key fundamental data structures, leads to the OOP bandwagon. Interfaces and inheritance are good tools but they should be used in moderation. In my project there is the pattern of BaseInterface, BaseClass, MoreCompleteInterface, MoreCompleteClass, ModuleLevelInterface, and ModuleLevelClass all to implement a DAO with nothingg but getters and setters. In one case this adds up to more than 2000 lines of code that does nothing that could not have bee done with a hash. Just to make sure that the code is completely non-understandable most serious classes have something like this
BaseInterface dataSource;
along with a setter that is filled in by Spring at runtime. I cant even explore the class structure. Contrast this with Python, where it would be a handful of lines of code, maybe 100 or 200 times fewer.
To get around the problems inherent in the language, Sun came up with beans and the Java community went charging off into complexity. And throw in XML configuration just to create an impedance barrier. Since this was all new territory, the result was an explosion of frameworks.
So now I have a stack that includes JBoss, Spring, Hibernate, MySQL, 200k of JS libraries (some of which I never heard of before), Flex, Granite, and more open source Java libraries than I can shake a stick at. I suspect there are many cases of a library being added for a single feature. Java OK, JS not so OK because of download bloat. Maybe Java is not OK either because I have to deal with what broke when I upgrades to a new version.
I particularly dislike the addition of Hibernate because it separates the programmer from the understanding of the database. In my case the database itself was generated from the the .hbm.xml files. The database was created from a Java-centric point of view as a network of classes. As a consequence Hibernate created hundreds of tables. One query to fill in a simple list in the browser created an SQL query with 10 joins. I'm surprised the db didn't roll over dead.
At least I'm being paid well.
C has a ridiculously small set of built-in data structures, but the unix core tools written in C are very elegantly designed.
The crux of the above argument is usually something like:
In most nontrivial programs you'll wind up with a lot of "sophisticated" code that performs some task but has a ton of parameters and behaviors it'd be at least theoretically useful to be able to tune or override at some specific point-of-use of said code.
Sometimes the right decision is to just hard-code in all the non-essential parameters to sensible defaults, and do only what you need; in places where this approach is warranted ("keep it simple, and only do what you need to"), Java is as annoying to use as ever but doesn't do much to shoot you in the foot beyond being more verbose than it needs to be.
It's also sometimes the case that the right decision is to make the code as fully-articulated-as-possible: as many parameters as possible are tunable, and as many behaviors as possible can be overridden. We'll call this the "complicated" scenario.
In the "complicated" scenario, you have at least the following choices for architecture:
- approach 1: a tower of interfaces and base classes; each interface+base class combination augments or extends some of the behavior or adds or removes points-of-articulation from the layer beneath it. Once you've built the tower, whenever you get to a point where you need to call on this functionality you pick the appropriate item from the tower, subclass+augment its behavior further (if necessary), and go to town.
- approach 2: similar to 1, but make heavy use of reflection (at least at certain points) to try and achieve some level of genericity (in the core methods and algorithms) and to make the implementation less dependent (at time of coding) on the specifics of any particular interface implementation.
- approach 3: have methods that're like void openConnection(IConnectionSpecification connectionSpecification, Map<String,Object> options), and then be smart about how you extract options from the options Map.
approach 1 and approach 2 have their problems but fit well into the tools Java gives you. Approach 1 is more suited for writing code that other people will use (ie, call into), whereas approach 2 is more suited for writing code that other people's code will be used in (ie, called) (yes, that's a bit vague, but it's good enough for today).
Approach 3 is common in the scripting languages, because it allows for a lot of ad-hoc extension / parameter setting without excessively cluttering the interface.
Approach 3 isn't impossible in Java, but the lack of literal syntax for eg maps and lists makes it unwieldy. If it were possible to write something like:
- ConnectionBroker.openConnection(cSpec, {"port":80,"username":"username","password":"password"});
you'd see a lot more use of Approach 3 in Java. As-is, you'd have to explicitly construct a Map of the right type, then one-item-and-line-at-a-time populate it with keys and values, and so on, at which point (especially if used often and in a big project) you're better off coding up an IConnnectionOptions interface, etc., and going from there.
So it's less: lack of data structures == bad design.
It's more: lack of literal syntax for the basic data structures means that it's a royal pain to do certain ad-hoc friendly tasks in an ad-hoc way. Assuming laziness (always a good assumption), given an idiomatic way to handle cases with lots of potential parameters most programmers would handle things the ad-hoc way, and save the "over-engineered" tower-of-interfaces for the situations which are actually necessary (eg: real-honest-enterprisey-software).
"Lack of key fundamental data structures, leads to the OOP bandwagon."
Lack of built-in data structures -> overuse of OOP
"Interfaces and inheritance are good tools but they should be used in moderation. In my project there is the pattern of BaseInterface, BaseClass, MoreCompleteInterface, MoreCompleteClass, ModuleLevelInterface, and ModuleLevelClass all to implement a DAO with nothingg but getters and setters." overuse of OOP -> Bad design
Therefore, Lack of built-in data structures -> Bad designThe overarching concept is the ""expressiveness" of a language. Concept is "How easily can I express my goal in the language. A crude measure of the relative expressiveness of two languages is the ratio of the number of tokens in the respective implementations of a program. I remember reading a study a decade ago that ranked C at 1, Java at 2, Python at 5 (roughly). Having hashes and their literals as part of the language makes it more expressive than one where hash is in a library. Look at JavaScript where the literals for arrays are used as a serialization protocol.
Expressiveness goes beyond just literals. In python functions are first class objects, so there is no need for the thunk/functor/inner class kludge that you have in Java. Or look at list comprehensions which are way more powerful than for each.
The expressiveness shapes how programs are coded. Programmers will use a concise, powerful idiom because it saves time and thinking and the program is shorter. There is a corresponding obligation on the language developer to make sure the expressive idiom is almost always the best way to go.
Naturally, this seems to imply that more powerful languages require less code to perform the same task. Python provides a good example of this, but it is not always true.
C is not a very expressive language. It is often cited as a portable assembly. Yet, decades of unix/GNU utilities show that powerful programs can be written in small amounts of C, which are combined to build elegant and decoupled systems.
Java is more expressive line by line, but its implementation of OOP requires so much boilerplate that it requires no less code than C to represent the same simple design as the C equivalent. Additionally, because each line of Java represents 2 lines of C, the resulting programs are far less efficient.
It is important to consider why this true. Java attempts to abstract many of the details of the underlying C implementation, allowing it to be much more expressive. This abstraction allows the higher level programmer to think about business logic rather than pointer arithmetic.
However, these abstractions constrain the programmer's ability to write code that the language designer hadn't considered.
Of course, you might consider these to be edge cases and that is a fair point. However, crippling the outliers pushes your userbase towards mediocrity.
As pg stated: "when you damp oscillations, you lose the high points as well as the low."
However, it's not unimaginable that a posting like yours could be submitted here 7 years down the road with Java replaced by Ruby/Rails or Python/Django. Piling on libraries is widespread in other communities as well. Attempting to avoid any knowledge of the DBMS even counts as cool in Ruby/Python circles.
At least many Java libraries are reasonably high quality. The same cannot be said about something like Django's ORM for example. I looked at the code the other day. They have no optimistic locking, and there is one completely useless existence check before each and every update. It's completely beyond me how anyone could use an ORM that doesn't support optimistic locking. But nobody seems to care. There are even postings in the archives with people claiming it's not necessary for web apps. That's just plain incompetence.
The whole idea of abolishing the separation between the database and the application is a huge disaster in the making. Up until now we could at least throw away legacy applications and still have a reasonably well designed database schema. Not any longer. In my view, that's the main contribution of Rails and it's rip-offs to software design.
So basically what I'm saying is that incompetence is is widespread and coolness trumps competence for extended periods of time, leaving behind a whole lot of crap like that one you're working with right now. The next generation of crap is piled on as we speak and it's written in today's fashion languages.
I'm all in favor of better programming languages because they improve the productivity of good programmers. But they don't make bad or even average programmers any better.
As Alan Kay said, "The revolution hasn't happened yet." He knows that OOP isn't the final answer (although I think we're all thankful for its existence). He is one of a small group, made up of mainly self-funded geniuses and eccentrics, who are working in this space. Everyone else gave up. We had PARC in the 70's and really nothing else since; we're still coasting on what they accomplished there.
Now that we know how important this is, why can't we fund a PARC again? Why can't we build a place where we throw it all out and start over? I know of a few places doing this now, including Viewpoint Research, but we need more.
"The thing that wasn't equal was the tendency of humans to fill a new development technique's capacity for scalability as long as that technique is used. As OOP provided greater ability to write ever-larger programs, humans wrote ever-larger programs — even when they didn't need to do so (and probably shouldn't have done so)."
It may be sold as a technique that the masses can use, but this is demonstrably not true, if you examine the code the masses are producing.
I am also sympathetic to the idea that we shouldn't have architects as a separate, distinct position, but some way of recognizing your good designers and architects seems necessary, because you don't really want just anybody doing it.
I agree strongly with your original premise: most don't understand how to use it properly. I recommend Allen Holub for learning.
That's why I point out that it applies to all design philosophies I encounter. I hate to be elitist as a general principle, but I regretfully must come to the conclusion there's a lot of programmers programming who really shouldn't be, based on the evidence.
Now, the textbooks all tend to make the same basic treatment. When I was a prof, since I am a "challenged" reader, rather than try to wade through the mess in the book I set apart my own derivation. When I was finished, I naturally had to tie it back into what the textbook was saying so that the students had something to hang onto. But when I went to make the link, what I found actually startled me.
In their derivations, all of the authors made the same phase-shift of the source signals and never mentioned it. I couldn't believe it, and tried to figure out where my mistake was. But I didn't make a mistake.
In a section of the book that didn't really figure in to the future of the students' education--could be easily skipped, in fact, because anybody that needed it would do it over "correctly" anyway-- everybody just sort of did what everybody else did.
Perhaps that's what is going on in OOP. I'm not trained as a programmer--I just pick up what I can. So I'm sure that most of my code is laughable, and I do sometimes paint myself into a corner. But I find that I am sometimes surprised by how easy it is to make big changes if I've encapsulated things. Just my personal experience.
Over enforcing the oop concept is a fantastic way of losing track of what is going on with your code, and it pretty much guarantees inefficiency, because 'abstraction' is just another way of saying "I don't want to know". What you don't know you can't analyze and what you can't analyze might very well be the cause of your problem.
Good programmer (tm) can write excellent code in Java, because like any other tool it will respond to its handler. If you are just using classes to pile layer upon layer of unwanted abstractions on top of each other because you fail to grasp your problem properly then you have nobody to blame but yourself.
Again, in some languages this kind of behaviour is actively encouraged and in some it is discouraged, I suspect that java is one of the former.
Don't turn OOP into the point of your programming style on any project.
Don't use it at all when it's not appropriate.
When you don't use OOP, make sure you're using the right thing for the problem domain at hand.
When you use something programming paradigm other than OOP, make sure you use that correctly, too.
And that was this week's edition of Simple Answers To Simple Questions.