About KCP and automatic initialize
Richard A. O'Keefe
ok at cs.otago.ac.nz
Fri Sep 12 03:19:34 UTC 2003
"Andreas Raab" <andreas.raab at gmx.de> wrote:
But then your example seems off. You are using the terms "correct" (as in:
correct for the year 1994) and "usable" (as in: adhering to the class
invariants) interchangeably.
Not at all.
When I talked about a TaxRule object being "usable" or not,
I meant the simple obvious thing that "TaxRule new rateFor: 20000"
bombs. Here's the class invariant (and it's a pity that Smalltalk
gives you no obvious place to put it:
"#initialise DOES establish this:"
(breakPoints isKindOf: SortedCollection) and: [
"it does NOT establish this:"
(breakPoints notEmpty) and: [
"it does NOT establish this:"
(breakPoints first = 0) and: [
"it DOES establish this:"
(rates size = breakPoints size)]]]
This has NOTHING WHATSOEVER TO DO WITH 1994!
Unless this class invariant is true, #rateFor: can't do anything
sensible.
I didn't _quite_ use the term "correct" interchangeably with "usable".
"Usable" meant "not going totally insane". "Correct" meant "working
the way you'd expect a tax table to work. This adds one more conjunct
to the class invariant:
(all the above) and: [
(2 to: rates size) allSatisfy: [:i |
(rates at: i-1) < (rates at: i) "or maybe <="]]
Once again, NOTHING in any of this has any reference whatsoever
to any particular year or any particular table.
In fact, I *never* used the term "correct" to mean "correct for 1994".
I am flabbergasted that someone could so misread what I wrote.
Good heavens, I gave an example of creating one concrete object,
showing how *ANY* table could be passed, and somehow this has been
read as if that was the *only* table that could ever be accepted.
In the absence of this goal of "client specific correctness" the class
invariants are all you can really shoot for.
*RIGHT*. And *THAT* is what I meant by "correct": is the class invariant
required for the other methods actually established by the #initialize
method or not?
And as long as these are preserved (either by initializing
TaxRule to 1994, to Date today year, or to some arbitrary
identity/null mapping) it largely doesn't matter how exactly you
initialize an object - you will get back an object which adheres
to class invariants which is the only thing you can (and should)
rely on if you have a general TaxRule and not a specific one
like TaxRule1994.
We are in vehement agreement, except for TaxRule1994, which is a figment
of your imagination.
Indeed, this is precisely my point.
This is a case where the #initialize method DOES NOT establish the
class invariant. My whole argument here is that putzing around with
#initialize has as its point to make it easy for people to use
#initialize without thinking about it, and that we shouldn't be doing that.
In this particular case, it seems to me that NO #initialize method could
do a proper job. Oh, we can initialize the object to a sane state:
breakPoints := #(0) as OrderedCollection new.
rates := #(0) copy.
That _would_ establish the class invariant. However, not being familiar
with US tax tables, I don't know whether it would give the right result
for any actual tax table; you would want the actual rate for income 0
to replace rates at: 1. Which leaves us right back at the point where
there is an object which *looks* initialised but shouldn't be used until
you've kicked it a bit from the outside.
I'm also concerned about things like Point new.
I'd be happier with
Point
class category: 'instance creation' methods:
new
self shouldNotImplement.
x: x y: y
^self basicNew setX: x setY: y
r: rho degrees: theta
^self basicNew setR: rho degrees: theta
Why? Because Points are not supposed to be changed after they are
created. There is no #x: method, no #y: method, no #rho: method, no
#theta: method. The #setX:setY: and #setR:degrees: methods are in
the 'private' category. So what good does it do someone to be able
to call (Point new) and get an object they _can't_ fill in? Would
having Point new call #initialize and maybe set x and y to 0 make it
any better? Here again we have a class where
- There is a class invariant
(x isKindOf: Number) and: [y isKindOf: Number].
It isn't stated anywhere, but most of the methods won't work
if it isn't true. It isn't checked on instance creation either.
- sending #new to the class produces an object which is
either *un*initialised (as currently) or *wrongly* initialised
(if #new sent #initialize and #initialize plugged in any specific
pair of numbers)
- programmers ought to be using a completely different instance
creation method which can be given all the information needed to
establish the class invariant
- especially if the instance isn't really intended to be changed in
normal use.
Don't get me wrong (again). I'm NOT saying that #new is always the wrong
thing to use. It's very often exactly right. But it is also very often
wrong. I'm not saying that calling #initialize is always wrong either.
Very often it's right. It all depends on how much you have to know to
honestly establish the class invariant.
What I think would be useful here would be having the system pop up a
message at some point "I see that your instance creation methods rely
on the default sending of #initialize. Are you sure that your class
invariant can be established without providing some more parameters?"
or something along those lines. From looking at my and other people's
Smalltalk code, I really do see relying on a default #initialize as
a "bad smell" that a Smalltalk lint checker should warn about.
More information about the Squeak-dev
mailing list
|