'. '

BuilderWithConditionalException

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(Throw only for I/O)
(Throw only for I/O)
Line 58: Line 58:
The actual code is shown at [https://github.com/jtulach/truffle/commit/a61f1e18356399ce6a8a2fbffa2b2a9fdc0e5301 my github commit]. It properly encourages users to handle I/O exceptions when needed, but doesn't annoy them when there is no need.
The actual code is shown at [https://github.com/jtulach/truffle/commit/a61f1e18356399ce6a8a2fbffa2b2a9fdc0e5301 my github commit]. It properly encourages users to handle I/O exceptions when needed, but doesn't annoy them when there is no need.
 +
 +
=== Changing the State ===
 +
 +
The nice thing on this pattern in [[Java]] is that it is only compile time only - thanks to the [[erasure]] of generic types, there is no overhead during runtime. Just the compiler has enough information to advice users whether they should expect an {{JDK|java/io|IOException}} or not.
 +
 +
And it has yet another benefit: the [[builder]] can change its state depending on the methods that are being called (see [https://github.com/jtulach/truffle/commit/584606a657d72cdbf1624eb53c0bc55fbf120ad9 change]):
 +
 +
<source lang="java">
 +
public Builder<R, RuntimeException> content(String code) {
 +
this.content = code;
 +
return (Builder<R, RuntimeException>) this;
 +
}
 +
</source>
 +
 +
Now one can start with a builder based on ''newFromFile'', and still switch to mode that doesn't throw an exception at all.

Revision as of 14:01, 10 June 2016

The builder pattern is gaining more and more popularity in the APIs that I design: In Truffle API we are trying to use it a lot. For example an instance of the PolyglotEngine is constructed via a builder obtained from its newBuilder() method.

Contents

Builder for Source

These days I am trying to use the builder pattern also for construction of Source. Rather than having various (and overloaded) methods with fromFileName, etc. we'd like to have:

Source source = Source.newFromFile(file).
   mimetype("text/javascript").
   name("FancyName.js").
   build();

It works fine and allows the users to specify only those properties that are needed - for example one can omit the name - then it would be derived from the name of the file. The same applies to mimetype.

Throw on I/O Error

The builder pattern delays reading of the actual file up until the build() method is called. As reading of a file means an I/O operation and as each I/O operation in Java may yield checked IOException, it is desirable for the build() method to propagate it to the caller:

public Source build() throws IOException {
   // construct and
   return source;
}

However there is more. The builder may also construct a {{truffle|com/oracle/truffle/api/source|Source} object from already provided text. The usage looks like:

Source source = Source.newFromText("6 * 7").
   mimetype("text/javascript").
   name("FancyName.js").
   build();

but in this case there is no I/O operation - e.g. throwing IOException from the build() method isn't appropriate. Doing so would actually expose the biggest flaw of checked exceptions in Java - forcing people to catch them when there is no need/no recovery. Can that be fixed?

Throw only for I/O

The option that I come up with is to parametrize the builder with the exception the build() method is going to throw:

public final class Builder<E extends Exception> {
  public Source build() throws E {
    // construct and/or read the content and
    return source;
  }
 
  public static Builder<IOException> newFromFile(File file) {
    // ....
  }
 
  public static Builder<RuntimeException> newFromText(String text) {
    // ...
  }
}

The actual code is shown at my github commit. It properly encourages users to handle I/O exceptions when needed, but doesn't annoy them when there is no need.

Changing the State

The nice thing on this pattern in Java is that it is only compile time only - thanks to the erasure of generic types, there is no overhead during runtime. Just the compiler has enough information to advice users whether they should expect an IOException or not.

And it has yet another benefit: the builder can change its state depending on the methods that are being called (see change):

public Builder<R, RuntimeException> content(String code) {
    this.content = code;
    return (Builder<R, RuntimeException>) this;
}

Now one can start with a builder based on newFromFile, and still switch to mode that doesn't throw an exception at all.

Personal tools
buy