If anything, it's a mistake that Exception
takes a message. You should never throw a plain new Exception("message")
.
The difference between Error
and Exception
(or really, any non-Error
thrown object) is that Error
represents a programming failure.
Your programs should not throw an Error
as part for normal operation. You should not catch a specific Error
and react to it. (It's fine for frameworks to catch all thrown objects and log them, in order to keep running, but they shouldn't react specifically to individual errors).
That means that it doesn't actually matter which class you use to represent an error. The only purpose of an error is to be shown to the programmer, so they can fix their bug.
Dart has a number of useful Error
subclasses which cover most of the situations, and which ensures a fairly consistent formatting of the error messages. There is nothing inherently requiring you to use a RangeError
for an out-of-bounds error, it's just a very convenient helper class, it helps the reader understand what's going on, and we do recommend that you use it.
Use ArgumentError
when an argument to a function does not satisfy the extra (non-type) requirements of it (say, a list must be non-empty).
For number arguments, there is a RangeError
for when the number is not in an expected range. For a number intended as an index (like a list.operator[]
argument), there's even a specialized IndexError
. They're all there to ensure that you get consistent error messages.
If an operation cannot be performed right now, because the object is not in a state which supports it, use StateError
.
If an operation cannot be performed by the current object ever, use UnsupportedError
.
In practice, StateError
, UnsupportedError
and ArgumentError
(and its subclasses) cover the wast majority of runtime errors thrown by user code.
Simply throwing an Error("message")
is less informative than using one of these, and inventing a new error type is very rarely worth it.
An exception, on the other hand, is intended to be caught and handled.
It's a message to the caller, at the same level of significance as a return value, and it should contain enough information for the caller to catch the specific documented exception that the function might throw, and react to that specific exceptional situation.
That's why an exception should, at the very least, have a unique type that allows you to recognize and catch it specifically, and if possible, it should contain any information needed to handle the exceptional situation.
Take FormatException
which is thrown by various parse
functions.
It's specific to the operation. It contains available information to help you figure out where the problem is (the input and a position related to the error, mainly for showing to the user since you can't be expected to fix the input.)