Thursday, September 8, 2011

Exceptions for Control Flow: Always Wrong?

It is generally accepted that exceptions should never be used for control of flow and should only be used for exceptional conditions. On face value, that assertion seems to make sense. For instance, given a method that iterates over an array, the user of the method should not be expected to catch a NoSuchElementException because the method does not make use of a hasNext() method.

If a state checking method is available to help determine if another method can be called, we would all assume it should be used. However, what about cases where a series of methods must be called in sequence to accomplish a larger overall task? In a scenario I'm thinking off, a series of individual methods must be called, where each in the sequence is called only if the previous call succeeded.

Would disobeying the exceptions rule possibly make for cleaner code in such a case?

To work out my thoughts, I thought of a home building example. Let's simplify building a house to three steps: laying a foundation, putting up the framing and walls, and then adding the roof.

Let's also imagine a contractor who's job is to build a house, ensuring its marked as condemned is any of the construction fails. If the contractor attempts to add the framing without the foundation, or the roof without the framing, a catastrophe would surely happen. To avoid the catastrophe, the builder must check the house to ensure the previous step completed before moving on to the next.

Like an Iterator with a hasNext() method, the House class has methods or state variables indicating whether it has the requirements for the next phase of construction and something similarly for its overall state: whether or not its livable or condemned.

    class House {
boolean hasFoundation
boolean hasFraming
boolean hasRoof
boolean isCondemned
}

The Contractor classs has one public method, buildHouse(), that returns a House, livable or not, and a private method for completing each step in the construction process:

    class Contractor {

public House buildHouse() {
def house = new House()

addFoundationTo(house)

if (house.hasFoundation) {
addFramingTo(house)
if (house.hasFraming) {
addRoofTo(house)
if (!house.hasRoof()) {
house.isCondemend = true
}
} else {
house.isCondemned = true
}
} else {
house.isCondemned = true
}

house
}

private def addFoundationTo(house) {
//...
}

private def addFramingTo(house) {
//...
}

private def addRoofTo(house) {
//...
}
}

This contractor is overly cautious as he wants to condemn the house as soon as possible if one of the constuction steps fails to avoid a catastrophe. Another implementation may wait till the building process is completed before condemning the house:

    class Contractor {

public House buildHouse() {
def house = new House()

addFoundationTo(house)

if (house.hasFoundation) {
addFramingTo(house)
if (house.hasFraming) {
addRoofTo(house)
}
}

if (!house.hasRoof()) {
house.isCondemned = true
}

house
}
}

This contractor only looks at the roof and the end of the process to determine if the house should be condemned, which may lead to a lawsuit if the someone where to change the order in which the steps were executed. The contractor's check at the end could include looking at the foundation and framing as well, but he is overly trusting of his workers not to proceeded and the extra checks may be costly.

What if a different contractor required a complaint be raised by a worker if any of the home building steps fails and condemns the house if he receives the complaint before attempting the next step?

    class Contractor {

public House buildHouse() {
def house = new House()

try {
addFoundationTo(house)
addFramingTo(house)
addRoofTo(house)
} catch (ConstructionComplaint complaint) {
house.isCondemned = true
}

house
}

private def addFoundationTo(house) throws ConstructionComplaint {
//...
}

private def addFramingTo(house) throws ConstructionComplaint {
//...
}

private def addRoofTo(house) throws ConstuctionComplaint {
//...
}
}

Here, a specific, but uniform, exception is given to the builder if any of the steps fail, ensuring the next step in the process is not attempted, avoiding a catastrophe.

An argument for this procedure may include that adding another step, like adding a garge, does not increase the complexity of the building process by adding more evaluation (branching), or require the builder to explicitly check that a garge can be added. Conversely, this approach may be overly trusting in that it requires a each worker method properly files a complaint.

I think it's safe to say we've all seen complex code as least as complex as the first implentation of the buildHouse() method. Is it worth asking that the use of exceptions would make the code cleaner? On the other hand, such cases usually present a situation where the user is required to code around what may be a poor design.

This topic and discussion may be nothing new, but I think it should help give thought to reasoning about the rule stated earlier regarding using exceptions for only exceptional conditions. Here were not talking avoiding programmer errors, such as trying to access a non-existent array element, but using exceptions to simplify a process.

While I'm not trying to suggest generally that using exceptions to control flow makes for better programs, I do think it is worthwhlie to consider such rules and understand why they may exist in the first place.

No comments:

Post a Comment