BuilderWithConditionalException

From APIDesign

(Difference between revisions)
Jump to: navigation, search
(Throw on I/O Error)
Current revision (07:55, 13 June 2016) (edit) (undo)
(Summary)
 
(11 intermediate revisions not shown.)
Line 3: Line 3:
== Builder for Source ==
== Builder for Source ==
-
These days [[I]] am trying to use the [[builder]] pattern also for construction of {{truffle|com/oracle/truffle/api/source|Source}}. Rather than having various (and overloaded) methods with '''fromFileName''', etc. we'd like to have:
+
These days [[I]] am trying to use the [[builder]] pattern also for construction of {{truffle|com/oracle/truffle/api/source|Source}}. Rather than having various (and overloaded) methods named '''fromFileName''' with various number and order of parameters, etc. we'd like to have:
<source lang="java">
<source lang="java">
Line 25: Line 25:
</source>
</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:
+
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 lang="java">
<source lang="java">
Line 38: Line 38:
=== Throw only for I/O ===
=== Throw only for I/O ===
-
[[TBD]]
+
The option that [[I]] came up with is to parametrize the [[builder]] with the exception the ''build()'' method is going to throw:
 +
 
 +
<source lang="java">
 +
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) {
 +
// ...
 +
}
 +
}
 +
</source>
 +
 
 +
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>
 +
 
 +
Obviously, when one specifies the content directly, there is no I/O when the ''build()'' method is called and there should be no need to require catching of an {{JDK|java/io|IOException}}. With the above change one can start with a builder based on ''newFromFile'', and still switch to mode that doesn't throw an {{JDK|java/io|IOException}} at all:
 +
 
 +
<source lang="java">
 +
Source source = Source.newFromFile(file).
 +
content("var x = 42\n").
 +
build(); // no IOException at all
 +
</source>
 +
 
 +
== Summary ==
 +
 
 +
By giving the [[builder]] a generic error type we allow fine grain control over possible errors states that may accumulate when calling various [[builder]] configuration methods. The whole solution keeps the runtime behavior of the [[builder]] pattern - after applying [[erasure]] of generic types, the whole type information (visible to compiler and [[API]] user) disappears.
 +
 
 +
 
 +
[[Category:APIDesignPatterns]]
 +
[[Category:APIDesignPatterns:Creational]]
 +
[[Category:APIDesignPatterns:Exceptions]]

Current revision

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 named fromFileName with various number and order of parameters, 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 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 came 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;
}

Obviously, when one specifies the content directly, there is no I/O when the build() method is called and there should be no need to require catching of an IOException. With the above change one can start with a builder based on newFromFile, and still switch to mode that doesn't throw an IOException at all:

Source source = Source.newFromFile(file).
   content("var x = 42\n").
   build(); // no IOException at all

Summary

By giving the builder a generic error type we allow fine grain control over possible errors states that may accumulate when calling various builder configuration methods. The whole solution keeps the runtime behavior of the builder pattern - after applying erasure of generic types, the whole type information (visible to compiler and API user) disappears.

Personal tools
buy