编写高质量代码改善C#程序的157个建议[避免finaly内的无效代码、避免嵌套异常、避免吃掉异常、注意循环异常处理]

前言  

  本文已同步到http://www.cnblogs.com/aehyok/p/3624579.html。本文主要来学习以下几点建议

  建议61、避免在finally内撰写无效代码

  建议62、避免嵌套异常

  建议63、避免“吃掉”异常

  建议64、为循环增加Tester-Doer模式而不是将try-catch置于循环内

建议61、避免在finally内撰写无效代码

先直接来看一下三个简单的try catch方法

    public class User
    {
        public string Name { get; set; }
    }

    class Program
    {

        static void Main(string[] args)
        {
            Console.WriteLine(Test1());
            Console.WriteLine(Test2());
            Console.WriteLine(Test3().Name);

            Console.ReadLine();
        }

        public static int Test1()
        {
            int i = 0;
            try
            {
                i = 1;
            }
            finally
            {
                i = 2;
                Console.WriteLine("\t 将int结果改为2,finnally执行完毕。");
            }
            return i;
        }

        public static int Test2()
        {
            int i = 0;
            try
            {
                return i = 1;
            }
            finally
            {
                i = 2;
                Console.WriteLine("\t 将int结果改为2,finnally执行完毕。");
            }
        }

看完代码你心里大概也有了一个答案了吧

这些如果通过IL来解释,还是比较容易的,在此就不进行赘述了。

  在CLR中,方法的参数以及返回值都是用栈来保存的。在方法内部,会首先将参数依次压栈,当需要使用这些参数的时候,方法会直接去栈里取用参数值,方法返回时,会将返回值压入栈顶。如果参数的类型是值类型,压栈的就是复制的值,如果是引用类型,则在方法内对于参数的修改也会带到方法外。

建议62、避免嵌套异常  

在建议59中已经强调过,应该允许异常在调用堆栈中往上传播,不要过多使用catch,然后再throw。果断使用catch会带来两个问题:

1、代码更多了。这看上去好像你根本不知道该怎么处理异常,所以你总在不停地catch.

2、隐藏了堆栈信息,使你不知道真正发生异常的地方。

来看一下下面的代码

        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("NoTry\n");
                MethodNoTry();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.StackTrace);
            }
            try
            {
                Console.WriteLine("WithTry\n");
                MethodWithTry();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception.StackTrace);
            }
            Console.ReadLine();
        }

        public static int Method()
        {
            int i = 0;
            return 10 / i;
        }

        public static void MethodNoTry()
        {
            Method();
        }

        public static void MethodWithTry()
        {
            try
            {
                Method();
            }
            catch (Exception exception)
            {
                throw exception;
            }
        }

执行结果

  可以发现,MethodNoTry的方法可以查看到发生异常错误的地方,而MethodWithTry根本不清楚发生错误的地方了。调用的堆栈倍重置了。如果这个方法还存在另外的异常,在UI层将永远不知道真正发生错误的地方,给开发者带来不小的麻烦。 

除了在建议59中提到的需要包装异常的情况外,无故地嵌套异常是我们要极力避免的。当然,如果真得需要捕获这个异常来恢复一些状态,然后重新抛出,代码来起来应该可以这样:

            try
            {
                MethodWithTry();
            }
            catch(Exception)
            {
                ///工作代码
                throw;
            }

或者稍作改动

            try
            {
                MethodWithTry();
            }
            catch
            {
                ///工作代码
                throw;
            }

尽量避免下面这样引发异常:

            try
            {
                MethodWithTry();
            }
            catch(Exception exception)
            {
                ///工作代码
                throw exception;
            }

直接throw exception而不是throw将会重置堆栈消息。

建议63、避免“吃掉”异常 

  看了建议62之后,你可能已经明白,嵌套异常是很危险的行为,一不小心就会将异常堆栈信息,也就是真正的Bug出处隐藏起来。但这还不是最严重的行为,最严重的就是“吃掉”异常,即捕获然后不向上层throw抛出。

  避免“吃掉”异常,并不是说不应该“吃掉”异常,而是这里面有个重要原则:该异常可悲预见,并且通常情况它不能算是一个Bug。

  想象你正在对上万份文件进行解密,这些文件来自不同的客户端,很有可能存在文件倍破坏的现象,你的目标就是要讲解密出来的数据插入数据库。这个时候,你不得不忽略那些解密失败的问题,让这个过程进行下去。当然,记录日志是必要的, 因为后期你可能会倍解密失败的文件做统一的处理。

  另外一种情况,可能连记录日志都不需要。在对上千个受控端进行控制的分布式系统中,控制端需要发送心跳数据来判断受控端的在线情况。通常的做法是维护一个信号量,如果在一个可接受的阻滞时间如(如500ms)心跳数据发送失败,那么控制端线程将不会收到信号,即可以判断受控端的断线状态。在这种情况下,对每次SocketException进行记录,通常也是没有意义的。

  本建议的全部要素是:如果你不知道如何处理某个异常,那么千万不要“吃掉”异常,如果你一不小“吃掉”了一个本该网上传递的异常,那么,这里可能诞生一个BUg,而且,解决它会很费周折。 

