On 27/03/2012, at 10:14 PM, Steven Clarke wrote:
> Yes, you're right Richard. Our study was focused on languages like Java and
> C# (and importantly, as they existed around 2006/2007). So as you say, we
> wouldn't generalize the results to languages that have named parameters. We
> described the two design choices we evaluated at the start of the paper:
>
> "There are two common design choices: provide only
> constructors that require certain objects (a "required
> constructor"). This option has the benefit of enforcing
> certain invariants at the expense of flexibility. An
> alternative design, "create-set-call," allows objects to
> be created and then initialized."
There are actually three design choices, and I've seen the third
one too often for comfort. Please note that this is a *different*
choice from the one you endorsed:
Good Smalltalk design:
Provide a variety of factory methods with keyword arguments,
all of which provide fully initialised objects satisfying
the class invariant; such objects often need little or no
mutation afterwards.
Good Eiffel design agrees in every respect except 'keyword arguments'.
Good create-set-call design:
Ensure that the default 'new' constructor returns a fully
initialised object satisfying the class invariant and
offering meaningful default behaviour; such objects almost always
require adjustment to get them into the state you really want but
all states are meaningful.
IN ADDITION make sure that all 'initialisation phase' methods
can safely be called AT ALL TIMES.
Ensure that every public method is *tested* with a default-
initialised object.
Bad create-set-call design:
Don't think about class invariants.
Rely on default constructors that leave fields with default
values (0, nil, &c) that satisfy types but not invariants.
Allow objects to be named outside their class in partially
initialised states that require 'initialisation phase' methods
to be called before 'work phase' methods, but do not check
that this has been done.
Allow 'initialisation phase' methods to be called at any time
without checking it it makes sense, allowing even well-initialised
objects to subsequently be put into inconsistent states.
Much as I love Smalltalk, a lot of the code that I see using the
create-set-call pattern is actually doing the >bad< version.
Here's an example that took only 2 minutes to find.
In Pharo 1.1 (which is not the current version)
Url new
which is the equivalent of System.out.println(new Url())
raises an exception. Url new created an uninitialised object.
That's _almost_ fair enough: this is supposed to be an abstract
class, but it should have been caught in #new. Go to a concrete
subclass:
FileUrl new
also raises an exception, trying to print the elements of a nil
String.
> However, I don't think our result is unsurprising,
All I can say is that it didn't surprise _me_.
Compare for example
f = fopen(x, y); /*C*/
s := FileStream read: x. "ST"
s = new FileStream(y, x); //C#
Smalltalk: obvious what it does.
C: which argument is which? Compiler can't help.
C#: three different things it could be; the compiler
can tell them apart, but it's not so easy for people.
And when you get to
new FileStream(String, FileMode, FileSystemRights,
FileShare, Int32, FileOptions)
this is obviously going to be a lot harder for people to
read than
FileStream read: string rights: rights share: share
bufferSize: int32 options: options
If you *could* do
s = new FileStream()
.FileName(string)
.Rights(rights)
.Share(share)
.BufferSize(int32)
.Options(options)
.Open();
that would be a lot clearer.
And that introduces a fourth design pattern, which the paper did
not investigate, call it "initializer object".
The general scheme for Initializer Object is
class X has a static Maker() method
returning an instance of X_Maker().
X_Maker() has methods like
Facet(value)
returning the same X_Maker() object
and a completion method called something
likeOpen()
or Create()
that returns a fully initialised instance of X.
This requires a creation style like
s = FileStream.maker()
.FileName(string) &c as before
.Open();
> You've highlighted the core of the debate when, referring to the people who
> prefer the create-set-call pattern, you said " the idea of them writing any
> code that might affect my life or the life of anyone known to me is not one
> that's going to help me sleep at night ". This was also the initial reaction
> of many people inside Microsoft when they heard the results of our study.
>
> Our response in this debate has always been that different programmers
> require