免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
1234下一页
最近访问板块 发新帖
查看: 9060 | 回复: 37
打印 上一主题 下一主题

我之异常观 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2010-06-09 08:54 |只看该作者 |倒序浏览
我之异常观

经常在论坛里看人们争论,该不该使用异常、何时使用异常,于是把自己的一些心得整理出来,欢迎大家拍砖。

注释:以下用Q表示Question,A表示Short Answer;另外,本文中的代码使用的是类似C++的格式。

Q:该不该使用异常?
A:应该使用异常。
解释:1)既然你所使用的语言提供了异常的机制,而且在几乎所有的(我所知道的)面向对象语言中,异常都是语言的一个重要部分,而不是一个可有可无、仅仅用来吸引使用者的“小甜头”,那么就应该使用这种机制。
        换言之,相信你所使用语言的“XXX之父”,相信他们在异常这个问题上的理解,要比你自己要深入。
      2)异常确实改善了代码的可读性。比较下面两段代码:
        a:
        if(fun1(a,b,c)!=0){
                error_log("some error");
        }
        if(fun2(e,f,g)!=0){
                error_log("some error");
        }

        b:
        try{
                fun1(a,b,c);
                fun2(e,f,g);
        }
        catch(Exception e){
                error_log("some error");
        }

        代码a将代码的正常执行和异常处理混杂在一起;在看别人代码的时候,碰到异常处理的地方,如非必要,我一般都是直接跳过的,你呢?

        另外,我记得有哪位高人提倡过,一个函数的长度,应该尽量保持在一个屏幕能显示的高度以内。一般情况下,代码a都会比代码b要长。
        当然,你发觉了,代码a里面进行了两次捕捉,而代码b只有一次。
        这不公平,你会说。
        不过,a)异常也可以有继承体系,如果fun1是抛出网络A异常,fun2是抛出网络B异常,那么我们也许捕捉网络异常这个“父异常”就够了,不需要区分究竟是哪种网络异常。
             b)你并不需要频繁的捕捉异常,在后面你会看到。
      3)使用特定返回值的方法污染了返回值的正常范围:

         if ((ret=func()) == 0)   //这是不需要正常返回值或返回值为正整数的情况

         if ((ret=func()) < 0)    //这是需要返回正整数或0的情况

         if (func(&ret) == 0)      //呼呼,需要返回整数范围,返回值被挤到函数参数里边去了。

         另外,这样还仅仅是指示发生了异常,但是还不知道发生了什么异常;想知道,找errno或GetLastError去。
       4)效率,运行效率,你会说。
         我知道,异常会有时间和空间的开销。
         但是,你确认,你真的需要去发掘/节省异常的开销?你确认你的程序没有在等待网络、没有在等待文件读取、没有在无谓的等待?确认程序的逻辑正确、运行合理,要远远胜过挖掘这一点语言的潜力。
         如果,如果你真的需要这样做,那么也许你根本就不应该选择C++(更遑论java)。即使使用了C++,也需要使用一个严格界定的子集,这时候别说异常,可能所有的C++库,甚至连继承等等特性能不能使用,都需要考察了,也许你真的需要的是C。
       5)是否使用异常,有的时候不取决于你。你使用的库抛出了异常,你也只好捕捉它。

Q:怎样使用异常?
A:异常的使用包括抛出和捕捉两个方面:
   1)尽可能地抛出异常。
   2)仅在需要时捕捉异常。
