大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: 垃圾桶 修改讲义

合并到:https://17bang.ren/Code/304



什么是异常?

Exception:通用规则之外的情况。比如:NullReferenceException,OutOfRangeException……(演示:注意F5和Ctrl+F5的区别)

讨论:(抛)异常 = (有)bug?

  • 正方:就是bug,你书写代码的时候为什么不考虑到这些异常情况呢?应该先做一些判断啊……
  • 反方:???

事实上,如果代码一跑就有异常,那肯定是bug;但是,有些异常是:

  • 我们无法预料的
  • 预料到了也难以/无法处理的

所以,只有把异常抛出来,交由开发/代码维护/……人员处理。比如:掉网了/磁盘满了/断电了……

异常,本来就是那些“正常情况下不会出现”、且“出现了之后‘’也无法处理”的问题,并且程序不应该继续执行(应该中断)


例如:


抛(throw)出异常

            if (score<0 || score > 100)
            {
                throw new Exception("成绩只能在0-100分之间");
            }

理解:

  1. 正常情况下,成绩只能是在0-100分(或者,我只能处理0-100分的成绩)
  2. 现在输入的成绩为负数,“我”(当前方法)也不知道怎么回事,该怎么处理……

所以只能抛出异常,交由“调用者”处理

抛出异常,会中断程序执行:这是必须的,以免程序继续执行造成数据错误等


理解:

  • 要暴露错误,错误暴露得越早越好(对比JavaScript和SQL)
  • 防御式编程


常见异常种类

基类:Exception,常用方法属性:

  • Message
  • StackTrace
  • InnerException
  • ToString():override(复习)

自定义异常:必须继承自Exception(体会继承)

@想一想@:为什么需要这么多种异常?


try...catch...finally

被抛出的异常,(如果没有被处理/捕获)会直接传递给它的调用者,再由调用者传递给它的调用者,...,直到程序最顶层调用,程序崩溃

为了避免程序崩溃,我们需要try...catch来捕获异常

            try   //尝试
            {
                SLevel level = Map(101);
            }
            catch (FileNotFoundException)  //捕获
            {
                //如果是FileNotFoundException
                //记录到日志,不再抛出
            }
            catch (IndexOutOfRangeException)
            {
                //如果是IndexOutOfRangeException
                //发送Email给维护人员,不再抛出
            }
            catch (Exception)
            {
                //其他异常处理
                throw;      //将异常再次抛出
            }


还可以根据不同的异常类型,进行不同的处理。

但注意:越是具体(子类)的异常,越是要放在前面,否则编译无法通过。

finally:无论有无异常(即使有return)都要执行的代码(比如关闭文件流)

  • 配合catch:没有catch就无法正常使用
  • 断点不会被击中


使用注意事项

throw:

  • 异常会带来较大的资源开销(性能损耗),所以要尽可能避免异常被抛出(不是不写throw exception的代码)
  • 不要使用exception做为分支判断条件
        string n = Console.ReadLine();
        try
        {
            int i = Convert.ToInt32(n);
        }
        catch (FormatException e)
        {
            Console.WriteLine("请输入整数……");
        }
  • 尽可能的使用具体的、.NET现有的异常

try...catch:

  • 不知道怎么处理的,就不要处理
  • 不要catch之后,啥都不做(或者就包裹一下),直接throw
  • 总是在程序入口(顶层方法)处catch未捕获的异常

finally:

  • 如果仅仅是为了释放资源,推荐使用using块
  • 使用using时注意和try...catch的搭配


示例

