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).