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

简介: 原文:编写高质量代码改善C#程序的157个建议[避免finaly内的无效代码、避免嵌套异常、避免吃掉异常、注意循环异常处理]前言     本文已同步到http://www.cnblogs.com/aehyok/p/3624579.html。
原文: 编写高质量代码改善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,让效率得到了极大的提升。

 

 

目录
相关文章
|
2月前
|
存储 SQL 数据库连接
C#程序调用Sql Server存储过程异常处理:调用存储过程后不返回、不抛异常的解决方案
本文分析了C#程序操作Sql Server数据库时偶发的不返回、不抛异常问题,并提出了解决思路。首先解析了一个执行存储过程的函数`ExecuteProcedure`,其功能是调用存储过程并返回影响行数。针对代码执行被阻塞但无异常的情况,文章总结了可能原因,如死锁、无限循环或网络问题等。随后提供了多种解决方案:1) 增加日志定位问题;2) 使用异步操作提升响应性;3) 设置超时机制避免阻塞;4) 利用线程池分离主线程;5) 通过信号量同步线程;6) 监控数据库连接状态确保可用性。这些方法可有效应对数据库操作中的潜在问题,保障程序稳定性。
159 11
|
8月前
|
安全 编译器 程序员
C# 中 foreach 循环和 for 循环深度比较
为什么建议你多数情况下使用 foreach 进行遍历循环?看完你就明白了
234 5
|
9月前
|
缓存 C# Windows
C#程序如何编译成Native代码
【10月更文挑战第15天】在C#中,可以通过.NET Native和第三方工具(如Ngen.exe)将程序编译成Native代码,以提升性能和启动速度。.NET Native适用于UWP应用,而Ngen.exe则通过预编译托管程序集为本地机器代码来加速启动。不过,这些方法也可能增加编译时间和部署复杂度。
515 2
|
7月前
|
算法 Java 测试技术
Benchmark.NET:让 C# 测试程序性能变得既酷又简单
Benchmark.NET是一款专为 .NET 平台设计的性能基准测试框架,它可以帮助你测量代码的执行时间、内存使用情况等性能指标。它就像是你代码的 "健身教练",帮助你找到瓶颈,优化性能,让你的应用跑得更快、更稳!希望这个小教程能让你在追求高性能的路上越走越远,享受编程带来的无限乐趣!
278 13
|
9月前
|
设计模式 程序员 C#
C# 使用 WinForm MDI 模式管理多个子窗体程序的详细步骤
WinForm MDI 模式就像是有超能力一般,让多个子窗体井然有序地排列在一个主窗体之下,既美观又实用。不过,也要小心管理好子窗体们的生命周期哦,否则一不小心就会出现一些意想不到的小bug
731 0
|
开发框架 前端开发 .NET
C#编程与Web开发
【4月更文挑战第21天】本文探讨了C#在Web开发中的应用,包括使用ASP.NET框架、MVC模式、Web API和Entity Framework。C#作为.NET框架的主要语言,结合这些工具,能创建动态、高效的Web应用。实际案例涉及企业级应用、电子商务和社交媒体平台。尽管面临竞争和挑战,但C#在Web开发领域的前景将持续拓展。
392 3
|
8月前
|
C# 开发者
C# 一分钟浅谈:Code Contracts 与契约编程
【10月更文挑战第26天】本文介绍了 C# 中的 Code Contracts,这是一个强大的工具,用于通过契约编程增强代码的健壮性和可维护性。文章从基本概念入手,详细讲解了前置条件、后置条件和对象不变量的使用方法,并通过具体代码示例进行了说明。同时,文章还探讨了常见的问题和易错点,如忘记启用静态检查、过度依赖契约和性能影响,并提供了相应的解决建议。希望读者能通过本文更好地理解和应用 Code Contracts。
154 3
|
7月前
|
存储 安全 编译器
学懂C#编程:属性(Property)的概念定义及使用详解
通过深入理解和使用C#的属性,可以编写更清晰、简洁和高效的代码,为开发高质量的应用程序奠定基础。
352 12
|
8月前
|
设计模式 C# 图形学
Unity 游戏引擎 C# 编程:一分钟浅谈
本文介绍了在 Unity 游戏开发中使用 C# 的基础知识和常见问题。从 `MonoBehavior` 类的基础用法,到变量和属性的管理,再到空引用异常、资源管理和性能优化等常见问题的解决方法。文章还探讨了单例模式、事件系统和数据持久化等高级话题,旨在帮助开发者避免常见错误,提升游戏开发效率。
305 4
|
10月前
|
安全 程序员 编译器
C#一分钟浅谈:泛型编程基础
在现代软件开发中,泛型编程是一项关键技能,它使开发者能够编写类型安全且可重用的代码。C# 自 2.0 版本起支持泛型编程,本文将从基础概念入手,逐步深入探讨 C# 中的泛型,并通过具体实例帮助理解常见问题及其解决方法。泛型通过类型参数替代具体类型,提高了代码复用性和类型安全性,减少了运行时性能开销。文章详细介绍了如何定义泛型类和方法,并讨论了常见的易错点及解决方案,帮助读者更好地掌握这一技术。
164 11