关于异常

今天看了 xuchaoqian的blog: http://xuchaoqian.com/?p=563 深有同感啊

关于异常,它的好处就是:

    1. 不需要在调用栈的每一 上判断出错 ,而只需要在关心错误的那一层处理就行

    2. 错误码侵入了或者说污染了常规输出值的值域了

如果是非常规情况,使用异常. 如文件不存在,网络挂掉之类,而如果是常规情况,getsize之类,不要用异常.

 

 

xuchaoqian 的例子,里面只有一层调用,看不出太大的优点

 

 

 

附原文:

 

是返回值(错误码、特殊值),还是抛出异常?说说我的选择

昨晚翻了翻《松本行弘的程序世界》这本书,看到他对异常设计原则的讲述,觉得颇为赞同。近期的面试,我有时也问类似的问题,但应聘者的回答大都不能令人满意。有必要理一理,说说我是怎么理解的,以及在编程实践中如何做出合适的选择。当然这只是一家之言,未必就是完全正确的。

在行文之前,我有一个观点需要明确:错误码和异常,这两者在程序的表达能力上是等价的,它们都可以向调用者传达“与常规情况不一样的状态”。因此,要使用哪一种,是需要从API的设计、系统的性能指标、新旧代码的一致性这3个角度来考虑的。本文主要从API的设计着手,试图解决两个问题:1)为什么要使用异常?2)什么时候应返回特殊值(注:这不是错误码)而不是抛出异常?

好,先来看一个使用返回错误码的例子:

 

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
30
31
32
33
34
35
36
37
38
39
#include <iostream>
using namespace std ;
int strlen ( char * string ) {
if ( string == NULL ) {
return - 1 ;
}
int len = 0 ;
while ( * string ++ != '\0' ) {
len + = 1 ;
}
return len ;
}
int main ( void ) {
int rc ;
char input [ ] = { 0 } ;
rc = strlen ( input ) ;
if ( rc == - 1 ) {
cout << "Error input!" << endl ;
return - 1 ;
}
cout << "String length: " << rc << endl ;
char * input2 = NULL ;
rc = strlen ( input2 ) ;
if ( rc == - 1 ) {
cout << "Error input!" << endl ;
return - 2 ;
}
cout << "String length: " << rc << endl ;
return 0 ;
}

 

与之等价的使用异常的程序是:

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
#include <iostream>
using namespace std ;
int strlen ( char * string ) {
if ( string == NULL ) {
throw "Invalid input!" ;
}
int len = 0 ;
while ( * string ++ != '\0' ) {
len + = 1 ;
}
return len ;
}
int main ( void ) {
char input [ ] = { 0 } ;
cout << "String length: " << strlen ( input ) << endl ;
char * input2 = NULL ;
cout << "String length: " << strlen ( input2 ) << endl ;
return 0 ;
}

 

从以上两个程序片段的对比中,不难看出使用异常的程序更为简洁易懂。为什么?

原因是:返回错误码的方式,使得调用方必须对返回值进行判断,并作相应的处理。这里的处理行为,大部份情况下只是打一下日志,然后返回,如此这般一直传递到最上层的调用方,由它终止本次的调用行为。这里强调的是,“必须要处理错误码“,否则会有两个问题:1)程序接下来的行为都是基于不确定的状态,继续往下执行的话就有可能隐藏BUG;2)自下而上传递的过程实际上是语言系统出栈的过程,我们必须在每一层都记下日志以形成日志栈,这样才便于追查问题。

而采用异常的方式,只管写出常规情况下的逻辑就可以了,一旦出现异常情况,语言系统会接管自下而上传递信息的过程。我们不用在每一层调用都进行判断处理(不明确处理,语言系统自动向上传播)。最上层的调用方很容易就可以获得本次的调用栈,把该调用栈记录下来就可以了。因此,使用异常能够提供更为简洁的API。

上述的例子还不是最绝的,因为错误码和常规输出值并没有交集,那最绝的情况是什么呢?错误码侵入了或者说污染了常规输出值的值域了,这时只能通过其它的渠道返回常规输出了。如:

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
30
31
32
33
34
#include <iostream>
using namespace std ;
int get_avg_temperature ( int day , int * result ) {
if ( day < 0 ) {
return - 1 ;
}
* result = day ;
return 0 ;
}
int main ( void ) {
int rc ;
int result ;
rc = get_avg_temperature ( 1 , & result ) ;
if ( rc == - 1 ) {
cout << "Error input!" << endl ;
return - 1 ;
}
cout << "Avg temperature: " << result << endl ;
rc = get_avg_temperature ( - 1 , & result ) ;
if ( rc == - 1 ) {
cout << "Error input!" << endl ;
return - 2 ;
}
cout << "Avg temperature: " << result << endl ;
return 0 ;
}

 

