Moose, DBIx::Class, and the _new_ OO

scrottie on 2010-06-04T07:19:57

Simula introduced OO in the 60s. Smalltalk took it to its logical and pure extreme in the 80s. C++ brought it to systems programming and gave it the performance that only static optimized code can enjoy.

Really smart people wrote really smart code using really powerful, really futuristic features in C++... and created great big steaming piles of crap.

Efforts to create any reasonable operating system or database system using C++ failed. Telephone switches, previous infallible, failed spectacularly in cascade. Countless klunky, slow, buggy, bloated, unmaintainable Windows apps were written. Government agencies swore off of it in favor of other systems, even avoiding systems with OO at all, favoring APL or C. Windows programmers eschewed the complexity of C++ in favor of VisualBasic.

This created a bit of a paradox.

Was it the language responsible for all of these flawed designs and executions? Did adding objects, destructors, multiple inheritance, and so on, create a language that it just isn't possible to write clean code in?

For a long time, people thought so. "C++ is a messy language", they said, conflating the learning curve of the syntax of the language with learning to design objects and APIs that made sense.

Gosling and those behind Java seemed to think so. They threw away multiple inheritance, operator overloading, and a pile of other things. For a time, Java code was clean. So clean that they started teaching it in schools. Projects started demanding it and all across the world, people with no prior programming knowledge quickly ramped up on the language. They joined the workforce and wrote buggy, overly complex, unmaintainable code.

Simula's idea of OO was to provide a new abstraction to programmers with which to better model the interactions of the parts of a complex system. If a programmer could conceptualize the program in a way that drew parallels to real objects acting on each other, the objects and their interactions could all be better understood. In so much as that's true, there's nothing wrong with OO and no reason it should lead to unmaintainable code.

Much earlier, Donald Knuth wrote _Literate Programming_. Local variables and functions were the celebrated abstraction with which people were making huge messes. Knuth sat down and asked what was going wrong and speculated about how those problems might be avoided. It proved far easier to offer people a new abstraction that they haven't yet ruined than to get them to example the psychological, social, and technical forces driving them to write crap.

When the C++ guys realized that not only were they writing terrible code but that they were predictably and consistently writing terrible code, they too sat down and put some of their profound intelligence into asking why this was. This was the birth of the field of "Object Oriented Design and Analysis". There were a lot of things that people tried to do with objects that just didn't make sense and didn't work in the long run. There are a lot of early warning signs of failures to conceptualize things well and map them to objects.

The Java guys, determined not to repeat the mistakes of the C++ guys, adopted less analytical, more rule-of-thumb versions of this and called them "Design Patterns", "Code Smells", and so on. They fairly successfully marketed to their own ranks the ideas of studying design for design's sake rather than merely learning a language.

The Perl camp briefly and very superficially flirted with these ideas too but the glamour wore off and sitting down and just winging it and seeing where it goes is just so gosh darn much fun. History is boring. Of course, if you've read history, repeating it is boring.

Even this sort of backfired for the Java camp; understanding so well how to build certain types of abstractions, they went nuts building them all over the place and then managed to construct great big steaming piles of crap out on a much higher level -- they built them composed out of large scale constructs devised to avoid problems at lower levels.

While Java failed to convince everyone to actually studying the inherent follies and blunders novices make when designing software in hopes of avoiding them, it did introduce the world to a different idea: rather than using objects to model programs in terms of parts that interact, use objects to create massive APIs.

It's a big step backwards but it kind of wins in the "worse is better" department in that it's a lot easier for people to wrap their heads around than OODA. The language vendor created a whole lot of objects for you to use that represent different parts of the system they built, and if you base your application largely around using these pre-created objects, you're less likely to fuck things up. The win of objects became easily traversing large sets of documentation to figure out how things are done. If you get a Foo back from calling Bar.bar, you can instantly look up the docs for it and see if maybe you can weasel a Baz out of that to pass to Quux. This began the umpteenth documentation push which started with flowcharts, spent years on massive shelves of spiral bound printed pages from vendors, and, at some point culminated in programmers having bookshelves full of O'Reilly books before branching out into CPAN display module docs.

Everyone got tired of Java and gave up hope that any sort of cure for the worlds programming ills would emerge from that camp. Yo dawg, I heard you like MVCs so I put an MVC in your MVC! Even reading histories of how Java fucked things up and repeatedly missed the point is boring, and history is interesting. Java just smears boring all over stuff.

Meanwhile, the Python folks were writing much better software without nearly so large of sticks up their butts. No one knows how the Python people did it so I guess they just supposed that Python is magical and code written in it is clean. Likewise, no one understands why Ruby is so magically delicious, so like amateurs banging on a piano keyboard after the maestro gets up, they're trying their hand at it.

