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'
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.