首页 > 其他分享 >第7章 异常

第7章 异常

时间:2025-01-11 23:11:03浏览次数:1  
标签:DO Exception DON 抛出 应该 异常

第7章 异常

7.1 抛出异常

  • DO​​​:操作失败应该通过 抛出异常 的方式报告,而非通过 返回错误码

  • CONSIDER​:在代码遇到严重问题且无法继续安全地执行时,要调用 System.Enviroment.FailFast ​ 终止进程,而不是抛出异常。

    该方法会向 Windows 应用程序事件日志写入消息,然后在发往 Microsoft 的错误报告中加入该消息和可选异常信息。

    Enviroment.FailFast("发生了一个无法挽回的异常", ex)
    

  • CONSIDER​​:抛出异常可能会对性能造成的影响。如果可以,支持 Tester-DoerTry-Parse 模式,避免抛出异常。
    对大多数程序来说,每秒抛出 100 个异常很可能会严重影响程序性能。
    为了避免触发异常,我们可以提供一些方法让用户做前置检查,这样可以防止触发异常:

    ICollection<int> collection = ...
    if (!collection.IsReadOnly) {
        collection.Add(additionalNumber);
    }
    

  • DO​​​:为所有的异常撰写文档,并将其作为契约的一部分。

    作为契约的一部分,也就意味着它不应该随版本而变化(既不应该改变 异常类型 ,也不应该 增加新的异常 )。

  • DON'T ​​​:抛出异常 不应该 出现可选项。

    // 垃圾设计
    public Type GetType(string name, bool throwOnError)
    

  • DON'T ​​​:异常 不能 作为返回值或 out 参数。

    // 不好的设计
    public Exception DoSomething() { ... }
    

  • CONSIDER ​: 使用 辅助方法来创建异常。

    从不同地方抛出同一个异常很常见,为了避免代码重复,可以使用辅助函数来抛出异常。

    此外,抛出异常的成员无法被 内联 ,把抛出异常的语句放在辅助函数中,那么该成员就有可能被 内联

    class File{
        string fileName;
    
        public byte[] Read(int bytes) {
            if(!ReadFile(handle, bytes)) ThrowNewFileIOException(...);
        }
    
        void ThrowNewFileIOException(...){
            string description = // 创建本地字符串
            throw new FileIOException(description);
        }
    }
    

  • DON'T:不要 从异常过滤块中抛出异常。

    例如,以下代码过滤程序判断异常信息中是否包含“foo”,如果 ex.Message​ 为 null​,when​ 语句会抛出异常,CLR 会将其识别为 false​,从而跳过异常处理。

    try{
        // 调用可能抛出异常的方法
    }
    catch (Exception ex) when (ex.Message.Contains("foo")){
        // 处理包含foo的异常
    }
    

  • AVOID:不要 显式地从 finally 代码块中抛出异常。
    在 finally 中抛出异常可能导致以下问题:

    • 如果 try-catch 中已经抛出异常,finally 中抛出的异常会 覆盖掉 之前的异常,导致原始异常信息 丢失
    • 如果 try-catch 中包含 return 语句,finally 中抛出异常会使 return 语句 失效 ,导致方法 提前终止 ,违背程序员预期。

    finally 中隐式地抛出异常,即在调用其他方法时由其他方法抛出异常, 是可以 接受的。

7.2 为抛出的异常选择合适的类型

异常是我们碰到错误时抛出的。错误一般分为两类:

  • 使用 错误

错误调用导致的错误,例如传入了 null 参数。此类错误不应该由框架处理,而应该修改调用方代码。

  • 执行 错误
    又分为两类:

  • 程序 错误

可以在程序中处理的错误。如 `File.Open`​ 未找到相应文件抛出 `FileNotFoundException`​ 异常,我们可以创建一个新文件并继续运行。
  • 系统失败
