I am requesting a more simple base class for Exceptions in the dart:core lib, making it easy to create application specific exceptions that require a simple message.
The dart:core lib has an Exception interface, but no base exception class (the coreimpl has an ExceptionImplementation, but coreimpl is off limits). Creating a custom exception requires too much boilerplate if you want to capture a message. Some of our exceptions have a message field, some don't. Some of our exceptions take a _s, some take a _message, some take a message in their constructor.
_This comment was originally written by sammcca...@google.com_
Java calls the inner exception the 'cause' which is concise and (usually) accurate, so my vote would be:
class Exception {
Exception([String message, Exception cause]);
final String message;
final Exception cause;
// And stack frame access hopefully!
}
Constructors aren't inherited, so we'll still need to write
class MyException extends Exception {
MyException([message, cause]) : super(message, cause);
}
but that's not so bad IMO.
I think throwing strings is the simplest and lightweightest way to throw exceptions with simple messages.
_This comment was originally written by sammcca...@google.com_
Srdjan: that's not enough for APIs exposed by libraries.
These need:
* machine-readable exception kind (preferably as a type, to be used with catch
* human-readable message (usually not 1:1 with exception kind)
* stack trace information
* cause information for debugging purposes
* sometimes further machine-readable information about this particular exception
It's possible to add these as language features, but exception objects are a natural way to express them and already have language support.
_This comment was originally written by @seaneagan_
I don't think anyone is asking to remove the ability to throw simple Strings, just to add the ability to extend a common base class to provide some useful base functionality.
For example, I think it would be nice to have Exception#toString use reflection to determine the declared type name of the exception (e.g. BadNumberFormatException), and include that, as well as the cause if it is not null (stack trace too ?):
class Exception {
Exception([this.message, this.cause]);
final String message;
final cause; // can be a simple String
String get stack() {
//... reflection
}
String get _typeName() {
//... reflection
}
String toString() {
final ret = """$_typeName: $message
Caused by:
$cuase
""";
if(cause !== null) {
ret = """$ret
Caused by: $cause
""";
}
return ret;
}
}
_This comment was originally written by [email protected]_
With regards to stacktrace: Dart intentionally doesn't put a stacktrace into the exception object, because collecting the it is rather costly. Instead, it is provided by a second "parameter" of the catch clause, which allows optimizing the stacktrace collection out (currently, it is always done, but in the future, I'm sure it will be avoided when not necessary). I think this is nice, but I'm not sure how does it play with exception chaining.
_This comment was originally written by jblo...@gmail.com_
I don't think you missed anything, Seth. I do believe that there should be such a class, and that it should support exception chaining (i.e., cause). I think that throwing strings is a bad idea, as violates "DRY" (don't repeat yourself). In fact, I wish it were syntactically invalid.
I also believe that thought should be given to exception suppression: if there's an exception on the stack and you throw a second exception, I believe that it should be suppressed in favor of the first one (but accessible via the first one), unless you take some specific action to indicate that you want exception translation (i.e., replacing the first exception with the second, and typically making the first the cause of the second). This would most definitely require a language change, and would be different from (most?) existing languages, but I believe it would be a win, and is worthy of investigation.
_This comment was originally written by dha...@google.com_
Unless we're talking about 'the VM pauses for 100ms to generate the stacktrace', optimizing throwing exceptions seems relatively unimportant. Exceptions shouldn't be thrown very often.
Also, which part of generating a stacktrace is slow? It looks like there are a few parts: allocating memory to hold the stacktrace; walking the stack to record return addresses; and converting the return addresses to file/line numbers.
It seems like the first two parts are relatively cheap -- you're already walking part of the stack to find the exception handler, and you can preallocate the buffer to a reasonable size since you know the stack size.
Once you have that information, you can defer the line number lookup until someone asks for it.
Experience with performance tuning of another very popular language shows that it is widely expected to have very efficient exception throwing. (I agree that well written programs should not throw exceptions often).
We expect to be able to recycle the generated code when reoptimizing methods, therefore remembering the PCs would not work, or it would complicate the system. Since most of the time the code does not need a stacktrace, it seem a waste to always allocate and compute it.
cc @lrhn.
cc @munificent.
_This comment was originally written by deakste...@gmail.com_
I heavily disagree with the people suggesting that 'exceptions shouldn't be thrown often enough to have always included stack traces be an issue'.
'Often' is entirely relative and based on the nature of an application. Indeed, in certain types of applications, there would be infrequent exceptions and even if stack traces took 100ms they would be unnoticeable among the IO and network traffic.
However, if you consider other types of applications like simulations and games, where you are performing almost 100% code coverage at a rate of 60 times a second (or more), you can see that even if it happens relatively infrequently, it can completely destroy the simulation if they are costly. It is not unusual for there to be at least one exception per frame, many API's correctly use exceptions to indicate entirely possible 'exception' cases.
I hope this clearly illustrates the issue: A stack trace that takes 50ms to create in your web request that is taking 2000ms anyway is not an issue. A stack trace that takes 50ms to create, when you have 16ms in total to complete your frame to keep your simulation/game at 60 frames per second is a complete deal breaker.
Is this part of M3? Is this done? (Do all exceptions extend Exception now?)
This is indeed not planned.
Errors are meant to represent actual erroneous behavior, caused by bad programming. They have (extra) stack traces, and are allowed (or even expected) to terminate the program.
Exceptions (really meaning: any non-Error that is thrown) are secondary return values. They should contain the data that makes sense for the message that they are sending, and there is no requirement that they have any specific type or structure. Exceptions thrown by a function should be documented, and the caller should probably catch them if necessary.
Please reconsider this. I'm writing a mobile app that must capture and log stack traces. Currently this requires a lot of confusing boilerplate and third-party modules. Please enhance.
Exceptions are not errors. It makes not sense to require an exception to have any of:
Generally reusable exceptions, like FormatException, usually have a message, because they are used in multiple situations, and integer.parse and Uri.parse may want to report who is throwing the exception.
Application specific exceptions will hold application speciifc data. They usually don't need a message field, they just override toString and use the data stored on the object. The message is for debugging or logging, so it should be in a consistent format, and allowing each throw-point to pick its own message is conuter-productive.
So, when we won't make the Exception class carry those values, this would mean having utility classes for some combination of features.
It's easy to write all of those combinations, but it's harder to determine which ones are actually needed.
abstract class ExceptionWithCause extends Exception {
final Object cause;
ExceptionWithCause(this.cause);
String get message;
String toString() => "$message\nCaused by:\n$cause";
}
class ExceptionWithCauseAndMessage extends ExceptionWithCause {
final String messagge;
ExceptionWithCauseAndMessage(this.message, Object cause) : super(cause);
}
Here the cause is typed as Object because it's unclear whether it's always an Exception, or it might be an Error. Are these classes actually what somebody need for their exception classes? Probably some, likely not everybody.
I think a consistent framework for creating exceptions or errors is fine as a package, and I'd help write one, but the use-cases are not consistent enough that putting one particular solution in the platform libraries is a good idea.
Hi lrhn,
I am preparing a commercial application for hundreds of thousands of users. At that scale, anything that can go wrong will go wrong. How shall we investigate errors? I would hope to see consistent stack traces, and chained exceptions with useful error messages at each throw. How could you do it without those things?
Michael
I would have to ask, for each of your thrown exceptions, what the catching code is expected to do with that exception. If it's always logging, then you should be able to do with just one
/// Exception class recording an event to be logged.
class LogException {
/// Message describing the
final String message;
final StackTrace stackTrace;
final Object cause;
LogException(this.message, {StackTrace stackTrace, this.cause})
: stackTrace = stackTrace ?? StackTrace.current;
String toString => "$message\n$stackTrace${cause != null ? "\nCaused by:\n$cause" : ""}";
}
That's a exception for a particular use-case. It doesn't carry any data to allow the catcher to recover, because that's not its purpose.
It doesn't cover all cases (for example FormatException would not want to use this class because it doesn't need most of the functionality).
Maybe the platform libraries should have a class like this, but I think it fits better in a specialized package, which could also have functionality to reduce the stack traces. Third party packages are not something to avoid.
Most helpful comment
_This comment was originally written by sammcca...@google.com_
Java calls the inner exception the 'cause' which is concise and (usually) accurate, so my vote would be:
class Exception {
Exception([String message, Exception cause]);
final String message;
final Exception cause;
// And stack frame access hopefully!
}
Constructors aren't inherited, so we'll still need to write
class MyException extends Exception {
MyException([message, cause]) : super(message, cause);
}
but that's not so bad IMO.