[Seaside] Smalltalk design advice -
howtoimplement genericbi-directional pointers?
ramon.leon at allresnet.com
Mon Dec 24 07:39:08 UTC 2007
> Ramon mentioned "Class Responsibility Collaborator" models,
> or just "CRC cards" for short. That terminology is new to me
> and the idea is pretty cool. For the uninitiated, here's a few links:
> Bitmap scans of the actual handwritten CRC cards used in the
> creation of "HotDraw":
> Review of "Ecode", a CRC card design tool for Squeak:
This is one of the best techniques I've ever picked up from anyone, and it's
no accident those HotDraw CRC cards are scanned from hand written cards.
Ward Cunningham does the things he does for very good reasons. Don't use
software to do CRC design sessions, it defeats the purpose and utility of
the technique. 3x5 index cards are one of the best thinking tools ever
invented, they let you design and simulate the execution of an object model
in minutes and being able to quickly write, pick up, move around, role play,
and discard the objects is vital to the usefulness of the technique.
Programs and white boards aren't nearly as useful as a handful of index
cards. A little play time spent simulating several variations of your model
(cards are easy to throw away) will really solidify your ideas, finding the
good ones, and working through and tossing out the bad ones very cheaply,
before you've invested yourself in code. Code is much harder to throw away,
you're more emotionally invested in it (which is why you don't use software
> Let's talk again about the "Car" and "Tire" objects. I agree
> with Ramon that generally a Car needs to know about the Tire
> objects but the inverse is not important. A Tire can be
> placed on a different car and it shouldn't care. But what
> about a luxury car? Is that any different? Could be. The Tire
> might want to tell the car when its air pressure is low. For
Nope, the tire should simply announce its air pressure and make no
assumptions about who's listening. It's more composable that way. I might
have the tire rigged up to a crazy 3 wheeled trike that doesn't care about
air pressure, and just because the tire can give air pressure readings
doesn't mean it should only work on cars it was made for.
> Ramon, maybe you can clarify. You mentioned your Tsunami
> framework but in the same breadth alluded that we shouldn't
> use it? Or I misunderstand. In what situations *should* we
> use Tsunami or is it that you once felt it was necessary but
> later learned it isn't? In the Tsunami framework it mentions
> inspiration from the book "Streamlined Object Modeling" which
> has examples for both Java and Squeak. Would you say this is
> an excellent book for us new guys to read?
It is an excellent book, one of my favorites. It goes into great detail
about exactly how to maintain relationships and model rules in business
objects using a minimal set of patterns they found all rules could be
reduced to. Every example in the book is done in both Java and Squeak,
which ends up being quite an advertisement for Squeak when you see just how
terse it is compared to Java doing the same thing.
Initially I was so enamored by the book that I adopted the patterns and went
overboard, using the patterns even when they weren't necessary because I
wanted consistency. Here's an example, say I have a Person class, I'd do
the #name accessor like so...
self testName: aName.
set doSetName: aName.
"throw exceptions for business rule violations"
name := aName
This would give me an #x, #doSetX:, #testX: pattern of methods, each with a
particular purpose, the primary get/set accessors for normal use, internally
delegating to #textX: and #doSetX: which separate the testing and setting so
subclasses can easily override and extend them to add new rules and
behavior, or for allowing the running or bypassing of the rules when
necessary. I really liked how they'd thought everything out, the rigor of
their model and detailed explanations of every technique.
I extended the Squeak IDE with my own create accessors routines that
generated all this boilerplate every time I added accessors. What I learned
very quickly afterwards was all this stubbed out code, this scaffolding for
rules and subclass hooks, more often than not, went unused. As my models
changed and I needed to refactor, I also realized the maintenance burden all
this extra code created for me, it made things hard to change and hard to
find. I'd have 10 test methods but only two or three with actual rules in
them, the rest just empty stubs waiting to be used.
I grew tired of this, figured there had to be a better way, needed to rid
myself of the boilerplate code, and the need to maintain it. That's when I
build Tsunami, I reified those relationships and patterns as runtime objects
and hacked doesNotUnderstand to deal with all this, I'd only add testX
methods I needed and they'd be picked up automatically, I'd get
bi-directional references maintained automatically, I'd get collections
encapsulated automatically via #addX #testAddX: #doAddX: #removeX
#testRemoveX: #doRemoveX:, and for a time, I was happy with it.
Later, after working with them for a while, I started to realize that
working with purely runtime generated constructs was a royal pain in the ass
(a lot of Ruby folk are find this out as well), debugging was more
difficult, stepping through the code was more difficult, nice things like
symbol completion and lookup to navigate the code didn't work so well,
because I'd gone to far, eliminated all the code including instance
variables and accessors in favor of runtime meta level constructs. It
actually is possible to be too dynamic and too abstract.
I have a strong tendency to get sucked into solving the general case that
often distracts me from what I'm actually trying to accomplish (often
because I think it's more fun). I'm learning to resist this urge, I've
learned to appreciate simplicity and direct solutions much more. I still
use many of the patterns I picked up from streamlined object modeling, but
only when I actually need them. I use real instance variables and simple
ordinary accessors (tools work much better now), and I never stub things out
ahead of time anymore. I only write code that's actually needed, when I
actually need it. Stubbing boilerplate stuff out for the future is a big
waste of time and gives you so much velocity in one direction that it makes
change harder. Change should be easy, so write as little code as necessary
to achieve a desired behavior and no more.
I used to assume bi-directional references, now I consider them an exception
to the rule, something to avoided if at all possible. Direct references
flow down from parent to child, but events bubble up from child to parent.
That's how I see things these days anyway, who knows what I'll think next
More information about the seaside