Harald Kirsch

genug Unfug.

2014-10-29

Use Cases for ValEx

The last blog entry introduced the idea of ValEx as a variation of Java's Optional. It can be initalized not only with a value, but alternatively with an exception which explains why no value is available. The central method of this class is the getter for the contained value:

    public class ValEx<T,E extends RuntimeException> {
      ...
      public T get() throws E {
        if (t==null) throw e;
        return t;
      }
    }

It allows the caller to decide how sure (s)he is that a value is available. If, from our code structure, we are sure that there is a value, we can just call get() without previously checking with isPresent(). If we are wrong, this is a bug and an unchecked exception is thrown. How does this feel in practical use. Lets look at some typical exceptions in Java.

Exceptions When Parsing Strings

There are many cases where strings are parsed or converted into objects. This includes parsing dates and regular expressions, or even very simple things like getting Charset or an encoding. As already shown in the last blog entry, converting static strings should not throw a checked exception. Who has not yet written code like

      try {
        ...
        Writer w = new OutputStreamWriter(out, "UTF-8");
        ...
      } catch (UnsupportedEncodingException e) {
        log.error("how can UTF-8 be missing", e);
        ...
      }

and wondered why the wrong character set results in a checked exception. The solution here is actually to use a slightly different call:

      Writer w = new OutputStreamWriter(out, Charset.forName("UTF-8"));

where forName() throws an unchecked exception that will only be thrown if things are getting weird. Does VarEx have an application here. Well, not as long as 100% of the cases involve static strings. But suppose the character set name is read from a property.

      String csName = System.getProperty("application.charset.name");
      Writer w = new OutputStreamWriter(out, Charset.forName(csName));

This code is now missing a try/catch, because it is not improbably that a system property contains a wrong string. In this particular case we can switch back to not using forName, but with a hypothetical factory method returning a VarEx we can have both easily. With a static string

       Writer w = Files.streamWriter(out, "UTF-8").get();

we get an exception from the get() in case we have a typo in the code. And with a character set name from a property

      String csName = System.getProperty("application.charset.name");
      VarEx<Writer,?> vw = Files.streamWriter(out, csName);
      if (vw.isEmpty()) {
        // handle the problem, possibly re-throwing
        throw w.getException();
      }
      Writer w = vw.get();

we can first check whether we got something back. So the VarEx allows us to have both, the convenience of an unchecked exception with the minor price to pay being the unshielded get() call, as well as the awareness about a potentially unsuccessfull operation that can be checked for with isEmpty().

IOException

An IOException is probably one of the most frequent exceptions to deal with. Let's see whether VarEx can help here too. With a hypothetical factory method again, assume we had

      String readFile(String name) throws IOException {
        VarEx<Reader,IOException> vr = Files.newReader(name);
        if (vr.isEmpty()) {
          throw vr.getException();
        }
        Reader r = vr.get();
        ...
      }

Is this any better than the try/catch version? Probably not if used this way. What still bugs me is that an exception is prepared in newReader() and eventually thrown despite the fact that absolutely nothing exceptional is going on. Opening a file and finding that this cannot be done is completely normal business. Even if we just wrote the file, some other process or thread could have intercepted already and messed with the file in all kinds of ways that prevent us from opening it — normal, not exceptional!

In this case it may be worth considering this implementation.

    VarEx<String,String> readFile(String name) {
      VarEx<Reader,String> vr = Files.newReader(name);
      if (vr.isEmpty()) {
        return VarEx.empty(vr.getCause());
      }
      Reader r = vr.get();
      ...
    }

Here I start to change my mind with regard to how exactly VarEx should be implemented. In the previous blog I proposed to always use an exception as the description of what went wrong. But this forces the provider of the VarEx to create an exception with its full stack trace, even for normal business. Therefore I now rather would implement the VarEx with a completely unrestrained second generic argument.

    public class VarEx<T,Cause> {
      ...
      public static <T,Cause> ValEx<T,Cause> of(T value) {
        return new ValEx<>(value, null);
      }
      public static <T,Cause> ValEx<T,Cause> empty(Cause cause) {
        return new ValEx<>(null, cause); 
      }
    

The get() method becomes a bit more involved, but would basically throw an IllegalStateException with the Cause either as the message or as the cause of the exception.

Wrapup

It looks like VarEx allows to combine the benefits of checked exceptions — visibility in the api — with the convenience of unchecked exceptions thrown only when the cause is a programming problem. By allowing VarEx to store a cause also just as a message, with no exception, the expensive exception generation can be prevented where a failure to provide a value is normal and expected. Still, if it is necessary to log the case, the VarEx improves over Optional in that in can provide the cause why no value is available.