无法在程序中进行处理的执行错误。如即时编译器(Just-In-Time compiler)用尽了内存而引发的 `OutOfMemoryException`​​​。

  • DON'T ​​​​:使用错误​​​​ 不应该 抛出自定义异常, 应该 抛出框架中已有的异常。

    因使用错误而抛出的异常,应该侧重于写好异常消息(比如在异常消息中给出详尽的解释),并使用.NET 框架中已有的那些异常类型。

  • CONSIDER ​​​:程序错误​​​ 应该 抛出自定义异常。

    前提是它的处理方式和其他异常的处理方式 有所不同 。否则应该抛出已有异常。

  • DON'T ​​​​:如果错误的处理方式和框架中已有异常并没有什么不同, 不要 自定义异常。如果不能通过框架中已有的异常来传达该错误, 自定义异常。不要仅仅为了拥有自己的异常而创建并使用新的异常。

    这种情况应该抛出框架中已有的异常。

    注意:在代码中抛出原异常的派生异常 不会 破坏已有代码。

  • AVOID​:避免设计出会导致系统失败的 API。如果有此类失败,则应该调用 Enviroment.FailFast ​,而不是抛出异常。

  • DO​:使用合理的、最具针对性的异常。

    使用最贴切的异常。

    例如:传入的参数是 null,应该抛出 ArgumentNullException ​,而非其基类 ArgumentException ​。

7.2.1 Exception.Message​ 的设计

  • DO​:抛出异常时应该为开发人员提供丰富而有意义的 错误消息 ,且语句 通顺清晰

    消息应该解释异常产生的原因,并告知用户应该 怎样避免该异常 。并且要做到直接展示给最终用户看也没有问题。

  • DO​​​:异常消息中的每个句子都要有 号,不要包含 号和 号。

  • DON'T​​​​:除非得到许可,否则不要在异常消息中 泄露安全信息

  • CONSIDER​​​:如果有 多语言 需求,异常消息应进行本地化。

7.2.2 异常处理

  • DON'T ​: 框架 ​代码不应该发生吞异常。

    吞异常,指捕获具体类型不确定的异常,并且不对异常进行处理,任由程序继续执行。

    try{
        File.Open(...);
    }
    catch (Exception e) { }  // 此处吞掉了所有异常。不要这么做!
    

    如果是为了把异常转移到另一个线程,则可以进行“吞异常”的操作,且这不会认为是“吞异常”。注意:转移异常后,另外一个线程应处理该异常,否则也是“吞异常”!

  • AVOID​: 应用程序代码 要避免发生吞异常。

    有时在 应用程序 中吞异常是可以接受的。

    吞异常并继续运行,和正常程序相比会有状态不一致的风险,进而导致奇怪的错误(如 OurOfMemoryException​、StackOverflowException​ 或 ThreadAbortException​)。

  • DON'T​​​: 转移异常 的 catch 块,不要剔除任何特定异常。

    catch (Exception e){
        // 错误代码,不要这样做!
        if (e is OutOfMemoryException || e is AccessViolationException)
            throw;
        ...
    }
    

  • CONSIDER​​​​:如果确实了解该异常产生的原因,并能对错误做出适当的响应,应该 捕获该异常 。否则应该 允许该异常沿着调用栈向上游传递

    每编写一个异常处理程序,都有可能导致 bug 被隐藏,进而削弱系统的稳固性,因此请勿滥用。

  • DO​​:进行清理工作时 应该 使用 try-finally,而 try-catch。

    清理工作通常是释放已分配的资源,而异常抛给上一层处理。另外,不要在 catch 块中清理代码。

    另外,C#有 using 语句,可以不用 finally 进行清理。

  • DO​:捕获并重新抛出异常时应该使用 空的 throw 语句。

    这样可以保持调用栈不变。

    try{
        // do something
    }
    catch(Exception e){
        // 上层捕获的异常信息,会包含异常抛出的原始位置。
        throw;
        // 上层捕获的异常信息,会指出此处是异常抛出的原始位置。
        throw e;
    }
    

  • DO​:当从不同的环境中重新抛出异常时,要使用 ExceptionDispatchInfo ​。

    当从其他线程转移异常,或者 catch 后未使用空的 throw 语句再次抛出异常,要使用 ExceptionDispatchInfo ​ 类,它会在重新抛出的过程中持续保存调用栈。

    private ExceptionDispatchInfo _savedExceptionInfo;
    
    private void BackgroundWorker() {
        try{
            ...
        } catch (Exception e){
            _savedExceptionInfo = ExceptionDispatchInfo.Capture(e);
        }
    }
    
    public object GetResult() {
        if(_done) {
            if(_savedExceptionInfo != null){
                _savedExceptionInfo.Throw();
                // 编译器无法理解该方法是抛出了一个异常,因此需要额外的return语句。
                return null;
            }
        }
    }
    

  • DON'T​​​​:不符合 CLS 标准 的异常(非 System.Exception​ 派生异常),不要使用无参数的 catch 块处理。

    经过 CLR2.0 的修改,不符合要求的异常会包装为 RuntimeWarppedException​​ 再抛出。因此可以改为捕获 RuntimeWarppedException​​。

