views:

323

answers:

10

I want to re-write a method that has way too many nested if statements.

I came up with this approach and wanted your opinions:

public void MyMethod()
{
   bool hasFailed = false;

   try
   {
      GetNewOrders(out hasFailed);

      if(!hasFailed)
          CheckInventory(out hasFailed);

      if(!hasFailed)
          PreOrder(out hasFailed);              

      // etc
   }
   catch(Exception ex)
   {
   }
   finally
   {
      if(hasFailed)
      {
           // do something
      }
   }
}
+1  A: 

This doesn't really look good to me. The use of the hasFailed variable is really not nice. if GetNewOrders fails with an exception, you for instance end up inside the catch block with hasFailed = false !

Opposed to other answers here I believe there MAY be legitimate uses for boolean "hasFailed" that are not exceptional. But I really don't think you should mix such a condition into your exception handler.

krosenvold
each method I am calling is wrapped in its own try/catch.
Blankman
why is that a bad thing, please explain! Shouldn't I be in the catch block with hasFailed = false??
Blankman
Blankman -- what happens if the first n calls succeed, then the last one throws an exception? From what you've shown the catch block does nothing, and the finally block only cleans up if hasFailed=true. So would things be left in an inconsistent state?
Dave Costa
+7  A: 

Not really. Your methods should raise an exception in case of an error to be caught by your "catch" block.

Otávio Décio
I think so, too. This also shortens your code as you don't need the if-statements anymore.
schnaader
and FWIW, the catch block should be doing something with the exceptions it catches - not silently ignore them all, which kind of defeat the point of having exceptions...
bruno desthuilliers
@ocdecio -- I disagree. There is (or could be) a difference between exceptional behavior and failure of a method. GetOrders may fail if there are no orders, but cause an exception if the connection string is invalid, for instance.
tvanfosson
@tvanfosson: "GetOrders may fail if there are no orders" - think about it, is the absence of orders an error??
Otávio Décio
@ocdecio - no, I don't think so. without reference to the current code, I would say that the absence of orders is a normal condition if none have been entered yet. If I were searching for a particular order and it wasn't found, that might be an exception.
tvanfosson
@tvanfosson: I think it is hard to judge without better understanding of his business requirements.
Otávio Décio
@ocdecio: agreed, but I think the blanket statement of "raise an exception on error" is too broad. Not all "errors" are exceptional, some may be expected and can be handled via return values. In this case he seems to expect both "failure" and "exception" and I think that's acceptable.
tvanfosson
+8  A: 

I've done stuff similar to that, but without the exception handling:

BOOL ok = CallSomeFunction();
if( ok ) ok = CallSomeOtherFunction();
if( ok ) ok = CallYetAnotherFunction();
if( ok ) ok = WowThatsALotOfFunctions();
if( !ok ) {
    // handle failure
}

Or if you want to be clever:

BOOL ok = CallSomeFunction();
ok &= CallSomeOtherFunction();
ok &= CallYetAnotherFunction();
...

If you are using exceptions anyway, why do you need the hasFailed variable?

Graeme Perrow
at the end of the method, I have to to something special if hasFailed = true. (business rules)
Blankman
Charles Bretana
+3  A: 

Without commenting on the try/catch stuff since I really don't know what is going on there, I would change it so the called methods return true/false for success and then just check them depending on the boolean short-circuiting to avoid calling later methods if the preceding method failed.

public void MyMethod()
{
    bool success = false;
    try
    {
         success = GetNewOrders()
                   && CheckInventory()
                   && PreOrder();
         // etc
    }
    catch(Exception ex)   {   }
    finally
    {
        if(!success)
        {
        }
    }
}
tvanfosson
A: 

I know I'll probably duplicate a few posts: What's wrong with else? You could also use lazy evaluation (a() && b()) to link methods - but that relies on status being given as return value, which is more readable anyhow IMHO.

I don't agree with posters that you should raise an exception, because exceptions should be raised if program faults occur or the program enters an exceptional state because of operations. Exceptions are not business logic.

mstrobl
A: 

I would do it like this:

public void MyMethod()
{
bool success = false;   
try
   {

      GetNewOrders(); // throw GetNewOrdersFailedException    
      CheckInventory(); // throw CheckInventoryFailedException
      PreOrder(); // throw PreOrderFailedException  
      success = true;            
   }
   catch( GetNewOrdersFailedException e)
   {
     // Fix it or rollback
   }
   catch( CheckInventoryFailedException e)
   {
     // Fix it or rollback
   }
   catch( PreOrderFailedException e)
   {
     // Fix it or rollback
   }
   finally
   {
      //release resources;
   }
}

