On 29/03/2012, at 4:00 PM, Brad Myers wrote:

> Three points Richard is missing are (1) the issue of what to do with the
> values are you are assembling them. One of the arguments for the
> create-set-call style that Richard is missing is that people don't like to
> have to invent a bunch of local variables to hold the parameters if they
> have to be computed in advance, or computed in a particular order.

I have a sticker from Brian Marick, for pointing to in discussions.
"An example would be useful around here."

Let's recall the Builder pattern.  There are actually two variables.
Builder:

        var x = DesiredResult.NewBuilder();
        x.SetThis(...);
        ...
        x.SetThat(...);
        var realObject = x.Build();

Configuration Object:

        var x = DesiredResult.NewConfiguration();
        x.SetThis(...);
        ...
        x.SetThat(...);
        var realObject = DesiredResult.Create(x);

In both variants, the helper object carries some or all of the
information needed to construct DesiredResults.  If it's only
some, the remainder can be provided in the final creation call.
A Builder actively takes charge of constructing the DesiredResult;
a ConfigurationObject is passive and DesiredResult is in charge.

Both variants of this pattern
 - let you compute properties in advance
 - let you compute properties in whatever order you want
 - never expose a DesiredResult object that is not
   fully initialised exactly the way you want it initialised
 - make it easy to make multiple similar objects, e.g.,

        var x = DesiredResult.NewBuilder();
        set most of the fields
        foreach (Datum in Data) {
            x.SetDatum(Datum);
            Collection.AddLast(x.Build());
        }

create-set-call gets the first two only.

What is the source level cost of using Builder rather than
Incomplete Initialisation?
        - one extra name for the builder
        - one extra line to set that up.
And as noted before, if you _don't_ want to spread out setting
the properties all over the place, you don't even need those:

        var realObject = DesiredResult.NewBuilder()
                .SetThis(...)
                ...
                .SetThat(...).Build();

> The users
> in the study preferred to set them directly into the object as they are
> calculated.

No, we don't know that, because the study did not have Builder as one
of the alternatives evaluated.  If people are never offered a choice
between A and B, you have no grounds for saying that B is preferred.
Maybe they _would_ have preferred B to A, maybe the other way round.

It's entirely possible that in the course of their training the subjects
of the experiment had never been shown the Builder pattern.  If they
*had* been given a little training in it, _maybe_ they would have
preferred that to Incomplete Initialisation.  We just can't tell.

I repeat: I am NOT attacking the study.  I've only ever tried to do one
software engineering experiment and it completely failed to address the
question I was interested because the subjects responded far more
strongly to "this is black and white on paper rather than syntax-coloured
on a screen like I demand it to be" than to the differences I wanted them
to look at.  This kind of stuff is HARD.  Nor do I question the results
of the study.

What *does* bother me is the idea of extrapolating beyond what the
study can legitimately claim.  In particular, the study FOR GOOD REASON
tells us nothing about user preferences for
- C# 4.0 optional and keyword parameters in constructors or for
- the Builder pattern
because those alternatives were not part of the study.

Oh, just in case someone else should raise a red herring, I should point
out that a Builder class can be automatically generated from the signature
of a constructor-with-named-and-optional-parameters and semi-automatically
from a brief annotation on another constructor.  So the use of a Builder
need not imply non-trivial additional coding effort to define it.

> Another issue (2) reported in the article is that the assumption that having
> the parameters required at object creation time assures that the object is
> always valid and that the implementer can then avoid checking things is
> flawed, because programmers send in invalid values, or send in valid values
> that become invalid later.

I didn't comment on that because I found it incredible that anyone would
make such an assumption or should assume that it was required to support
full initialisation.

ANY values coming in from an untrusted source must of course be checked.

In fact the argument goes the other way, because there is no shortage
of constructors where the parameters have to be checked *together* and
cannot be checked one at a time.

Sticking with my FileStream example, and using an imaginary FileStreamBuilder
because you *can't* change the settings after creation,

        var b = FileStream.NewBuilder();
        b.SetMode(FileMode.CreateNew);
        b.SetRights(FileSystemRights.ReadData);
        var s = b.Build("foo/bar.txt");

The two b.Set* calls are separately sensible.  Neither can be rejected,
not even the second one, because it might be followed by another call
to b.SetMode.  It's only at the point where b.Build is called that it
is possible to check the parameters for consistency.

This is, of course, an argument *against* the create-set-call style.
For the same reason that the call to b.SetRights() above cannot be
rejected, neither could a similar call to s.SetRights() -- if it existed.

> So the object must always be checking its fields'
> values at runtime anyway.

Why?  One of the reasons for Configuration Object is that quite often
initial parameters are *only* needed at the point where the object is
created, not during the operational phase.  Complete Initialisation,
Builder/Configuration Object, none of these require the object to store
forever information needed only during setup.  Create-set-call *does*
require the object to store such information forever.

Switching over to Smalltalk, consider 

        FileStream write: 'foo/bar.txt'
                   mode:  #create
                   check: true
                   type:  #text

Once the object has been created, the 'type' parameter is reflected
in the class of the result, but the other fields are stored *nowhere*.
The object doesn't check its fields' values at run time because they
AREN'T THERE to need checking.

Some _other_ information is there, but you can't change it, so that
doesn't need to be checked either.  That -is-, after all, the main
point of OO: encapsulation so that you can trust your private data. 

> So why not ALLOW users to create the object empty
> and fill it up as values are calculated.
> 
>       o = new Obj(n: NIL; size: 0, name: "")
> 
> or
> 
>       o = new Obj(n: param1_val, ...);
>       destroy param1_val;
>       use o;

I'm not quite sure what this is supposed to be an example of.
An object should normally share private data with other objects
*only* when the data in question are read-only (like strings).
Sharing mutable data is a well known breeding ground for bugs,
and I for one am heartily glad that FindBugs warns about it in Java.
If param1_val *could* be destroyed, I would expect the constructor
for Obj to *make a copy* so that its own copy is always safe.

What you seem to be saying is that dangerous coding practices
are even easier if you use create-set-call, but doubtless I am 
misunderstanding you.

A more realistic example would be good.
> 
> Finally, (3) the article (as well as our similar articles), discuss WHAT
> REALLY HAPPENS in the field, and how people think, which is only somewhat
> influenced by the particular language features. Thus, I disagree that the
> study is not relevant to C#4.0 or other languages.

It is not relevant because it DID NOT TEST something that can now be
done in C#.  C# is now a BETTER PROGRAMMING LANGUAGE with new possibilities
for writing clearer code.  API designers ought to be allowed to exploit
them, not shackled to the limits of the past.

In particular, I really really REALLY look forward to the parameter
names of the constructors being published as part of the interface
so that for example
        new String(c, Length: n)
can be used,making it really obvious to even a hasty reader.

Oh yeah, there is of course the point that naming issues and API design
need to be judged more on the ease of *understanding* code than on the
ease of *writing* it.




-- 
The Open University is incorporated by Royal Charter (RC 000391), an exempt 
charity in England & Wales and a charity registered in Scotland (SC 038302).

Reply via email to