Hacker News new | ask | show | jobs
by kstrauser 2686 days ago
I'm soundly in that camp. For instance, we write API endpoints that look a lot like this:

  def update_password_view(session_cookie):
      values = request_params(['old_password', 'new_password'])
      user = get_user(session_cookie)
      verify_password(user, values['old_password'])
      update_password(user, values['new_password'])
      return 200
  
  def request_params(param_names):
      values = {}
      for key in param in param_names:
          try:
              values[key] = request.params[key]
          except KeyError:
              raise BadRequestError('Missing parameter', key)
      return values
  
  def get_user(session_cookie):
      users = db.get_user_with_session(session_cookie)
      if len(users) != 1:
          raise NotFoundError('No user with that session')
  
      return users[0]
  
  def verify_password(user, old_password):
      if hash(user.old_password) != hash(old_password):
          raise BadRequestError('Bad password')
  
  def update_password(user, new_password):
      user.password = hash(new_password)
      db.update_user(user)
Notice that each function raises an HTTP-ready exception, so update_password_view has no explicit error handling of its own. You can look at that function and read the intent of how it actually works, as each line is only reachable if the one before it 100% succeeded. After `user = get_user(...)`, you know that `user` will have valid data and not some sentinel value you have to check for.

Our actual implementations are more subtle. We have our own exception hierarchy with classes like `UserNotFoundError` or `BadPasswordError` that subclass the corresponding HTTP error classes, so you can still write code like:

  def upsert_user(data):
      try:
          user = get_user_by_email(data.email_address)
      except UserNotFoundError:
          user = User(data)
          db.save(user)
      do_something_with(user)
in the cases where that exception isn't fatal.

In practice, we've found this coding style to be much easier maintain than idioms like `if not_found(user): return None` where you spend half your lines of code explicitly checking return values for error sentinels. Life's too short to live like that.