I think we are the heart of the disagreement.  I do not think that "someone added an enum constant" is necessarily a classpath insanity thing.  Its just as likely to be a garden-variety "you made one assumption over here and another over there" within the same codebase.  (And, we've told people for years that its OK to add enum constants.)

I think its at least as likely that this is in the same category as any other assumption we make about code we call.  Like "this method never returns null".  Or "this method says it returns Object, but I know it always returns String, so I'll cast to String."

So, it seems that the purpose of this exception is not to cast blame on the classpath, but to pinpoint the erroneous assumption. Maybe Y shouldn't have gotten new enum constants.  Maybe X made bad assumptions about the future maintenance trajectory of Y. Either way, X and Y have to get together and get their story straight.  So the exception should broker that meeting.  And its seems at least as likely as not that X and Y are the same maintainer.

If you got a stack trace that said:

    UnexpectedEnumValueException: surprising enum constant TrafficLight.BLUE; expected one of RED, YELLOW, GREEN
        at: MyClass.switchOnTrafficLightColors (line 666)

or

    UnexpectedSealedTypeMemberException: surprising subtype Blue of sealed type TrafficLightColors, expected one of Red, Yellow, Green

doesn't that meet your requirement?  You look at the exception, and it says that a value was found that violated an assumption in your code.  It could either be the fault of the enum maintainer or of the switch maintainer (who might well be the same person.)


On 3/30/2018 11:09 AM, Kevin Bourrillion wrote:
I think my overarching point is still this one:

    "Today, an experienced developer knows that there is a category of
    Errors that, when you see them in the absence of reflection,
    always implicate this kind of classpath issue. I can't see why
    this would not belong in that same category."


The distinction, when a stack trace has just ruined my day, of whether I need to start thinking hard about what /real/ mistake I might have made /in my code, /or whether I probably just have Class Path Insanity I should check out first, seems to be like a very high order distinction - more useful to illuminate than other various distinctions we can make.



On Wed, Mar 28, 2018 at 12:48 PM, Brian Goetz <brian.go...@oracle.com <mailto:brian.go...@oracle.com>> wrote:



    I have been figuring that if the client /has/ a reasonable way to
    handle unknown values then it will probably go ahead and do that
    (with a `default`).

    I think that's a fair assumption for your codebase, but not in
    general.  Developers will surely do this:

        x = switch (trafficLight) {
            case RED -> ...
            case YELLOW -> ...
            case GREEN -> ...
        }

    and leave out a default because they can.  So they get a default
    default, one that throws.  No problem.

    The only question here is: what to throw.  My argument is that
    Error is just too strong an indicator.  (It's like using fatal as
    your logging level for everything; it would be more useful to use
    warning for things that aren't fatal).

    From the Error doc:

    An|Error|is a subclass of|Throwable|that indicates serious
    problems that a reasonable application should not try to catch.
    Most such errors are abnormal conditions.

    Serious problems mean that underlying VM mechanism have failed. 
    Encountering an unexpected input is not in this category.  Sure,
    it deserves an exception, but its not an ICCE.

    Therefore I assumed that what we're talking about in this
    conversation is the/other/ kind, where there is nothing safe they
    can do - for example if I wrote a method that displays a time
    interval as "10 ns" or "20 s", I may not find it acceptable for
    me to start displaying "30 <unknown unit>" once I get handed
    TimeUnit.DAYS. My code is broken either way. If a constant is
    added, I need to react to that, just like I do with a new
    interface method. What does it really mean to say that this
    client "brings a piece of the responsibility" if it doesn't
    really have a choice?

    It's not unlike this:

        AnEnum e = f(...);
        switch (e) {
            ...
        }

    and not being prepared for a null.  You'll get an NPE. The local
    code isn't expected to deal with it, but somewhere up the stack,
    someone is prepared to deal with it, discard the offending
    incoming work item, log what happened, and re-enter the work loop.

    So, I'm not quite yet following why the binary/source
    compatibility distinction, or the opt-in distinction, really
    makes all the difference here.

    Some incompatibilities are more of a fire drill than others. 
    Binary incompatibilities (e.g., removing a method) are harder to
    recover from than unexpected inputs.  Further, while there may be
    no good _local_ recover for an unexpected input, there often is a
    reasonable global recovery.  Error means "fire drill".  I claim
    this doesn't rise to the level of Error; it's more like
    NumberFormatException or NPE or ClassCastException.





--
Kevin Bourrillion | Java Librarian | Google, Inc. |kev...@google.com <mailto:kev...@google.com>

Reply via email to