Thursday, June 11, 2009

What's Wrong with OOP

Proponents of Object Oriented Programming feel the paradigm yields code that is better organized, easier to understand and maintain, and reusable. They view procedural programming code as unwieldy spaghetti and embrace OO-centric design patterns as the "right way" to do things. They argue objects are easier to grasp because they model how we view the world. If the popularity of languages like Java and C# is any indication, they may be right. But after almost 20 years of OOP in the mainstream, there's still a large portion of programmers who resist it. If objects truly model the way people think of things in the real world, then why do people have a hard time understanding and working in OOP?

I suspect the problem might be the focus on objects instead of actions. If I may quote from Steve Yegge's Execution in the Kingdom of Nouns:

Verbs in Javaland are responsible for all the work, but as they are held in contempt by all, no Verb is ever permitted to wander about freely. If a Verb is to be seen in public at all, it must be escorted at all times by a Noun.

OOP focuses primarily on the object and expresses actions in terms of the object's abilities. A airplane object can be flown (Airplane.fly()). A door object can be opened (Door.open()). But we really don't view the world in terms of objects and what actions can be done to them. It's backwards. We view the world in terms of ourselves and our abilities. We are the ultimate object. (And no, I don't mean a God object.)

Imagine you are returning from a trip to the local flower garden. Would you say "I smelled the flowers" or "The flowers were smelled by me?" Now you want to tell a friend to go and smell the flowers. Would you say "Go and smell the flowers" or "The flowers must be smelled by you?" When we convey instructions, we give them in terms relative to ourselves. What is programming but conveying instructions to a computer process how some sort of work should be done on our behalf.

How to make a Peanut Butter Sandwich:
  • Get Jar of Peanut Butter
  • Get Loaf of Bread
  • Get Knife
  • ...
All of these things (jar of peanut butter, bread, and knife) can be thought of as objects.
class PeanutButterJar extends Jar ...
Express them as such and they take on methods.
PeanutButterJar.open()
Knife.spread(PeanutButter, Bread)
Whoa! In my world, peanut butter doesn't do anything but sit there and taste yummy. And I'd start looking for a good exorcist the moment a knife starts performing actions all by by itself. A more realistic transcription of the instructions would be:
You.open(PeanutButterJar)
You.spread(Knife, PeanutButter, Bread)
Specifying You as a universal object would seem rather redundant as the scenario progressed, so a good language designer would remove the need to expressly identify it. This would yield
open(PeanutButterJar)
spread(Knife, PeanutButter, Bread)
which starts to look vaguely procedural.

The truth is, procedural and OOP paradigms languages express the complexity of their problem space in different ways. Procedural code is flat and wide with functions. OO code is hierarchical with inheritance. OO-code is not inherently better organized than procedural code merely because it is encapsulated in objects. A reusable library can consist of functions just as easily as a collection of objects.

