1

I'm wondering where in a process should the control of a script exiting be placed?

If a function is used to determine whether a script should continue or not, should control, based on the result, be in the caller or the callee?

Are there scenarios where it could be in either?

(I'm sure this question has broader implications so please feel free to extend the answer to a higher level practice in programming. That would be great actually)

I will list some examples below to consider as options of conditional script exiting and how control could be delegated or not.

Imagine should_continue is checking that a supplied arg is valid and its validity is required for the script to continue. Otherwise it exits.

'''
ex 1: return state to parent process to determine if script continues
'''
def should_continue(bool):
  if bool:
    return True
  else:
    return False

def init():
  if should_continue(True):
    print 'pass'
  else:
    print 'fail'

'''
ex 2: return state only if script should continue
'''

def should_continue(bool):
  if bool:
    return True
  else:
    print 'fail'
    sys.exit() # we terminate from here

def init():
  if should_continue(True):
    print 'pass'


'''
ex 3: Don't return state. Script will continue if should_continue doesn't cause termination of script
'''

def should_continue(bool):
  if not bool:
    print 'fail'
    sys.exit()

def init():
  should_continue(True)
  print 'pass'
BIOS
  • 1,655
  • 5
  • 20
  • 36

1 Answers1

1

It depends.

If it is feasible to exit as soon as should_continue checks as false, then do so. The alternative is to pass "stop" returns all the way up the call-chain which need to be checked at each level; this is error-prone and hard to read.

If merely exiting is not-so-very-desirable, and it often is not in case cleanup or "hey I quit because ..." is needed, then you shouldn't just exit. Since you used Python as the example, it has a spectacularly clean method of Exception handling:

class TimeToSayGoodbye(Exception):
    def __init__(self, reason):
        self.reason = reason

with a deeply nested function:

def way_down_the_stack():
    if not should_continue:
         raise TimeToSayGoodbye('drive safely')

and then if you do nothing else, Python generates a useful (but not pretty) backtrace. If your main looks like

def main():
    try:
        my_application()
    except TimeToSayGoodbye as e:
        print (e.reason)

Then your top-level is in control of a Goodbye that happens anywhere and none of the fifteen methods between my_application and way_down_the_stack needs to even know that the program might spontaneously end.

msw
  • 42,753
  • 9
  • 87
  • 112
  • Thanks alot for the reply. One question. Is it good practice to have the exception handled by main rather than handled locally by the method that calls the method which raises the exception? – BIOS Jun 26 '13 at 23:30
  • 1
    "Catch what you can fix, adorn and re-raise what you expect but can't fix, and let the caller deal with what you didn't expect, even if the code that "deals" is seven levels up the stack in main." -me in a [prior answer](http://stackoverflow.com/questions/2843112/how-can-i-find-a-list-of-all-exceptions-that-a-given-library-function-throws-in/2843649#2843649). – msw Jun 26 '13 at 23:41
  • Great answer. Very concise. Thanks alot. Your other answer is really helpful for my purposes too :) – BIOS Jun 26 '13 at 23:46