|
Boost Users : |
From: Roman Neuhauser (neuhauser_at_[hidden])
Date: 2006-09-12 09:45:22
# usenet_at_[hidden] / 2006-09-11 22:08:33 -0700:
> I've always been a big fan of the "don't let users create objects they
> can't really use" philosophy. So, in general, I don't like the idea of
> "empty" objects or two-phase construction. It's too easy for people to
> make mistakes. So, for example, if I were to design an interface for an
> event log that required a user-specified ostream on which to log events,
> I'd declare the constructor taking an ostream parameter (possibly with a
> default, but that's a separate question, so don't worry about it):
>
> EventLog::EventLog(std::ostream& logstream); // an ostream must be
> // specified
>
> I've slept soundly with this philosophy for many years, but lately I've
> noticed that this idea runs headlong into one of the ideas of
> testability: that classes should be easy to test in isolation. The
> above constructor requires that an ostream be set up before an EventLog
> can be tested, and this might (1) be a pain and (2) be irrelevant for
> whatever test I want to perform.
My take on (2) is that an object which provides operations that
don't depend on its state does too much. IOW such methods should be
static or another object's responsibility.
There /are/ valid cases of optional behavior, looks like that's not
what you're talking about.
> In such cases, offering a default constructor in addition to the above
> would make the class potentially easier to test.
As would moving the dissociated behavior elsewhere, no?
> Another thing I've noticed is that some users prefer a more exploratory
> style of development: they want to get something to compile ASAP and
> then play around with it. In particular, they don't want to be bothered
> with having to look up a bunch of constructor parameters in some
> documentation and then find ways to instantiate the parameters, they
> just want to create the objects they want and then play around with
> them.
What does "play around with them" mean? I mean, if the constructor
argument is essential for the object, the game is reduced to a list
of exceptions thrown from the objects' methods. If the argument
isn't essential for an instance's behavior, the presence of the
unrelated behavior in the class is suspicious.
> My gut instinct is not to have much sympathy for this argument,
Rhymes with "Me no like manual me want buttons me be pushing!" :)
> but then I read in "Framework Design Guidelines" that it's typically
> better to let people create "uninitialized" objects and throw exceptions
> if the objects are then used.
I'd like to see the rationale. What's the benefit?
Constructors provide an universally understood, statically enforced,
syntactically lightweight version of having no or parameterless
constructors and most/all method bodies preceeded with
if (!initialized_) {
throw new exception("This object is broken.");
}
That's somewhat like OOP in C, and FMPOV a huge step back.
I don't really see how this can benefit the library author who needs
to write all those if (!initialized_) checks or the users (including
testers) who need to ask themselves (and often dissect the code, the
developers have lost track of it long ago): "What combinations of
constructor arguments enable individual behaviors?" This hints at
the core problem, which is the explosion in the number of states
such program contains, and *that* hampers testability).
This is especially damaging in dynamic languages (or with unchecked
exceptions).
> In fact, I took the EventLog example from
> page 27 of that book, where they make clear that this code will compile
> and then throw at runtime (I've translated from C# to C++, because the
> fact that the example is in C# is not relevant):
>
> EvengLog log;
> log.WriteEntry("Hello World"); // throws: no log stream was set
>
> This book is by the designers of the .NET library, so regardless of your
> feelings about .NET, you have to admit that they have through about this
> kind of stuff a lot and also have a lot of experience with library users.
I haven't read the "Framework Design Guidelines" document, does it
contain any arguments in support of this guideline?
> But then on the third hand I get mail like this:
>
> > However, the SqlParameter class has six constructors. Only two
> > constructors create a sqlParameter object that can be immediately used.
> > The others all require that you set additional properties (of course,
> > which additional properties is unclear). Failure to prepare the
> > SqlParameter object correctly typically generates an un-helpful database
> > error when the SQL statement is executed.
Exactly my gripe.
> So I'm confused. Constructors that "really" initialize objects detect
> some kind of errors during compilation, but they make testing harder,
> are arguably contrary to exploratory programming, and seem to contradict
> the advice of the designers of the .NET API. Constructors that "sort
> of" initialize objects are more test-friendly (also more loosely
> coupled, BTW) and facilitate an exploratory programming style, but defer
> some kinds of error detection to runtime (as well as incur the runtime
> time/space costs of such detection and ensuing actions).
Defunct constructors are IMO a code stink. They're a way to rape OOP
(procedural code written in terms of classes is ugly and stiff).
Surely they ease testing once you lump two or more classes into one,
but they shouldn't be tolerated because such siamese twin classes
are a basic design mistake (tightly coupled classes) driven to the
extreme.
> So, library users, what do you prefer, and why?
I prefer using code that's easy to understand, and that means code
that has as few (nonproductive) states as possible. I dislike defunct
constructors both as a library user, author, and post-factum unit
test author, because in all those roles I need to keep track of
things the compiler / interpreter would cover for me.
The only situation I would like classes with defunct constructors
is if I wanted to ridicule their author by using unit tests to
demonstrate how easy it is to produce a pile of bugs using that
approach.
-- How many Vietnam vets does it take to screw in a light bulb? You don't know, man. You don't KNOW. Cause you weren't THERE. http://bash.org/?255991
Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net