Rare Uses of a “ControlFlowException”
Control flows are a “relict” from imperative programming, which has leaked into various other programming paradigms, including Java’s object oriented paradigm. Apart from the useful and ubiquitous branch and loop structures, there are also primitives (e.g. GOTO) and non-locals (e.g. exceptions). Let’s have a closer look at these controversial control flow techniques.
GOTO
goto
is a reserved word in the Java language. goto
is also a valid instruction in JVM bytecode. Yet, in Java, it isn’t easily possible to peform goto
operations. One example taken from this Stack Overflow question can be seen here:
Jumping forward
label: { // do stuff if (check) break label; // do more stuff }
In bytecode:
2 iload_1 [check] 3 ifeq 6 // Jumping forward 6 ..
Jumping backward
label: do { // do stuff if (check) continue label; // do more stuff break label; } while(true);
In bytecode:
2 iload_1 [check] 3 ifeq 9 6 goto 2 // Jumping backward 9 ..
Of course, these tricks are useful only in very very rare occasions, and even then, you might want to re-consider. Because we all know what happens when we use goto
in our code:
Drawing taken from xkcd: http://xkcd.com/292/
Breaking out of control flows with exceptions
Exceptions are a good tool to break out of a control flow structure in the event of an error or failure. But regular jumping downwards (without error or failure) can also be done using exceptions:
try { // Do stuff if (check) throw new Exception(); // Do more stuff } catch (Exception notReallyAnException) {}
This feels just as kludgy as the tricks involving labels, mentioned before.
Legitimate uses of exceptions for control flow:
However, there are some other very rare occasions, where exceptions are a good tool to break out of a complex, nested control flow (without error or failure). This may be the case when you’re parsing an XML document using a SAXParser
. Maybe, your logic is going to test the occurrence of at least three <check/>
elements, in case of which you may want to skip parsing the rest of the document. Here is how to implement the above:
Create a ControlFlowException
:
package com.example; public class ControlFlowException extends SAXException {}
Note that usually, you might prefer a RuntimeException
for this, but the SAX contracts require handler implementations to throw SAXException
instead.
Use that ControlFlowException
in a SAX handler:
package com.example; import java.io.File; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; public class Parse { public static void main(String[] args) throws Exception { SAXParser parser = SAXParserFactory .newInstance() .newSAXParser(); try { parser.parse(new File("test.xml"), new Handler()); System.out.println( "Less than 3 <check/> elements found."); } catch (ControlFlowException e) { System.out.println( "3 or more <check/> elements found."); } } private static class Handler extends DefaultHandler { int count; @Override public void startElement( String uri, String localName, String qName, Attributes attributes) { if ("check".equals(qName) && ++count >= 3) throw new ControlFlowException(); } } }
When to use exceptions for control flow:
The above practice seems reasonable with SAX, as SAX contracts expect such exceptions to happen, even if in this case, they’re not exceptions but regular control flow. Here are some indications about when to use the above practice in real world examples:
- You want to break out of a complex algorithm (as opposed to a simple block).
- You can implement “handlers” to introduce behaviour into complex algorithms.
- Those “handlers” explicitly allow throwing exceptions in their contracts.
- Your use case does not pull the weight of actually refactoring the complex algorithm.
A real-world example: Batch querying with jOOQ
In jOOQ, it is possible to “batch store” a collection of records. Instead of running a single SQL statement for every record, jOOQ collects all SQL statements and executes a JDBC batch operation to store them all at once.
As each record encapsulates its generated SQL rendering and execution for a given store()
call in an object-oriented way, it would be quite tricky to extract the SQL rendering algorithm in a reusable way, without breaking (or exposing) too many things. Instead, jOOQ’s batch operation implements this simple pseudo-algorithm:
// Pseudo-code attaching a "handler" that will // prevent query execution and throw exceptions // instead: context.attachQueryCollector(); // Collect the SQL for every store operation for (int i = 0; i < records.length; i++) { try { records[i].store(); } // The attached handler will result in this // exception being thrown rather than actually // storing records to the database catch (QueryCollectorException e) { // The exception is thrown after the rendered // SQL statement is available queries.add(e.query()); } }
A real-world example: Exceptionally changing behaviour
Another example from jOOQ shows how this technique can be useful to introduce exceptional behaviour that is applicable only in rare cases. As explained in issue #1520, some databases have a limitation regarding the number of possible bind values per statement. These are:
- SQLite: 999
- Ingres 10.1.0: 1024
- Sybase ASE 15.5: 2000
- SQL Server 2008: 2100
In order to circumvent this limitation, it will be necessary for jOOQ to inline all bind values, once the maximum has been reached. As jOOQ’s query model heavily encapsulates SQL rendering and variable binding behaviour by applying the composite pattern, it is not possible to know the number of bind values before traversing a query model tree. For more details about jOOQ’s query model architecture, consider this previous blog post: http://blog.jooq.org/2012/04/10/the-visitor-pattern-re-visited
So the solution is to render the SQL statement and count bind values that are effectively going to be rendered. A canonical implementation would be this pseudo code:
String sql; query.renderWith(countRenderer); if (countRenderer.bindValueCount() > maxBindValues) { sql = query.renderWithInlinedBindValues(); } else { sql = query.render(); }
As can be seen, a canonical implementation will need to render the SQL statement twice. The first rendering is used only to count the number of bind values, whereas the second rendering will generate the true SQL statement. The problem here is that the exceptional behaviour should only be put in place, once the exceptional event (too many bind values) occurs. A much better solution is to introduce a “handler” that counts bind values in a regular “rendering attempt”, throwing a ControlFlowException
for those few exceptional “attempts” where the number of bind values exceeds the maximum:
// Pseudo-code attaching a "handler" that will // abort query rendering once the maximum number // of bind values was exceeded: context.attachBindValueCounter(); String sql; try { // In most cases, this will succeed: sql = query.render(); } catch (ReRenderWithInlinedVariables e) { sql = query.renderWithInlinedBindValues(); }
The second solution is better, because:
- We only re-render the query in the exceptional case.
- We don’t finish rendering the query to calculate the actual count, but abort early for re-rendering. I.e. we don’t care if we have 2000, 5000, or 100000 bind values.
Conclusion
As with all exceptional techniques, remember to use them in the right moment. If in doubt, think again.