前言
记得刚写正式写项目的时候, 编写代码的时候总会由于一些原因, 比如传入参数的不规范、自己代码逻辑不够完善、所查找数据不存在等原因,总会抛出一些异常如
ValueError
、ZeroDivisionError
。 每次遇到类似情况的时候都是让我头疼的时候, 然后会粗暴的捕获异常(直接excetpException
, 或者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动作。