When I worked at my last company, we had to expose an easy-to-use API for an optimistic-concurrency database that required cooperating clients to retry operations in a variety of situations. Rather than asking every client to implement correct retry behavior, we tried to expose a simple retry loop in an idiomatic way in each client language.
Python was far and away my favorite, since we could take advantage of decorators to make everything relatively seamless.
Consider a function something like this:
def do_something_transactionally(db, *args, **kwargs):
tr = db.create_transaction()
# do some things with the transaction object
tr.commit().get()
This code was broken in a number of ways -- there's no retry, or handling of errors from commit, etc.
So we exposed a decorator in Python to handle all of these issues for the common case:
@transactional
def do_something_transactionally(db, *args, **kwargs):
# do some things with the transaction object
This was more or less equivalent to:
def do_something_transactionally(db, *args, **kwargs):
tr = db.create_transaction()
while True:
try:
# do some things with the transaction object
tr.commit().wait()
break
except Error as e:
tr.on_error(e).get()
It was actually even niftier than that, though, because the decorator allowed you to pass in either a transaction object or a database object. If the caller provided a transaction rather than a database, then the decorator did not create a new transaction or commit it; this allowed composition of more complicated transactional calls of decorated functions.
So I will definitely grant you that if you encountered this code, you would need to understand what @transactional meant, but I would definitely argue that this was cleaner and easier to deal with than asking every function to explicitly implement the retry logic.