Holub and OOP flavors

Just want to surface this link to everyone involved in the (rather large)
subthread of OOP flavors:

james_b posted it as a footnote but I thought people might easily miss it.

I read this 6-part series a couple of years ago and it really challenged the
way I thought about OO. While Holub is certainly “extreme” in his views, I
think he does make some very illuminating–and often convincing–arguments.
(It’s a long series, but very worth it IMO…)

What finally resolved it for me was reading some of the articles Bill
Venners wrote on artima.com about OOP. He said something to the effect of,
“OOP is for programmers, not computers.” In other words, the level of
success of a particular design is generally defined by how effective that
design is in managing complexity.

If your program has four simple datatypes with fairly inflexible semantic
definitions (let’s call them, oh, String, Fixnum, Array and Hash) and 99 use
cases involving those datatypes, it probably makes sense to have the
datatypes operating on the data. If, on the other hand, you have 99
arbitrarily complex datatypes that are very likely to change from release to
release–say you’re representing bank accounts once served by 40 different
legacy systems–and four very concrete use cases (withdrawal, deposit,
transfer, close), then it probably makes sense for your 99 types to have the
actions hanging off them.

OK, those were pretty bad examples… :slight_smile:

The point is, I don’t think the following should be the primary factors
that decide whether x.foo(y) or y.fooTo(x) is best:

  • x.foo(y) is more natural because it’s x that does the fooing.
  • In the Real World, you don’t tell a y to fooTo x, you just foo the y with
    x.
  • It just feels wrong, I don’t know why.

Instead I think the thought process should sound more like:

  • x is more likely to change in the future, so I would like to keep its
    implementation particularly opaque and its public interface particularly
    compact. Therefore I will choose x.foo(y).
  • Many classes are likely to have an “is-a” relationship to y, and I would
    like future developers to be able to provide their own implementations of
    fooTo, so I will choose y.fooTo(x).

The latter two questions may not be as intuitive as the former, but I
believe they are much more relevant if the goal of a design is to
effectively manage complexity.

Thoughts?

  • x is more likely to change in the future, so I would like to keep its
    implementation particularly opaque and its public interface particularly
    compact. Therefore I will choose x.foo(y).
  • Many classes are likely to have an “is-a” relationship to y, and I would
    like future developers to be able to provide their own implementations of
    fooTo, so I will choose y.fooTo(x).

The latter two questions may not be as intuitive as the former, but I
believe they are much more relevant if the goal of a design is to
effectively manage complexity.

That would be my first concern, but it’s also worth considering the
‘it makes sense’ feature as important. In my (limited ruby, but good
few years in maintaining other peoples scripts) experience, code that
doesn’t ‘make sense’ can sometimes be as hard to maintain as code
that has its methods in the wrong object.

But then I always have trouble reading other peoples code, especially
<no_flame_bait>Perl</no_flame_bait>. It’s frightening to think that
people you sit next to all day have thought processes that
alien to yours :slight_smile: That is the current seller to me on OOP scripting -
you get to agree on an interface and as long as Bob sticks to that, I
keep out of his implementation and he keeps out of mine.

···


I could dance till the cows come home. On second thought, I’d rather
dance with the cows till you come home.
– Groucho Marx
Rasputin :: Jack of All Trades - Master of Nuns

Just want to surface this link to everyone involved in the (rather large)
subthread of OOP flavors:
The point is, I don’t think the following should be the primary factors
that decide whether x.foo(y) or y.fooTo(x) is best:

I agree completely.

  • x.foo(y) is more natural because it’s x that does the fooing.
  • In the Real World, you don’t tell a y to fooTo x, you just foo the y with
    x.

There’s not many reasons to model as in the Real World (the one I can
think of being that it might make things easier to grasp), and anyway
the task at hand is building abstractions to solve a problem, not
replicating nature.

  • It just feels wrong, I don’t know why.

Depending on the experience of the developer this feeling might encompass
several problems he’s able to “sense” in some way. It’s up to one to
decide how much trust he places on his “sixth sense”.

Instead I think the thought process should sound more like:

  • x is more likely to change in the future, so I would like to keep its
    implementation particularly opaque and its public interface particularly
    compact. Therefore I will choose x.foo(y).
  • Many classes are likely to have an “is-a” relationship to y, and I would
    like future developers to be able to provide their own implementations of
    fooTo, so I will choose y.fooTo(x).

