eclipse-archived/ceylon

Make `AssertionError` extend `Exception`

Closed this issue · 52 comments

Currently, AssertionError extends java.lang.Error, which is wrong. It should instead extend ceylon.language.Exception.

The Java conventions are:

  1. Never throw a java.lang.Error unless the system is totally hosed
  2. Never catch an Error without re-throwing an Error or ending the process. The very rare exception to this rule is when the potential for a specific Error is expected

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.

It’s true that (2) is sometimes violated, for instance, by recovery code in servlet containers, but those few violations are irrelevant to this issue.

These conventions are good and correct, because in the face of an Error, the integrity of the process is in doubt. Carefully reviewed code that must run correctly, such as the following, may fail with an Error, which is totally unexpected:

try {
    // do stuff
}
finally {
    // the following may fail to complete due to an Error such as
    // StackOverflowError or OutOfMemoryError.
    clearAuthenticationTokenInThreadLocal(); // simple code that "can't" fail
}

A caller should not attempting to recover from an Error thrown by the above code, for example:

shared void run() {
    while (true) {
        try {
            serveRequest();
        }
        catch (Throwable t) {
            log(t);
            // It’s totally wrong to continue here!
        }
    }
}

However, this recovery code is perfectly fine:

while (true) {
    try {
        serveRequest();
    }
    catch (Exception e) {
        log(e); // probably bad user input or something
        reportErrorToUser(e);
    }
    catch (Error e) {
        try {
            log(e);
            reportErrorToUser(e);
        }
        finally {
            throw e; // system is totally hosed, be sure to re-throw
        }
    }
}

This recover code would also be fine without catch (Error e) { ... }.

In Ceylon 1.3.3, Errors in the form of AssertionError are thrown for perfectly ordinary things, such as failed parses, bad input, and common programming bugs such as equivalents to Java’s NullPointerException, ClassCastException, and IndexOutOfBoundsException (these should never happen in finally blocks).

For Java code calling Ceylon code, this causes a significant problem. Java code may, and often does, catch (Exception e), which misses Ceylon AssertionErrors which should be caught and handled.

For general/top-level Ceylon recovery code, having AssertionError extend Error requires the rather awkward catch (Exception | AssertionError e), rather than the simple catch (Exception e).

OTOH, the backends generate code that throws AssertionErrors in a few places that perhaps should be Errors. These could be reviewed separately.

How about we make it extend java.lang.AssertionError?

(I mean at the bytecode level.)

I think that would be a huge mistake. j.l.AssertionError extends j.l.Error

I think that would be a huge mistake. j.l.AssertionError extends j.l.Error

I don't see how you can possibly be arguing that the Java convention is to not throw Java AssertionErrors for assertion failures.

@gavinking I don't think you read the issue summary. You're also disregarding the very fundamental difference between j.l.AssertionError and c.l.AssertionError (who in their right mind throws j.l.AssertionError for input validation?), and you are also asking me to defend the mistake that is (the rarely used) j.l.AssertionError.

@jvasileff I don't think they're very different.

who in their right mind throws j.l.AssertionError for input validation?

I don't think we do that, do we?

We only usually throw AssertionErrors from asserts. (And also from some native Java code in the language module.)

Seriously???

