Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple 'endings' to a function - return an object, return a string, raise an exception?

Tags:

python

This one's a structure design problem, I guess. Back for some advice.

To start: I'm writing a module. Hence the effort of making it as usable to potential developers as possible.

Inside an object (let's call it Swoosh) I have a method which, when called, may result in either success (a new object is returned -- for insight: it's an httplib.HTTPResponse) or failure (surprising, isn't it?).

I'm having trouble deciding how to handle failures. There are two main cases here:

  1. user supplied data that was incorrect
  2. data was okay, but user interaction will be needed () - I need to pass back to the user a string that he or she will need to use in some way.

In (1) I decided to raise ValueError() with an appropriate description. In (2), as I need to actually pass a str back to the user.. I'm not sure about whether it would be best to just return a string and leave it to the user to check what the function returned (httplib.HTTPResponse or str) or raise a custom exception? Is passing data through raising exceptions a good idea? I don't think I've seen this done anywhere, but on the other hand - I haven't seen much.

What would you, as a developer, expect from an object/function like this?

Or perhaps you find the whole design ridiculous - let me know, I'll happily learn.

like image 836
maligree Avatar asked Dec 06 '22 20:12

maligree


1 Answers

As much as I like the approach of handling both cases with specifically-typed exceptions, I'm going to offer a different approach in case it helps: callbacks.

Callbacks tend to work better if you're already using an asynchronous framework like Twisted, but that's not their only place. So you might have a method that takes a function for each outcome, like this:

def do_request(on_success, on_interaction_needed, on_failure):
    """
    Submits the swoosh request, and awaits a response.

    If no user interaction is needed, calls on_success with a
    httplib.HTTPResponse object.

    If user interaction is needed, on_interaction_needed is
    called with a single string parameter.

    If the request failed, a ValueError is passed to on_failure
    """
    response = sumbit_request()
    if response.is_fine():
        on_success(response)
    elif response.is_partial()
        on_interaction_needed(response.message)
    else:
        on_failure(ValueError(response.message))

Being Python, there are a million ways to do this. You might not like passing an exception to a function, so you maybe just take a callback for the user input scenario. Also, you might pass the callbacks in to the Swoosh initialiser instead.

But there are drawbacks to this too, such as:

  • Carelessness may result in spaghetti code
  • You're allowing your caller to inject logic into your function (eg. exceptions raised in the callback will propagate out of Swoosh)
  • My example here is simple, your actual function might not be

As usual, careful consideration and good documentation should avoid these problems. In theory.

like image 107
detly Avatar answered May 24 '23 22:05

detly