The way some OOP languages (like Java and C#) force objects on the programmer borders on the absurd. If I'm writing a library of reusable code that needs to maintain its own state, then of course writing classes with proper encapsulation and dating hiding makes sense. On the other hand, If I'm generating a web page with some data stored in a database, then some procedural code and a handful of function calls makes more sense. One of the things I like about PHP so much is that it allows the programmer to decide which paradigm is best suited for the task at hand.

Sadly though, that decision isn't left to the programmer who has been tasked with developing and maintaining a system. Management can tend to focus too much on buzzword compliance. A procedural programming language designed today would never receive wide-spread adoption if it didn't offer some sort of OO construct despite both paradigms having produced successful libraries and applications. And the programmers that don't learn to think beyond themselves will be unfairly left in the dust.

9 comments:

  1. When looking at PHP in particular, I would venture to say that procedural programming will be on better footing than OOP come 5.3. Classes will still be bound to names, whereas functions are now first-class citizens with the addition of support for lambda functions and closures. This coupled with the ability to maintain state via static members makes them more portable. Horizontal reuse is accomplished by functions simply calling/composing other functions without the constraits of single inheritance. I definitely think the procedural paradigm deserves reexamination both in general and in the enterprise. It should not be abandoned in favor of OOP simply because it came earlier.

    ReplyDelete
  2. I disagree with your comments on people understanding OOP. I think that people (and developers really) get too hung up on the actions of an object more than the object. They get a method of an object to do a certain action, and in the end result it works fine, but add extra code that complicates things like encoding, encrypting, or sorting which could be handled by another object greatly reducing method size and adding readability to ones code

    ReplyDelete
  3. I would put forth that many developers using OOP are "doing it wrong." Textbook examples of OOP tend to focus on modeling literal, real world "objects." This is great for learning the basics of OOP, but in practice it is usually quite silly to model objects directly on "real" objects. I would suggest reading Eric Evans' Domain-Driven Design book. The book has lots of great advice on how to create useful models using OOP. The book also talks about how OOP is not the only way to create useful models.

    ReplyDelete
  4. I'd agree with bradely-holt. Most people do OO wrong. I am not sure if that's because it's taught wrong, or just that it's hard to do right.

    Java is a great example of OO gone wrong. The wrong patterns are used and there is a class explosion of heirarchies and dependencies. However, OO doesn't dictate that outcome.

    When I started programming for the Mac about 5 years ago I saw OO in a new light, one that was much flatter and used patterns like delegation much more often to keep coupling down. But frankly OO and procedural are really no different; both have functions that mutate state, it's only a matter of where the state is kept. Of course that's oversimplifying a bit, but there is a lot of truth to it.

    ReplyDelete
  5. See, the real benefit of OO comes from encapsulation and inheritance structures. This generally (if not done poorly) leads to more flexible, readable and maintainable code.

    Of course, with closures and such we're be able to introduce some of the flexibility into the procedural world, but that won't really help for readability or maintainability if that's the *only* approach you're using for altering behaviour of existing methods, etc.

    The 'real world' object relationships are always up for debate (as you're doing with the knife/peanut butter example), but they're usually adequate for what we all seem to need them to do..

    ReplyDelete
  6. Misintegration is the essence of object-oriented design. Kantian subjectivism is the conserving principle that governs the gravitation of OO anti-concepts into a mutually reinforcing system.

    One example of OO misintegration is the basic problem decomposition method of OO. The class-instance pattern doesn't follow the whole-to-parts hierarchy that is universal for organic beings.

    An organic being such as an animal can be decomposed naturally in a whole-to-parts hierarchy - from the whole being to organs to subsystems to elementary parts - and the form of each part naturally implies its function.

    A machine is more or less organic depending on how well its overall form implies its function, and how well the forms of its constituent parts imply their the respective functions.

    An example of an organic machine is an airplane. At the other extreme would be the class of Rube-Goldberg machines, where accidental connections are emphasized and purposeful connections are minimized for the overall goal of amusement. The principle of integration for a Rube-Goldberg machine is the opposite of that used to build an airplane, it is anti-organic in order to be a physical joke.

    The OO premise that all software problems should be expressed in terms of classes and instances is an arbitrary assertion of form over function that necessarily negates organic design on principle. OO provides the license for building whimsical Rube-Goldberg machine software instead of naturally determined, teleologically driven, purposeful implementations.

    OO isn't the only anti-organic software methodology -- all subordination of function to arbitrary form entails a negation of organic design.

    ReplyDelete
  7. Great article!

    The PeanutButterJar example is spot on. It's great to see an example of Procedural application opposed to OOP, that isn't blatantly wrong or done thoughtlessly done- in a cheap cop-out attempt to trumpet OOP.

    ReplyDelete
  8. Very intelligently written.

    I got a good laugh out of the self-articulating knife exorcism, lol.

    Fantastic.

    Peter.

    ReplyDelete
  9. There is one problem. The developer smashes the peanut butter jar over the table to open it instead of turning the lid. This is why we left procedural programming to begin with. The peanut butter jar isn't opened correctly because every Tom, Dick, and Harry will be smashing it over counters, desks, and each others' heads just to get some dang peanut butter.

    Don't think of OOP as a physical action that YOU force to happen. We're not English teachers and we're not writing sentences. Think of it as giving voice commands. "Peanut Butter jar, open." And it opens. "Car door, Close." "Car, drive."

    Automation is key.

    I would love to see you write Me.Read(SomeOnlineArticle) and the prompt comes back saying, "I'm waiting for you to read to me."

    ReplyDelete