Is there a 'best' way to exit functions?

I've been reading about Flow Control for Aborts trying to learn the differences between Abort, AbortOnValue and return. It leaves me wondering which is the "best" way to exit a function. My functions included the following if() snippet after DoPrompts until I discovered AbortOnValue is quicker. Sometimes I need to return to a data folder before exiting though so I'm forced to use the if() version.

For errors, a simple Abort with message seems appropriate. For 'Cancel' buttons, I can't decide which is better - perhaps return is so that calling functions have a chance to continue? Does anyone have a good understanding of why you might choose one over the other?


    // typical exit if user cancels DoPrompt
    If (V_flag)
        return -1       // returns -1
    endif
   
    // quicker exit
    AbortOnValue V_flag, -1     // returns NAN
   
    // need to return user home
    If (V_flag)
        SetDataFolder sav0
        return -1       // this one
        //AbortOnValue 1, -1    // or this one?
    endif
Abort and AbortOnValue are for use with fatal errors when you want procedure execution to stop. This is generally for situations that should never happen (to catch bugs) and for situations that are so rare that it is not worth your time to handle them more gracefully, or for informal programming where gracefulness is not needed.

For other situations, you should return an error code that the calling routines can interpret. We use 0 to mean success and non-zero to mean failure. -1 is usually used to mean cancel. The calling routine can then decide whether to retry, abort or return the error code to its calling routine.

I forgot to mention that AbortOnValue can be used with in a single function to transfer control to a catch block. If called outside of a try block, it aborts procedure execution completely. To see this, exercise the following code as follows:

Tester(1)   // Aborts procedure execution
Tester(2)   // Transfers control to a catch block


Function Test1()
    Print "Starting Test1"
   
    // AbortOnValue outside of a try-catch block aborts procedure execution
    AbortOnValue 1, 1234
   
    Print "Finished Test1"  // Will never execute
End

Function Test2()
    Print "Starting Test2"
   
    // AbortOnValue inside of a try-catch block tranfers control to the catch block
    try
        AbortOnValue 1, 1234
    catch
        Printf "An error was thrown with code %d\r", V_abortCode
    endtry
   
    Print "Finished Test2"
End

Function Tester(which)
    Variable which  // 1 for Test1, 2 for Test2

    Print "Starting Tester"
   
    switch(which)
        case 1:
            Test1()
            break
        case 2:
            Test2()
            break
    endswitch
   
    Print "Finished Tester"
End

I generally adhere to the notion that using Abort is not good programming (AbortOnValue in a try/catch block is fine).

If I want to alert the user to a problem, I'll use DoAlert to present a simple message, and then use Howard's idea of returning a simple success/fail/cancel value.

I haven't used Abort for 15 years.

--Jim Prouty
Software Engineer, WaveMetrics, Inc.
JimProuty wrote:
I generally adhere to the notion that using Abort is not good programming (AbortOnValue in a try/catch block is fine).

If I want to alert the user to a problem, I'll use DoAlert to present a simple message, and then use Howard's idea of returning a simple success/fail/cancel value.


I partially agree, partially disagree with this. I'd deepen your statement to say that Abort should never be part of any program flow that you expect the user to reach. That means that, if your program executes perfectly (no bugs), no Abort statements should ever be executed.

I tend to sprinkle quite a few Abort statements into my code to aid me in catching possible bugs. Basically, if I know that something should never occur, then I'll add in that case and have it trigger an Abort statement. An example here would be adding a default statement in a switch statement even where I don't expect one to be necessary. Example:
string color // my code should only call this with red, green or blue
strswitch(color)
 case "green":
 case "blue":
 case "red":
  //do something
  break
 default:
  Abort "Invalid input \"" + color "\" to strswitch at blah"
  break
endswitch

The idea here is that, when my program enters undefined territory, I would like it to blow up as soon as possible and as irreversibly as possible.
Thanks everyone for clearing it up. I'm glad to hear Return is preferred over Abort so I won't have to edit anything.

741, that's an excellent idea for catching bugs - simple & informative!
741 wrote:
The idea here is that, when my program enters undefined territory, I would like it to blow up as soon as possible and as irreversibly as possible.


If I'm developing code that I plan to not give to anyone else, I'd be okay with that. But code the user "should never reach" has a way of reaching users. So I'd rather use:

default:
DoAlert 0, "invalid input..."
return errorCodeThatAllowsMeToLimpAlongGracefully


Remember, Abort alters the control flow, often in ways that may surprise you.

--Jim Prouty
Software Engineer, WaveMetrics, Inc.