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