Objects Should Be Composable
By Ramon Leon - 27 September 2006 under Databases, Programming
Sometimes I look back at my past programming mistakes, in order to gain better insight into my current understanding of what I consider current best practices. This often happens when I'm reviewing someone else's code, and I see my past mistakes in their code. I begin wondering what I learned to get over that hump, and I think about what's an easy way to explain that one thing, to someone who hasn't gotten over the hump yet. I try to make up little rules to live by. One of those rules is that objects should be composable. Sure, it's not an original thought, but it is one gained through much pain.
I've quite often run across magic objects. Magic objects are those that don't use constructors, or construct themselves, or configure themselves, or take things like hashes or data rows as a single parameter to the constructor, or write themselves to the database, or do any number of things that don't related directly to their core responsibility. I've heard justifications like "OO says the object should be smart", or "OO is about the object doing their own work", basically justifications about why they wrote something they way they did, but more importantly, insight into that thing they still don't know. And the problem with these approaches is always the same, some set of domain objects that are usable only within a very specific context and fail miserably when one attempts to reuse them somewhere else.
This has always seemed a symptom of rote memorization of some set of rules. Trying to learn something by remembering the patterns, but not understanding them. The solution is always the same, ask "why" should I follow this rule? If you don't know why the rule exists, you stand little chance of applying the rule in the correct context. When it comes to patterns, context is everything. The answer to almost any question, honestly, is "it depends".
There was a time when I was into anti-patterns. I'd try to learn by looking at the common mistakes people make when doing something new. But over time I've come to see anti-patterns, as an anit-pattern. Teaching people what not to do, is a very hard way to teach someone what they should be doing. Anti-patterns are akin to treating the symptoms of a problem, rather than treating the problem itself. Anti-patterns themselves are simply the obvious result, of a misunderstanding of some core principle. The trick is to fix that misunderstanding. One of those misunderstandings that I've seen over and over in the code of someone learning object oriented code, is the magic object.
Magic objects seem to be a symptom of programmers not understanding that the intention of constructors, is to make the object ignorant of where its data came from. The object shouldn't know it came from disk, or database, or configuration file, you should. Objects should never construct themselves, because doing so kills their composable nature. The whole point of object oriented programming is to take a bunch of composable objects, wire them up in a particular context, and let the virtual machine you just built, do its thing. If an object knows where its data comes from, or where it goes, it becomes tied down to that one context, literally, you can no longer pick it up, and use it somewhere else, because it's tied down. The reason you don't put a save method on a business object is because it would tie that object to a particular persistence strategy. That's something you can't know for sure ahead of time. The reason you pass in configuration data, is because you don't know ahead of time, where that data will come from. It might come from a web.config file today, and an app.config file tomorrow, and from hand crafted code the next day when you find out you need this object somewhere new, yet again.
Another thing often missed, is that objects are data. When an object is created, it becomes a new primitive type, and should be used as such. How many times have you seen email addresses, social security numbers, credit cards, addresses, and companies passed around as strings? How many times have you seen money passed around with doubles and decimals and pennies lost here and there because of rounding errors? Objects should be composable, that means using all your objects as primitives themselves, and building yet more objects out of little bundles of them. How many times in a program are you going to validate that email string, or credit card string?
For some reason, when people first learn OO, they don't put their objects on par with whatever objects are native to the programming language they're using. I can't tell you how many times I've seen one object, pass its id, to another object, only for that object to use the id to go find the original object again, by looking it up in the database. There's so much focus on the primitive types of the language, people fail to see how much extra work they're creating for themselves by always programming in those built in native types and not simply passing around their own business objects.
The backwards nature of the relational paradigm, children having keys to their parents rather than parents having collections of their children, leads to object models that don't compose well because the children become bound to the context of their parent. Sometimes you want this, most times you don't, and it's usually a mistake. Addresses are much more usable, if they don't know what customer, or what order they're attached to, else the same one can't be used for both. Nor does an address need to know all the customers it's attached to, it's almost always more correct to look at the customers addresses, than the addresses customers.
Relational databases are like politicians, they do everything in their power, to avoid have an opinion on how the data should be modeled. They want to allow random queries from any direction. This is OK for the database, but once you have the data in memory, into an object model, the model itself needs to be the exact opposite. It needs to be very opinionated about how things are done and who points to whom, because it's a model, it's supposed to be machine that can deterministically solve a problem. You have to make choices about how to access the data, these choices become the domain model. You shouldn't be able to solve the problem 10 different ways. Building a good model, is just as much about knowing what not to build, as what to build. Composable parts, mean independent parts, which naturally lead you down the path of good solutions, even when you don't know what that path is.
I feel a little silly every time I say it, but objects are supposed to be Legos, a bunch of small, very focused parts, each good at doing one thing really well, that you compose into larger solutions. Just like the Unix metaphor of many little stateless utilities wired together via pipes into a data stream, objects are meant to be wired together into a stateful virtual machine. The end result being that problems are solved quickly and easily via composition of existing parts. All paradigms, be it OO, functional, data flow, or procedural, work best when the programmer keeps composition of parts, first and foremost in his mind.
Comments (automatically disabled after 1 year)
I agree, I think unit testing code forces one to see coupling problems the code has. Composeable objects, by their very nature, are easily testable.
Tests can help turn the light on for some people, but once that light comes on... you don't need tests to write nice composeable objects. Not that having tests is ever a bad thing.
As far as pain goes, yes, I think we do all have to feel the pain ourselves. Pain is memorable, some of us can learn from a more experienced person, but most need to suffer a little bit themselves to "believe" what they're being told.
If you follow your line of reasoning - which I strongly encourage - you'll find that objects should not be anything but conceptual models designed for human spatial limitations and nothing do with modelling software (since it has an inherent cost as you seem to have figured out).
Once you cross that barrier, you'll find that the correction to software modelling already has a name - functional programming.
I admit my apparent "zealotry" in encouraging the use of a pure, monadic, lazily evaluated functional language (for similar reasons re: modelling software) - so I would recommend Haskell.
Gah, I just realised I'm on a Smalltalk forum - I'll probably get nailed for this comment.
LOL, nah, I like many languages, and I like functional programming as well.
Functions should also be composeable.
Of course there's the matter of whether state is better modelled by objects or monads, as a Smalltalker, you can guess my response to that.
What books/tutorials/articles should an OO newbie read to really understand how to write good OO stuff and not make the dumb mistakes? It seems everyone has a different idea of how to architect OO programs. Is there really a right/wrong way?
Personally, I think there is a right and wrong way, however, that's just my opinion. I've learned what I know from books, and reading great code.
I picked up many of the little things by reading a fantastic book by Kent Beck called "Smalltalk Best Practice Patterns", which despite the title, applies to far more than just Smalltalk. It's a must read to understand how to think about everyday programming and what little things simply must be done well to write good readable object oriented code.
Some more favorites were Refactoring, Analysis Patterns, and Enterprise Application Patterns from Martin Fowler. He's an awesome pattern sleuth and a great writer. Anything he writes is worth reading.
Then I read a fantastic book called Streamlined Object Modeling by by Jill Nicola, Mark Mayfield, and Mike Abney that really gets down and dirty about modeling business rules with the simplest possible set of patterns. Samples came in Smalltalk and Java, which had a nasty habit of showing just how ugly and verbose Java and its ilk really are, when stacked up with identical code in Smalltalk.
Domain Driven Design, by Eric Evans is also a great, if not extremely long read. This guy's done some serious thinking about code. His depth is amazing, and quite beyond me, but I still pick it up on occasion and try to grok more and more of his thinking.
I've also learned a great deal simply from reading the sources of Seaside, Magritte, and Scriptaculous. Lukas and Avi are fantastic programmers and I've learned a great deal about how good frameworks are written, from using and extending theirs. There's nothing like using great code, to teach you how to write great code.
These are the programs and books that have shaped my style the most, I'm sure others have different lists, and I've left quite a few out, but these are my favorites.
Very good article, I put it on the reading list of our interns.
TO MARK : The first book about OO should be Meyer's Object-Oriented Software Construction. It is about statically typed world but it is very very good. Quite provocative but the reasoning behind each choice (ex : when to use inheritance vs composition) is always clear and well thought. Design Patterns should come next I suppose and Refactoring third.
I purposely left Meyer's Object-Oriented Software Construction out of my list, because I think he's extremely long winded and overly technical. You should definitely read it, but it's not a beginner book.
Thanks for the book list. I just put two of them on order.
What about learning Squeak? It is very easy to get lost/confused in Squeak. Here's an example:
- Open up an image that has Seaside installed.
- Open a SystemBrowser and navigate to any renderContentOn: method (like WAStore renderContentOn:).
- Where does the "html" argument come from? How can I find out what class "html" is? In a static language, this is easy since the method signature has the type included.
Why is there a PackageBrowser and a SystemBrowser? I notice that the PackageBrowser has comments for the Objects (necessary when getting to know the architecture) but the SystemBrowser does not (at least by default). Is there a way to view Object comments in the System Browser? When do you use one versus using the other?
I realize that issues like these are relatively minor stumbling blocks for newbies like me and I can sense the power behind Seaside, but a bunch of these stumbling blocks in a row create frustration. Are there any videos demonstrating "typical development" in Squeak? The Lisp videos out there are inspiring for people wanting to get into Lisp. Where are the Squeak videos? How 'bout some screencasts Ramon?
Your blog is a must read for Squeak newbie developers wanting to get into Seaside development. Your writing style is clear and to the point. Please keep it up, and sorry for the rambling.
Thanks, glad you like the blog.
It's easy in Smalltalk as well, just type in "html inspect." in your render method, and you'll see an inspector on it. From there, you can see what kind of object it is, highlight the class name, click alt + b, and you'll be looking at a browser on the class. Be sure to read my Terse Guide to Seaside, where I simply tell you what all those things are.
Types aren't as necessary in a runtime system like Smalltalk, you have the live object there, just look at it. Static languages let you see what type an object "should" be, Smalltalk lets you see what the object actually is, and pick it up and play with it, send messages to it, things you can't really do in many other languages.
You're right, it'd be easier if you could see how Smalltalkers work, but I've never done a screen cast, maybe I'll look into it.
Oh, and personally, I never use the package browser, just the system, hierarchy, senders, implementors, and various utility browsers that help me navigate code better.
A couple of other books that might be of interest to you guys - one for Mark and one for Ramon.
The one for Mark is Object Design: Roles, Responsibilities, and Collaborations, by Rebecca Wirfs-Brock. It's useful for a newbie to OO because it helps you learn to think about what you should make into objects, what other objects each one should know about and what each type should do. It's not a coding book, it's more of a "how to think about the problem" book.
Ramon, have you seen Working Effectively with Legacy Code, by Michael Feathers. This book deals with converting a big, existing OO application that was built with pervasive dependencies of the kind you mentioned above to one that has eliminated the dependencies enough to build unit tests for everything. It looks at specific situation(with Java examples) in a similar way to Kent's Smalltalk Best Practice Patterns. I bought it while working on a large, 10 year old VA Smalltalk app that's deployed in production in corporate America, and is being improved and extended.
Ditto on Object Design, I have it as well, not sure why I forgot to mention it; it's a great book. As for Working Effectively with Legacy Code, it's already on my wish list. I've heard good things about it, but haven't bought it just yet.
"I notice that the PackageBrowser has comments for the Objects (necessary when getting to know the architecture) but the SystemBrowser does not (at least by default)."
When browsing using the System Browser with a class selected, click the question mark in the second top pane to see the class comments.
I'm a little late to the discussion, but I would second Lionel Barret's recommendation of Meyer's OOSC - the first edition is a classic in the OOP field, is extremely readable, and you will learn a lot from it. Of course, I don't know if the first edition is still available. IIRC, the second edition doubled in size, which would probably scare anyone (including me) away from reading it! :)
[...] Objects Should Be Composeable | OnSmalltalk: A Squeak, Smalltalk, Seaside, Web Development Blog (tags: programming interesting design) [...]
enjoyed this very principle statements of clear and sound object design! i can remember my own 'magic objects' and the hard way to leave them behind. i wonder if you always have to make your own experiances in order to recognize and understand the do's and dont's ... ;o)
another guide to avoid coupling to your surrounding infrastructure / context is test driven design. don't understand me wrog: you don't have to write your tests first (for me test driven is not implicitly test first), but keep testability in mind. i often ask myself 'how big would be the pain to test this class? ... can i mock the dependencies in an easy way?'. you almost come automatically to a design with low coupling and therefore to a better chance to reuse or combine your objects in new ways.
the only question that remains is if you also have to make your own experiances in order to 'feel' the pain should you write some tests for your classes ... :o)