通过异常处理让代码更整洁

前言

记得刚写正式写项目的时候, 编写代码的时候总会由于一些原因, 比如传入参数的不规范、自己代码逻辑不够完善、所查找数据不存在等原因,总会抛出一些异常如ValueErrorZeroDivisionError。 每次遇到类似情况的时候都是让我头疼的时候, 然后会粗暴的捕获异常(直接excetp Exception, 或者except), 反正就是通过异常基类来达到一次性处理所有异常的目的。 然后review代码的时候就被人骂了, 说不规范。 当时不以为然,感觉自己用的挺爽的。 等有了一定经验后觉得当时的方式不但没有给自己带来好处, 反而加重了调试的困难。 再到后来,逐渐发现正确的使用异常可以使得代码更清晰, 项目更具有扩展性, 反正好处多多。

后来, 在实践中自己开发独立的模块时。自己会定义一些异常来帮助自己更好的组织代码。 下面就说说个人在实践中的处理方法。

一、 精确的捕获异常

我们有个发送信息的函数send_message, 如果信息发送成功了返回True, 失败返回False。 HTTP请求处理函数vertify_code_handler负责发送注册验证码。伪代码如下:

  from myapp import settings
  msg_server = MessageSender(api_key=settings.APP_KEY,  
  api_secret=settings.APP_SECRET)

  def get_mobile(request):
      """Get phone number from request query"""
      mobile = request.get("mobile")
      return mobile
    
  def send_message(mobile,  message):
      """ Send message to mobile phone
      :retrun: return `True` if send success else return `False`
      :rtype: `bool`
      """
      try:
          response = msg_server.send(mobile, message)
          if response.success:
               return True
      except:
         pass
      return False

  def vertify_code_handler(request):
      mobile = get_mobile(request)
      code =  str(random.randint(100000, 999999))
      send_status = send_message(mobile,  code)
      return send_status

在 send_message 函数中我们的except 捕获任何异常, 然后有异常出现的时候我们认为短信发送失败了。 不管他是什么异常反正我们认为短信发送失败了。 然后返回False 就可以了。 这种做法在一定程度上是比较简单粗暴的。返回的结果可能没有错误。 但是考虑特殊情况, 如果我们传入参数有误的话(服务器获取手机号码的函数出现BUG), 或者vertify_code_handler传入的request的某些属性有问题的话, send_message中的excpet会吃掉这些异常, 让人误以为发送短信的服务有问题 。 这样的话你很难找到发送失败的原因。

二、 主动的抛出异常

我们还是通过上面的例子来说明, 我们在获取手机号码这一步加了验证, 当手机号码不符合规范时候抛出异常InvalidMobileException

  import re
  from myapp import settings
  
  msg_server = MessageSender(api_key=settings.APP_KEY,  
  api_secret=settings.APP_SECRET)
  RE_MOBILE_FORMATER = r'^1[3578]\d{9}$|^147\d{8}'
  
  class InvalidMobileException(Exception):
      pass

  def get_mobile(request):
      """Get phone number from request query"""
      mobile = request.get("mobile")
      if not re.match(RE_MOBILE_FOMATTER):
          raise InvalidMobileException
      return mobile
    
  def send_message(mobile,  message):
      """ Send message to mobile phone
      :retrun: return `True` if send success else return `False`
      :rtype: `bool`
      """
      try:
          response = msg_server.send(mobile, message)
          if response.success:
               return True
      except MessageServerError:
          return False

  def vertify_code_handler(request):
      try:
          mobile = get_mobile(request)
      except InvalidMobileException:
          return False
      code =  str(random.randint(100000, 999999))
      send_status = send_message(mobile,  code)
      return send_status

我们自己定义了异常 InvalidMobileException, 当手机号码不合规范的时候我们raise InvalidMobileException。 这样在调用了get_mobile这个函数的地方, 我们就可以通过捕获异常InvalidMobileException来知道是否得到了合法的的手机号码。

当然我们可以通过另一种方法获得手机号码

  def get_mobile(request):
      """Get phone number from request query
      :return: None or phone number
      """
      mobile = request.get("mobile")
      if not re.match(RE_MOBILE_FOMATTER):
           return None
      return mobile

如果手机号码符合规范的时候我们返回手机号码, 否则返回False, 但是这种方法相对于前面一种方法是比较不友好的。

