问题:
1、错误处理的方法有哪些?
2、使用函数返回值还是抛出异常?
3、PHP 原框架下的异常处理机制是怎样的?
4、PHP Yii框架下的错误处理方案是怎样的?有什么参考意义?
错误处理的方法有哪些?
函数返回值:既return true|false,数组,对象 等
使用成员变量保存错误信息:在对象内部预设一个 errors变量(可能更多的时候是一个数组),把内部处理的报错信息暂存起来,最后使用getErrors函数获取错误信息。
抛出异常
触发事件:需要框架支持。
暂时只想到这么多了,盼指正!
使用函数返回值还是抛出异常
如上所述四种处理方法,成员变量的方式多数使用在处理结果不用立即返回的情况,譬如对多个数据字段进行校验返回校验结果,使用场景比较明确。触发事件就主要依赖于框架来实现,复杂的处理逻辑可以考虑,使用场景也相对明确。而函数返回值和抛出异常则用得比较纠结,在编写代码的时候就时常需要考虑什么时候使用异常处理,什么时候考虑返回值,而且在嵌套较深的时候还会让人好好纠结一番。
函数返回值的特点
1)函数返回值比较直接,可以使用true|flase直接表达处理结果,使用状态代码来表示更多的执行结果(推荐使用http的状态码,易读易懂),使用数据或者对象来携带更多的数据,性能较高。
2)返回值的修改、添加、减少会对依赖方带来改动,如果调用的地方很多,修改起来也很麻烦。添加返回值后还需要调用方添加相应的处理代码,往往表现为一个if语句。
3)嵌套很深时,层层都需要对返回值进行处理,层层都需要显式判断,会带来不少冗余代码,而且经常出现处理漏状态的情况。
异常处理
1)异常处理是面向对象语言的处理方案,把错误信息抽象为固定的对象,携带着对象固有的报错信息、格式等等,具有较高的可读性,可能从异常名字就可以判断报错类型。
2)使用异常来处理报错信息,可以让程序变得更简洁,代码只需要关注主流程,省了很多状态判断。同时开发语言的异常处理机制封装了很详细的报错信息,便于定位问题。
3)对调用方依赖较少,新增的异常也不需要一定要求调用方去处理,调用方只需要关注该处理的异常即可。即使深层嵌套也可以把底层异常有效暴露给顶层。
4)异常处理是一种对象处理,而且try和catch也会对数据的出入栈带来消耗,所以性能稍低,这点不难理解。
5)开发人员需要对各种各样的异常创建类定义,开发成本较高。
综上所述,使用哪种方式来反馈错误,的确是要看场景。个人觉得,操作频率高的代码模块尽量少使用异常处理,但是要确保返回信息的简洁。而一些失败因素无法穷举的场景,如果不会出现性能问题,则考虑使用异常。
PHP 原框架下的异常处理机制是怎样的
使用异常的基本思路是抛出异常、捕捉异常、处理异常。如下代码所示,可以在任何你希望的地方,通过throw语句来抛出异常对象。
抛出异常
throw new Exception("错误描述,描述清楚错误原因和可能的解决方案...");
1
捕捉异常
如果异常没有被捕获,而且又没用使用 set_exception_handler() 作相应的处理的话,那么将发生一个严重的错误(致命错误),并且输出 “Uncaught Exception” (未捕获异常)的错误消息。
try
{
//If the exception is thrown, this text will not be shown
}
catch(Exception $e)
{
echo 'Message: ' .$e->getMessage();
}
1
2
3
4
5
6
7
8
以上是PHP语言中最基本的异常处理代码,Exception对象是PHP内置的异常类,只是简单得接收异常message,然后展示message。
自定义异常类
一个更成熟的做法是根据自己系统的业务特点来定义异常类,然后处理特定业务场景的异常,实现一种另类的条件选择结构。。如下例子,在找不到对应的商品的时候可以抛出找不到商品异常对象。
class goodsNotFoundException extends Exception
{
public function errorMessage()
{
//error message
$errorMsg = '找不到 '.$this->getMessage().' 这个商品.';
return $errorMsg;
}
}
1
2
3
4
5
6
7
8
9
设置顶层异常处理器 (Top Level Exception Handler)
可以设置顶层异常处理类来处理所有异常。
set_exception_handler('allException');
1
扩展阅读
Exception 的源码阅读:
class Exception {
protected $message = 'Unknown exception'; //异常信息
private $string;
protected $code = 0; //用户自定义异常代码
protected $file; //发生异常的文件名
protected $line; //发生异常的代码行号
private $trace;
private $previous;
final private function __clone () {} //还不知道什么场景下需要用到
function __construct($message = null, $code = 0){} //构造方法
final function getMessage(){} //返回异常信息
final function getCode(){} //返回异常代码
final function getFile(){} //返回发生异常的文件名
final function getLine(){} //返回发生异常的代码行号
final function getTrace(){} //backtrace() 数组
final function getTraceAsString(){} //已格成化成字符串的 getTrace() 信息
function __toString(){} //可输出的字符串
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
上面这段代码只为说明内置异常处理类Exception的结构,并不是一段有实际意义的可用代码,在IDE里查看PHP的源码基本都是这样的结构代码,内部实现是C代码,在src包里面可以找到。如果使用自定义的类作为异常处理类,则必须是扩展内置异常处理类Exception,非Exception类的子类是不能作为异常处理类使用的。如果在扩展内置处理类Exception时重新定义构造函数的话,建议同时调用parent::construct()来检查所有的变量是否已被赋值。当对象要输出字符串的时候,可以重载__toString()并自定义输出的样式。可以在自定义的子类中,直接使用内置异常处理Exception类中的所有成员属性,但不能重新改写从该父类中继承过来的成员方法,因为该类的大多数公有方法都是final的。
网上摘录了一段样例代码,供参考!
class MyException extends Exception{
//重定义构造器使第一个参数 message 变为必须被指定的属性
public function __construct($message, $code=0){
//可以在这里定义一些自己的代码
//建议同时调用 parent::construct()来检查所有的变量是否已被赋值
parent::__construct($message, $code);
}
public function __toString() { //重写父类方法,自定义字符串输出的样式
return __CLASS__.":[".$this->code."]:".$this->message."
";
}
public function customFunction() { //为这个异常自定义一个处理方法
echo "按自定义的方法处理出现的这个类型的异常
";
}
}
try {
//使用自定义的异常类捕获一个异常,并处理异常
$error = '允许抛出这个错误';
throw new MyException($error); //创建一个自定义的异常类对象,通过throw语句抛出
echo 'Never executed'; //从这里开始,try代码块内的代码将不会再被执行
} catch (MyException $e) { //捕获自定义的异常对象
echo '捕获异常: '.$e; //输出捕获的异常消息
$e->customFunction(); //通过自定义的异常对象中的方法处理异常
}
echo '你好呀'; //程序没有崩溃继续向下执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
PHP Yii框架下的错误处理方案是怎样的?有什么参考意义?
yii框架的异常定义集中在vendoryiisoftyii2base目录下,但是限于个人能力,还有部分代码没看懂…
UML的类图结构如下:
1、其中从PHP原生异常对象中扩展 ErrorException,用于代表所有虚拟机级别的异常,可能需要重启服务才可以重新提供服务。
2、扩展UnexpectedValueException,用于不可知的异常,也就是模糊无法定位具体原因的异常。
3、扩展BadMethodCallException,用于调用不正确的类、方法、错误参数等。
4、扩展Exception,代表框架内部一些专有的报错信息,例如加载配置文件失败、架构初始化异常等等。
从Yii框架的异常类的定义来看,PHP语言的内部异常类的确不足够用于所有的场景,而不同名字的异常又充当了 errorCode的角色,无形中也是带框架内部加了不少的if分支。
————————————————
版权声明:本文为CSDN博主「zoujunjie202」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zoujunjie202/article/details/51329950
摘要:catch(exception &ex)是捕获所有标准库定义中的类std:exception;catch(...)则是捕获所有的异常。
1.简介
异常是由语言提供的运行时刻错误处理的一种方式。提到错误处理,即使不提到异常,你大概也已经有了丰富的经验,但是为了可以清楚的看到异常的好处,我们还是不妨来回顾一下常用的以及不常用的错误处理方式。
C++异常之网络知识
1.1常用的错误处理方式
返回值。我们常用函数的返回值来标志成功或者失败,甚至是失败的原因。但是这种做法最大的问题是如果调用者不主动检查返回值也是可以被编译器接受的,你也奈何不了他:)这在C++中还导致另外一个问题,就是重载函数不能只有不同的返回值,而有相同的参数表,因为如果调用者不检查返回值,则编译器会不知道应该调用哪个重载函数。当然这个问题与本文无关,我们暂且放下。只要谨记返回值可能被忽略的情况即可。
全局状态标志。例如系统调用使用的errno。返回值不同的是,全局状态标志可以让函数的接口(返回值、参数表)被充分利用。函数在退出前应该设置这个全局变量的值为成功或者失败(包括原因),而与返回值一样,它隐含的要求调用者要在调用后检查这个标志,这种约束实在是同样软弱。全局变量还导致了另外一个问题,就是多线程不安全:如果多个线程同时为一个全局变量赋值,则调用者在检查这个标志的时候一定会非常迷惑。如果希望线程安全,可以参照errno的解决办法,它是线程安全的。
1.2不常用的处理方式
setjmp()/longjmp()。可以认为它们是远程的goto语句。根据我的经验,它们好象确实不常被用到,也许是多少破坏了结构化编程风格的原因吧。在C++中,应该是更加的不要用它们,因为致命的弱点是longjmp()虽然会unwindingstack(这个词后面再说),但是不会调用栈中对象的析构函数--够致命吧。对于不同的编译器,可能可以通过加某个编译开关来解决这个问题,但太不通用了,会导致程序很难移植。
1.3异常
现在我们再来看看异常能解决什么问题。对于返回值和errno遇到的尴尬,对异常来说基本上不存在,如果你不捕获(catch)程序中抛出的异常,默认行为是导致abort()被调用,程序被终止(coredump)。因此你的函数如果抛出了异常,这个函数的调用者或者调用者的调用者,也就是在当前的callstack上,一定要有一个地方捕获这个异常。而对于setjmp()/longjmp()带来的栈上对象不被析构的问题对异常来说也是不存在的。那么它是否破坏了结构化(对于OOparadigms,也许应该说是破坏了流程?)呢?显然不是,有了异常之后你可以放心的只书写正确的逻辑,而将所有的错误处理归结到一个地方,这不是更好么?
综上所述,在C++中大概异常可以全面替代其它的错误处理方式了,可是如果代码中到处充斥着try/throw/catch也不是件好事,欲知异常的使用技巧,请保持耐心继续阅读:)
2.异常的语法
在这里我们只讨论一些语法相关的问题。
2.1try
try总是与catch一同出现,伴随一个try语句,至少应该有一个catch()语句。try随后的block是可能抛出异常的地方。
2.2catch
catch带有一个参数,参数类型以及参数名字都由程序指定,名字可以忽略,如果在catch随后的block中并不打算引用这个异常对象的话。参数类型可以是build-intype,例如int,long, char等,也可以是一个对象,一个对象指针或者引用。如果希望捕获任意类型的异常,可以使用“...”作为catch的参数。
catch不一定要全部捕获tryblock中抛出的异常,剩下没有捕获的可以交给上一级函数处理。
2.3throw
throw后面带一个类型的实例,它和catch的关系就象是函数调用,catch指定形参,throw给出实参。编译器按照catch出现的顺序以及catch指定的参数类型确定一个异常应该由哪个catch来处理。
throw不一定非要出现在try随后的block中,它可以出现在任何需要的地方,只要最终有catch可以捕获它即可。即使在catch随后的block中,仍然可以继续throw。这时候有两种情况,一是throw一个新类型的异常,这与普通的throw一样。二是要rethrow当前这个异常,在这种情况下,throw不带参数即可表达。例如:
try{
...
}
catch(int){
throwMyException("hello exception"); //抛出一个新的异常
}
catch(float){
throw; // 重新抛出当前的浮点数异常
}
2.4函数声明
还有一个地方与throw关键字有关,就是函数声明。例如:
void foo() throw (int); //只能抛出int型异常
voidbar() throw (); //不抛出任何异常
voidbaz(); // 可以抛出任意类型的异常或者不抛出异常
如果一个函数的声明中带有throw限定符,则在函数体中也必须同样出现:
void foo() throw (int)
{
...
}
这里有一个问题,非常隐蔽,就是即使你象上面一样编写了foo()函数,指定它只能抛出int异常,而实际上它还是可能抛出其他类型的异常而不被编译器发现:
void foo() throw (int)
{
throw float; // 错误!异常类型错误!会被编译器指出
...
baz(); // 正确!baz()可能抛出非int异常而编译器又不能发现!
}
voidbaz()
{
throw float;
}
这种情况的直接后果就是如果baz()抛出了异常,而调用foo()的代码又严格遵守foo()的声明来编写,那么程序将abort()。这曾经让我很恼火,认为这种机制形同虚设,但是还是有些解决的办法,请参照“使用技巧”中相关的问题。
3.异常使用技巧
3.1 异常是如何工作的
为了可以有把握的使用异常,我们先来看看异常处理是如何工作的。
CSND连接:C++异常(exception)第一篇--综合讲解_发展是曲折的但也是前进的-CSDN博客



