Skip to content

Add checked exceptions. #984

Closed
Closed
@bsutton

Description

@bsutton

It has to be said...

The one thing I miss moving from Java to Dart is the lack of checked exceptions.

It makes it really hard to put complete error handling mechanisms in place if you don't know the full set of exceptions that a method can throw.

I find it rather ironic that dart doesn't have checked exceptions whilst 'Effective Dart' lints generate an error essentially saying 'check your exceptions'.

try {
      Directory(path).createSync(recursive: recursive);
    } 
    catch (e) {
      throw CreateDirException(
          'Unable to create the directory ${absolute(path)}. Error: ${e}');
    }

generates the following lint:
Avoid catches without on clauses.

To fix this lint I need to read source code to discover the exceptions that are going to be thrown.

Exceptions were created to stop people ignoring error.
Unchecked exceptions encourages people to ignore errors.

Have we learnt nothing?

I know this probably won't get anywhere due to the current religious movement against checked exceptions and the disruption to the dart eco system but this implementation decision was simply a bad idea.

Activity

julemand101

julemand101 commented on May 23, 2020

@julemand101

I do agree but you should properly move this issue to the language project: https://github.com/dart-lang/language/issues since the language itself does not support checked exceptions.

transferred this issue fromdart-lang/sdkon May 26, 2020
added
requestRequests to resolve a particular developer problem
on May 26, 2020
lrhn

lrhn commented on May 26, 2020

@lrhn
Member

Most languages created after Java has learned from Java and not introduced checked exceptions.
They sound great in theory, but in practice they are (rightfully or not) vilified by the people having to maintain the throws clauses. I think the general opinion is that it's technically a good feature, but it doesn't carry its own weight.

I don't see Dart going that way at the current time.

To fix this lint I need to read source code to discover the exceptions that are going to be thrown.

You can write on Object catch (e) if you just want to satisfy the lint. Or you can drop the lint.
If you actually want to catch the exception being thrown (and you should when it's an exception, not an Error), then the method documentation should document it clearly.

The standard way to document exceptions is a paragraph starting with "Throws ...". I admit that not all code follows that standard, and dart:io suffers from being written before most standards were formed.

bsutton

bsutton commented on May 26, 2020

@bsutton
Author

I admit that not all code follows that standard

And this is exactly the problem.

I would argue that checked exceptions more than carry their weight.
With modern IDE's the overhead of managing checked exceptions is largely automated.

The lesson that doesn't seem to have been learned is that developers are lazy and inconsistent (myself included) and if we don't force them to directly address errors then they simply ignore them and the quality of software suffers as a result.

I use a fair amount of flutter plugins and largely there is simply no documentation on what errors can be generated.
Dart actually makes it harder to document errors as a developer now actually has to write documentation.
Checked errors force devs to document their errors and actually make it easier as the IDE inserts them.

The end result is that for a large chunk of the code base we use on a daily basis, errors are simply not documented and we have to do additional testing and investigation to determine what errors need to be dealt with.

In the business world they have the concept of 'opportunity cost' which is essentially if I invest in 'A', I can't invest in 'B'. What is the cost not doing 'B'? That is the opportunity cost.

With unchecked exceptions we wear the cost of maintaining the checked exceptions but we don't have to wear the cost of investigating what errors can be generated nor the debugging time spent because we didn't handle an exception in the first place.

If a library maintainer has to spend time to declare the checked exceptions that time is more than offset by developers that use that library not having to investigate/test for what errors will be generated.

I believe the backlash against checked exception is because most developers prefer to ignore errors and checked exceptions require them to manage them.

munificent

munificent commented on May 28, 2020

@munificent
Member

I think if a function's failure values are so important to be handled that you want static checking for them, then they should part of the function's return type and not an exception. If you use sum types or some other mechanism to plumb both success and failure values through the normal return mechanism of the function, then you get all of the nice static checking you want from checked exceptions.

More pragmatically, it's not clear to me how checked exceptions interact with higher-order functions. If I pass a callback to List.map() that can throw an exception, how does that fact get propagated through the type of map() to the surrounding caller?

You describe the movement away from checked exceptions as "religious", but I think that's an easy adjective to grab when you disagree with the majority. Is there a "religious movement" towards hand washing right now, or is it actually that the majority is right and hand washing is objectively a good idea? If almost everyone doesn't like checked exceptions, that seems like good data that it's probably not a good feature.

most developers prefer to ignore errors and checked exceptions require them to manage them.

You're probably right. But if you assume most developers are reasonable people, then that implies that it should be relatively easy to ignore errors. If it harmed them to do it, they wouldn't do it. Obviously, individuals make dumb short decisions all the time, but at scale I think you have to assume the software industry is smart enough to not adopt practices that cause themselves massive suffering.

bsutton

bsutton commented on May 29, 2020

@bsutton
Author
bsutton

bsutton commented on May 29, 2020

@bsutton
Author

Just a final thought.

Why would you move from a system (checked exceptions) which automates the documentation of code to one that requires developers to document their code.

The empirical evidence (again look at pub.dev) is that developers do a terrible job of documenting and view documentation as a burden.
Managing checked exceptions is a far smaller burden than documenting code.

The lack of checked exception really has to be the best example of developers shooting themselves in the foot :)