OK, so the places where it is used like that, that I can find, are:

  • in formatFloat()/formatInteger(), for meaningless args, and
  • in Iterable.by() / Iterable.partition()` for negative lengths, step sizes.

These are cases which can only arise due to a bug in the program. They are not anything to do with validation of user-entered data. They occur because I as a programmer passed a meaningless argument to a method.

In none of those cases would I ever try to catch the resulting AssertionError.

On the contrary, the principle I've always followed in Ceylon, when designing APIs, is that our functions never throw in cases where Java would give you an IllegalArgumentException. Instead they return null, or ParseException, or whatever.

AssertionError always represents a bug. From its doc:

An error that occurs due to an incorrectly written program.

To be clear, I don't think you should ever catch AssertionError. Indeed I would be happy to add a warning to catch expressions that aren't a subtype of Exception. WDYT?

I guess the way I see it is:

  • Exception -> things you might want to catch
  • Throwables that aren't Exceptions -> things you probably shouldn't catch

AssertionError is used all over the place in Ceylon code. There's even a special feature to document input validation rules that are enforced by assert in Ceylon docs.

They are not anything to do with validation of user-entered data

where does data come from?

An error that occurs due to an incorrectly written program

java.lang.Error does not indicate an incorrectly written program!!! That's Exception and/or RuntimeException.

In none of those cases would I ever try to catch the resulting AssertionError

You still haven't read the issue description. Of course you don't want to crash out Eclipse or Tomcat when there is a c.l.AssertionError. Isn't that obvious?

To be clear, I don't think you should ever catch AssertionError. Indeed I would be happy to add a warning to catch expressions that aren't a subtype of Exception. WDYT?

Why would I want that?

AssertionError is used all over the place in Ceylon code.

C'mon! That's just not true. In fact, I bet the warning I just added will very few matches in our whole codebase, and the code it does match is probably wrong.

where does data come from?

From explicitly typed arguments in the code.

Of course you don't want to crash out Eclipse or Tomcat when there is a c.l.AssertionError.

Alright, so show me that Eclipse and Tomcat catch Exceptions, but exit on Errors. If that's truly the case, then I agree with you. But I don't think it is true, as a matter of fact. I don't think WildFly or Eclipse crash on every OOME or stack overflow. I think you're making that up, in fact.

For the record: I actually don't have a strong opinion either way on the proposed change. It just seems really strange to me that you're arguing that a particular convention exists when the JDK itself doesn't follow the convention.

I don't think WildFly or Eclipse crash on every OOME or stack overflow.

Come to think of it, I already know for sure that they don't.

Oh, no, wait, sorry, I think I do have a strong opinion on the topic. In Ceylon code, catch (e) catches every java.lang.Exception. I think quite it's wrong for that catch to catch assertion failures.

What I wouldn't mind is if AssertionError directly extended Java's Throwable, instead of Error. That would be OK I suppose.

I don't think WildFly or Eclipse crash on every OOME or stack overflow.
Come to think of it, I already know for sure that they don't.

Did you not read the issue description, where I said the following:

It’s true that (2) is sometimes violated, for instance, by recovery code in servlet containers, but those few violations are irrelevant to this issue.

Now, I could, but won't, dig through chat logs to find where you lamented that IntelliJ's error reporting isn't quite right sometimes because they are only catching Exceptions, which misses AssertionErrors.

I'm sure you are also aware that lots of code that runs in servlet containers catches Exception and reports nice errors to the user, while Errors are left to be handled by ugly outer error handlers.

Alright, so show me that Eclipse and Tomcat catch Exceptions, but exit on Errors. If that's truly the case, then I agree with you.

Gavin, please either just close this issue or agree to at least try to understand the arguments. You said AssertionErrors shouldn't be caught. My argument was that if they aren't caught, then Eclipse and Tomcat will just crash for stupid NPE type things and data validation errors.

I think quite it's wrong for that catch to catch assertion failures.

Why? c.l.Exception and c.l.AssertionError are used quite interchangeably in Ceylon code, with both meaning "An exception for which we don't really care about having a granular subclass, since we don't expect the immediately containing code to be able to do much with it anyway.

What I wouldn't mind is if AssertionError directly extended Java's Throwable, instead of Error. That would be OK I suppose

My analysis in the issue description makes clear what I'd think of this.

From my point of view the only real sticking point is I don't think catch(Exception) should catch AssertionErrors. Other than that, I don't really care.

From my point of view the only real sticking point is I don't think catch(Exception) should catch AssertionErrors

I can't think of a case where catch (Exception) would be more correct than catch (Exception | AssertionError). The former is usually (always?) a bug. (Of course, stuff like catch (ParseException) clearly makes sense.)

To distinguish them, and also to be somewhat correct on the JVM, I guess it would be:

                          c.l.Throwable (same as j.l.Throwable)
                                   |
                          c.l.BaseException (same as j.l.Exception)
                                   |
                    -------------------------
                    |                       |
            c.l.Exception              c.l.AssertionError

But then it would even more-so be a bug to catch (Exception) in Ceylon, since it would exclude Java exceptions.

To be honest, I agree with @gavinking here, @jvasileff. c.l.AssertionErrors are supposed to be unrecoverable errors. Similar to j.l.Errors, a c.l.AssertionError should not be caught. Throwing a c.l.AssertionError to validate the input of a function is supposed to indicate that that input should have never been sent to the function and that there is a bug in the caller if it was.

Throwing c.lcAssertionErrors does not indicate an exceptional case that can be treated and dealt with, it represents something that should never have happened.

@jvasileff it seems to me that we have a really concrete difference of opinion about what kind of condition an AssertionError represents in Ceylon.

  • I hold that an AssertionError represents a bug in the program. It makes no more sense to try to recover from a failed assert than it makes to recover from a stack overflow. I pretty much never want to catch and carry on after an assertion fails because by nature it represents something that was not anticipated by the programmer.
  • You, on the other hand, think that assertion failures are often recoverable. That is, you think it makes sense for a function or component to communicate information back to its caller in the form of an AssertionError, and that the caller with then handle it and decide to fulfill its responsibilities by some other mechanism.

I consider that usage to be an abuse. AssertionError should not be used to communicate information to the caller, and AssertionErrors are not part of the return contract of a function or component. The only way they occur in signatures is when a function uses an assert on its parameters to enforce its contract about what arguments are meaningful. That is, they capture things like length>0 that we would enforce as part of the static type of the function if we had a type system powerful enough to do so.

If a function uses exceptions to return information to the caller—something we actually actively discourage in Ceylon, since it's not typesafe—then it should always make a meaningful subclass of Exception for that. It should never use an assert to return information to the caller.

The one major exception to this rule is an exception that actually proves the rule: assertions in tests are not meaningfully "recoverable", even though obviously the test framework handles them and carries on with the test suite.

Another way of stating this:

assertions never fail in well-written programs.

[T]hey capture things like length>0 that we would enforce as part of the static type of the function if we had a type system powerful enough to do so.

I really like this explanation. Very well-put, @gavinking 😄.

It makes no more sense to try to recover from a failed assert than it makes to recover from a stack overflow.

That's not true: there is no defense against stack overflow if the caller has already burned through 95% of the stack. Even the most carefully designed and code reviewed finally block may behave in very crazy ways when j.l.Errors occur.

I pretty much never want to catch and carry on after an assertion fails because by nature it represents something that was not anticipated by the programmer

Exceptions due to programmer error happen constantly on production servers and even GUI apps. (But not in finally blocks that perform critical housekeeping chores. Those few lines of code must be bug free.)

You, on the other hand, think that assertion failures are often recoverable. That is, you think it makes sense for a function or component to communicate information back to its caller in the form of an AssertionError, and that the caller with then handle it and decide to fulfill its responsibilities by some other mechanism.

That is a mischaracterization! You are defining "recoverable" very narrowly. I've consistently said that AssertionErrors and Exceptions are not normally recoverable by the immediate (or nearby) caller.

That doesn't mean that Eclipse or Tomcat (if written in Ceylon) should system.exit() on AssertionErrors (programming bugs). Haven't we both already agreed on that?

I consider that usageto be an anti-pattern. AssertionError should not be used to communicate information to the caller, and AssertionErrors are not part of the contract of a function or component

I'm not disagreeing with that at all, and I never have. It's great in Ceylon that we never use exceptions for messaging, like non-RuntimeException Exceptions in Java.

But @gavinking, now you're getting into opinions on how basic error handling should work, which 1) would send this debate into all sorts of endless tangents, 2) is not relevant to the issue at hand, and 3) is probably stuff we 99% agree on anyway.

What you are not addressing, which is in the issue description, is that java.lang.Errors generally indicate things that are not recoverable at any granularity. With j.l.Errors, it's provably dangerous to just throw away the call stack and handle another request. You can't defend against OOME's by writing a better finally block.

By conflating programmer errors with truly unrecoverable system errors, you lose the valuable distinction that system errors currently have on the JVM.

Now, perhaps you still disagree, and think that Errors are recoverable (!!!) and feel that folks should just do catch (Throwable). But that's simply not what people do in Java, and it's widely considered to be an anti-pattern. In Java (and Ceylon too), programmers expect catch (Exception) to catch all non-really-bad-system-errors (i.e. exceptions due to programmer error). There is a lot of existing code that does that, and that code does exactly the wrong thing for AssertionErrors (by ignoring them).

Further, given how common the catch (Exception) pattern is, troubleshooting Errors can be really difficult. Errors hit infrequently used code paths and often bypass logging and other general error handling logic.

assertions never fail in well-written programs

I agree. But I've also never written one of those programs.

@Zambonifofex if you want to end your program on any unexpected error, that's fine and quite reasonable. But you don't need j.l.Error to do that. If you ignore all Throwables, both failed non-empty assertions and, say, divide-by-zeros will be handled just the same: your program will exit.

But as has already been pointed out, not all programs should behave that way (some need to keep running.) And, there's a great deal of code that expects all errors due to bugs to be Exceptions, even code that just logs and then exists.

You are defining "recoverable" very narrowly.

I'm defining "recoverable" to mean meaningfully recoverable by program logic. By "recoverable" I don't mean "can be handled by some generic server code right at the top of the stack. Almost all Throwables, including Errors can be handled like that.

That doesn't mean that Eclipse or Tomcat (if written in Ceylon) should system.exit() on AssertionErrors (programming bugs). Haven't we both already agreed on that?

We agree on that. But they don't exit on OOMEs or stack overflows either, so I don't know what the point is.

What you are not addressing, which is in the issue description, is that java.lang.Errors generally indicate things that are not recoverable at any granularity.

This is just not true! Eclipse, WildFly, and Tomcat all handle at least some Errors. I have not checked the code, but I bet they just catch Throwable.

Indeed, I bet there is no exception that you could possibly throw in your program code that would cause WildFly to exit.

@jvasileff

It seems that you think j.l.Errors are meant only for system errors (the ones thrown by the JVM itself).

I think that if that was true, one wouldn't be able to just subclass j.l.Error with their own class and throw it. In fact, one wouldn't even be able to just simply instantiate j.l.Error directly and throw it.

The fact that one can subclass and throw their own errors imply that the Java authors intended for errors to be able to represent abnormal conditions that should not be caught other than system errors.

I'm defining "recoverable" to mean meaningfully recoverable by program logic.

then your definition of recoverable isn't relevant to this issue.

Almost all Throwables, including Errors can be handled like that.

No. There is no expectation that code in finally blocks runs in any sane manner in the face of Errors. Why are you ignoring this point?

This is just not true! Eclipse, WildFly, and Tomcat all handle at least some Errors. I have not checked the code, but I bet they just catch Throwable.

Why do you keep trying to debate things that are totally irrelevant? I've already acknowledged that behavior, I'm not here to defend default configs of any app server. But FTR, I have seen an Eclipse dialog box after a SOE that recommended exiting.

And why are you totally ignoring things that do matter, like the standard practice of using catch (Exception e) for outer error handling, and as of yet, the lack of any reason why someone might want to catch (Exception e) but exclude Ceylon assertion errors?

Indeed, I bet there is no exception that you could possibly throw in your program code that would cause WildFly to exit.

Who cares? I want well behaved programs, and for all catch (Exception e)s to do what they are supposed to do. Clearly you don't understand my position at all if you think my goal is to not have WildFly exit.

The fact that one can subclass and throw their own errors imply that the Java authors intended for errors to be able to represent abnormal conditions that should not be caught other than system errors.

No one subclasses j.l.Error for programmer error type things. Except Ceylon. You're making quite a leap to suggest that's the intention of the designers.

And, there has been no claim of any benefit to subclassing j.l.Error. OTOH, there are clear downsides.

@jvasileff

No one subclasses j.l.Error for programmer error type things.

Why else would someone subclass j.l.Error for? It's not like they can make the JVM throw their custom errors for them.

Oh, and before you come and say there has been no claim of any benefit to subclassing j.l.Error, why would it be possible, if it wasn't intended, then?

Why else would someone subclass j.l.Error for? It's not like they can make the JVM throw their custom errors for them.

  1. It doesn't matter. Not having a reason in no way means that Errors should be preferred over Exceptions or RuntimeExceptions, and
  2. maybe you want to wrap a caught Error in another Error, so calling code knows it's "a big unrecoverable deal"

Why do you keep trying to debate things that are totally irrelevant?

Because you keep saying stuff like this:

That doesn't mean that Eclipse or Tomcat (if written in Ceylon) should system.exit() on AssertionErrors (programming bugs).

As if that were a difference between assertion failures and stack overflows.

No one subclasses j.l.Error for programmer error type things. Except Ceylon.

Oh, and also the people who wrote j.l.AssertionError.

No. There is no expectation that code in finally blocks runs in any sane manner in the face of Errors. Why are you ignoring this point?

OK, so this is somehow important to you, but I'm not sure precisely why. As far as I understand, according to the JLS, a finally is supposed to run even for Errors. Now, I'm not sure that it actually does in practice, but if it doesn't, it seems to me that that is an un-specced difference between Errors and im-not-sure-what other class of exceptions (~Error?)

a finally is supposed to run even for Errors.

Yeah, exactly. Now, if I'm not mistaken, what is not guaranteed is how code will behave after an error is thrown by the JVM itself, but that doesn't apply to errors thrown by throw.

Because you keep saying stuff like this: ...

That doesn't mean that Eclipse or Tomcat (if written in Ceylon) should system.exit() on AssertionErrors (programming bugs).

You are the one that said AssertionErrors should "never" be caught. And I said, yes they should, at least for Tomcat and Eclipse. And I thought we agreed on that?

Separately, that WildFly may have catch (Throwable) in an outermost catch block isn't relevant.

As if that were a difference between assertion failures and stack overflows.

"As if"??? Yes, java.lang.StackOverflowError and ceylon.language.AssertionError are different. Errors can indicate that even the most simple, bug-free, and correct code may have failed to execute properly.

The only reason that's relevant is that it helps explain why catch (Exception e) is what people do, instead of catch (Throwable t). The fact that catch (Exception e) is what people do is why AssertionErrors should be j.l.Exceptions.

Oh, and also the people who wrote j.l.AssertionError.

Who cares about that silly class? That has nothing to do with c.l.AssertionError other than its name.

As far as I understand, according to the JLS, a finally is supposed to run even for Errors

What happens before attempting to run the finally block doesn't matter at all. What matters is that the JVM tries to run the code, but it may just throw an Error instead. So, for example, a thread local containing an authentication token may not be cleared.

Again, this helps us understand why people less-often try to recover from Errors, but what's bottom-line important is that catch (Exception e) is what they do. And not having their catch block code run is... unexpected.

@Zambonifofex

The fact that one can subclass and throw their own errors imply that the Java authors intended for errors to be able to represent abnormal conditions that should not be caught other than system errors.

@gavinking

Oh, and also the people who wrote j.l.AssertionError

Given that you guys seem to want an appeal to authority argument, a few quotes from Bloch, Joshua. Effective Java (2nd Edition) (Java Series) (p. 244). Pearson Education. Kindle Edition.:

While the Java Language Specification does not require it, there is a strong convention that errors are reserved for use by the JVM to indicate resource deficiencies, invariant failures, or other conditions that make it impossible to continue execution. Given the almost universal acceptance of this convention, it's best not to implement any new Error subclasses. Therefore, all of the unchecked throwables you implement should subclass RuntimeException (directly or indirectly).

And:

The cardinal rule in deciding whether to use a checked or an unchecked exception is this: use checked exceptions for conditions from which the caller can reasonably be expected to recover.

And:

Use runtime exceptions to indicate programming errors. The great majority of runtime exceptions indicate precondition violations.

As for catching exceptions, Bloch argues that neither Errors nor RuntimeExceptions should be caught (I doubt he's talking about an outer error handler of an application server):

There are two kinds of unchecked throwables: runtime exceptions and errors. They are identical in their behavior: both are throwables that needn't, and generally shouldn't, be caught. If a program throws an unchecked exception or an error, it is generally the case that recovery is impossible and continued execution would do more harm than good.

Surely, Ceylon Exceptions would naturally be RuntimeExceptions, but given Ceylon's total rejection of checked exceptions, promoting them to Exception, as they are, seems very reasonable.

This debate hasn’t shown signs of being productive. We haven’t been able to agree on the simple foundational fact that Errors such as OOME, SOE, and LE are by nature different than AssertionErrors in Ceylon code, and without agreeing on the basics, further reasoned discussion is impossible.

There also doesn’t seem to be interest in discussing my concrete claims, such as catch(Exception | AssertionError e) always being at least as good as catch(Exception e) in Ceylon code.

I do hope someone else picks up the debate, because adjusting the exception hierarchy would make Ceylon better, but I’m going to bow out now. Closing.

By the way, @jvasileff, I was going to say this later, but you've stepped up and closed this before I could. I feel like you've convinced at least me that c.l.AssertionError should extend j.l.RuntimeException, but I don't think it should extend c.l.Exception. As you've said, RuntimeException shouldn't be caught, so I don't really understand why you think catch(Exception exception) or catch(exception) should catch it. If you really want to catch c.l.AssertionError and/or j.l.RuntimeException, you can always catch c.l.Throwable.

Either way, I'm not sure what's the best way to proceed about this. I feel like catch(c.l.Exception exception) should catch j.l.Exception~j.l.RuntimeException, but I don't know how to achieve this.

We haven’t been able to agree on the simple foundational fact that Errors such as OOME, SOE, and LE are by nature different than AssertionErrors in Ceylon code

Look, I'm happy to stipulate that they're different. What's unclear is in precisely what way are they different. I don't think anyone has nailed that down to my satisfaction yet.

From what I've seen of the SDK and IDE codebases, we indeed don't commonly catch AssertionError (there is some infrastructure code where we handle them in the IDE, but that's at the level of like concurrency management code, and indeed it's in contexts where the IDE actually catches all Throwables, including Errors.)

At present it's already easy to only catch AssertionErrors, and not other Errors. On the other hand I don't see a need to catch all Errors except AssertionErrors. (According to you, we shouldn't generally be catching Error to begin with.)

So the real question to me seems to be:

  • should catch (Exception) also catch AssertionErrors (I still believe not), or
  • do you have to write catch (Exception|AssertionError) for that (seems much better to me).

If it helps at all, I'm perfectly happy to have three root varieties of Throwable:

  • Exceptions,
  • AssertionErrors, and
  • VM-related Errors.

That's totally acceptable to me. But you seemed to be dead against it.

I mean is catch (Error~AssertionError) something anyone has ever needed to do? I doubt it.

in precisely what way are they different

Because if an AssertionError (or any other Exception) happens in a very important finally block, "it's your own damned fault." But an Error is impossible to protect against, and therefore the presence of one indicates that the application state may be compromised, at least without the caller having very special knowledge of the code that was called (e.g. SOE in the type checker)

But I've already said that in different ways here, and in prior conversions.

From what I've seen of the SDK and IDE codebases, we indeed don't commonly catch AssertionError (there is some infrastructure code where we handle them in the IDE, but that's at the level of like concurrency management code, and indeed it's in contexts where the IDE actually catches all Throwables, including Errors.)

The SDK isn't an application, so that makes sense. In the IDE, it is at least suspect that Throwables are caught, rather than Exception | AssertionErrors. But I haven't looked at the specific code you are taking about.

But you seemed to be dead against it

Yes, I am.

I mean is catch (Error~AssertionError) something anyone has ever needed to do? I doubt it.

You'd want catch (Exception | AssertionError), for sane course-grained, top-level recovery.

but I’m going to bow out now

Ok, really, that's the last from me. I've made all of my arguments, so I'm going to unsubscribe from this thread.

Because if an AssertionError (or any other Exception) happens in a very important finally block, "it's your own damned fault." But an Error is impossible to protect against, and therefore the presence of one indicates that the application state may be compromised, at least without the caller having very special knowledge of the code that was called (e.g. SOE in the type checker)

Could you please explain what you're talking about here, with code examples, because I really don't know what are the things you're thinking of. What are the phenomena you're trying to protect against? You're arguing at too high a level of abstraction here without showing any concrete code examples.

I think what he was getting at got muddied because these exceptions have similar names and the original issue involves catching Exception from Java code. So, something like this:

// Java code:
try {
    // Call some code in a Ceylon module
}
catch (java.lang.Exception e) {
    // Do some very important error handling
}

John's point may have been that if an assert fails in the Ceylon code, the AssertionError isn't going to be caught by the catch statement in that Java code and the error handling won't happen. A Java developer trying to use this Ceylon module sees this and naively switches the catch statement to catch Throwable, which you're not supposed to do. John may want AssertionError to extend ceylon.lang.Exception so blocks that catch java.lang.exception catch them with no extra work and fewer developers get frustrated and have naive "just catch Throwable" reactions.

If that is indeed the argument, then I get the reasoning behind it. I also agree that Ceylon code that throws too many AssertionExceptions is probably not the greatest. I do much prefer the Ceylon style where the lookup operator returns null, instead of throwing an ArrayIndexOutOfBoundsException or failing an assertion. Maybe, if there were a specific library module that led somebody to catch Throwable, starting off this whole discussion, we could address the issue there?

The flip-side to the point could be that use of assert statements in Ceylon needs more discipline; they're not supposed to fail.

Either way, I like the warning for attempting to catch stuff in Ceylon that doesn't extend Exception!

I agree with jvasileff. In Java I never use assert keyword, therefore usually I use catch (Exception ex) and all works fine. But in Ceylon assert keyword is very useful, so I have to write catch (Throwable ex) to catch all possible exceptions. But catching Throwable is bad practice.

So, if assert throws Error, I think best practice is never use "assert" keyword in ceylon code, use "if" instead and throws exception. Or always catch Throwable on top level to catch all exceptions.

@MikhailMalyutin But you don't!

You should write catch (AssertionError|Exception e) if you want to catch both varieties. That works in both Java and Ceylon.

Right, but the average Java developer who already has thought it necessary to catch Exception probably isn't going to do that; they're going to catch Throwable so they can move on to something else.

I think it really does boil down to figuring out what Ceylon code is failing so many assertions and fixing that, if there even was something specific that kicked off this discussion.

You should write catch (AssertionError|Exception e) if you want to catch both varieties. That works in both Java and Ceylon.

Yes, and no one does that in Java… we’re going in circles here.

I find myself agreeing more with John here as well… I think his argument is that in Java, Errors are something that you can’t recover from, except in some circumstances, because in general the same thing that caused the exception (stack overflow, out of memory) might happen again at any point in your finally blocks or anywhere else, so you have almost no guarantees about your application state. The exception from this is code like the outermost event loop of an application server, or of an IDE, or something – at that level, it can be acceptable to catch Error.

The fact that Java’s AssertionError extends Error is almost irrelevant to this issue, I feel, because Java asserts are completely different from Ceylon asserts, even if you put aside the issue how well Java asserts are designed in the first place.

I don't understand. As said by @jvasileff and Bloch:

Use runtime exceptions to indicate programming errors. The great majority of runtime exceptions indicate precondition violations. [The same as c.l.AssertionError]

As for catching exceptions, Bloch argues that neither Errors nor RuntimeExceptions should be caught

So, even if AssertionError was a subtype of RuntimeException, why do you guys think that catch(Exception exception) or catch(exception) should catch RuntimeExceptions?

To be honest, it seems to me that it was a mistake from Java's part to have RuntimeException subclass Exception.

Right, but the average Java developer who already has thought it necessary to catch Exception probably isn't going to do that; they're going to catch Throwable so they can move on to something else.

I don't really know this average programmer. All the code I've seen would suggest that average response to this type of situation is to add another catch clause like this:

// java
try {
    // call some Ceylon code
}
catch(Exception e) {
   // handle the exception
}
catch (c.l.AssertionError e) {
   // handle the exception
}

You're lucky, then!