Extending an exception is rather trivial,

public NewExecption : BaseExceptionType {}

chris
Using exceptions to implement business logic when return values could suffice is an expensive way to accomplish this. I prefer to reserve exceptions for truly exceptional conditions.
tvanfosson
Well, based on the question, we don't really know why this is happening. I'm assuming that data has been validated, and failure truly is exceptional. Although, I would probably assume that CheckInventory will fail due to normal circumstances, and probably should return false instead.
chris
+2  A: 

As far as I can see this is an example of cascade steps where second and third one will be executed if first and first and second are valid, i.e. return hasFailed==false.

This code can be made much more elegant using Template Method and Decorator design pattern.

You need one interface, concrete implementation, abstract class and several subclasses of the abstract class.

public interface Validator {
    public boolean isValid();
}

public class GetNewOrders implements Validator {
    public boolean isValid() {
       // same code as your GetNewOrders method
    }
}

public abstract class AbstractValidator implements Validator {
    private final Validator validator;

    public AbstractValidator(Validator validator) {
        this.validator = validator;
    }
    protected boolean predicate();
    protected boolean isInvalid();

    public final boolean isValid() {
        if (!this.validator.isValid() && predicate() && isInvalid())
            return false;
        return true;
    }
}

public class CheckInventory extends AbstractValidator {
    public CheckInventory(Validator validator) {
        super(validator);
    }
    @Override
    public boolean predicate() {
        return true;
    }
    @Override
    public boolean isInvalid() {
        // same code as your CheckInventory method
    }
}

public class PreOrder extends AbstractValidator {
    public CheckInventory(Validator validator) {
        super(validator);
    }
    @Override
    public boolean predicate() {
        return true;
    }
    @Override
    public boolean isInvalid() {
        // same code as your PreOrder method
    }
}

Now your method can look much more elegant:

public void MyMethod() {
    bool success = false;
    try {
        Validator validator = new GetNewOrders();
        validator = new CheckInventory(validator);
        validator = new PreOrder(validator);
        success = validator.isValid();
    } finally {
        if (!success) {
            // do something
        }
    }
}

Validator object can be created in one line, but I prefer this style since it makes obvious the order of validation. Creating new validation link in the chain is matter of subclassing AbstractValidator class and implementation of predicate and isInvalid methods.

Boris Pavlović
A: 

Well, I don't like code that appears to get a list of orders and then process them, and then stop processing them when an error occurs, when surely it should skip that order and move to the next? The only thing to completely fail on is when the database (source of orders, destination of preorders) dies. I think that the entire logic is a bit funky really, but maybe that's because I don't have experience in the language you are using.

try {
  // Get all of the orders here
  // Either in bulk, or just a list of the new order ids that you'll call the DB
  // each time for, i'll use the former for clarity.
  List<Order> orders = getNewOrders();

  // If no new orders, we will cry a little and look for a new job
  if (orders != null && orders.size() > 0) {
    for (Order o : orders) {
      try {
        for (OrderItem i : o.getOrderItems()) {
          if (checkInventory(i)) {
            // Reserve that item for this order
            preOrder(o, i);
          } else {
            // Out of stock, call the magic out of stock function
            failOrderItem(o, i);
          }
        }
      } catch (OrderProcessingException ope) {
        // log error and flag this order as requiring attention and
        // other things relating to order processing errors that aren't database related
      }
    }
  } else {
    shedTears();
  }
} catch (SQLException e) {
  // Database Error, log and flag to developers for investigation
}
JeeBee
A: 

Your new approach is not that bad for a simple set of instructions, but what happens when additional steps are added? Do you / would you ever require transactional behavior? (What if PreOrder fails? or If the next step after PreOrder fails?)

Looking forward, I would use the command pattern: http://en.wikipedia.org/wiki/Command_pattern

...and encapsulate each action as a concrete command implementing Execute() and Undo().

Then it's just a matter of creating a queue of commands and looping until failure or an empty queue. If any step fails, then simply stop and execute Undo() in order on the previous commands. Easy.

Robert Venables
A: 

Chris solution is the most correct. But I think you should not do more than you need. Solution should be extandable and that's enough.

  1. Change value of a parameter is a bad practice.
  2. Never use empty generic catch statement, at least add a comment why you do so.
  3. Make the methods throw exception and handle them where it is appropriate to do so.

So now it is much more elegant :)

public void MyMethod()
{
   try
   {
      GetNewOrders();
      CheckInventory();
      PreOrder();
      // etc
   }
   finally
   {
      // do something
   }
}
Arthur