leafpetersen

leafpetersen commented on May 29, 2020

@leafpetersen
Member

Why would you move from a system (checked exceptions) which automates the documentation of code to one that requires developers to document their code.

My general take on this is that effect type systems (of which checked exceptions is one) tend to interact badly with higher-order code, and modern languages including Dart have leaned in heavily to using first class functions in libraries etc. Indeed, the first search result I got when I just looked to see what Java does with this these days was this, which isn't inspiring. There may be better technology for making this non-clunky now, but last I looked at it, it took some pretty hairy technology to be able to express higher-order functions that were parametric in the set of exceptions that could be thrown. Inference can help with that, but as the saying goes, now you have two problems... :)

The empirical evidence (again look at pub.dev) is that developers do a terrible job of documenting and view documentation as a burden.

I will admit that it personally bothers me a lot that even for well-documented Dart team owned code, I often have to go poke around in the implementation to figure out whether an exception can be thrown, and in what circumstances.

rrousselGit

rrousselGit commented on May 29, 2020

@rrousselGit

Exceptions were introduced to solve the failings of return types.

  1. having to pass return types up the call stack.
  2. using the return to indicate error conditions means that you can't use
    the return type to pass the 'good path' values.
  3. poor documentation
  4. devs choosing to ignore error codes.

Exceptions are not the only way to solve these.
There are multiple languages out there with no exception mechanism at all and that do just fine.

The alternative is usually a combination of union-types and tuples and destructuring. This leads to self-documenting code, where it's impossible to ignore errors, while still being able to return valid values

For example using unions, instead of:

int divide(int value, int by) {
  if (by == 0) {
    throw IntegerDivisionByZeroException();
  }
  return value / by;
}

void main() {
  try {
    print(divide(42, 2));
  } on IntegerDivisionByZeroException catch (err) {
    print('oops $err');
  }
}

we'd have:

IntegerDivisionByZeroException | int divide(int value, int by) {
  if (by == 0) {
    return IntegerDivisionByZeroException();
  }
  return value / by;
}

void main() {
  switch (divide(42, 2)) {
    case IntegerDivisionByZeroException (error) => print('oops $error'),
    case int (value) => print(value);
  }
}
bsutton

bsutton commented on May 29, 2020

@bsutton
Author

I'm not certain unions result in more readable code than catch blocks.
If I'm reading the code correctly it does provide a level of documentation but it appears that it will still allow the caller to ignore the error and fail to pass it back up.
So once again we are dependant on the developer to do the correct thing and we are stuck with undocumented code.

Tuples will have the same issues.

bsutton