To sum up, I’d just try to ensure that changes don’t propagate throughout
the system, which is what “complexity management” is mostly about
(attempting to limit the growth of the problems you have to face as the
system grows). I guess this is strongly related to PragProg’s
orthogonality.

I’d not always follow “local rules” when deciding on a API or a class
hierarchy, but rather go for a “global optimum”, keeping in mind that my
goal is making something that will do V and W, with properties X and Y
(which probably include maintainability and expansion capabilities).

At any rate, IMHO a design should reflect the expectations of the devel.
about what’s likely to me modified/expanded in the future. Some of the
XP crowd would condemn this as “doing something unneeded”, but I see it
as a (rational) bet: if I know that the probability of myself needing X
is p(need X), the cost of doing Y if I got X is C(Y/X) and the cost if I
don’t do X is C(Y/not X), I’d always do X first even if I don’t need it
at the moment if p(need X) * C(Y/not X) > p(need X) * C(Y/X) + C(X).

···

On Wed, Sep 03, 2003 at 04:41:19PM +0900, Joe Cheng wrote:


_ _

__ __ | | ___ _ __ ___ __ _ _ __
'_ \ / | __/ __| '_ _ \ / ` | ’ \
) | (| | |
__ \ | | | | | (| | | | |
.__/ _,
|_|/| || ||_,|| |_|
Running Debian GNU/Linux Sid (unstable)
batsman dot geo at yahoo dot com

No, that’s wrong too. Now there’s a race condition between the rm and
the mv. Hmm, I need more coffee.
– Guy Maor on Debian Bug#25228

Just want to surface this link to everyone involved in the (rather
large)
subthread of OOP flavors:

http://www.javaworld.com/javaworld/jw-07-1999/jw-07-toolbox_p.html

james_b posted it as a footnote but I thought people might easily miss
it.

I read this 6-part series a couple of years ago and it really
challenged the
way I thought about OO. While Holub is certainly “extreme” in his
views, I
think he does make some very illuminating–and often
convincing–arguments.
(It’s a long series, but very worth it IMO…)

I read his articles after you posted your note. His ideas are
certainly extreme and show a marked misunderstanding of the proper way
to implement the MVC paradigm. For some reason he does a reasonable
job of explaining that objects are bundles of capabilities (or
responsibilities) then completely ignores that fact in presuming that a
Model object in an MVC world is simply a bundle of data with a bunch of
getter’s and setter’s.

Sure… if you design your model objects to violate encapsulation then
your system is not very Object Oriented (presuming, of course, that you
accept encapsulation as a tenet defining Object Oriented-ness). But if
your model, view, and controller objects are designed as objects,
following the proper definition of object, how could you reasonably
make the claim that the system is other than Object Oriented?

There are some valuable ideas in those articles… but some very poor
ones as well.

Scott

Joe Cheng wrote:

Just want to surface this link to everyone involved in the (rather large)
subthread of OOP flavors:

http://www.javaworld.com/javaworld/jw-07-1999/jw-07-toolbox_p.html

james_b posted it as a footnote but I thought people might easily miss it.

I will add it to my list of things to read. Near the top of the
list, as the list grows faster than it shrinks.

[snip]

The point is, I don’t think the following should be the primary factors
that decide whether x.foo(y) or y.fooTo(x) is best:

  • x.foo(y) is more natural because it’s x that does the fooing.
  • In the Real World, you don’t tell a y to fooTo x, you just foo the y with
    x.
  • It just feels wrong, I don’t know why.

Instead I think the thought process should sound more like:

  • x is more likely to change in the future, so I would like to keep its
    implementation particularly opaque and its public interface particularly
    compact. Therefore I will choose x.foo(y).
  • Many classes are likely to have an “is-a” relationship to y, and I would
    like future developers to be able to provide their own implementations of
    fooTo, so I will choose y.fooTo(x).

The latter two questions may not be as intuitive as the former, but I
believe they are much more relevant if the goal of a design is to
effectively manage complexity.

OK, I find this logic compelling.

This points up the fact that there is much more to maintainability
than just readability. (Although I’m a big fan of readability in its
own right.)

I guess in a situation like this, I’d try as usual to get the best of
both worlds. I’d probably have a set of front-end objects that behaved
intuitively, interfacing to a set of backend objects that were
implemented in a more maintainable way. That would introduce a kind of
extra layer of abstraction.

But I’m being vague here because I haven’t thought this through. Let me
just say that I like both sets of constraints. And perhaps the first
set should not be of primary importance, but I think they should run
a very close second. I wouldn’t write “weird OO” unless I really needed
to, just as I wouldn’t drop into C unless I really needed to.

This has been thought-provoking. If I am not careful, I may learn
something.

Hal

Joe Cheng wrote:

The point is, I don’t think the following should be the primary factors
that decide whether x.foo(y) or y.fooTo(x) is best:

  • x.foo(y) is more natural because it’s x that does the fooing.
  • In the Real World, you don’t tell a y to fooTo x, you just foo the y with
    x.
  • It just feels wrong, I don’t know why.

On these points, I wholeheartedly disagree.

If x does the fooing, I presume that x is part of a level of abstraction
already in-place. That is, if I can call ‘printf(stderr, “something”)’,
the I consider stderr to be part of an abstraction called “printing, but
to an error stream”. If stderr is part of a group of functions, all of
which were developed BEFORE my OO program came along, then to prevent my
program from a whole lot of non-OO code, I would wrap up stderr into an
object.

Therefore, that “* x.foo(y) is more natural because it’s x that does the
fooing” is, to me, almost exclusively the rule.

We’ve also already talked a lot about the difference between
string.print and device.print(string) and experience tells me that the
most useful, re-usable pattern is device.print(string), which is a
Real-World-imitating pattern.

Therefore, the statement “* In the Real World, you don’t tell a y to
fooTo x, you just foo the y with x” is a darn good reason to organize
objects a certain way.

Also, regarding developer habits: I think experience teaches a person a
whole lot that you can’t get out of a book; most of that experience
comes from “being there” and not “reading about it”. So, if I know an
excellent, excellent coder who shows me some code and can’t articulate
why it’s a good way to do something, I don’t reject his efforts on that
basis alone. His experience tells me there’s probably much more wisdom
in the decisions he’s made than anyone could reasonably articulate
without spending a fair amount of time reflecting.

Therefore, “* It just feels wrong, I don’t know why” is often a
perfectly valid reason.

Instead I think the thought process should sound more like:

  • x is more likely to change in the future, so I would like to keep its
    implementation particularly opaque and its public interface particularly
    compact. Therefore I will choose x.foo(y).

I find that “changing code” is not a criteria for design a set of
classes, but often I will break code which is decidely transient into
its own class so that unique implementations can be provided more
easily. So, I agree more or less.

  • Many classes are likely to have an “is-a” relationship to y, and I would
    like future developers to be able to provide their own implementations of
    fooTo, so I will choose y.fooTo(x).

Organizing classes according to their abstractions (devices, strings,
images, etc.), and by-task (printing, encrypting, etc.) will already
show “is-a” relationships.

I agree, you do have to design to allow developers to extend. You need
to pick out your inheritance lines, keep methods using the lowest base
class possible, etc. This is a finer point than was previously
mentioned. There are other issues, too. But they don’t override the
basics: natural organization, encapsulate your abstractions.
Unification of abstractions is another issue. Often you need to import
code that is utterly, shockingly different from your projects design
patterns.

But I find that, if I stick to Real-World patterns and look for my
inheritance lines, that’s everything. If my code base is filled with
classes that have natural relationships and are ordered into inheritance
lines that minimize change from one level of inheritance to another, I
find from there I can program just about anything I want, extend it
later as much as I want, and I’m happy as a clam.

The latter two questions may not be as intuitive as the former, but I
believe they are much more relevant if the goal of a design is to
effectively manage complexity.

Manging complexity is what it’s all about. If you can get the
complexity down far enough, there is no limit to what a project can acheive.

Sean O'Dell

Scott Thompson wrote:

I read his articles after you posted your note. His ideas are
certainly extreme and show a marked misunderstanding of the proper way
to implement the MVC paradigm. For some reason he does a reasonable
job of explaining that objects are bundles of capabilities (or
responsibilities) then completely ignores that fact in presuming that
a Model object in an MVC world is simply a bundle of data with a bunch
of getter’s and setter’s.

Sure… if you design your model objects to violate encapsulation then
your system is not very Object Oriented (presuming, of course, that
you accept encapsulation as a tenet defining Object Oriented-ness).
But if your model, view, and controller objects are designed as
objects, following the proper definition of object, how could you
reasonably make the claim that the system is other than Object Oriented?

There are some valuable ideas in those articles… but some very poor
ones as well.

I haven’t finished reading all the articles yet (I’m on the second).
However, I get the impression that he
doesn’t expect the ideas he presents to be used in all general
contexts. For example, if you’re designing
the standard library for some language, the String class needs to be
used in many different ways. However,
the only way to have the string able to visually display itself is to
couple String with a complete GUI
library. Each GUI library would have to have its own implementation of
String which displayed itself
in accord with the components of that library, and Strings from
different libraries wouldn’t be interchangable.
That’s not to mention that you might want to either display a string, or
display something that allows a
string to be inputted (is that a word?). Thus, giving String visual
display capabilities isn’t really the domain
his advice concerns itself with.

Rather, he’s talking about the design of object oriented programs that
do something. If you’re designing
a chess game, and have a Knight class, there’s no reason to make that
class interchangable with a
Knight class from a fantasy role-playing game. Thus, you know how the
Knight class will represent
itself in that context, and can make it specific to its intened context.

As another example, suppose you’re desgning a text editor. You could
have a text area that the
user enters stuff into, and then the program queries the text editor for
the underlying String and does
things with it. However, this isn’t very object oriented (he’d argue).
You’re using objects, but it’s
not much different from

str = gets
result = process(str)
puts result

Which isn’t really object oriented at all. What you really want is a
Text class that knows not only
how to display itself as a text area and accept user input, but knows
how to do everything that
your specific program might want to do with text. It’s the difference
between:

str = text_area.underlying_string
str.gsub(/blah/, “”)
text_area.underlying_string = str

and:

text_area.delete_word “blah”

Since you’re writing the class for text_area, you know you’ll need it.

As a nice summary, I’d say he’s talking more about implementing object
oriented applications, and
less about writing frameworks with which object oriented applications
will be built, if that makes any
sense.

I could be totally off, I suppose, as I’m sure Holub is much more
experienced than I am. However,
I can’t agree with many of his assertions unless I take them in the
light I discussed above. His
ideas don’t make sense in many cases if you’re trying to write general,
reusable, interchangable
libraries.

Somebody shoot me down if I’m wrong.

  • Dan

Hi Sean,

If x does the fooing, I presume that x is part of a level of abstraction
already in-place. That is, if I can call ‘printf(stderr, “something”)’,
the I consider stderr to be part of an abstraction called “printing, but
to an error stream”. If stderr is part of a group of functions, all of
which were developed BEFORE my OO program came along, then to prevent my
program from a whole lot of non-OO code, I would wrap up stderr into an
object.

When I said “x does the fooing” I didn’t mean x, the programming construct,
has method foo. For the purposes of this discussion I’m referring to
concepts that do not yet exist as programming constructs–starting from a
clean slate, as it were.

Therefore, that “* x.foo(y) is more natural because it’s x that does the
fooing” is, to me, almost exclusively the rule.

According to your assumptions, i.e. x is a part of a level of abstraction
already in-place, I totally agree. I also agree that if the abstraction
doesn’t fit your needs, you write an object adapter.

We’ve also already talked a lot about the difference between
string.print and device.print(string) and experience tells me that the
most useful, re-usable pattern is device.print(string), which is a
Real-World-imitating pattern.

Sure, that’s a simple example. I can think of situations where it would
make a lot more sense to have printee.printTo(device) when the printee is
more complex than a string.

To turn it around a little… consider Ruby’s blocks and iterators. Surely
they’re not a Real-World-imitating pattern? If you have a box of pencils
and you want to sharpen them all, you take them out one by one (or all at
once) and sharpen them. You don’t pass a pencil-sharpening module to the
box and tell the box to apply it to each pencil. Yet blocks and iterators
are tremendously useful and convenient.

Therefore, the statement “* In the Real World, you don’t tell a y to
fooTo x, you just foo the y with x” is a darn good reason to organize
objects a certain way.

It is certainly a factor. Just not the most important one, IMO.

Also, regarding developer habits: I think experience teaches a person a
whole lot that you can’t get out of a book; most of that experience
comes from “being there” and not “reading about it”. So, if I know an
excellent, excellent coder who shows me some code and can’t articulate
why it’s a good way to do something, I don’t reject his efforts on that
basis alone. His experience tells me there’s probably much more wisdom
in the decisions he’s made than anyone could reasonably articulate
without spending a fair amount of time reflecting.

Therefore, “* It just feels wrong, I don’t know why” is often a
perfectly valid reason.

And plenty of immature coders might also have intuitions that are not at all
correct. If a Dog is-an Animal, does that mean a DogList is-an AnimalList?
A lot of programmers would “feel” the answer is yes. The correct answer is
no.

I’m not saying that you or I should not go with our gut when we’re writing
code–none of us go through an internal discussion like this when we work on
each project, we just do it. I’m just saying if you’re looking for a set of
guiding principles for designing systems of classes, there are more powerful
ones than “not feeling right” (and really, I was specifically targeting the
“not feeling right” because the abstractions are incongruous to the natural
world).

Instead I think the thought process should sound more like:

  • x is more likely to change in the future, so I would like to keep its
    implementation particularly opaque and its public interface particularly
    compact. Therefore I will choose x.foo(y).

I find that “changing code” is not a criteria for design a set of
classes, but often I will break code which is decidely transient into
its own class so that unique implementations can be provided more
easily. So, I agree more or less.

I find that likelihood of future change is one of the most important driving
forces in all of my designs. Perhaps it is a reflection of the kind of
projects and companies I’ve been exposed to, but when I read a spec I
automatically think “What parts of this are most likely to grow or
change–probably right before the deadline?”.

  • Many classes are likely to have an “is-a” relationship to y, and I
    would
    like future developers to be able to provide their own implementations
    of
    fooTo, so I will choose y.fooTo(x).

Organizing classes according to their abstractions (devices, strings,
images, etc.), and by-task (printing, encrypting, etc.) will already
show “is-a” relationships.

If you don’t have a polymorphic fooTo method, then what difference does it
make if there are is-a relationship in place? (I’m making the assumption
that whatever fooTo does, it can vary enough from y-subclass to y-subclass
that you couldn’t simply pull data out using the y interface.)

I agree, you do have to design to allow developers to extend. You need
to pick out your inheritance lines, keep methods using the lowest base
class possible, etc. This is a finer point than was previously
mentioned. There are other issues, too. But they don’t override the
basics: natural organization, encapsulate your abstractions.
Unification of abstractions is another issue. Often you need to import
code that is utterly, shockingly different from your projects design
patterns.

But I find that, if I stick to Real-World patterns and look for my
inheritance lines, that’s everything. If my code base is filled with
classes that have natural relationships and are ordered into inheritance
lines that minimize change from one level of inheritance to another, I
find from there I can program just about anything I want, extend it
later as much as I want, and I’m happy as a clam.

I agree that most of the time, if obvious Real-World analogs are available
it is generally going to work out best to use them. The reason, I believe,
is because the Real-World abstractions are stable abstractions–whereas
less “natural” abstractions that happen to fit the spec’s 3 or 4 use cases
are less likely to withstand the use cases that will be added with the next
version of the spec, and so you end up having to rewrite the code that
modeled those abstractions.

But what happens when the Real-World analogs are not so clear? Compare
Holub’s visual proxy architecture to pseudo-MVC (or PAC or whatever he calls
it). Or how about designing non-blocking IO libraries, or modular RPC
mechanisms, or compilers. Trying to find real-world analogs to these
problems can be counterproductive, but the underlying principles of
complexity management and extension points still apply. So I think there’s
value in conditioning yourself to thinking in terms of the underlying
principles, and let the fact that your designs often resemble the natural
world simply be a nice side-effect–as opposed to making resemblance to the
natural world your goal.

The latter two questions may not be as intuitive as the former, but I
believe they are much more relevant if the goal of a design is to
effectively manage complexity.

Manging complexity is what it’s all about. If you can get the
complexity down far enough, there is no limit to what a project can
acheive.

Yeah, that was a half-rhetorical “if”… you might not be too worried about
managing complexity if you’re just trying to get a throwaway demo done for
Friday so you can spend the weekend with the kids. :slight_smile: