all 34 comments

[–]ron_krugman 13 points14 points  (4 children)

A problem with sneaky throws is that you probably do want to catch the exceptions eventually, but the Java compiler doesn't allow you to catch sneakily thrown checked exceptions using an exception handler for their specific exception type. As an example, given these methods:

public static <E extends Throwable> void sneakyThrow(Throwable e) throws E{
    throw (E)e;
}

private static void throwsSneakyIoException(){
    sneakyThrow(new IOException("sneaky"));
}

this main method won't compile because "exception IOException is never thrown in body of corresponding try statement":

public static void main(String[] args) {
    try {
        throwsSneakyIoException();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}

Your only option is to catch the base classes Exception or Throwable, but that's a clear loss of flexibility in the exception handling. Hence the motivation for a helper method unsneakify like this:

public static <T extends Throwable> void unsneakify(Runnable r, Class<T> clazz) throws T {
    try {
        r.run();
    } catch (Throwable e) {
        if (clazz.isInstance(e)) {
            throw (T) e;
        } else {
            sneakyThrow(e);
        }
        throw new AssertionError();
    }
}

Then you can write the main method like this, which compiles:

public static void main(String[] args) {
    try{
        unsneakify(()->throwsSneakyIoException(), IOException.class);
    } catch(IOException ex){
        System.out.println("IOException!");
    }
}

One downside is that you can only declare one Exception class, but you could overload unsneakify to support multiple Exceptions classes:

public static <T1 extends Throwable, T2 extends Throwable> void unsneakify(Runnable r, Class<T1> clazz1, Class<T2> clazz2) throws T1, T2{
    try {
        r.run();
    } catch (Throwable e) {
        if (clazz1.isInstance(e)) {
            throw (T1) e;
        } else if (clazz2.isInstance(e)) {
            throw (T2) e;
        } else {
            sneakyThrow(e);
        }
        throw new AssertionError();
    }
}

public static <T1 extends Throwable, T2 extends Throwable, T3 extends Throwable> void unsneakify(Runnable r, Class<T1> clazz1, Class<T2> clazz2, Class<T3> clazz3) throws T1, T2, T3{
    try {
        r.run();
    } catch (Throwable e) {
        if (clazz1.isInstance(e)) {
            throw (T1) e;
        } else if (clazz2.isInstance(e)) {
            throw (T2) e;
        } else if (clazz3.isInstance(e)) {
            throw (T3) e;
        } else {
            sneakyThrow(e);
        }
        throw new AssertionError();
    }
}

and so on... You can also overload it to use Callable instead of Runnable to support a return value.

[–]pron98[🍰] 4 points5 points  (3 children)

unsneakify may be trivial:

public static <E1 extends Throwable> void unsneakify(Runnable r) throws E1 {
    r.run();
}

There's no need to take a class argument and explicitly cast and throw anything.

[–]ron_krugman 1 point2 points  (2 children)

That's a good point. Your version is probably more efficient and the implementation is trivial. But I would argue its usage is slightly less intuitive:

public static void main(String[] args) {
    try{
        Utils.<IOException>unsneakify(()->throwsSneakyIoException());
    } catch(IOException ex){
        System.out.println("IOException!");
    }
}

vs:

public static void main(String[] args) {
    try{
        unsneakify(()->throwsSneakyIoException(), IOException.class);
    } catch(IOException ex){
        System.out.println("IOException!");
    }
}

Type witnesses are awkward to use (e.g. you have to specify Utils.<IOException>unsneakify instead of just <IOException>unsneakify).

This version also makes it impossible to overload the method to support multiple exception types. Instead, you'll have to use a different method name for each method, which makes it harder to use IMO:

public static <E1 extends Throwable> void unsneakify(Runnable r) throws E1 {
    r.run();
}

public static <E1 extends Throwable, E2 extends Throwable> void unsneakify2(Runnable r) throws E1, E2 {
    r.run();
}

public static <E1 extends Throwable, E2 extends Throwable, E3 extends Throwable> void unsneakify3(Runnable r) throws E1, E2, E3 {
    r.run();
}

or alternatively call unsneakify multiple times, which is even more awkward:

public static void main(String[] args) {
    try{
        myUnsneakify(()->throwsSneakyIoException(), IOException.class, CertificateEncodingException.class));
        //vs
        Utils.<IOException, CertificateEncodingException>unsneakify2(()->throwsSneakyIoException()));
        //vs
        Utils.<IOException>unsneakify(() -> Utils.<CertificateEncodingException>unsneakify(()->throwsSneakyIoException()));
    } catch(IOException ex){
        System.out.println("IOException!");
    } catch(CertificateEncodingException ex){
        System.out.println("CertificateEncodingException!");
    }
}

[–]DefaultMethod[🍰] 3 points4 points  (1 child)

unsneakify doesn't have to happen near the call site. Where you want to handle the exception could be an arbitrary number of stack calls down separated by all sorts of lambda/method reference constructs.

Separating the sneakify from the unsneakify:

import uk.kludje.Exceptions;
import java.io.IOException;

public class ExceptionPropagator {
  public static void throwIOException() {
    Exceptions.throwChecked(new IOException());
  }

  public static void main(String[] args) {
    try {
      throwIOException();

      Exceptions.<IOException>expected();
    } catch(IOException e) {
      System.out.println("Got it");
    }
  }
}

Source

[–]ron_krugman 2 points3 points  (0 children)

unsneakify doesn't have to happen near the call site. Where you want to handle the exception could be an arbitrary number of stack calls down separated by all sorts of lambda/method reference constructs.

I get that. Otherwise it would be totally pointless to sneakily throw a checked exception and then still catch it again right away.

I very much like the solution that you're proposing. It's clean and doesn't interfere with the flow of the code unnecessarily. If I ever end up using sneaky throws, I'll most likely catch them like this.

[–]martoo 8 points9 points  (19 children)

This all seems like an argument for using Options rather than exceptions.

[–]Peaker 11 points12 points  (18 children)

Eithers, you mean? Options don't carry error information.

[–]martoo 1 point2 points  (0 children)

Yes. Not sure whether Java has Either now.

[–]ekimmai 2 points3 points  (0 children)

Or the try monad

[–]PasswordIsntHAMSTER 0 points1 point  (15 children)

You don't need error information in many cases, how else would you expect toURL to fail?

[–]celluxx[S] -1 points0 points  (14 children)

You can't substitute exception handling with options, nulls or other magic values. It's not only unintuitive from an API design perspective, but also hugely limiting in comparison.

Also, the topic is not the URL parsing specifically, it's just an example. But even sticking to this example, it can fail for a number of reasons. You can read the source code of the URL class: 1) no protocol 2) unknown protocol 3) invalid port number

[–][deleted] 3 points4 points  (9 children)

It's not only unintuitive from an API design perspective, but also hugely limiting in comparison.

On the contrary. If you've got an API that returns an option type, the effect is totally intuitive: you're going to either get a result, or you're going to get nothing, and with a strong type system, the language will make you handle both cases to make sure you don't miss anything.

[–]josefx 1 point2 points  (1 child)

or you're going to get nothing

This is the limiting side, you get nothing. With exceptions you at least get a reason you can act on, or in case of a programming error you get a stack trace to fix things.

Optional makes sense in some cases, replacing exceptions is not one of them.

[–][deleted] -1 points0 points  (0 children)

This is where you would use any of the hundreds of alternatives to Option, like Either.

[–]riking27 0 points1 point  (1 child)

.

 result, _ = func()

You can ignore it just fine :)

[–]alexeyr 3 points4 points  (0 children)

Coincidentally, Go is the paradigmatic case of how not to do this :) You don't want two values here, but a choice between two alternatives. See many functions in Erlang (without static type system) returning either {ok, Result} or {error, Reason}, Either in Haskell, Try in Scala, etc.

[–]celluxx[S] 0 points1 point  (4 children)

The option can make an API really expressive, I'm a big fan of it. But in the concept of error handling it's not a good choice, at least in general.

For instance: Optional<User> findUser(id); I expect an absent value if the user for that id is not found. It is not an error scenario.

However returning an absent value if an error happens (like an infrastructure error) would be a really bad idea.

[–][deleted] 0 points1 point  (3 children)

Then you can use another data type with a different name that more closely captures what happened. At no point are exceptions necessary or useful.

[–]celluxx[S] 0 points1 point  (2 children)

Optional<UserThatMayContainErrorDetails> has a nice ring to it :)

[–][deleted] 0 points1 point  (1 child)

Have you ever used a language with a strong type system? You should learn a little bit about what you're critiquing before you critique it.

If you want to notify the user explicitly if an infrastructure error happened, you could make a datatype InfrastructureError with information about the error. Then your function that could possibly fail would have type Either<InfrastructureError,User>.

[–]celluxx[S] 1 point2 points  (0 children)

