解析C#中断言与异常的应用方式及异常处理的流程控制

断言与异常(Assertion Vs Exception)
在日常编程实践中,断言与异常的界限不是很明显,这也使得它们常常没有被正确的使用。我也在不断的与这个模糊的怪兽搏斗,仅写此文和大家分享一下我的个人看法。我想我们还可以从很多角度来区别断言和异常的使用场景,欢迎大家的意见和建议。

异常的使用场景:用于捕获外部的可能错误

断言的使用场景:用于捕获内部的不可能错误

我们可以先仔细分析一下我们在.net中已经存在的异常。

  • System.IO.FileLoadException
  • SqlException
  • IOException
  • ServerException

首先,我们先不将它们看成异常,因为我们现在还没有在异常和断言之间划清界限,我们先将它们看成错误。

当我们在编码的第一现场考虑到可能会出现文件加载的错误或者服务器错误后,我们的第一直觉是这不是我们代码的问题,这是我们代码之外的问题。

例如下面这段代码

public void WriteSnapShot(string fileName, IEnumerable<DbItem> items)
    {
      string format = "{0}\t{1}\t{2}\t{3}\t{4}\t{5}";
      using (FileStream fs = new FileStream(fileName, FileMode.Create))
      {
        using (StreamWriter sw = new StreamWriter(fs, Encoding.Unicode))
        {
           ...
          foreach (var item in items)
          {
            sw.WriteLine(string.Format(format, new object[]{
              item.dealMan,
              item.version,
              item.priority,
              item.bugStatus,
              item.bugNum,
              item.description}));
          }
          sw.Flush();
        }
      }
    }

上面的代码在写入文件,很显然会导致IOException。稍微有经验的程序员都会考虑到IO上可能出问题,那我们应该如何处理这个问题呢?在这个上下文中,我们别无它法,只能让这个错误继续往上抛,通知上面一层的调用者,有一个错误发生了,至于上一层调用者会如何处理,不是这个函数要考虑的问题。但在这个函数中,要记得一点,将当前函数中所占用的资源释放了。因此,当我们不能控制的外部错误出现时,我们可以将其作为异常往上抛,这时,我们该使用异常。

现在再来看看断言,我们还是以下面的一段代码为例子。

public Entities.SimpleBugInfo GetSimpleBugInfo(string bugNum)
    {

      var selector = DependencyFactory.Resolve<ISelector>();

      var list = selector.Return<Entities.SimpleBugInfo>(
        reader => new Entities.SimpleBugInfo
        {
          bugNum = reader["bugNum"].ToString(),
          dealMan = reader["dealMan"].ToString(),
          description = reader["description"].ToString(),
          size = Convert.ToInt32(reader["size"]),
          fired = Convert.ToInt32(reader["fired"]),
        },
        "select * from bugInfo",
        new WhereClause(bugNum, "bugNum"));

      Trace.Assert(list != null);

      if (list.Count == 0)
        return null;
      else
        return list[0];

    }

当我贴出这段代码时,心情有些坎坷,因为我本人在这里也纠结了很久,这也是我一直没有将断言和异常划清界线的原因之一。

首先我们来回顾一下之前定义的断言使用场景:内部不可能发生的错误。

selector.Return这段代码是不是内部代码?如果我们能够修改Return中的代码,说明它是内部代码;反之,说明它是外部代码。对于内部代码,我们可以用断言来保护其逻辑的不变性,当断言被触发时,我们就可以确信是内部代码的错误,我们应该立即修复。

再纠结一下,假设Return是外部代码,我们没有办法去修改它。那么上面的代码可以有两种写法(如果你有更多的想法,请赐教)。

第一种,直接抛出异常。

If(list == null)
{
  throw new NullReferenceException();
}

第二种,调整代码。

if(list == null || list.Count == 0)
{
  return null;
}
else
{
  return list[0];
}