7.2.3 封装异常

  • CONSIDER​:如果低层异常对用户没有意义,高层应该 捕获后将其封装成有意义的异常再抛出 ;如果用户想查看低层异常,则不要 对其进行封装

    • 当然,这么做会抹去异常发生的原始位置。只有当原来的异常几乎没有什么意义,对调试也没有帮助时,才应该这么做。

      try{
          // 读取transaction文件
      }
      catch (FileNotFoundException e) {
          throw new TransactionFileMissingException(..., e);
      }
      

  • AVOID​:类型不确定的异常不应该 二次封装

    这是“吞异常”的另一种形式。

    不过也有例外,当发生了极其严重的错误,其重要性已经超过让调用者知道异常的具体类型,可以对其二次封装。

    例如 TypeInitializationException对静态构造函数中引发的所有异常都进行了封装。

  • DO​​​:封装异常时应为其指定 内部异常(InnerException

    • throw new ConfigurationFileMissingException(..., e);
      

7.3 标准异常类型的使用

7.3.1 Exception​ 与 SystemException

  • DON'T:不要 抛出 System.Exception​ 或 System.SystemException​ 异常。
  • DON'T:不要 在框架代码中捕获 System.Exception​ 或 System.SystemException​ 异常。
  • AVOID​:除非在 程序顶层 ,不要捕获 System.Exception​ 或 System.SystemException ​异常。

7.3.2 ApplicationException

  • DON'T:不要 抛出 System.ApplicationException ​及其子类

    设计之初,SystemException​ 的派生类用于表示 CLR(或系统)自身抛出的异常,ApplicationException​ 的派生类用于表示非 CLR 异常(应用程序异常)。但是很多异常类没有遵循这一模式,如 TargetInvocationException​ 派生自 ApplicationException​,却由 CLR 抛出。因此 ApplicationException​ 已失去原有意义。

7.3.3 InvalidOperationException

  • DO​​:如果对象处于 不正确的状态 ,抛出该异常。

    例如:往只读的 FileStream​​ 写入数据。

7.3.4 ArgumentException​、ArgumentNullException​、ArgumentOutOfRangeException

  • DO​:用户传入 错误参数 时,要抛出 ArgumentException​ 或其派生类,并设置 ParamName ​ 属性。

    如果可以,尽量选择位于继承层次 末尾 的异常类型。

  • DO​:对于属性的 setter​,若要抛出 ArgumentException​ 异常及其派生类,ParamName​ 应该赋值为 value

    public FileAttributes Attributes{
        set {
            if (value == null) {
                throw new ArgumentNullException("value", ...);
            }
        }
    }
    

7.3.5 NullReferenceException​、IndexOutOfRangeException​ 及 AccessViolationException

  • DON'T​:公共 API 不应该抛出上述异常。这些异常应该由 执行引擎抛出 ,表示 代码存在缺陷

    做好参数检查避免抛出这些异常。这些异常会暴漏方法的实现细节,而这些细节可能会随着时间而改变。

7.3.6 StackOverflowException

该异常会在无限递归中发生

  • DON'T​​:只有 CLR 才能抛出该异常,自己编写的代码不应该抛出该异常。

  • DON'T:不要 捕获该异常。

    栈溢出时,几乎不可能让托管代码保持状态一致。发生该异常 CLR2.0 默认会让程序立即终止。

7.3.7 OutOfMemoryException

该异常会在分配内存失败时发生

  • DON'T​​:只有 CLR 才能抛出该异常,自己编写的代码不应该抛出该异常。

7.3.8 ComException​、SEHException​ 以及 ExecutionEngineException

  • DON'T​​:只有 CLR 才能抛出该异常,自己编写的代码不应该抛出该异常。

ComException
用于表示 COM 组件或 Windows API 返回未知的 HRESULT 而抛出的异常。

SEHException
用于表示 C++ 或 Windows API 中未映射到.NET Framework 的异常。

ExecutionEngineException
用于表示致命的执行引擎错误。通常由内存损坏、堆栈溢出、访问冲突或其他严重问题导致。

7.3.9 OperationCanceledException​ 和 TaskCanceledException

  • DO​:要抛出 OperationCanceledException​ 来表明调用者发起了 中止取消 操作。

  • DO​​:要倾向于捕获 OperationCanceledException ​​,而非 TaskCanceledException ​​。

    TaskCanceledException​ ​派生自 OperationCanceledException​,由 Task 执行引擎内部使用。需要对 TaskCanceledException​ ​进行特殊处理的 catch 块一般也处理 OperationCanceledException​。

7.3.10 FormatException

  • DO​:要抛出​该异常​来表明文本解析方法中的输入字符串不符合要求或指定格式。

  • DO​:要抛出 FormatException ​ 来表明文本解析方法中或文本格式化方法中用于限定格式的字符串是无效的。

7.3.11 PlatformNotSupportedException

  • DO​​:要抛出该异常来表明 在当前的运行时环境下 无法完成操作,但在不同的 运行时或操作系统上 可以完成。

7.4 自定义异常的设计

  • DO​:自定义异常应该派生自 System.Exception ​ 或其他 常用的基类异常 ,且继承层次不应该 过深 ,命名使用 “Exception” 后缀。如果多种错误 可以通过一种方式来处理 ,则它们应该属于同一类型的异常。

  • DO ​:异常 要可以 序列化。

    • 便于跨应用程序域、跨远程边界时仍可以使用。

  • DO​​:异常(至少)要有如下构造函数:

    public class SomeException : Exception, ISerializable {
        public SomeException();
        public SomeExcepiton(string message);
        public SomeExcepiton(string message, Exception inner);
    
        // 序列化所需构造函数
        protected SomeException(SerializationInfo info, StreamingContext context);
    }
    

  • DO:应该覆写 ToString 用于报告与异常相关的信息。如果该信息与安全性相关,该信息应该保存在私有成员中,并确保只有可信赖的代码才能获得该信息。
    注意:如果没有获得许可,不要泄露安全信息。

    第三版移除此准则。

  • CONSIDER​​​:可以为异常定义属性,用于获取除 message 以外的额外信息。

7.5 异常与性能

我们可以通过 Tester-Doer 模式、Try-Parse 模式提高性能。不要因为影响性能而回归错误码。

7.5.1 Tester-Doer 模式

  • CONSIDER​​:使用 Tester-Doer 模式避免 因异常引起的性能 问题。

    • ICollection<int> numbers = ...
      ...
      if(!numbers.IsReadOnly) {
          numbers.Add(1);
      }
      

      其中 numbers.IsReadOnly​ 成员是“tester”,numbers.Add(1)​ 成员是“doer”。

      能这么做的前提是框架提供了“tester”。

注意:该模式要小心多线程访问造成的竞态条件。

7.5.2 Try-Parse 模式

  • CONSIDER​​​:如果成员在 常用代码中 都可能抛出异常,应使用 Try-Parse 模式避免因异常引起的性能问题。

  • DO​​:Try-Parse 模式要使用“ Try ”前缀,用 bool 作为返回类型。

  • DO​​​​:Try 方法返回 false​ 的原因只有一种,其余类的失败则要 抛出异常

    调用该方法的开发者可以为该失败情况写一个处理程序,并理解它所打标的状态。同时,对哪些不太可能的错误,继续抛出异常。

  • DO​:要为 Try 方法提供 等价抛出异常 的方法。

    public struct DateTime {
        public static DateTime Parse(string dateTime) { ... }
        public static bool TryParse(string dateTime, out DateTime result) { ... }
    }
    

7.5.2.1 Try 方法的值生成

  • DO​​:要通过 out 参数 ,返回 Try 方法的值。

  • DO​:要在 Try 方法返回 false 时,将 default(T) 赋值给 out 参数。

    public partial class HashSet<T> {
        public bool TryGetValue(T equalValue, out T actualValue) {
            Node node = FindNode(equalValue);
            if (node != null) {
                actualValue = node.Item;
                return true;
            }
            acutalValue = default(T);
            return false;
        }
    }
    

  • AVOID​​:避免在 抛出异常时 向 Try 方法的 out 参数写入数据。

7.5.2.2 静态的 TryParse(string, out T)方法

  • DO​​:对于静态的 TryParse(string, out T)​​​方法,若输入的是空字符串,要 返回 false

    NET BCL 类型中所有静态 TryParse​ 都将空输入视为“非有效字符串”,而不是异常。为保持一致性,我们的静态 TryParse 方法也要遵循该准则。

标签:DO,Exception,DON,抛出,应该,异常
From: https://www.cnblogs.com/hihaojie/p/18666342/chapter-7-2agy54

相关文章

  • 第28章 汇编语言--- 异常处理
    在汇编语言中,异常处理是一个重要的概念,它涉及到处理器如何响应和处理程序运行时发生的非正常情况。异常可以是硬件错误(例如除零错误、非法指令)或者软件触发的中断(例如系统调用)。当发生异常时,处理器会暂停当前正在执行的程序,并转移到一个预先定义好的位置来处理这个异常。为......
  • Java异常处理
    1.异常:异常是错误,运行时出错(编译时可以通过),编译时异常就是敲代码的时候错误2.抛异常:创建一个错误对象,把错误对象丢出来3.捕获异常:默认由JVM来把错误信息进行捕获,在错误处停止运行,后面的正确代码不会再运行4.异常的分类:runtimeexception运行时异常其他exceptionerrorexcept......
  • java添加企微 群机器人 异常通知 流程
    1.在群设置点击添加群机器人要记住webhook地址此处前置条件已完成程序这是官方文档案例importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Component;importjava.io.O......
  • KUKA机器人控制柜异常无法匹配故障维修
    KUKA机器人本体与控制柜无法匹配该如何维修?一、问题现象1.机器人上电以后,A5轴只能在-60~+60度的范围移动,示教器上显示的却是-120~+120度;2.从示教器的‘帮助’一>‘关于’一>‘机器人’查看机器人型号,与机器人本体铭牌上的型号不符合;二、维修办法1.在......
  • Scala分布式语言二(基础功能搭建、面向对象基础、面向对象高级、异常、集合)
    章节3基础功能搭建46.函数作为值三packagecn.itbaizhan.chapter03//函数作为值,函数也是个对象objectFunctionToTypeValue{defmain(args:Array[String]):Unit={  //Studentstu=newStudent()  /*val......
  • Win32汇编学习笔记07.筛选器异常
    Win32汇编学习笔记07.筛选器异常-C/C++基础-断点社区-专业的老牌游戏安全技术交流社区-BpSend.net钢琴od调试老师给的多媒体钢琴运行找到Piano的过程函数里去找到处理WM_KEYDOWN消息的那下个断点,然后按键断下来在这分析上图汇编代码:moveax,dwordptr[ebp+10]拿wPa......
  • 【运维】如何检查电脑正常异常和关机日志? 1074正常关机或重启 6006正常关机 41非正常
    事件ID1074:正常关机或重启,由用户或程序请求触发。事件ID6006:正常关机,表示系统已正确关闭。事件ID41:非正常关机,可能是由于电源问题、硬件故障或系统崩溃导致。事件ID6008:异常关机,通常是由于系统崩溃、电源中断或硬件问题导致的非正常关闭。要在Windows中查看事件......
  • 【A/B实验常见问题】实验异常值应该如何处理?
    作者:京东零售周佳慧背景大家在做实验时有没有遇到过以下的问题?实验分流不太稳定,多次分流以后,发现随机分组历史数据指标波动特别大实验结果不符合预期,在去掉几个特殊用户后结果变化较大、甚至正负反转不同的业务场景设置的指标过滤规则不同,例如A场景过滤掉了成单超过100单的......
  • SAP SE37函数模块异常抛出
    今天写了一个功能,生产订单批量打删除标识,我用一个函数来封装相应的功能,并且使用一下RAISE异常的功能在函数里面判断用户是否在ALV界面选中了数据,如果没有选中则抛出异常打断点发现,RAISE异常之后代码会中断执行,退出相应的函数模块。是非常好用的代码。 函数部分调用部分在......
  • 云服务器安装安全狗后功能异常的处理方法及注意事项
    当云服务器安装安全狗后出现功能异常时,可以采取以下步骤来解决问题,并确保安全狗卸载后不会影响服务器功能:步骤描述1.修改关键密码修改所有可能被原运维公司掌握的密码,包括但不限于:<br>-WDCP管理后台密码<br>-网站后台账号密码<br>-数据库密码<br>-FTP密码<br>-SS......