• Util应用程序框架公共操作类(五):异常公共操作类


      任何系统都需要处理错误,本文介绍的异常公共操作类,用于对业务上的错误进行简单支持。

      对于刚刚接触.Net的新手,碰到错误的时候,一般喜欢通过返回bool值的方式指示是否执行成功。

    public bool 方法名() {
        //执行代码,成功返回true,否则返回false
    }

      不过上面的方法有一个问题是,无法知道确切的错误原因,所以需要添加一个out参数来返回错误消息。

    public bool 方法名( out string errorMessage ) {
        //执行代码,成功返回true,否则返回false
    }

      由于out参数用起来很麻烦,所以有些人干脆直接返回字符串,用特殊字符代表成功,比如返回”ok”代表成功。

    public string 方法名() {
        //执行代码,成功返回"ok",否则返回错误消息
    }

      随着经验的不断提升,很快就会意识到用方法返回错误不是太方便,主要问题是如果调用栈很长,比如方法A调用方法B,B又调用C,C调用D,现在D出了问题,需要返回到A就要层层返回。所以需要找到一种更高效的错误处理手段,这就是异常。

      .Net提供了大量异常类来支持不同的错误类型,所有异常都派生自基类Exception。

      刚用上异常的初学者,一般会直接抛出Exception,主要目标是获取抛出的错误消息。

    public void 方法名() {
        //执行代码,如果发生错误就执行下面的代码
        throw new Exception("发生错误了,快处理");
    }

      还有些高标准的团队,要求对业务上所有的错误创建自定义异常,以提供精确和清晰的异常处理方式。

      上面两种异常处理方式是两个极端。

      直接使用Exception的好处是省力,坏处是无法识别出究竟是系统异常还是业务上的错误,这有什么关系?需要分清系统异常和业务错误的原因是,你可能不想把系统内部的异常暴露给终端客户,比如给客户提示“未将对象引用设置到对象的实例”感觉如何,当然,这可能只是让客户摸不着头脑,还不是很严重,有一些异常会暴露系统的弱点,从而导致更易受攻击。

      为每个业务错误创建一个自定义异常,好处是可以对异常精确定位,另外可以方便的为异常处理提供相关数据。这种方式的主要毛病是工作量很大,一般程序员都不会这么干。

      现在来考虑我们一般是如何处理异常的?大部分时候,可能只是记录了一个日志,然后将该异常转换为客户端能识别的消息,客户端会把异常消息显示出来。更进一步,可能会识别出系统异常,给客户端提示一个默认消息,比如“系统忙,请稍后再试”,如果是业务错误,就直接显示给客户。

      可以看到,只有在你真正需要进行特定异常处理的时候,创建业务错误对应的自定义异常才会有价值,如果你创建出来的自定义异常,仅仅记录了个日志,那就没有多大必要了。

      现在的关键是你需要把系统异常和业务错误识别出来,以指示你是否应该把错误消息暴露给客户。我们可以创建一个自定义异常来表示通用的业务错误,我使用Warning来表示这个异常,即业务警告。

      单元测试WarningTest的代码如下。

    using System;
    using System.Text;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    using Util.Logs;
    
    namespace Util.Tests {
        /// <summary>
        /// 应用程序异常测试
        /// </summary>
        [TestClass]
        public class WarningTest {
    
            #region 常量
    
            /// <summary>
            /// 异常消息
            /// </summary>
            public const string Message = "A";
    
            /// <summary>
            /// 异常消息2
            /// </summary>
            public const string Message2 = "B";
    
            /// <summary>
            /// 异常消息3
            /// </summary>
            public const string Message3 = "C";
    
            /// <summary>
            /// 异常消息4
            /// </summary>
            public const string Message4 = "D";
    
            #endregion
    
            #region TestValidate_MessageIsNull(验证消息为空)
    
            /// <summary>
            /// 验证消息为空
            /// </summary>
            [TestMethod]
            public void TestValidate_MessageIsNull() {
                Warning warning = new Warning( null, "P1" );
                Assert.AreEqual( string.Empty, warning.Message );
            }
    
            #endregion
    
            #region TestCode(设置错误码)
    
            /// <summary>
            /// 设置错误码
            /// </summary>
            [TestMethod]
            public void TestCode() {
                Warning warning = new Warning( Message, "P1" );
                Assert.AreEqual( "P1", warning.Code );
            }
    
            #endregion
    
            #region TestLogLevel(测试日志级别)
    
            /// <summary>
            /// 测试日志级别
            /// </summary>
            [TestMethod]
            public void TestLogLevel() {
                Warning warning = new Warning( Message, "P1", LogLevel.Fatal );
                Assert.AreEqual( LogLevel.Fatal, warning.Level );
            }
    
            #endregion
    
            #region TestMessage_OnlyMessage(仅设置消息)
    
            /// <summary>
            /// 仅设置消息
            /// </summary>
            [TestMethod]
            public void TestMessage_OnlyMessage() {
                Warning warning = new Warning( Message );
                Assert.AreEqual( Message, warning.Message );
            }
    
            #endregion
    
            #region TestMessage_OnlyException(仅设置异常)
    
            /// <summary>
            /// 仅设置异常
            /// </summary>
            [TestMethod]
            public void TestMessage_OnlyException() {
                Warning warning = new Warning( GetException() );
                Assert.AreEqual( Message, warning.Message );
            }
    
            /// <summary>
            /// 获取异常
            /// </summary>
            private Exception GetException() {
                return new Exception( Message );
            }
    
            #endregion
    
            #region TestMessage_Message_Exception(设置错误消息和异常)
    
            /// <summary>
            /// 设置错误消息和异常
            /// </summary>
            [TestMethod]
            public void TestMessage_Message_Exception() {
                Warning warning = new Warning( Message2, "P1", LogLevel.Fatal, GetException() );
                Assert.AreEqual( string.Format( "{0}
    {1}", Message2, Message ), warning.Message );
            }
    
            #endregion
    
            #region TestMessage_2LayerException(设置2层异常)
    
            /// <summary>
            /// 设置2层异常
            /// </summary>
            [TestMethod]
            public void TestMessage_2LayerException() {
                Warning warning = new Warning( Message3, "P1", LogLevel.Fatal, Get2LayerException() );
                Assert.AreEqual( string.Format( "{0}
    {1}
    {2}", Message3, Message2, Message ), warning.Message );
            }
    
            /// <summary>
            /// 获取2层异常
            /// </summary>
            private Exception Get2LayerException() {
                return new Exception( Message2, new Exception( Message ) );
            }
    
            #endregion
    
            #region TestMessage_Warning(设置Warning异常)
    
            /// <summary>
            /// 设置Warning异常
            /// </summary>
            [TestMethod]
            public void TestMessage_Warning() {
                Warning warning = new Warning( GetWarning() );
                Assert.AreEqual( Message, warning.Message );
            }
    
            /// <summary>
            /// 获取异常
            /// </summary>
            private Warning GetWarning() {
                return new Warning( Message );
            }
    
            #endregion
    
            #region TestMessage_2LayerWarning(设置2层Warning异常)
    
            /// <summary>
            /// 设置2层Warning异常
            /// </summary>
            [TestMethod]
            public void TestMessage_2LayerWarning() {
                Warning warning = new Warning( Message3, "", Get2LayerWarning() );
                Assert.AreEqual( string.Format( "{0}
    {1}
    {2}", Message3, Message2, Message ), warning.Message );
            }
    
            /// <summary>
            /// 获取2层异常
            /// </summary>
            private Warning Get2LayerWarning() {
                return new Warning( Message2, "", new Warning( Message ) );
            }
    
            #endregion
    
            #region TestMessage_3LayerWarning(设置3层Warning异常)
    
            /// <summary>
            /// 设置3层Warning异常
            /// </summary>
            [TestMethod]
            public void TestMessage_3LayerWarning() {
                Warning warning = new Warning( Message4, "", Get3LayerWarning() );
                Assert.AreEqual( string.Format( "{0}
    {1}
    {2}
    {3}", Message4, Message3, Message2, Message ), warning.Message );
            }
    
            /// <summary>
            /// 获取3层异常
            /// </summary>
            private Warning Get3LayerWarning() {
                return new Warning( Message3, "", new Exception( Message2, new Warning( Message ) ) );
            }
    
            #endregion
    
            #region 添加异常数据
    
            /// <summary>
            /// 添加异常数据
            /// </summary>
            [TestMethod]
            public void TestAdd_1Layer() {
                Warning warning = new Warning( Message );
                warning.Data.Add( "key1", "value1" );
                warning.Data.Add( "key2", "value2" );
    
                StringBuilder expected = new StringBuilder();
                expected.AppendLine( Message );
                expected.AppendLine( "key1:value1" );
                expected.AppendLine( "key2:value2" );
                Assert.AreEqual( expected.ToString(), warning.Message );
            }
    
            /// <summary>
            /// 添加异常数据
            /// </summary>
            [TestMethod]
            public void TestAdd_2Layer() {
                Exception exception = new Exception( Message );
                exception.Data.Add( "a", "a1" );
                exception.Data.Add( "b", "b1" );
    
                Warning warning = new Warning( exception );
                warning.Data.Add( "key1", "value1" );
                warning.Data.Add( "key2", "value2" );
    
                StringBuilder expected = new StringBuilder();
                expected.AppendLine( Message );
                expected.AppendLine( "a:a1" );
                expected.AppendLine( "b:b1" );
                expected.AppendLine( "key1:value1" );
                expected.AppendLine( "key2:value2" );
                Assert.AreEqual( expected.ToString(), warning.Message );
            }
    
            #endregion
        }
    }

      Warning的代码如下。

    using System;
    using System.Collections;
    using System.Text;
    using Util.Logs;
    
    namespace Util {
        /// <summary>
        /// 应用程序异常
        /// </summary>
        public class Warning : Exception {
    
            #region 构造方法
    
            /// <summary>
            /// 初始化应用程序异常
            /// </summary>
            /// <param name="message">错误消息</param>
            public Warning( string message )
                : this( message, "" ) {
            }
    
            /// <summary>
            /// 初始化应用程序异常
            /// </summary>
            /// <param name="message">错误消息</param>
            /// <param name="code">错误码</param>
            public Warning( string message, string code )
                : this( message, code, LogLevel.Warning ) {
            }
    
            /// <summary>
            /// 初始化应用程序异常
            /// </summary>
            /// <param name="message">错误消息</param>
            /// <param name="code">错误码</param>
            /// <param name="level">日志级别</param>
            public Warning( string message, string code, LogLevel level )
                : this( message, code, level, null ) {
            }
    
            /// <summary>
            /// 初始化应用程序异常
            /// </summary>
            /// <param name="exception">异常</param>
            public Warning( Exception exception )
                : this( "", "", LogLevel.Warning, exception ) {
            }
    
            /// <summary>
            /// 初始化应用程序异常
            /// </summary>
            /// <param name="message">错误消息</param>
            /// <param name="code">错误码</param>
            /// <param name="exception">异常</param>
            public Warning( string message, string code, Exception exception )
                : this( message, code, LogLevel.Warning, exception ) {
            }
    
            /// <summary>
            /// 初始化应用程序异常
            /// </summary>
            /// <param name="message">错误消息</param>
            /// <param name="code">错误码</param>
            /// <param name="level">日志级别</param>
            /// <param name="exception">异常</param>
            public Warning( string message, string code, LogLevel level, Exception exception )
                : base( message ?? "", exception ) {
                Code = code;
                Level = level;
                _message = GetMessage();
            }
    
            /// <summary>
            /// 获取错误消息
            /// </summary>
            private string GetMessage() {
                var result = new StringBuilder();
                AppendSelfMessage( result );
                AppendInnerMessage( result, InnerException );
                return result.ToString().TrimEnd( Environment.NewLine.ToCharArray() );
            }
    
            /// <summary>
            /// 添加本身消息
            /// </summary>
            private void AppendSelfMessage( StringBuilder result ) {
                if ( string.IsNullOrWhiteSpace( base.Message ) )
                    return;
                result.AppendLine( base.Message );
            }
    
            /// <summary>
            /// 添加内部异常消息
            /// </summary>
            private void AppendInnerMessage( StringBuilder result, Exception exception ) {
                if ( exception == null )
                    return;
                if ( exception is Warning ) {
                    result.AppendLine( exception.Message );
                    return;
                }
                result.AppendLine( exception.Message );
                result.Append( GetData( exception ) );
                AppendInnerMessage( result, exception.InnerException );
            }
    
            /// <summary>
            /// 获取添加的额外数据
            /// </summary>
            private string GetData( Exception ex ) {
                var result = new StringBuilder();
                foreach ( DictionaryEntry data in ex.Data )
                    result.AppendFormat( "{0}:{1}{2}", data.Key, data.Value, Environment.NewLine );
                return result.ToString();
            }
    
            #endregion
    
            #region Message(错误消息)
    
            /// <summary>
            /// 错误消息
            /// </summary>
            private readonly string _message;
    
            /// <summary>
            /// 错误消息
            /// </summary>
            public override string Message {
                get {
                    if ( Data.Count == 0 )
                        return _message;
                    return _message + Environment.NewLine + GetData( this );
                }
            }
    
            #endregion
    
            #region TraceId(跟踪号)
    
            /// <summary>
            /// 跟踪号
            /// </summary>
            public string TraceId { get; set; }
    
            #endregion
    
            #region Code(错误码)
    
            /// <summary>
            /// 错误码
            /// </summary>
            public string Code { get; set; }
    
            #endregion
    
            #region Level(日志级别)
    
            /// <summary>
            /// 日志级别
            /// </summary>
            public LogLevel Level { get; set; }
    
            #endregion
    
            #region StackTrace(堆栈跟踪)
    
            /// <summary>
            /// 堆栈跟踪
            /// </summary>
            public override string StackTrace {
                get {
                    if ( !string.IsNullOrWhiteSpace( base.StackTrace ) )
                        return base.StackTrace;
                    if ( base.InnerException == null )
                        return string.Empty;
                    return base.InnerException.StackTrace;
                }
            }
    
            #endregion
        }
    }

      需要注意的是,除了给Warning添加了一些有用的属性以外,还重写了Message属性。这是因为当一个异常被抛出以后,其它代码可能会进行拦截,之后这些代码会抛出自己的异常,并把之前的异常包装在自己内部,所以你要访问之前的异常,就需要通过递归的方式访问InnerException,直到InnerException为null。所以大家会在后面看到Warning类不仅被用来充当业务异常,还是一个获取异常全部消息的公共操作类。

      最后,再补充一个重构小知识,观察Warning的代码,多个构造方法中,只有参数最多的方法实现了功能,其它构造方法挨个调用,这称为链构造函数。这个手法对于重载方法都适用,不要在每个方法中重复实现代码,把实现代码放到参数最多的方法中,其它重载只是该方法提供了默认值的版本而已。

      本文简单介绍了在开发过程中与异常相关的内容,下一篇将回到领域实体,我将介绍如何以规约模式等方式对领域实体进行验证。

      .Net应用程序框架交流QQ群: 386092459,欢迎有兴趣的朋友加入讨论。

      谢谢大家的持续关注,我的博客地址:http://www.cnblogs.com/xiadao521/

      下载地址:http://files.cnblogs.com/xiadao521/Util.2014.11.19.1.rar

     

  • 相关阅读:
    江湖盛传“阿里三板斧”,其实这才是全部真相!
    PHP算法之四大基础算法
    PHP实现的毫秒定时器,同时解决进程不重复堆积
    leetcode小题解析
    PHP算法之二分查找
    elastic学习笔记
    php中mysqli 处理查询结果集总结
    PHP中的 Iterator 与 Generator
    Laravel源码解析之反射的使用
    PHP下的异步尝试四:PHP版的Promise
  • 原文地址:https://www.cnblogs.com/xiadao521/p/4107895.html
Copyright © 2020-2023  润新知