当然,还有一种就是什么也不做,让代码执行下去直至系统为你抛出空引用错误。但这种做法违背了防卸性编程的原则,我们总是应行尽早或离错误的发生地最近的地方处理错误,避免错误数据流向系统的其它地方,产生更加严重的错误。

总结

对异常或断言的使用取决于你要防卸的是一个内部错误还是外部错误以及你认为它是一个内部错误或外部错误。如果你决定防卸一个内部错误,那请果断使用断言,反之,请使用异常。

异常处理
异常处理对于流程的控制,就像抛出与捕获一样分为两个方面:

如果错误(或某种情况)发生,是否允许程序的控制流继续执行下去(异常的抛出)
如果当前有异常发生,当前的代码是否有机会让程序的控制流进入到一个合理的状态(异常的捕获)
我认为可以用以上两条,作为判断异常处理的准绳。其实大家现在应该可以发现,这个所谓的准绳的着重点在于异常对于流程的影响,而不再是在什么情况下才使用异常。

对于流程控制,最直接的莫过于下面这段代码

try
          {
            foreach (var lockGroup in lockGroups)
            {

              ...

              foreach (var newlock in lockGroup.ToArray())
              {
                ...
                if (diningBlocks.Exists(n => testLockRange.IsOverlapped(n.StartTime, n.EndTime)))
                {
                  status = LockStatus.InResourceBlock;
                  throw new LockException();
                }

                var diningAvail = availabilities.Find(n => n.Time == newlock.StartTime.TimeOfDay);
                if (diningAvail == null)
                {
                  status = LockStatus.Failed;
                  throw new LockException();
                }

                ...

                if (newLockQuantity > diningAvail.MaxAvail && !canOverrideLock.AllowOverBook)
                {
                  status = LockStatus.Override;
                  throw new LockException();
                }
                else if (newLockQuantity + reservedQuantity + currentLockedAvail > diningAvail.MaxAvail && !canOverrideLock.AllowOverBook)
                {
                  status = LockStatus.Override;
                  throw new LockException();
                }

                ...
              }
            }
          }
          catch (LockException)
          {
            return new DiningLock[] { };
          }

在上面的代码中,有两层for循环,当最内层出现某种情况时,要求停止整个for循环的执行,显然用两个break是不行的,还得加入一个辅助变量。

但是,如果用异常,这个处理就简单多了。可以直接在最内层的抛出异常,在最外层(或是流程控制需要的地方)捕获异常。

在上面的代码中,异常处理起到了流程控制的作用,而不仅仅传递错误信息,对代码的简化做出了贡献。

(0)

