关于异常的总结
编程技术  /  houtizong 发布于 3年前   109
什么是异常
=========================
个人认为异常是语言发明者提供给开发者的一种表达工具。开发者
在设计接口时,可以使用异常与外界(接口的调用方)进行交流。
为什么是异常
=========================
在早期,语言没有提供异常机制时,方法内部的“异常”情况通常
会用特殊返回值的方式来告知方法的调用者。对于这种特殊的返回
值,调用方需要写额外的代码来处理,这在一定程度上保证了程序
的健壮性,因为我们将最坏的情况都考虑并且处理掉了(是否优雅
地处理还要因人而异),但是也很容易使代码看起来臃肿,正常的
逻辑往往陷入到一堆代码中,变得支离破碎。
然而,异常机制的出现可以改善这种状况。开发者通过定义不同的
异常类型来提高接口的可读性和可维护性。开发者可以只关注正常
情况下的逻辑,这样做的好处是代码看起来清晰,流畅,易读。大
多数情况下,异常的出现会导致整个处理过程中断,这个时候做一
些补救的措施已经无济于事,通常会做一些善后工作,比如资源清
理,事务回滚,错误记录等等,但是,这些操作都会集中于一个共
用的模块。同时,异常对于运维也很友好,可以根据异常的堆栈来
定位问题。简而言之,程序应该集中精力关注正常的逻辑。
此外,异常也能够提高代码的可读性。通过定义异常,接口可以清
晰地告诉代码阅读者出现了异常的类型是什么。错误码和异常对于
计算机没有区别,但是对于代码的维护者和阅读者,后者肯定更易
读,并且提供更多的信息。举个栗子,在获得备份文件的接口
(get_backup_file)中,返回文件的绝对路径,如果文件不存在怎
么办,怎么告诉调用方,当然,可以返回None,也可以声明一个
NoBackupFileError的异常。个人认为抛出异常更舒服,易读,也
更不容易产生BUG,如果调用方认为文件不存在会返回空字符串,
或者哪一天另外一个开发者维护接口,将接口逻辑改为返回空字符串,
都有可能产生BUG。舒服。在下面的章节中,我们可以看到各种开源
软件定义的一些异常。
他山之石可以攻玉
=========================
一些优秀的开源软件都会设计一个清晰完整的异常体系,从这些
设计中可以管中窥豹,大概领略一些良好异常设计的味道。
-- redis-py
reids-py是我们在用的redis的Python客户端,可以从这里:
https://github.com/andymccurdy/redis-py 得到源码。他特别
设计了一个exception.py的包来定义自己的异常:
"Core exceptions raised by the Redis client"
class RedisError(Exception):
pass
class AuthenticationError(RedisError):
pass
class ConnectionError(RedisError):
pass
class ResponseError(RedisError):
pass
class InvalidResponse(RedisError):
pass
class DataError(RedisError):
pass
class PubSubError(RedisError):
pass
class WatchError(RedisError):
pass
class NoScriptError(ResponseError):
pass
他的代码里适时地抛出上面的异常,例如,他将底层的网络通信
错误封装成ConnectionError抛给上层,如果链接认证失败,他会
抛出AuthenticationError异常。我们在使用他的接口,大脑里
其实是假定我们的操作都会成功,他的接口看起来也很清晰。其
时,正确的做法是在必要的时候,我们要捕捉RedisError的异常
来进行额外的处理。我们调用Redis类提供的一系列接口时,并没
有被这些异常所困扰,我们也不会根据一个特殊的返回值,比如
None来判断接口是否正常调用成功(实际上,某些接口在“正常”
的状况下也会返回None)。我们使用的方便,归功于作者把这些
形形色色的错误用异常给隐藏掉了,我们有权力选择是否或者何时
处理这些异常。
-- mysql-replicant-python
这个库是《Mysql High Availability》
(http://shop.oreilly.com/product/9780596807290.do)
的作者在该书中提到的一个用于mysql复制操作的模块。该模块
也有该书作者实现。可以从这里看到源码:
http://bazaar.launchpad.net/~mkindahl/mysql-replicant-python/trunk/files/head:/
下面是该库定义的一些异常类:
"""
Module holding all the exceptions of the Replicant package.
"""
class Error(Exception):
"""
Base class for all exceptions in this package
"""
pass
class EmptyRowError(Error):
"""
Class to handle attempts to fetch a key from an empty row.
"""
pass
class NoOptionError(Error):
"Exception raised when ConfigManager does not find the option"
pass
class SlaveNotRunningError(Error):
"Exception raised when slave is not running but were expected to run"
pass
class NotMasterError(Error):
"""Exception raised when the server is not a master and the
operation is illegal."""
pass
class NotSlaveError(Error):
"""Exception raised when the server is not a slave and the
operation is illegal."""
pass
class QueryStatusVariableError(Error):
"""Exception raised when a bad number for a non-existing status
variable is seen in a query event.
"""
pass
class BinlogMagicError(Error):
"""Exception raised when the binary log magic number is not
correct. This usally indicates that it's not a binary log file,
but it could also mean that the file is corrupt.
"""
pass
class UnrecognizedSchemeError(Error):
"""Exception raised when a URL is used with an unrecognized scheme.
"""
pass
这个库的功能和我们系统的部分模块相似。作者将涉及复制操作中可能
出现的错误抽象成一系列的异常,我认为这提高了代码的可读性,对于
排查问题也提供了便利。
-- Voldemort
Voldemort是LinkedIn开源的基于Amazon Dynamo那篇论文一个NoSql
项目。在这里可以得到更多的信息:
http://www.project-voldemort.com/voldemort/design.html
我早先研究了Voldemort部分代码,给我感触最大的是他的异常设计。
Voldemort只设计了极少的几个异常,根异常是VoldemortException,
该类是一个RuntimeException(在Java中,RuntimeException不需要
强制捕获处理)。Voldemort将底层的存储异常,网络异常等等都
用VoldemortException封装,使代码看起来非常清爽,因为你看到
的代码都是正常的业务逻辑。在Voldemort中,只有在极少的地方对
某些异常进行了处理,很多情况下,异常会直接抛给客户端。
我们的系统是如何做的
=========================
大部分的接口都是用特殊的返回值来标识接口出现了异常,也有
一部分模块直接抛出Exception,然后由公共模块来处理这些
Exception。目前没有定义的业务异常,使代码阅读和修改
的成本非常高,因为每调用一个接口,都要考虑到该接口用于
异常的特殊返回值,对这些返回值还需要做额外的处理,其实
大部分的处理都是打一条log,然后返回(False, MSG)。我个人
认为这是一种不必要的做法。我们可以直接把异常往上抛,由
公共模块做善后工作,或者在特殊的地方对特殊的异常做额外
的处理即可,代码写起来和读起来也顺畅。
因为,我们知道自己在干什么,我们调了一个接口,我们就已经对
接口的返回值有把握,只要我们是按照正常的逻辑往下走。
在这种情况下发生异常,其实我们也无能为力,唯一能做的是在元
数据库做一些记录,记录日志,然后人工去处理。
一个令人印象深刻的特点是,代码里充斥着大量的if else。
这其实是一种坏味道。
总结
=========================
关于异常和特殊返回值的讨论一直都是仁者见仁智者见智。这里
只是提供了一些个人的看法,观察了一下其他优秀开源工程的做
法,以及发表了一些对系统目前代码的个人观点。我认为:
1)必要的业务异常必不可少。
2)对底层的错误,类似网络异常,数据库访问异常等等,我
觉得不应该吃掉这些异常,最好是往上抛,让上层的逻辑
能感受到异常的发生。
请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!
技术博客集 - 网站简介:
前后端技术:
后端基于Hyperf2.1框架开发,前端使用Bootstrap可视化布局系统生成
网站主要作用:
1.编程技术分享及讨论交流,内置聊天系统;
2.测试交流框架问题,比如:Hyperf、Laravel、TP、beego;
3.本站数据是基于大数据采集等爬虫技术为基础助力分享知识,如有侵权请发邮件到站长邮箱,站长会尽快处理;
4.站长邮箱:[email protected];
文章归档
文章标签
友情链接