解释:尽可能的抛出异常很容易理解,你的代码里边出现了预料不到的情况,就应该作为异常抛出。
      仅在需要时捕捉,也可以表述为:如果没有必要,那么就不要捕捉它。
      与之相对应的,是异常的滥用----层层捕捉异常,层层抛出异常。比如:
      
      void fun1(){
            try{
                some_fun();
            }
            catch(SomeException){
                throw MyException1();
            }
      }

      void fun2(){
            try{
                other_fun();
            }
            catch(OtherException){
                throw MyException2();
            }
      }

      void fun3(){
            try{
                fun1();
                fun2()
            }
            catch(SomeException,OtherException){
                throw MyException3();
            }
      }

      void main(){
            try{
                fun3();
            }
            catch(MyException3){
                ...
            }
      }

      而理想的异常捕捉方式是:

      void fun1(){
                 some_fun();
      }

      void fun2(){
                 other_fun();
      }

      void fun3(){
                 fun1();
           fun2();
      }

      void main(){
                 try{
                fun3();
           }
           catch(SomeException){
                ...
           }
           catch(OtherException){
                ...
           }
      }
               
      对比两段代码,可以看到:
      1、上面的那段代码非常丑陋。
      2、上面的那段代码不必要的引入了三个异常类别:MyException1、MyException2、MyException3。
      3、最重要的,上面的那段代码丢失了异常信息。在main函数里边,已经无从区别到底是some_fun里触发的异常,还是other_fun触发的异常。而要避免信息的丢失,就需要在每个调用层次定义、捕捉、重新抛出同样数量的异常。设想一下具有10个调用层次,10个底层的异常传递到顶层,就需要额外定义、捕捉、抛出90个异常,这种重复毫无意义。
      4、下面的代码更容易调试。推荐为调试写不带任何异常捕捉的测试代码,这样出错的时候可以直接定位到出错位置。
      
      需要捕捉异常的场合:
      1、具有图形界面的程序,应该在每个功能的调用点捕捉异常,根据异常类别给出明确的错误信息。这样的程序用户会接受;程序的内部运行信息对最终用户毫无用处,而且招致用户的反感。
      void button1_click(void){
                 try{
                do_something();
                do_otherthing();
           }
           catch(SomeException){
                show_msg("Some exception");
           }
           catch(OtherException){
                show_msg("OtherException");
           }
      }

      2、需要不间断重复运行的程序,比如系统服务程序,你当然不希望遇到一点意外程序就关闭。所以在重复处理的循环里面,捕捉异常并记录日志,然后开始下次重复执行。

      while (1){
            try{
                do();
            }
            catch(Exception e){
                log(e);
            }
      }

论坛徽章:
12
巳蛇
日期:2013-09-16 15:32:242015年辞旧岁徽章
日期:2015-03-03 16:54:152015年亚洲杯之约旦
日期:2015-02-11 14:38:37双鱼座
日期:2015-01-05 11:05:47戌狗
日期:2014-12-08 09:41:18戌狗
日期:2014-08-15 09:29:29双子座
日期:2014-08-05 09:17:17卯兔
日期:2014-06-08 15:32:18巳蛇
日期:2014-01-27 08:47:08白羊座
日期:2013-11-28 21:04:15巨蟹座
日期:2013-11-13 21:58:012015年亚洲杯之科威特
日期:2015-04-17 16:51:51
2 [报告]
发表于 2010-06-09 10:33 |只看该作者
C++不使用异常是有道理的,这个要根据情况综合考虑,而不是因为提供了就用。

对于Java 和 C#,异常的确是不错的工具。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
3 [报告]
发表于 2010-06-09 10:38 |只看该作者
丢掉不切实际的空想, 楼主!
比如这段,最低要求,就是发生任何异常,程序立即restart.

      void main(){
                 try{
                fun3();
           }
           catch(SomeException){
                ...
           }
           catch(OtherException){
                ...
           }
      }

论坛徽章:
0
4 [报告]
发表于 2010-06-09 12:08 |只看该作者
丢掉不切实际的空想, 楼主!
比如这段,最低要求,就是发生任何异常,程序立即restart.

      void mai ...
群雄逐鹿中原 发表于 2010-06-09 10:38



    没有看懂你的意思。

另外这里之所以用main,是指的尽量在最外层捕捉的意思,实际中也许是在某个子函数中。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
5 [报告]
发表于 2010-06-09 12:19 |只看该作者
话是这么讲,但是最外层捕捉的话,如何能保证别人不在里层随意的丢异常呢?如何要捕捉哪些异常呢?
最终会发现异常满天飞,稍不留意程序就意外“被异常”了,于是变成像全局变量一样可恶的东西。

C++缺少强制声明,对可能抛出的异常进行约束。

当下,个人觉得和楼主的处理方式相反会比较好点,
尽可能的将异常处理掉,消灭在心智所能及的范围内。

论坛徽章:
0
6 [报告]
发表于 2010-06-09 13:25 |只看该作者
本帖最后由 没本 于 2010-06-09 13:53 编辑