原因很简单, 所有调用了get_mobile这个函数的地方, 在获得手机号码后都要验证一次。 这乍看起来无所谓, 可是你有多个这样的函数的时候, 验证起来就相当痛苦!

  def get_mobile(request):
      """Get phone number from request query
      :return: None or phone number
      """
      mobile = request.get("mobile")
      if not re.match(RE_MOBILE_FOMATTER):
           return None
      return mobile
 
  def get_password(request):
      """Get password from request query
      :return: None or password
      """
      password = request.get("password")
      if not re.match(RE_PASSWORD_FOMATTER):
           return None
      return password

  def test_handler(request):
      mobile = get_mobile()
      password = get_mobile()
     if mobile is None:
         return False, "invalid phone number" 
     elif password is None:
         return Fasle, "invalid password"
     else:
         return check_account(mobile, password)

但是用了捕获异常的方法后, 上面的例子变得友好多了。

  def get_mobile(request):
      """Get phone number from request query
      :return: None or phone number
      """
      mobile = request.get("mobile")
      if not re.match(RE_MOBILE_FOMATTER):
          raise InvalidMobileException
      return mobile
 
  def get_password(request):
      """Get password from request query
      :return: None or password
      """
      password = request.get("password")
      if not re.match(RE_PASSWORD_FOMATTER):
           raise InvalidPasswordException
      return password

  def test_handler(request):
      try:
          mobile = get_mobile()
          password = get_mobile()
      except InvalidMoblieException:
          return False, "invalid phone number" 
      except  InvalidPasswordException):
          return Fasle, "invalid password"
      else:
          return check_account(mobile, password)

好像看起来比较容易一点了, 但是还是有点乱。 如果我们给自定义的一场中携带信息的话, 那么会让代码更美观。

三、 让异常携带信息

还是接着上面的例子。 我们在定义异常或者抛出异常的时候, 我们给异常附加一些信息, 如状态码、错误消息等。 这样捕获一场的时候可以根据异常的错误信息来进行返回。

  class InvalidMobileException(Exception):
      def __init___(self, status_code=400, error_msg="invalid  phone number"):
          self.status_code = status_code
          self.error_msg = error_msg   

  class InvalidPasswordException(Exception):
      def __init___(self, status_code=400, error_msg="invalid  password"):
          self.status_code = status_code
          self.error_msg = error_msg   

  def get_mobile(request):
      """Get phone number from request query
      :return: None or phone number
      """
      mobile = request.get("mobile")
      if not re.match(RE_MOBILE_FOMATTER):
          raise InvalidMobileException
      return mobile
 
  def get_password(request):
      """Get password from request query
      :return: None or password
      """
      password = request.get("password")
      if not re.match(RE_PASSWORD_FOMATTER):
           raise InvalidPasswordException
      return password

  def test_handler(request):
      try:
          mobile = get_mobile()
          password = get_mobile()
      except (InvalidMoblieException, InvalidPasswordException) as e:
          return e.status_code, e.error_msg
      else:
          return check_account(mobile, password)

前面的例子都是比较简单的。 当我们写复杂业务的时候, 如果能正确的运用好一场的话, 会有一种既省时又省力的感觉。 最常见的例子是我们要造一个HTTP Server的轮子。 然后要处理很多的状态信息, 比如500、400、404等。 又或者是验证一些东西的时候 , 这时候用异常来简化代码是最好的选择了。

结束

异常处理也就这么些内容, 当你raise 一个异常的时候, 其实是隐形的传递一些信息, 告诉自己哪里出现问题了, 出现什么样子的问题。 你raise一个exception的时候, 就要在调用该函数的地方做except动作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,263评论 25 707
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,204评论 0 4
  • 熟读唐诗三百首,不会吟诗也会吟。剖析阿城系列,峡谷。 峡谷描写一个骑手经过峡谷补给,继续赶路。全文用字谨慎,收拢得...
    拉美西斯神灯阅读 258评论 0 0
  • 主要内容来自 https://www.tutorialspoint.com/apache_kafka/apache...
    ZhangXX_1897阅读 409评论 1 0
  • 玉甲金冠烁烁,红披飒飒如风。 金睛火眼摄魔怪,圣名万里颤妖魂。 佛祖敬三分。 皈依征途漫漫,八十一难人生。 魑魅魍...
    墨枫独语阅读 300评论 2 5