一直很难理解异常处理,比如我的程序底层使用了 mysql 数据库连接,而且我的上层所有程序都建立在此基础上(不考虑缓存等其他),比如一个页面要取出当前 url 中 id 指定的 post 内容,当调用底层数据库连接时,结果 mysql_connect 无法连接,那建立在此基础上的应用也再没有执行的必要了,我的 mysql_connect 处不应该直接 exit/die 终止程序吗?即使说要友好的错误提示,那我可以自定义一个函数比如 MyError($code),在此函数中来美化我的输出再在里面决定要不要 exit/die 啊。
如果使用异常处理,那在我觉得所有可能出现不正确的地方,我都必须加上 try/catch 了?那我的一段程序下来岂不是一堆 try/catch ... 这和我直接用 die/exit 或者调用自定义 MyError() 有何分别?如果说 Exception 可以往上传递,但我很多时候就应该当即处理啊,就比如数据库连不上,或者我 include 系统配置的时候文件不存在,这个时候难道不应该当即做出处理吗?
比如我做了单入口,Router -> Controllers -> Services/Models -> ModelBase -> DbFactory -> MySQL -> Driver,是不是我在 Router 外面加个 try/catch 就可以了,里面全都 throw?比如我的 MySQL 中 mysql_connect 出现问题了,我的 Controller 里面在 get 取 id 时,url 中的 id 在数据库中不存在,那这两处我都要 try/catch/throw,然后在 Router 外面的 catch 中再处理吗?
实在是很混乱,求解答,手册都是教怎么用,没教应用场景,还有为什么要用?(那个为什么真心看不出为什么),求指点!能指出具体的应用场景最好了,谢谢~
2015-06-19
很感谢各位的详细回答,谢谢!还有一些问题想麻烦各位,写到评论里不太好,就拿来写到这里了。
比如配置文件丢失这个问题,处理的方法:
1、最原始的直接 require,这样配置文件不存在就直接报错了(报错打开),这样显然不合适,不友好而且会暴露物理路径。
2、我已经预先意识到了 require 可能会出现问题,所以决定在 require 之前先 is_file/file_exists 判断文件是否存在,那么:
$file = '/a/b/c.php';
is_file($file) or die('config file not found');
require($file);
或者
$file = '/a/b/c.php';
try{
is_file($file) or throw new Exception('config file not found');
}catch(Exception $e){
//todo
}
require($file);
第一个代码可能简陋了点,不多那个 die 可以换成自定义错误提示函数,也可以输出友好的错误信息。
那第二个代码有什么好处呢?是不是我在 //todo 处依旧抛出这个 $e,或者干脆这里不要 try/catch 了?假设我是单入口,最后整个系统的 Exception 都交给最上一层处理?
1、那依照分层处理的概念,现在就假设 config 这段代码为“config层”,首先可以肯定的是我的“Dispatcher层”、“Controller层”、“DB层”、“Model层”统统依赖于它,难道说我要这些层自己各自去处理这个异常吗?还是说这些层都各自再 throw 出去?或者这些层统统不管(我才不管呢,谁最后谁管)?这种“config层”一对多为其它层服务,那“config层”在配置文件丢失的时候不应该自己就处理掉吗?这不也符合异常尽量不扩散吗?
2、由“config层”自己处理的话,那 //todo 处理的时候不也应该 exit/die 吗?否则岂不是又去执行 require 了?
3、那如果它自己 exit/die 掉了,这个时候异常的好处不就仅仅是比我自定义的 MyError 多了 trace 信息吗?
基于你的这个问题来说,你需要有一定的抽象能力,要有封装的概念,其实我更喜欢称之为分层,也就是我们要建立类似下列概念:
基于上述概念来说,无论exit/die还是Exception可以分为2种情况:
对于a来说,你要考虑的做出的行为的接受方Upper Role是谁?如果Upper Role是Top Role(顶层角色,一般基于我们的应用来说,顶层角色指的是使用浏览器的人),那么就应该用exit/die,这个是要Top Role做出处理的,也就是除了Top Role以外的所有Upper Role无权干涉,而当Upper Role非Top Role,那么就应该用Exception,这样除了Top Role以外的所有Upper Role才有权利去进行一些额外处理。
基于a来说mysql_connect连接的事情,(按照我的设计)在使用mysql_connect时,Current Role指的是DB处理连接mysql相关逻辑,而Upper Role是调用DB的未知逻辑(想想这里我为什么要用“未知”这个概念),Current Role仅仅知道mysql_connect连接失败了,而并不知道Upper Role是否还有其他DB或额外选择,那么Current Role为了让Upper Role有一定选择权,这里(我)选择使用Exception,让Upper Role决定是交给Top Role处理还是更上层的Upper Role处理。
对于b的情况来说,Current Role要判断Lower Role是不是会有一些非Current Role期望的情况出现(即:Exception),则有以下选择
基于b来说mysql_connect连接的事情,Current Role指的是需要用到DB的相关逻辑,而Lower Role是DB处理连接mysql相关逻辑,在Current Role使用的时候,Current Role期望mysql可以正常连接,但是额外情形也会出现,那么Current Role就要考虑自身的情况来选择,比如说Current Role是一个select操作,那么Current Role就是可以返回为空,那么对于msyql连接失败或者数据表真的为空,性质一样,Current Role就可以将非期望舍弃掉(捕获不处理)返回为空,而对于一个update来说,可以选择处理或者不处理都可以,而对于一个可以选择n个mysql 连接的逻辑来说,就必须处理,等等
总之,啰嗦了一堆,就是说你要用好异常,那么你要有上下层的概念,并且在上下层逻辑处理中,要分清Current Role是可以终止程序执行(exit)还是交由Upper Role处理(throw Excepton)
最后,throw是Current Role反馈给Upper Role,try/catch是Current Role处理Lower Role反馈,希望你能更好的使用Exception