June 9, 2008

What I Like About Object-Oriented Programming

This is the text for a lightning talk in-progress.
The question: "what do I like about object-oriented programming" As you might gather from the fact that this is a lightning talk, the real answer is "not very much". There's a lot to dislike about OOP: in the mid-80s it was sold as yet-another silver bullet that was going to save the world; and it was going to do that by encouraging code-reuse through the new mechanism of inheritance! These days, one of the first things an OOP advocate will tell you is that you should avoid using inheritance in favor of aggregation (which is to say you should go back to building things up out of sub-components just the way we always have). OOP advocates have always liked to say lots of mysterious things about the right way of using Objects (these days, they normally invoke "Design Patterns"), but it always looks like a lot of jargon chasing a small number of ideas (it might be compared to other 80's intellectual fads like "postmodernism"...).
But Objects are intuitive! But then, object-oriented code is bound to be more intuitive because it models the real world, which is also full of objects.

For example: Hold up a piece of paper put it on one side of the table, hold up a pair of scissors, put it on the other side of the table. "Scissors, cut!" (pointing at scissors), "Scissors cut!" (point at scissors, point at paper.). Shake head, and bang on the scissors, pause expectantly. "It's not returning an error object!"

The notion that Objects are a "metaphor" doesn't really work: if you start off with a rule of thumb like "identify the nouns", you're likely to end up with something that's over-designed and that under-performs. My opinion (at the moment at least) is that it's best to just think about classes as bundles of routines that need access to the same data -- and it's perfectly okay to invent new abstractions that have no analog to some physical object (typically you need to come-up with bogo-nouns to convince people these are really "objects": "This is the Wangifier Handler Manager class").
Namespaces The name of the game is namespaces and the goal of the game is to get the size of the namespace to be small enough to grasp, but not so small that it leaves you feeling trapped.

The one thing I really like about oop:
OOP is a widely accepted compromise between the wide-open style of using globals all over the place, and the tight, purely functional style of passing everything in and out explicitly.
historical digression: Structured Programming in Pascal When I was getting started in this business, the latest silver bullet was "Structured Programming", and we were all going to save the world by carefully drawing flow charts and implementing them in Pascal, writing pure functions with no side-effects. We'd all gotten disgusted with spaghetti code and action-at-a-distance, and we had sworn off global variables.

Examples of pure-functional programming (like every other programming methodology demo I've ever seen) always picked the really easy cases, e.g. something that takes a just few arguments and returns one piece of information. In actually trying to write code like this, I kept getting the funny feeling that I must not be designing it correctly, because I always had functions that needed something like a dozen ordered parameters passed in, and eight of them might be the same for nearly every routine.
avoiding globals in perl Much later, while getting into perl programming I was still trying to follow the doctrine of avoiding global variables, but I was running into similar problems involving passing a bunch of stuff around. At one point I hit on a simple solution for this: I could write routines that would accept a hash (I liked to call it the %bag) as an argument, and return a modified version of it, so any number of named arguments could be passed around that way... but is that any better than just having a global name space? What's the difference?

Actually, there is at least a small difference: rather than just letting everything talk to everything, it takes a conscious decision to bundle a piece of data into that %bag, and you don't have to have just one "bag", you can maintain multiple different hashes, appropriate for different sets of routines.

Recognize where that's going? I was "inventing" perl objects.
Summary So in summary:
  1. I don't like globals.
  2. I don't like having to explicitly pass everything.
So, I want a compromise, a boundary to keep things organized without forcing me to be too anal about it: And that is what OOP means to me.

I don't claim it's the only way, but at this point it is a familiar way that a lot of people understand (or at least think that they do), which makes it a good way of doing it.

A close competitor: procedural with package globals

problems with procedural modules But what about the good, old-fashioned procedural module? Perl's "global" variables are really just package globals, so modules with procedural routines using some shared package globals would seem to provide much the same benefits I claim for perl OOP modules.

I have a few objections to the package global approach:
  • If you use Exporter, then in the client code you can't tell at a glance where the routines were defined; in the OOP style the $obj-> prefix acts as a brief alias for the module name.
  • When debugging OOP code (at least for href-based objects) you can dump the shared data with a single step (e.g. in the debugger, x $obj).
  • With objects you can create multiple objects of the same class... in effect the "package global" approach is like an object class that only allows for class data.

Clumsiness of perl oop can be fixed

Some of the objections to perl-OOP (even in the traditional href-based style that I still favor) can be easily fixed using a few tricks of the language, and a system of code-generation via templates and so on.
Perl tricks
Separate object creation from initialization
I often use the cpan module Class::Base to get a new that automatically calls an init
Hash::Util lock_keys
My init routines use lock_keys to prevent the accidental creation of an undeclared hash field later (catches spelling errors, ala use strict 'vars')
AUTOLOAD
An AUTOLOAD routine can be used to automatically generate the accessor routines, if they haven't been explicitly created.
Emacs tricks:
template.el (via perlnow.el)
I use template.el templates (usually via my own perlnow.el package) to begin perl OOP modules with a framework deploying the above mentioned perl-tricks already set-up, waiting to be filled in
I've written some simple emacs commands (not yet made public) to insert stubs of new methods (sub definition preceded by a pod =item). I also have emacs commands to insert accessors, but don't usually use them, having switched over to the AUTOLOAD approach.

But why not use Class::Std or Class::InsideOut or Object::InsideOut or Moose or ...

There's an embarrassment of riches in the field of alternative ways of doing OOP in perl. I'm intentionally a late-adopter; hype and reality have very little connection where software is concerned. Each one of these approaches might be a better solution, but I won't believe it until I get around to experimenting with them personally.

My first impression: these new techniques are falling into the trap of not letting perl be perl. If I wanted to switch to a different language, it would be easy enough to do so...

They go through a lot of work to make perl's encapsulation bullet-proof rather than just advisory, but I suspect you could get 90% of the way there with a test that looked for improper accesses ( $obj->{ ).

Joseph Brenner, 26 Apr 2008