Back to present day and Perl. OO and OO abstractions mean something entirely different than what the Simula guys had in mind.

Now, when we sit down and create a system, we don't conceptualize the parts of the system and their interactions. We don't model the problem space using analogues to real world ideas and real world interactions. We don't search for the correct idiom.

Instead, we use APIs, and the more, the better. There is no User; instead, there are DBIx::Class ResultSet objects for a user or login table; there are admin screens that check things in that; there are Apache Response objects for talking to the user through; there are piles of Moose meta things that have nothing to do with hypothetical objects modeling a hypothetical universe but do a neat job of setting up neat things. Everything is abstracted -- except for the concepts the program is trying to actually model in its own logic. If there are objects definitively, unique, and authoritative representing things and nothing but that thing in a normalized SQL sense, then those objects are all thrown together in a big pile. A lot of the C++ OODA textbook's pages are concerned with finding idioms for how things interact to model those interactions. In Perl, we just pass stuff. And we're proud of our redneck selves.

In C++, and again in Java, and most certainly in Perl, we've shat on the grave of Simula. Smalltalk did not; Ruby had a blessed birth by virtue of drawing so heavily from Smalltalk. Python programmers tried hard to keep the faith even though OO is borderline useless -- nearly as bad as Perl's -- in that language.

If we were to sit down and try to represent our actual problem space as objects -- what the program is trying to do rather than the APIs it's trying to use -- we'd find that we're knee deep in shit.

This isn't one man's opinion. C++ programmers trying to claw their language's way out of its grave named parallel inheritance hierarchies as a thing to avoid; Java redubbed them as "code smells". If you have multiple objects each representing a user in some capacity, but not representing some limited part or attribute of the user, you have this disease. If you're using a bunch of abstraction layers to represent the User, for example, you have this disease.

Yet it has been escaped from. There are cures. You can have your objects representing things which your program deals with and have good abstractions to program in too. MVC frameworks aren't the cure but some people benefit from any restraint they can get.

And here it is, 2010. Everyone wants to learn the language syntax and fuck around with it, which is fine and great. But not everyone is here, reading this, being told that you can and will paint yourself into a corner in any language -- even assembly language -- and that it isn't the language's fault but especially: the language won't help you.

Perl programmers and Perl programs suck because Perl programmers think that rather than Perl fostering bad code, it'll help you dig your way out, with all of the magical things it can do. This is what C++ programmers thought. Perl programmers would be far better off if they actually thought that Perl fostered bad code and worked against this imagined doom.

So, let me say it: every programmer in every language, if he lets himself tackle large or ill defined enough tasks, will code himself into a corner. Not him nor perhaps anyone who follows him will be able to dig him out. The house of cards will implode. Trusting in abstractions of the language to save you will accelerate this process unless, just perhaps, you're privy to the lore. Books talk about how to design inheritance hierarchies that make sense. They talk about how to handle multiple inheritance and how to conceptualize things as objects. There's lots of benefit to modeling your problem space not as "objects" in the sense of the API you're using but in the sense of actors and props in a drama.

Like C++ programmers of yore, Perl programmers reliably, consistently build houses of cards.

As Ruby programmers start to build larger systems and have the time to grow things to that point, they'll discover that merely representing things as objects isn't enough, and that the interactions between objects are out of hand.

This isn't to say that I'm not susceptible to these same forces. I most certainly am. And I fear them.


Mostly Agree, but....

chromatic on 2010-06-04T15:42:38

... there's often a balance between what the language or current design or available libraries let you do and how best to model the particular view of the universe which best matches the problem you're attempting to solve. All metaphors are lies.

With that said, the internal implementation of an object system (especially a metaoperator protocol) won't match the problem domain of user code, because it has to model how objects and methods and roles and classes and prototypes and introspection and reflection work, and all of that in a sufficiently encapsulated, generic, and reusable way that doesn't bias too far away from good solutions in user code. There's less room to lie in those metaphors.

Re:Mostly Agree, but....

perigrin on 2010-06-05T00:40:31

All metaphors are lies.

I prefer to think of them as useful fictions that illuminate a particular aspect of the truth. It is why good fiction is more true than bad non-fiction.

With that said, the internal implementation of an object system (especially a metaoperator protocol) won't match the problem domain of user code

Nor should they. MOPs especially are design to model the problem domain of an Object System. I've had a lot of serendipity about this recently, I hope I can get it all into my talk at YAPC which is about exactly this.