一但使用异常,就象一楼提到的那样,无可避免的,要把异常处理机制“传染”到更低层的代码和更多的代码。所以要么不用异常,要么全用异常。本着多一事不如少一事的理念,我不用异常。
见过不少人写的多线程程序,因使用异常导致没有正确的释放资源或开锁,造成多线程程序死锁的例子。C++异常本身就有缺陷,具体问题在D语言手册的异常处理中有提到。

  1. #include <iostream>
  2. using namespace std;

  3. void gfun()
  4. {
  5.         cout << "gfun()" << endl;
  6.         throw 1;
  7. }
  8. void lock()
  9. {
  10.         cout << "lock()" << endl;
  11. }
  12. void unlock()
  13. {
  14.         cout << "unlock()" << endl;
  15. }
  16. struct A
  17. {
  18.         A()
  19.         {
  20.                 cout << "A()" << endl;
  21.        
  22.         };
  23.         ~A()
  24.         {
  25.                 cout << "~A()" << endl;
  26.         }
  27.         void fun()
  28.         {
  29.                 lock();
  30.                 gfun();
  31.                 unlock();
  32.         }
  33. };
  34. int main()
  35. {
  36.         try
  37.         {
  38.                 A aa;
  39.                 aa.fun();
  40.         }
  41.         catch(int e)
  42.         {
  43.                 cout << "catch():" << e << endl;
  44.         }
  45. }
复制代码
然后就杯具的死锁了。

  1. Z:\>cl /EHsc trr.cpp
  2. Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
  3. Copyright (C) Microsoft Corporation.  All rights reserved.

  4. trr.cpp
  5. Microsoft (R) Incremental Linker Version 10.00.30319.01
  6. Copyright (C) Microsoft Corporation.  All rights reserved.

  7. /out:trr.exe
  8. trr.obj

  9. Z:\>trr
  10. A()
  11. lock()
  12. gfun()
  13. ~A()
  14. catch():1

  15. Z:\>
复制代码

论坛徽章:
0
7 [报告]
发表于 2010-06-09 22:51 |只看该作者
话是这么讲,但是最外层捕捉的话,如何能保证别人不在里层随意的丢异常呢?如何要捕捉哪些异常呢?
最终会发现异常满天飞,稍不留意程序就意外“被异常”了,于是变成像全局变量一样可恶的东西。

C++缺少强制声明,对可能抛出的异常进行约束。

当下,个人觉得和楼主的处理方式相反会比较好点,
尽可能的将异常处理掉,消灭在心智所能及的范围内。
群雄逐鹿中原 发表于 2010-06-09 12:19


1、对于里面“满天飞“的异常,我的办法很简单,捕捉感兴趣的,执行对应的方案;不感兴趣的,一并捕捉,是忽略还是给出简单提示,看需要了。
try{ do();}
catch(SomeException){ do_something();}
catch(OtherException){do_otherthing();}
catch(...)(show_msg("unknown error."}

2、如果是“尽可能”及时将异常处理掉,设想一个有十层调用嵌套的任务,因为最底层有一个未满足条件,需要放弃整个任务,你会怎么做呢,在第二层捕捉以后?
a、任务继续执行,这不行,因为条件不满足。
b、第二层重新包装,抛出;第三层捕捉,重新抛出。。。;重复不说,没有解决什么问题。
c、第二层以上用返回值表示执行失败,这又走到返回值的老路了。

论坛徽章:
0
8 [报告]
发表于 2010-06-09 23:05 |只看该作者
一但使用异常,就象一楼提到的那样,无可避免的,要把异常处理机制“传染”到更低层的代码和更多的代码。所 ...
没本 发表于 2010-06-09 13:25



呵呵,使用C/C++的好处就是,不阻止你犯错误。
为何不这样?

class Lock{
  Lock(){cout<<"lock"<<endl;}
  ~Lock(){cout<<"unlock"<<endl;}
};

void A::fun(){
  Lock l;
  gfun();
}

论坛徽章:
0
9 [报告]
发表于 2010-06-09 23:10 |只看该作者
本帖最后由 没本 于 2010-06-09 23:11 编辑

回复 8# shaver


    这样写当然没问题,但是手写lock/unlock语句的程序员比比皆是,尤其是Windows下无封装大段代码使用EnterCriticalSection/LeaveCriticalSection函数的程序员。那他们怎么办?难道只能证明他们没掌握异常,不配用C++?

项目里一个人的代码用了异常,那么该项目所有程序员都要有与之相同的素质,这在现实中往往不现实。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
10 [报告]
发表于 2010-06-09 23:12 |只看该作者
本帖最后由 OwnWaterloo 于 2010-06-09 23:15 编辑

同意楼上的, 要使用异常, 就要遵守异常的规矩, 比如要做到异常安全。
而且异常安全也不是什么难做的事。

因为代码没做到异常安全而导致各种问题, 反过来责怪异常 —— 和没明白指针是怎么回事, 导致各种问题后责怪指针一样。


—— 被插队了, 同意的是8楼
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP