WhiningBuilder
From APIDesign
(→Explaining Why) |
|||
Line 52: | Line 52: | ||
one gets a compilation error, as the build method throws a [[checked exception]]. Even if one specifies '''name''', the error still remains. Only if both '''name''' and '''mimeType''' are specified the [[checked exception]]s go away (as they are replaced by runtime [[exception]]s). | one gets a compilation error, as the build method throws a [[checked exception]]. Even if one specifies '''name''', the error still remains. Only if both '''name''' and '''mimeType''' are specified the [[checked exception]]s go away (as they are replaced by runtime [[exception]]s). | ||
+ | |||
+ | The runtime and compile time behavior work in orchestration: if a [[checked exception]] is reported - it will also be thrown. E.g. trying to put a '''try'''/'''catch''' block around the the '''build()''' call makes no sense. If the [[exception]] is declared, it will really be generated. | ||
=== Explaining Why === | === Explaining Why === | ||
+ | |||
+ | One problem of the [[ChameleonBuilder]] lies in insufficient reporting of the proper error when an essential attribute isn't specified. That is not that great in the previous example either - one only gets an {{JDK|java/lang|Exception}} not really specifying the reason. But that can be easily extended: | ||
+ | |||
+ | <source lang="java"> | ||
+ | public final class MissingNameException extends Exception { | ||
+ | } | ||
+ | public final class MissingMIMETypeException extends Exception { | ||
+ | } | ||
+ | |||
+ | // and change the method | ||
+ | public static Builder<MissingNameException, MissingMIMETypeException> newFromText(String content) { | ||
+ | // uninitialized builder | ||
+ | return theBuilder; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | with this change the user always needs to handle the specific exception. If '''name''' isn't specified, one needs to catch '''MissingNameException'''. If '''mimeType''' attribute isn't specified, one needs to catch '''MissingMIMETypeException'''. Both exception classes may have proper [[Javadoc]] explaining that rather than catching them, it is advised to set the corresponding attribute. | ||
+ | |||
+ | With such support the error recovery for programmers should be easy. | ||
=== Drawbacks === | === Drawbacks === |
Revision as of 13:50, 16 June 2016
One problem with ChameleonBuilder is that the change of the return type works only for a single essential attribute. Can we modify the [[builder] pattern to work with multiple such attributes? Another problem is the reported error - it doesn't push the user towards proper fix. Can we improve that and encourage cluelessness in users of our builders?
Contents |
Multiple Throws
Java allows only a single return value - yet, it allows multiple throws - and throwing an exception is kind a return value as well. As the BuilderWithConditionalException it is possible use a generic type parameter to control whether the exception thrown from the build() method is checked exception or unchecked one. Let's do the same with multiple throws for two essential attributes (name and mimeType):
public final class Builder<MissingName extends Exception,MissingMIMEType extends Exception> { public Source build() throws MissingName, MissingMIMEType { if (this.name == null) { throw (MissingName)new Exception("name missing"); } if (this.mimetype == null) { throw (MissingMIMEType)new Exception("MIME type missing"); } // create and return source; } public static Builder<Exception,Exception> newFromText(String content) { // create new builder without name and MIME type return theBuilder; } public Builder<RuntimeException,MissingMIMEType> name(String name) { // specifying name this.name = name; // keeps the "mime type" exception unchanged but // turns the "name" exception into runtime one return (Builder<RuntimeException,MissingMIMEType>)this; } public Builder<MissingName,RuntimeException> mimeType(String mimeType) { // specifying MIME Type this.mimeType = mimeType; // keeps the "name" exception unchanged but // turns the "mime type" exception into runtime one return (Builder<MissingName,RuntimeException>)this; } }
Please note that MissingName and MissingMIMEType are generics type parameters - e.g. they aren't real exceptions, just specifications/place holders for the actual Exception subclass. This gives us early compile time warning as in ChameleonBuilder. When one tries to compile:
private static Source buildMySource() { return Source.newFromText("function hi() { print('Hi'; }"). build(); }
one gets a compilation error, as the build method throws a checked exception. Even if one specifies name, the error still remains. Only if both name and mimeType are specified the checked exceptions go away (as they are replaced by runtime exceptions).
The runtime and compile time behavior work in orchestration: if a checked exception is reported - it will also be thrown. E.g. trying to put a try/catch block around the the build() call makes no sense. If the exception is declared, it will really be generated.
Explaining Why
One problem of the ChameleonBuilder lies in insufficient reporting of the proper error when an essential attribute isn't specified. That is not that great in the previous example either - one only gets an Exception not really specifying the reason. But that can be easily extended:
public final class MissingNameException extends Exception { } public final class MissingMIMETypeException extends Exception { } // and change the method public static Builder<MissingNameException, MissingMIMETypeException> newFromText(String content) { // uninitialized builder return theBuilder; }
with this change the user always needs to handle the specific exception. If name isn't specified, one needs to catch MissingNameException. If mimeType attribute isn't specified, one needs to catch MissingMIMETypeException. Both exception classes may have proper Javadoc explaining that rather than catching them, it is advised to set the corresponding attribute.
With such support the error recovery for programmers should be easy.