bsutton commented on May 29, 2020

@bsutton
Author

My general take on this is that effect type systems (of which checked exceptions is one) tend to interact badly with higher-order code, and modern languages including Dart have leaned in heavily to using first class functions in libraries etc

This appears to be a broader problem with how do you handle errors in lambda etc.
Whether its a checked/unchecked exception, error, union or tuple you still need to handle the errors in the top level function.
Error returns simply allow you to (incorrectly) ignore the error.
Your code looks nice, but does it actually behaviour correctly?

rrousselGit

rrousselGit commented on May 29, 2020

@rrousselGit

Error returns simply allow you to (incorrectly) ignore the error.

That is not the case.
Unions forces you to check all possible cases, or it is otherwise a compilation error.

Continuing with the code I gave previously, it would be impossible to write:

int value = divide(42, 2); // IntegerDivisionByZeroException is not assignable to int

You would have to either cast the value or check the IntegerDivisionByZeroException case.

This is in a way non-nullable types, but broadened to apply to more use cases

bsutton

bsutton commented on May 29, 2020

@bsutton
Author

OK, sure.

There however seems little point to introducing a new mechanism when we already have exceptions in the language.
We could possible even start by requiring checked exceptions via a lint.

We need some language experts to comment on this.

leafpetersen

leafpetersen commented on May 30, 2020

@leafpetersen
Member

We need some language experts to comment on this.

Touché. I'm afraid we're the best you're going to get though - Google can't afford to hire better.

46 remaining items

ryanheise

ryanheise commented on Mar 14, 2023

@ryanheise

If your goal is for exceptions to be documented

The primary goal is for exceptions to be "checked" so that I can be told by the compiler when my code was broken by an update.

Whether that goal be achieved via explicit (e.g. annotations or documentation) or implicit (e.g. inference) means is the means to that goal.

Another approach I don't think has been talked about enough is to introduce more stringent guidelines on what constitutes a breaking change for semantic versioning purposes. Quite often, package developers only consider visible API changes (e.g. to the method signature) when they decide to bump the major version, but they don't consider exceptions. Even if we never get checked exceptions in the language or in the linter, it would still be nice to have more stringent guidelines for package developers so that they are more aware of when they should be flagging a release as a breaking change due to hidden changes in exception handling.

bsutton

bsutton commented on Mar 14, 2023

@bsutton
Author
dancojocaru2000

dancojocaru2000 commented on Mar 12, 2024

@dancojocaru2000

It's disappointing that error handling in Dart is bad.

Reading through this issue, one thing that surprisingly came up a lot was "but higher order functions", and, having used Swift, it's puzzling that it was considered such a big problem.

Even if this is rejected, I'll add this here so that people will know that it's definitely possible to have checked exceptions and higher order functions:

func rethrowingFunction(throwingCallback: () throws -> Void) rethrows {
    try throwingCallback()
} 

// Because the callback isn't throwing, rethrowingFunction also isn't throwing
rethrowingFunction({
    print("Hello!")
})

// Because the callback is throwing, calling rethrowingFunction must use the try keyword
try rethrowingFunction({
    throw SomeError
})

A function that rethrows may not throw any exceptions itself, and so it will only throw if the callbacks passed in will throw.

gavinliu

gavinliu commented on Dec 10, 2024

@gavinliu

Many apis in sdk throw exceptions, but for dart novices, they don't realize that they need to handle exceptions.

For example:

https://github.com/dart-lang/sdk/blob/main/sdk/lib/core/iterable.dart#L699-L705

If you are not try-catch, accidents will occur.

You all say that checked exceptions are not elegant enough and not concise enough.

But how to improve the robustness of the program? Checking sdk apis one by one is a nightmare

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    requestRequests to resolve a particular developer problemstate-rejectedThis will not be worked on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @fredgrott@munificent@julemand101@simophin@bernaferrari

        Issue actions

          Add checked exceptions. · Issue #984 · dart-lang/language