Boost logo

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