当然,如果能忍受低效率,也可以把错误码和常规输出捆到一个结构里再返回,如下:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
using namespace std ;
typedef struct {
int rc ;
int result ;
} box_t ;
box_t get_avg_temperature ( int day ) {
box_t b ;
if ( day < 0 ) {
b . rc = - 1 ;
b . result = 0 ;
return b ;
}
b . rc = day ;
b . result = 0 ;
return b ;
}
int main ( void ) {
box_t b ;
b = get_avg_temperature ( 1 ) ;
if ( b . rc == - 1 ) {
cout << "Error input!" << endl ;
return - 1 ;
}
cout << "Avg temperature: " << b . result << endl ;
b = get_avg_temperature ( - 1 ) ;
if ( b . rc == - 1 ) {
cout << "Error input!" << endl ;
return - 2 ;
}
cout << "Avg temperature: " << b . result << endl ;
return 0 ;
}

 

与之等价的使用异常的程序是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std ;
int get_avg_temperature ( int day ) {
if ( day < 0 ) {
throw "Invalid day!" ;
}
return day ;
}
int main ( void ) {
cout << "Avg temperature: " << get_avg_temperature ( 1 ) << endl ;
cout << "Avg temperature: " << get_avg_temperature ( - 1 ) << endl ;
return 0 ;
}

 

哪一个丑陋,哪一个优雅,我想应该不用我多说了。异常机制虽好,但要是使用不当,设计出来的API反而会比较难用。举个例子:

 

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
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>
#include <map>
using namespace std ;
class database {
private :
map < string , int > store ;
public :
database ( ) {
store [ "a" ] = 100 ;
store [ "b" ] = 99 ;
store [ "c" ] = 98 ;
}
int get ( string key ) {
map < string , int > :: iterator iter = store . find ( key ) ;
if ( iter == store . end ( ) ) {
throw "No such user!" ;
}
return iter -> second ;
}
} ;
int main ( void ) {
database db ;
try {
cout << "Score: " << db . get ( "a" ) << endl ;
} catch ( char const * & e ) {
cout << "No such user!" << endl ;
} catch ( . . . ) {
cout << e << endl ;
}
try {
cout << "Score: " << db . get ( "d" ) << endl ;
} catch ( char const * & e ) {
cout << "No such user!" << endl ;
} catch ( . . . ) {
cout << e << endl ;
}
return 0 ;
}

 

这个例子也使用了异常,但却是不恰当的使用。因为,“找”这个操作只有两个结果:要么“找到”,要么“没找到”。换句话说,“没找到“也是一种常规输出值。一旦抛出常规输出值,那在调用链上的所有层次里都需要捕获该异常并进行处理,那么使用异常的初衷和好处也就消失了。实践中,在这种查找类的功能里,如果没找到相应记录,一般是通过返回一个特殊的值来告知调用方,比如:NULL、特殊的对象(如iterator)、特殊的整数(如EOF)等等(为什么?一是使用异常没带来什么好处,二是逻辑统一可能为后续处理带来便利)。因此,上述例子可以改造为:

 

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <string>
#include <map>
using namespace std ;
class database {
private :
map < string , int > store ;
public :
database ( ) {
store [ "a" ] = 100 ;
store [ "b" ] = 99 ;
store [ "c" ] = 98 ;
}
map < string , int > :: iterator get ( string key ) {
return store . find ( key ) ;
}
inline map < string , int > :: iterator end_iterator ( ) {
return store . end ( ) ;
}
} ;
int main ( void ) {
database db ;
map < string , int > :: iterator iter ;
iter = db . get ( "a" ) ;
if ( iter == db . end_iterator ( ) ) {
cout << "No such user!" << endl ;
} else {
cout << "Score: " << iter -> second << endl ;
}
iter = db . get ( "d" ) ;
if ( iter == db . end_iterator ( ) ) {
cout << "No such user!" << endl ;
} else {
cout << "Score: " << iter -> second << endl ;
}
return 0 ;
}

 

接下来再举一些例子:

使用特殊值的例子:
1、检索数据时,对应某一键不存在相应的记录的情况。
2、判断是与否。

使用异常的例子:
1、读取文件时,文件不存在的情况。
2、修改用户资料时,用户不存在的情况。
3、参数出错。
4、数组越界。
5、除0错。
6、入栈,栈满;出栈,栈空。
7、网络错误。

综上所述,本文的结论是:

1、异常能提供更为简洁的API,并且能更早地发现隐藏的BUG。如有可能,要尽量采用。
2、不要抛出原本属于返回值值域里的值,一般是直接返回特殊值。经典使用场景是查找和判断。

—The end.

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值