Best way to check for "break" conditions when calling a function

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Pomelo

I have a function do_something(), that checks for different conditions in order to “break”, like so:

func do_something():
    if check_1:
        print("You cant do something because of check_1")
        return
    if check_2:
        ...
    ...
    # actual body of do_something()

All fine and dandy, but lets say I need to call do_something() inside another function another_func(), now I need to know whether do_something() does or does not “break” when I call another_func(). Of course I could copy and paste the same checks, but there must be a better solution. One solution I came about, was to make do_something() also return a value (in my game the functions I am dealing with just manipulate data and dont return anything) and check whether this value was returned when I called the function:

func do_something():
    # several checks
    ... 
    # body of do_something() 
    ...
    return true

func another_func():
    ...
    var didnt_break = do_something()
    if didnt_break:
        # continue with another_func()

Although this works it just feels wrong since do_something() has a name (and purpose), more akin to shuffle_deck(), and calling it when declaring a var seems kinda ugly. Another solution I thought of was emiting signals and waiting for them, but since there are lots of functions, I dont want to start defining and conecting tons of signals just for this.

Anyway, is there a “correct” way of aproaching this?

:bust_in_silhouette: Reply From: jgodfrey

If you “need to know something” as a result of calling a function, the most straight-forward mechanism for that is to have the function return a value (as you mentioned). It’s not at all uncommon for functions to return a success or failure result code, which it seems is what you’re after here.

If it helps, you could rename such functions to be more clear. For example do_something_or_fail(). Also, assuming a Boolean (True / False) return value, you could clean up the caller a little like this:

if do_something():
    do_something_else():

So, no intermediate variable…

Thanks! Since I dont have a background in coding, there are some practices that I am not so sure about how common or correct they are. Will do that then!

Pomelo | 2022-06-06 12:44

:bust_in_silhouette: Reply From: Ninfur

If the checks you are doing is to verify that the function is called with valid arguments, one alternative is to use assertions instead. These are simple conditional checks that will raise an error if the condition is not met.

Ex.

func set_speed(speed):
    assert(speed >= 0) # Will raise an error if speed is negative
    # ...

Otherwise I agree with jgodfrey’s answer. Use the return value to indicate success or failure. This could be something like true/false, null or -1, depending on the context.

Didnt know about assertions, will check it out!

Pomelo | 2022-06-06 12:46

For anyone reading this later, in my game I ended up not using jgodfrey awnser since as the assert method pauses the project, the rest of another_func() will not execute without me knowing, when do_something() fails. This didnt happen using the return nothing, way of managing “breaks”.

Still good to know that checking if a function did succed, by returning a value, is a common method. It might be usefull for dealing with other situation where the do_something() is expected to sometimes fail as a common part of the game.

Pomelo | 2022-06-06 15:59

Be aware that asserts are only useful in a DEBUG build - that is, they are IGNORED in non-DEBUG builds. If that’s what you want/need, then they are quite appropriate. Really, they’re for checking basic assumptions about the logic and state of your code. For non-debug, runtime flow control, you’ll need to use other, non-assert based mechanisms.

jgodfrey | 2022-06-06 23:42

Thanks for the clarification! I did read that in the documentation. Since i am checking for not intended behaviour, once everything is tested, these checks should have outlived their use I guess, at least in principle.

I get that in a plausible future alpha or test build, people are bound to find bugs, but I am only using these checks in very “structural” or “ground level” data manipulation that I expect to only break by making silly mistakes when adding stuff.

Thanks a lot for the insight!

Pomelo | 2022-06-07 16:54

Yep, your described use-case sounds like a good match for asserts. Good luck with the project.

jgodfrey | 2022-06-07 17:20