views:

146

answers:

4

Hi*,

I have been doing some work with python-couchdb and desktopcouch. In one of the patches I submitted I wrapped the db.update function from couchdb. For anyone that is not familiar with python-couchdb the function is the following:

def update(self, documents, **options):
    """Perform a bulk update or insertion of the given documents using a
    single HTTP request.

    >>> server = Server('http://localhost:5984/')
    >>> db = server.create('python-tests')
    >>> for doc in db.update([
    ...     Document(type='Person', name='John Doe'),
    ...     Document(type='Person', name='Mary Jane'),
    ...     Document(type='City', name='Gotham City')
    ... ]):
    ...     print repr(doc) #doctest: +ELLIPSIS
    (True, '...', '...')
    (True, '...', '...')
    (True, '...', '...')

    >>> del server['python-tests']

    The return value of this method is a list containing a tuple for every
    element in the `documents` sequence. Each tuple is of the form
    ``(success, docid, rev_or_exc)``, where ``success`` is a boolean
    indicating whether the update succeeded, ``docid`` is the ID of the
    document, and ``rev_or_exc`` is either the new document revision, or
    an exception instance (e.g. `ResourceConflict`) if the update failed.

    If an object in the documents list is not a dictionary, this method
    looks for an ``items()`` method that can be used to convert the object
    to a dictionary. Effectively this means you can also use this method
    with `schema.Document` objects.

    :param documents: a sequence of dictionaries or `Document` objects, or
                      objects providing a ``items()`` method that can be
                      used to convert them to a dictionary
    :return: an iterable over the resulting documents
    :rtype: ``list``

    :since: version 0.2
    """

As you can see, this function does not raise the exceptions that have been raised by the couchdb server but it rather returns them in a tuple with the id of the document that we wanted to update.

One of the reviewers went to #python on irc to ask about the matter. In #python they recommended to use sentinel values rather than exceptions. As you can imaging just an approach is not practical since there are lots of possible exceptions that can be received. My questions is, what are the cons of using Exceptions over sentinel values besides that using exceptions is uglier?

+1  A: 

exceptions intended to be raised. It helps with debugging, handling causes of the errors and it's clear and well-established practise of other developers.

I think looking at the interface of the programme, it's not clear what am I supposed to do with returned exception. raise it? from outside of the chain that actually caused it? it seems a bit convoluted.

I'd suggest, returning docid, new_rev_doc tuple on success and propagating/raising exception as it is. Your approach duplicates success and type of 3rd returned value too.

SilentGhost
+4  A: 

I think it is ok to return the exceptions in this case, because some parts of the update function may succeed and some may fail. When you raise the exception, the API user has no control over what succeeded already.

olt
The benefit of simple "sentinel" over exception objects is simpler testing. But since you already include a success flag, exceptions are OK. However overloading revision/exception in the same field seems inconvenient. Consider `(doc_id, rev_or_None, exc_or_None)` which allows simpler code for users that only need details for one case.
Beni Cherniavsky-Paskin
Beni: You're right, two separate fields for rev and exception is a better solution.
olt
+1  A: 

Exceptions cause the normal program flow to break; then exceptions go up the call stack until they're intercept, or they may reach the top if they aren't. Hence they're employed to mark a really special condition that should be handled by the caller. Raising an exception is useful since the program won't continue if a necessary condition has not been met.

In languages that don't support exceptions (like C) you're often forced to check return values of functions to verify everything went on correctly; otherwise the program may misbehave.

By the way the update() is a bit different:

  • it takes multiple arguments; some may fail, some may succeed, hence it needs a way to communicate results for each arg.
  • a previous failure has no relation with operations coming next, e.g. it is not a permanent error

In that situation raising an exception would NOT be usueful in an API. On the other hand, if the connection to the db drops while executing the query, then an exception is the way to go (since it's a permament error and would impact all operations coming next).

By the way if your business logic requires all operations to complete successfully and you don't know what to do when an update fails (i.e. your design says it should never happen), feel free to raise an exception in your own code.

Alan Franzoni
+1  A: 

Raising an Exception is a notification that something that was expected to work did not work. It breaks the program flow, and should only be done if whatever is going on now is flawed in a way that the program doesn't know how to handle.

But sometimes you want to raise a little error flag without breaking program flow. You can do this by returning special values, and these values can very well be exceptions.

Python does this internally in one case. When you compare two values like foo < bar, the actual call is foo.__lt__(bar). If this method raises an exception, program flow will be broken, as expected. But if it returns NotImplemented, Python will then try bar.__ge__(foo) instead. So in this case returning the exception rather than raising it is used to flag that it didn't work, but in an expected way.

It's really the difference between an expected error and an unexpected one, IMO.

Lennart Regebro