3

I'm currently working on a big file I have to parse and process and each step needs to be done in an order as I do SQL queries and need inserted IDs to make other insertions ... The problem is I often find my code to look like this :

if( function1() ) {
    // process things to continue
    if( function2() ) {
        // process things to continue

        // ... and so on

    } else {
        // fail2
    }
} else {
    // fail1
}

And same goes for form validations, etc... and it gets really hard to follow the steps, and it's just not pleasant to see in the code.

As I said, I need function1 to be done before function2, so I can't do otherwise than be sure previous functions are done correctly.

Is there as proper way, or a design pattern, to code this kind of chained conditional functions ?

(FYI : I use OO PHP5, and I'm pretty much a novice in terms of standards in programming, so please don't behead me if this is casual !)

(concerning possible duplicate of When, if ever, should I daisy chain functions? ) : the functions can be used seperately and thus can't be used with daisy-chaining (see "B" in the given link).

The //Fails are here to "raise a flag" to say there has been a problem and further in the code (after the if/elses) it rollbacks all changes (with PDO's transaction rollback). But I would like to warn user there has been a problem, write a message (database error ? empty values ? ...) and maybe pass more informations like variables to be displayed/debugged.

Strannch
  • 95
  • 1
  • 5
  • Monads are nice for this; especially the Optional/Maybe monads. If your language supports them, I'd look into these. – Carcigenicate Jun 11 '15 at 14:32
  • I use PHP5 and according to google, it doesn't natively support Monads (I didn't really grasp the concept of a Monad though). I'll check it anyway, thanks. – Strannch Jun 11 '15 at 14:36
  • 3
    I'd say that it highly depends on the `//fail` part. For example if it's common to all `else`s, then maybe just throwing exception from functionN() would be fine? – Gerino Jun 11 '15 at 14:37
  • Righ now, they just "raise a flag" to say there has been a problem and further in the code, after the if/elses, it rollbacks all changes (transaction rollback with PDO). But then I would like to warn user there has been a problem, write a message (database error ? empty values ? ...) and maybe pass more informations like variables to be displayed. – Strannch Jun 11 '15 at 14:51
  • For rare errors with no proper local response, apart from aborting the transaction, I'd use exceptions. (e.g. your database error) – CodesInChaos Jun 11 '15 at 14:57
  • What do you mean with "For rare errors with no proper **local response**" ? I thougt of exceptions, but can they be used to pass variables ? – Strannch Jun 11 '15 at 15:06
  • Just in case, there is a [library for functional programming in PHP](https://github.com/widmogrod/php-functional). It includes all the stuff that would be needed for easy chaining of functions that may fail. It would take a certain change of approach in the rest of the code, though. Also related to the problem: [Railway-Oriented programming](http://www.slideshare.net/ScottWlaschin/railway-oriented-programming) slides. – 9000 Jun 11 '15 at 15:07
  • You don't need to say "UPDATE" and "UPDATE2." Every Stack Exchange post has a detailed [edit history](http://programmers.stackexchange.com/posts/286485/revisions) that anyone can view. – Robert Harvey Jun 11 '15 at 15:27
  • 1
    see also: [Elegant ways to handle if(if else) else](http://programmers.stackexchange.com/q/122485/31260) – gnat Jun 11 '15 at 21:21
  • 1
    Thank you @gnat. I should consider more [orthogonality](http://programmers.stackexchange.com/a/122625/183274) and probably apply the return/break solutions. – Strannch Jun 12 '15 at 07:31

2 Answers2

4

the easiest way to refactor that is to use a success variable, eg

res = do_stuff();
if (res)
    res = do_more_stuff();
if (res)
    ....

If you use a counter instead of a boolean success variable, then you can also tell if all of the steps succeeded at the end (ie. the counter will equal the number of steps that successfully ran)

The one thing you cannot do with this pattern is to have different 'else' clauses for error, but you can instead throw an exception to handle the flow (not so recommended if you expect the steps to fail). You could track the last method that was executed and have a switch handler at the end of the method that called the appropriate fail routine for each, but that can get messy - depends on your circumstances of course.

There are more complex ways to handle this - eg instead of calling each method, call a helper that takes 2 functions as parameters, the method to call and the method to call on failure, then the helper can return true/false as before, or construct a collection of functions to call and iterate through it, calling each one in turn.. but these are best left for special circumstances when you need them. As you say you're a bit of a novice, I'd go for the pattern I first suggested, its simple.

gbjbaanb
  • 48,354
  • 6
  • 102
  • 172
  • I think I will go with your first suggestion. Thanksyou. I let the question unanswered for now, but I'll validate it later if no one comes with other ideas ^^ – Strannch Jun 11 '15 at 15:17
  • I use this pattern too. Clear and simple, easy enough to follow and understand. – Spidey Jun 11 '15 at 16:52
0

You can factor out the error handling, so that your function can simply return on error. In C/C++ this would look something like this:

int handleError(int errorCode, char* message) {
    if(errorCode) {
        //Notify user, whatever
    }
    return errorCode;
}

int functionThatCanFail(...) {
    int result;

    if(result = handleError(do_stuff(), "do_stuff() failed")) return result;
    if(result = handleError(do_more_stuff(), "do_more_stuff() failed")) return result;
}

Of course, you can have a small set of such error handling functions to distinguish, for example, between fatal errors, errors and warnings or that handle different error returning conventions (error code vs. NULL pointer, for instance). In C++, the later differentiation can be handled by a template, but that's not a language agnostic consideration anymore.