The major problem with unchecked exceptions is that it is not
class consumer business.
What do you say when someone throws SomethingIsBrokenInsideOfMe
to you? I'd say "hey, take a vacation, fix yourself, you look
unhealthy". Is it not my business what exactly is broken (leg or
arm, whatever, it doesn't help to fix my issue). But it is fair
enough if library throws YouAreWrong exception in my face when
I'm screwed up.
When you make a typo in your Container implementation, it could
fail with something like \TypeError. It is "unhappy-path", isn't
it? Is it MisconfiguredServiceException? I don't think so. What
do you do then? You go and fix your implementation. What happens
if configuration is broken? You go and fix it as well. I don't
see much difference here.
MisconfiguredServiceException for me is an illusory solution for
"unhappy-path". You can't trust it. What does it exactly mean?
What if you make a syntax typo in your PHP configuration (with
arrays, let's say), should it raise the
MisconfiguredServiceException? Or it should bubble up
\ParseError? I mean, it is more like
ConfigurationIsTotallyScrewedUp rather than simple
MisconfiguredService. It's vague exception that means "something
is wrong with *them*". I'd rather catch ALL other
(non-NotFoundException) exceptions if I want to avoid program
crash for some reason. I can trust \Exception (or even
\Throwable). The meaning is simply same: "something is wrong with
them" and I don't really care what exactly is.
Can you please provide real-life example when you want to catch
MisconfiguredServiceException (excepting "I want to log it
differently", because it doesn't look real-life, honestly; and it
is too universal answer for any kind of exceptions)?
On Monday, November 7, 2016 at 5:27:58 PM UTC+3, Larry Garfield
wrote:
Container nesting is part of the spec, and a stated goal of
the spec, so it's a valid use case to consider.
True, there are many different ways that things can break.
Consistent and constructive handling of the not-happy path is
a critical part of spec development. See also: HTML5, the
majority of which is not new stuff but standardizing the many
different ways that browsers used to handle badly formed
HTML. It's a total mess because the unhappy path was never
well-defined, so everyone did it differently, so code broke
in a variety of inconsistent ways. My issue is that "throw
anything other than X" is not a consistent and constructive
handling of the non-happy path.
The biggest distinction I would draw would be between "you
asked for something that's not there" and "you asked for
something that's broken". Those are very different things;
one implies I screwed up, the other implies the configurer
screwed up. I can see the argument for not specifying a
separate exception for every possible way that the requested
service is broken (there are many, that's true), but a clear
distinction between those two broad categories ("missing" and
"broken") seems like a much better baseline.
In that case, I would revise my ask to defining two exceptions:
* NotFoundException extends ContainerExceptionInterface
(thrown if the requested service isn't defined anywhere)
* MisconfiguredServiceException extends
ContainerExceptionInterface (thrown if the requested service
is defined, but for whatever reason can't be instantiated)
And implementers are free to subclass the latter if they
choose, but must still have that exception flag on them. (I'm
flexible on the name, the one I have there is likely not the
best name.)
--Larry Garfield
On 11/05/2016 12:59 PM, Daniel Plainview wrote:
> A situation of "if your child container throws exception
X, you're required to catch it and turn it into anything
that's not X but is still Y" seems needlessly convoluted
You did it by introducing "child container", Container
contract doesn't have any child containers, this contract is
very simple and straightforward. By saying about child
containers, you mean that you know how internals of the
Container work, you know about "child containers", but you
shouldn't care about it when you want to use Container.
> but doesn't provide me as a developer sufficient debug
information. I'd potentially want to log differently
depending on which exception it is, but I can't do that if I
have no idea what the second exception is going to be; I
just know what it's *not* going to be, which means I'd need
a Pokemon-catch if I wanted to log it. That's what I am not
comfortable with.
I asked you above, what do you think about
DependencyArgumentTypeMismatchException,
MissingRequiredArgumentException and many other exceptions,
what makes them less important than
DependencyNotFoundException? They are all about wrong
configuration (or similar issues that indicates of internal
problems of Container, not user's failure).
I mean, you can say that "I'd potentially want to log
differenty DependencyArgumentTypeMismatchException and
MissingRequiredArgumentException, but I don't know how to
catch them". There are millions of reasons why container can
be broken, after all, you can't predict them all.
DependencyNotFoundException is unchecked, because it is not
client's problem, but internal issue of Container itself.
Service does exist, but configuration is wrong, client has
nothing to do with it, there is no sense to reflect it in
the interface.
On Saturday, November 5, 2016 at 3:38:06 AM UTC+3, Larry
Garfield wrote:
On 11/04/2016 06:27 AM, David Négrier wrote:
I'll try an analogy with Java.
In Java, there is a difference between checked and
unchecked exceptions. Checked exceptions are the
exceptions that should be catched by the user.
Unchecked exceptions are the exceptions for which it
makes no sense to catch them. There is no reason to
catch an "unchecked exception" because there is no way
the user can provide an interesting alternative
behaviour in the catch statement. So unchecked
exception should essentially always bubble up all the
way to the top of the application and trigger an
exception middleware.
For PSR-11, we deemed that the
NotFoundExceptionInterface was a checked exception
(because if the container does not contain the entry
you are looking for, the user can maybe try an
alternative behaviour like looking for an alias or
creating a default entry).
We also deemed that the
DependencyNotFoundExceptionInterface was an unchecked
exception (because it means the container is poorly
configured and there is little to do about it except
display an error message).
Finally, we think that checked exceptions should be
part of a PSR while unchecked exceptions should be out
of any PSR (because there is no need to standardize an
exception if you don't need to catch it).
Of course, there is no absolute truth here. We could
decide that the NotFoundExceptionInterface should be
"unchecked" (because you can always call "has" before
"get" so there is no reason this exception should be
catched). Also, since it boils down to "what valid use
case can I have to catch a
DependencyNotFoundExceptionInterface?", we could also
find a valid use case for catching
DependencyNotFoundExceptionInterface and decide it
should be part of the PSR. But so far, a quick survey
of frameworks out there has shown that no-one ever
catches the "dependency not found exceptions".
Also, Larry, you say:
/... based on the spec alone (no metadoc, no GitHub
threads) the following would be legal:
try {
$c1->get('a');
} catch (NotFoundExceptionInterface $e) {
print $e->getMessage();
// prints "Service 'b' not found"
}
/
This is not completely true. The spec states that:
/A call to |get| can trigger additional calls to |get|
(to fetch the dependencies). If one of those
dependencies is missing, the
|NotFoundExceptionInterface| triggered by the inner
|get| call SHOULD NOT bubble out. Instead, it should be
wrapped in an exception implementing the
|ContainerExceptionInterface| that does not implement
the |NotFoundExceptionInterface|.
/
So your code example is only valid if the container
decides not to follow the recommendation (we used
"SHOULD NOT" instead of "MUST NOT" to cope with
existing containers). Of course, we could also
strengthen the wording and write: /If one of those
dependencies is missing, the
|NotFoundExceptionInterface| triggered by the inner
|get| call MUST NOT bubble out./
This way, your code sample would be illegal (but it
would be harder for existing containers to implement
PSR-11). I have no strong opinion about the "SHOULD
NOT" vs "MUST NOT" idea so far. Your comments are welcome.
++
David.
See, I would disagree with dependency-not-found being an
unchecked exception. I think that's the fundamental
difference. A situation of "if your child container
throws exception X, you're required to catch it and turn
it into anything that's not X but is still Y" seems
needlessly convoluted, but doesn't provide me as a
developer sufficient debug information. I'd potentially
want to log differently depending on which exception it
is, but I can't do that if I have no idea what the
second exception is going to be; I just know what it's
*not* going to be, which means I'd need a Pokemon-catch
if I wanted to log it. That's what I am not comfortable
with.
I am generally very skeptical about SHOULD, and favor
MUST in nearly all cases by default. SHOULD should be
read as "you're allowed to be incompatible here", which
is a statement a spec should make as rarely as possible.
--Larry Garfield