几个同学共同开发,完成一个成绩转换等级的代码

        //小马负责这个方法
        static SLevel Map(int score)
        {
            if (score > 100 || score < 0)
            {
                //小马完全不知道这个方法会被谁调用
                //于是当输入值不符合要求时,直接抛出异常
                throw new ArgumentOutOfRangeException("");
            }

            //if (score >= 90)
            //{
                //return SLevel.Perfect;
            //}
            else if (score >= 80)
            {
                return SLevel.Excellent;
            }
            else if (score >= 60)
            {
                return SLevel.Passed;
            }
            else
            {
                return SLevel.Failed;
            }
        }
        //文轩负责该部分模块
        static void Congratulate(int score)
        {
            try
            {
                SLevel level = Map(score);
                switch (level)
                {
                    case SLevel.Excellent:
                    case SLevel.Passed:
                        Console.WriteLine($"恭喜你获得{level}级勋章!");
                        break;
                    case SLevel.Failed:
                        Console.WriteLine($"很遗憾,你没有通过测试哟!");
                        break;
                    default:
                        //因为文轩的谨慎,在此后代码发生改动时,及时的暴露了改动造成的问题(演示)
                        throw new NotImplementedException($"输入了{level}级的未处理成绩");
                }
            }
            catch (Exception e)
            {
                //需要记录这些异常信息,看看究竟传入的是什么参数  -- 日志文件
                File.AppendAllText("C:\\17bang\\wx-error.log",
                    $"{DateTime.Now}:输入的{score}未能成功转换"/*+ e.ToString()*/+ Environment.NewLine);
                //但还是无权吞掉(swallow)这个异常,并且认为这是一个无效转换的异常
                throw new InvalidCastException($"score(${score})未能成功转换成等级", e);
            }
        }
        //小龙负责该部分模块
        static void InputScore()
        {
            //假设没有int.TryParse()方法
            //遇到无法转换成整数的情况,就会抛出异常
            //小龙也不知道该如何处理,所以不予处理
            int score = Convert.ToInt32(Console.ReadLine());
            Congratulate(score);
        }
            while (true)
            {
                //老程调用InputScore(),发现
                //1、该方法可能抛出异常,
                //2、且不愿让异常导致程序崩溃
                //于是只能try...catch进行处理
                try
                {
                    InputScore();
                }
                catch (FormatException e)
                {
                    Console.WriteLine("你输入的不是一个整数,请重新输入");
                    Console.WriteLine();
                    //File.AppendAllText("C:\\17bang\\lc-error.log", e.ToString() + Environment.NewLine);
                }
                catch (InvalidCastException e)
                {
                    Console.WriteLine("你输入的数值不能转换成等级,请重新输入");
                    Console.WriteLine();
                    //File.AppendAllText("C:\\17bang\\lc-error.log", e.ToString() + Environment.NewLine);
                }
                catch (Exception e)
                {
                    File.AppendAllText("C:\\17bang\\lc-error.log", e.ToString() + Environment.NewLine);
                    //给用户一个友好提示
                    Console.WriteLine("程序遇到了未处理的异常情况,请稍后重试或者联系我们……");
                    Console.WriteLine();
                    //因为这里是入口(顶级)函数,不需要再throw;了
                }
            }
    enum SLevel
    {
        //Perfect,
        Excellent,
        Passed,
        Failed
    }


作业

  1. 修改之前的属性验证:problem.Reward为负数时直接抛出“参数越界”异常
  2. 内容(Content)发布(Publish)的时候检查其作者(Author)是否为空,如果为空抛出“参数为空”异常
  3. 在ContentService中捕获异常
    1. 如果是“参数为空”异常,Console.WriteLine()输出:内容的作者不能为空,将当前异常封装进新异常的InnerException,再将新异常抛出
    2. 如果是“”参数越界”异常,Console.WriteLine()输出:求助的Reward为负数(-XX),不再抛出异常
  4. ContentService中无论是否捕获异常,均要Console.WriteLine()输出:XXXX年XX月XX日 XX点XX分XX秒(当前时间),请求发布内容(Id=XXX)
  5. 在Main()函数调用ContentService时,捕获一切异常,并记录异常的消息和堆栈信息
学习笔记
源栈学历
今天学习不努力,明天努力找工作

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 垃圾桶 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码