建议64、为循环增加Tester-Doer模式而不是将try-catch置于循环内

  如果需要在循环中引发异常,你需要特别注意,因为抛出异常是一个相当影响性能的过程。应该尽量在循环当中对异常发生的一些条件进行判断,然后根据条件进行处理。可以做一个测试:

        static void Main(string[] args)
        {
            CodeTimer.Initialize();

            CodeTimer.Time("try..catch..", 1, P1);

            CodeTimer.Time("Tester-Doer", 1, P2);

            Console.ReadLine();
        }

        public static void P1()
        {
            int x = 0;
            for (int i = 0; i < 1000; i++)
            {
                try
                {
                    int j = i / x;
                }
                catch
                { 
                    
                }
            }
        }

差距相当明显。以上代码中,我们预见了代码可能会发生DivideByZeroException异常,于是,调整策略,对异常发生的条件进行了特殊处理:Continue,让效率得到了极大的提升。

英语小贴士

1、 How much?—— 多少钱? 2、I'm full.—— 我饱了。 3、 I'm home.—— 我回来了。 4、 I'm lost. ——我迷路了。 5、 My treat.—— 我请客。 6、 So do I.—— 我也一样。 7、 This way.—— 这边请。 8、 After you.—— 您先。 9、 Bless you! ——祝福你! 10、 Follow me.—— 跟我来。

作者:aehyok

出处:http://www.cnblogs.com/aehyok/

感谢您的阅读,如果您对我的博客所讲述的内容有兴趣,那不妨点个推荐吧,谢谢支持:-O。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python研发

一杯咖啡带你读懂状态机

最近公司做棋牌的项目,当时还是不理解什么是状态机,当一个项目做完之后,大脑里已经有了一个状态机的概念,所以今天就与大家分享出来,由于本人的技术博浅,有很多地方没...

13230
来自专栏逸鹏说道

EntityFramework 外键值映射

如果在 EF OnModelCreating 中配置了实体外键映射,也就是 SQL Server 中的 ForeignKey,那么我们在添加实体的时候,主实体的...

88750
来自专栏林德熙的博客

win10 uwp json

本文讲的是关于在uwp使用json的简单使用,json应用很多,因为我只是写简单使用,说的东西可能不对或者不符合每个人的预期。如果觉得我有讲的不对的,就多多包含...

12310
来自专栏木宛城主

Unity应用架构设计(10)——绕不开的协程和多线程(Part 1)

在进入本章主题之前,我们必须要了解客户端应用程序都是单线程模型,即只有一个主线程(Main Thread),或者叫做UI线程,即所有的UI控件的创建和操作都是...

40660
来自专栏王亚昌的专栏

程序的存储空间布局

栈 由编译器自动分配释放管理。局部变量及每次函数调用时返回地址、以及调用者的环境信息(例如某些机器寄存器)都存放在栈中。新被调用的函数在栈上为其自动和临时变量分...

14020
来自专栏Jackson0714

C#多线程之旅(3)——线程池

38060
来自专栏Java3y

【Java】几道让你拿offer的面试题

之前在刷博客的时候,发现一些写得比较好的博客都会默默收藏起来。最近在查阅补漏,有的知识点比较重要的,但是在之前的博客中还没有写到,于是趁着闲整理一下。

51500
来自专栏机器之心

资源 | 简单快捷的数据处理,数据科学需要注意的命令行

16850
来自专栏青玉伏案

设计模式(九): 从醋溜土豆丝和清炒苦瓜中来学习"模板方法模式"(Template Method Pattern)

今天是五.四青年节,祝大家节日快乐。看着今天这标题就有食欲,夏天到了,醋溜土豆丝和清炒苦瓜适合夏天吃,好吃不上火。这两道菜大部分人都应该吃过,特别是醋溜土豆丝,...

18390
来自专栏.NET后端开发

ADO.NET入门教程(六) 谈谈Command对象与数据检索

摘要 到目前为止,我相信大家对于ADO.NET如何与外部数据源建立连接以及如何提高连接性能等相关知识已经牢固于心了。连接对象作为ADO.NET的主力先锋,为用户...

43370

扫码关注云+社区

领取腾讯云代金券