Should a Perl constructor return an undef or a “invalid” object?

后端 未结 4 1711
南旧
南旧 2021-02-14 10:06

Question:

What is considered to be \"Best practice\" - and why - of handling errors in a constructor?.

\"Best Practice\" can b

4条回答
  •  囚心锁ツ
    2021-02-14 10:22

    First the pompous general observations:

    1. A constructor's job should be: Given valid construction parameters, return a valid object.
    2. A constructor that does not construct a valid object cannot perform its job and is therefore a perfect candidate for exception generation.
    3. Making sure the constructed object is valid is part of the constructor's job. Handing out a known-to-be-bad object and relying on the client to check that the object is valid is a surefire way to wind up with invalid objects that explode in remote places for non-obvious reasons.
    4. Checking that all the correct arguments are in place before the constructor call is the client's job.
    5. Exceptions provide a fine-grained way of propagating the particular error that occurred without needing to have a broken object in hand.
    6. return undef; is always bad[1]
    7. bIlujDI' yIchegh()Qo'; yIHegh()!

    Now to the actual question, which I will construe to mean "what do you, darch, consider the best practice and why". First, I'll note that returning a false value on failure has a long Perl history (most of the core works that way, for example), and a lot of modules follow this convention. However, it turns out this convention produces inferior client code and newer modules are moving away from it.[2]

    [The supporting argument and code samples for this turn out to be the more general case for exceptions that prompted the creation of autodie, and so I will resist the temptation to make that case here. Instead:]

    Having to check for successful creation is actually more onerous than checking for an exception at an appropriate exception-handling level. The other solutions require the immediate client to do more work than it should have to just to obtain an object, work that is not required when the constructor fails by throwing an exception.[3] An exception is vastly more expressive than undef and equally expressive as passing back a broken object for purposes of documenting errors and annotating them at various levels in the call stack.

    You can even get the partially-constructed object if you pass it back in the exception. I think this is a bad practice per my belief about what a constructor's contract with its clients ought to be, but the behavior is supported. Awkwardly.

    So: A constructor that cannot create a valid object should throw an exception as early as possible. The exceptions a constructor can throw should be documented parts of its interface. Only the calling levels that can meaningfully act on the exception should even look for it; very often, the behavior of "if this construction fails, don't do anything" is exactly correct.

    [1]: By which I mean, I am not aware of any use cases where return; is not strictly superior. If someone calls me on this I might have to actually open a question. So please don't. ;)
    [2]: Per my extremely unscientific recollection of the module interfaces I've read in the last two years, subject to both selection and confirmation biases.
    [3]: Note that throwing an exception does still require error-handling, as would the other proposed solutions. This does not mean wrapping every instantiation in an eval unless you actually want to do complex error-handling around every construction (and if you think you do, you're probably wrong). It means wrapping the call which is able to meaningfully act on the exception in an eval.

提交回复
热议问题