相关推荐

  • C#异常处理总结及简单实例

    C#异常处理总结及简单实例 一.异常处理的理解? 异常处理是指程序在运行过程中,发生错误会导致程序退出,这种错误,就叫做异常. 因此处理这种错误,就称为异常处理. 二.异常处理如何操作? C# 异常处理时建立在四个关键词之上的:try.catch.finally 和 throw. 1.try:一个 try 块标识了一个将被激活的特定的异常的代码块.后跟一个或多个 catch 块. 2.catch:程序通过异常处理程序捕获异常.catch 关键字表示异常的捕获. 3.finally:finally

  • 轻松学习C#的异常处理

    异常是程序运行中发生的错误,异常处理是程序设计的一部分.错误的出现并不总是编写应用程序者的原因,有时候应用程序会因为终端用户的操作发生错误.无论如何,在编写程序前,都应该预测应用程序和代码中出现的错误.一般良好的编程规范也会避免一些不必要的程序错误的出现.         在项目的开发过程中,并不是所有的代码执行都和想象那样理想,总是避免不了异常的发生.这就需要编程语言的去处理这些异常,C#语言中有三种异常处理语句:         try...catch;//处理异常         try.

  • 详解C#编程中异常的创建和引发以及异常处理

    创建和引发异常 异常用于指示在运行程序时发生了错误.此时将创建一个描述错误的异常对象,然后使用 throw 关键字"引发"该对象.然后运行时搜索最兼容的异常处理程序. 当存在下列一种或多种情况时,程序员应引发异常: 方法无法完成其中定义的功能. 例如,如果方法的参数具有无效值: static void CopyObject(SampleClass original) { if (original == null) { throw new System.ArgumentException

  • C#异常处理详解

    异常介绍 1.System.Exception类 Message属性:发生异常的原因和异常的内容 Souce属性:抛出异常程序集的名称 StackTrace属性:发生异常的方法调用情况 InnerException属性:次异常中包含的异常 2.try{}catch{}finally{} 处理异常 a.不带参数的catch和catch(Exception)是有区别的 catch(Exception)可以捕获所有以Exception类派生的异常,而不带参数的catch可以捕获所有异常,不管异常是不是

  • .NET(C#):Emit创建异常处理的方法

    目录 Emit异常处理流程 显示Exception对象的Message属性 返回目录 Emit异常处理流程来看这种C#异常处理代码: 复制代码 代码如下: static void doo(Exception e) { try { throw e; } catch (ApplicationException ex) { Console.WriteLine("捕获ApplicationException"); } catch { Console.WriteLine("捕获Exce

  • C#异常处理的一些经验和技巧

    1.什么时候该异常处理?1)代码最外层,如WinFrom,避免用户看到内部异常信息用户体验不好,或者造成程序崩溃.2)遇到异常需要恢复状态或者重试的地方.例如连接数据库偶然失败了,可以有个重连机制,在Catch块重新连接数据库.3)对于一系列有可能失败的任务,其中有一个任务失败,不想影响到其他任务.例如要上传100张图片,不想因为一张图片上传发生异常而失败,进而终止整个上传任务,仅需要记录下失败的图片,提醒用户重传即可.2.异常处理需要注意的地方1)Catch和Finally代码应该非常短,而且

  • C#异常处理中try和catch语句及finally语句的用法示例

    使用 try/catch 处理异常 try-catch 块的用途是捕捉和处理工作代码所生成的异常. 有些异常可以在 catch 块中处理,解决问题后不会再次引发异常:但更多情况下,您唯一能做的是确保引发适当的异常. 示例 在此示例中,IndexOutOfRangeException 不是最适当的异常:对本方法而言 ArgumentOutOfRangeException 更恰当些,因为错误是由调用方传入的 index 参数导致的. class TestTryCatch { static int G

  • c#异常处理示例分享

    复制代码 代码如下: using System;using System.Collections.Generic;using System.Linq; using System.Text;//2014.3.14namespace _6.异常{    class Program    {        static void Main(string[] args)        {            try            {                Console.WriteLi

  • 解析C#中断言与异常的应用方式及异常处理的流程控制

    断言与异常(Assertion Vs Exception) 在日常编程实践中,断言与异常的界限不是很明显,这也使得它们常常没有被正确的使用.我也在不断的与这个模糊的怪兽搏斗,仅写此文和大家分享一下我的个人看法.我想我们还可以从很多角度来区别断言和异常的使用场景,欢迎大家的意见和建议. 异常的使用场景:用于捕获外部的可能错误 断言的使用场景:用于捕获内部的不可能错误 我们可以先仔细分析一下我们在.net中已经存在的异常. System.IO.FileLoadException SqlExcepti

  • 简单解析MySQL中的cardinality异常

    前段时间,一大早上,就收到报警,警告php-fpm进程的数量超过阈值.最终发现是一条sql没用到索引,导致执行数据库查询慢了,最终导致php-fpm进程数增加.最终通过analyze table feed_comment_info_id_0000 命令更新了Cardinality ,才能再次用到索引. 排查过程如下: sql语句: select id from feed_comment_info_id_0000 where obj_id=101 and type=1; 索引信息: show in

  • 解析PHP中Exception异常机制

    异常的基本使用 当异常被抛出时,其后的代码不会继续执行,PHP 会尝试查找匹配的 "catch" 代码块. 如果异常没有被捕获,而且又没用使用 set_exception_handler() 作相应的处理的话,那么将发生一个严重的错误(致命错误),并且输出 "Uncaught Exception" (未捕获异常)的错误消息. 让我们尝试抛出一个异常,同时不去捕获它: <?php //create function with an exception funct

  • 解析iOS10中的极光推送消息的适配

    iOS10发布后,发现项目中的极光推送接收消息异常了. 查了相关资料后才发现,iOS10中对于通知做了不少改变.同时也发现极光也很快更新了对应的SDK. 现在就把适配修改的做法分享一下,希望对有需要的童鞋有所帮助. 具体做法如下: 注意:必须先安装Xcode8.0版本. 一.添加相关的SKD,或framework文件 1.添加UserNotification.framework 2.更新jpush的SDK(最新版本:jpush-ios-2.1.9.a)https://www.jiguang.cn

  • 解析SpringCould中的Hystrix

    一.简介 源码地址:https://gitee.com/xiaocheng0902/my-cloud.git 1,定义 Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时.异常等.Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性. "断路器"本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的.可处理的备

  • PHP中断言函数的使用详解

    原来一直以为断言相关的函数是 PHPUnit 这些单元测试组件提供的,在阅读手册后才发现,这个 assert() 断言函数是 PHP 本身就自带的一个函数.也就是说,我们在代码中进行简单的测试的时候是不需要完全引入整个单元测试组件的. assert() 断言函数 assert(1==1); assert(1==2); // assert.exception = 0 时,Warning: assert(): assert(1 == 2) // assert.exception = 1 时,Fata

  • 解析Spring中面向切面编程

    一.AOP--另一种编程思想 1.1.什么是 AOP AOP (Aspect Orient Programming),直译过来就是 面向切面编程.AOP 是一种编程思想,是面向对象编程(OOP)的一种补充.面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面. 从<Spring实战(第4版)>图书中扒了一张图: 从该图可以很形象地看出,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块. 1.2.为什么需要 AOP 想象下面的场景,开发中在多个模块间有

  • 解析java中的condition

    一.condition 介绍及demo Condition是在java 1.5中才出现的,它用来替代传统的Object的wait().notify()实现线程间的协作,相比使用Object的wait().notify(),使用Condition的await().signal()这种方式实现线程间协作更加安全和高效.因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作. Condition是个接口,基本的方法就是await()和signal()方法:

  • 深度解析SpringBoot中@Async引起的循环依赖

    目录 事故时间线 猜想 什么是循环依赖 什么是@Async 啊,昨晚发版又出现了让有头大的循环依赖问题,按理说Spring会为我们解决循环依赖,但是为什么还会出现这个问题呢?为什么在本地.UAT以及PRE环境都没有出现这个问题,但是到了PROD环境就出现了这个问题呢?本文将从事故时间线.及时止损.复盘分析等几个方面为大家带来详细的分析,干货满满! 事故时间线 本着"先止损.后复盘分析"的原则,我们来看一下这次发版事故的时间线. 2021年11月16日晚23点00分00秒开始发版,此时集

  • 老生常谈java数组中的常见异常

    数组的定义 1:单个变量能存储信息 2:用来存储具有相同数据类型的数据集合,可以使用共同的名字来引用数组中存储的数据. 特点 数组可以存储任何类型的数据,包括原始数据类型和引用数据类型,但是一旦指定了数组的类型之后,就只能用来存储指定类型的数据. 数组的使用 声明一个数组变量来存放该数组 java基础之数组中的常见异常,代码如下 package com.atguigu.java; /* * 数组中的常见异常: * 1. 数组角标越界的异常:ArrayIndexOutOfBoundsExcetio

随机推荐