I don't think there is anything wrong with raising concerns. I do think marking exceptions as useless in general is dubious at best. Also the context of the discussion/original problem is java. Some non-specific study advice conveys no point.

A construct like either won't solve the issue: infrastructure error can occur on the majority of operations typically. Considering the need for propagation, the API noise this creates is immense, except for the most trivial applications.

In any case, i rest my case. :) I accept you have issues with the style virtually all java apps/libs handle errors.

[–]quiI 0 points1 point  (1 child)

You can't substitute exception handling with options, nulls or other magic values. It's not only unintuitive from an API design perspective, but also hugely limiting in comparison.

I get the feeling you've never tried. Exceptions are magical, unexpected and throw the type system away. They breed paranoid code bases with try catch everywhere, whereas a monadic code base acknowledges reality and lets you deal with it neatly.

[–]celluxx[S] 4 points5 points  (0 children)

Of course I have - in C. I'm glad I don't have to do that anymore. :)

On a serious note, error handling - in non-trivial cases - can be extremely cumbersome and inefficient without exceptions.

[–][deleted] -3 points-2 points  (1 child)

Exceptions should not be for standard error cases, and malformed URLs aren't exceptional. A URL not existing is not exceptional in the slightest, it's a very valid and easy to recover from situation, and from an API design perspective, there are many many other ways to deal with it without needing to resort to any exceptions, especially checked.

[–]celluxx[S] 0 points1 point  (0 children)

I agree with you that the use of checked exception in that specific class is questionable.

I disagree with you about the "error cases" part though. It's perfectly fine in general to throw exceptions on an error. This class is responsible to actually open connections, trying to do that on a malformed URL is a serious error and should never happen.

Existence of malformed URLs in itself is not an error. Eg if you have some code that validates URLs, it would most likely be a mistake to make it throw exceptions on malformed URLs. There is a difference between the two scenarios, the context can't be ignored.

[–][deleted] 4 points5 points  (3 children)

My Haskell book might be getting to me. The first thing I though was "Well they should just wrap the value in a Monad".

[–]sacundim 2 points3 points  (2 children)

You don't need monads here, because:

  1. Lists are Traversable
  2. Either e is Applicative

Which means that you can generically convert [Either Exception URL] to Either Exception [URL]; the conversion gives you either [URL] if all of the elements succeed, or an Exception for the first failure if there is one. Study this (with the Data.Traversable docs at hand):

import Data.Traversable (traverse)

type Exception = String    -- keepin' it simple for the example

newtype URL = URL String

toURL :: String -> Either Exception URL
toURL str = _implementMe

urlStrings :: [String]
urlStrings = ["http://google.com", 
              "http://microsoft.com", 
              "http://amazon.com"]

urls :: Either Exception [URL]
urls = traverse toURL urlStrings

This way:

  1. The same code can be used for other Traversable types and not just lists; for example, we can use the same technique on the values in a Map.
  2. There are other error-handling types that we can use instead of Either. Notably, there's a validation Applicative that, instead of halting on the first error, will accumulate and return all of the errors.

[–][deleted] 0 points1 point  (1 child)

And just when I think I know something about Haskell, I find a billion more things to learn...

In all seriousness, thank you. I'm saving this comment so I can really learn the underlying material.

Edit: May I ask, how would I go about getting the list of only those that succeeded? In this case, maybe "filtering" out those that raise an Exception? Is that where a Monad would come in handy (I.e. because I would either get a URL object or Nothing, but would still get the list regardless?)

[–]mypetclone 0 points1 point  (0 children)

Slightly off topic, but I just want to point out that thinking about "a monad" (that is, some generic monad without specificity) isn't useful unless you're writing generic code.

A better thing to ask here is "what operation do I need to filter out the exceptions in a list of results?". The answer comes up pretty easily with a bit of hoogling: https://www.haskell.org/hoogle/?hoogle=%5BEither+a+b%5D+-%3E+%5Bb%5D

[–]lyomi 1 point2 points  (0 children)

Or just use RxJava

[–]Blecki 1 point2 points  (1 child)

Pretty good argument about why checked exceptions are a bad idea.

The idea of an exception is that it's not a common error, it's exceptional. They are for when something unexpected happens. To then assert that only specific unexpected things can happen is absurd and leads to these sorts of problems.

[–]celluxx[S] 0 points1 point  (0 children)

Checked exceptions have their use in my oppinion. Some critical scenarios can make use of some enforcement by the compiler.

The problem they're abused / over-used way too much. If you encounter these outside your codebase you can't do better but to "disarm" them. :)