Hacker News new | ask | show | jobs
by kstrauser 2326 days ago
For example, in a web service I maintain, we maintain an internal exception hierarchy like:

  class OurExceptions:
      http_status_code = 500

  class DatabaseRecordNotFoundError:
      http_status_code = 404
Database queries are all written like:

  rows = db.select(...)
  if not rows:
      raise DatabaseRecordNotFoundError
and the top-level Flask error handler has code like:

  try:
      return call_view()
  except OurExceptions as exc:
      return response(status_code=exc.http_status_code)
This is grossly oversimplified, but you get the idea. So, this means that we can write views like:

  def some_view(object_id):
      obj = fetch_obj_from_db(object_id)
      return {"found": obj.name}
If the object isn't found, the caller gets a 404 response without the person writing the view having to do a single thing. However, they can still handle the problem themselves if they really want to:

  def another_view(object_id):
      try:
          obj = fetch_obj_from_db(object_id)
      except DatabaseRecordNotFoundError:
          return {"error": "Not found. Try again later?"}, 404
      return {"found": obj.name}
I absolutely love this coding style because exceptions are still being handled everywhere, but don't have to be explicitly dealt with deep inside a nested call stack. That lets us write very uncluttered, testable view code like:

  def change_password(userid, oldpass, newpass):
      # This raises an exception if the user can't be found
      user = get_user_by_id(userid)

      # This raises an exception if the old password is wrong
      verify_password(user, oldpass)

      # This raises an exception if the DB couldn't be updated,
      # perhaps because of a race condition with another request
      update_password(user, newpass)

      # By the time we get to this line, everything above has to have
      # succeeded, with zero manual error checking inside this view
      return {"result": "Password successfully updated."}