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}\r\n{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}\r\n{1}\r\n{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}\r\n{1}\r\n{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}\r\n{1}\r\n{2}\r\n{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 == )
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
Util应用程序框架公共操作类(五):异常公共操作类的更多相关文章
- Util应用程序框架公共操作类
随笔分类 - Util应用程序框架公共操作类 Util应用程序框架公共操作类 Util应用程序框架公共操作类(五):异常公共操作类 摘要: 任何系统都需要处理错误,本文介绍的异常公共操作类,用于对业务 ...
- Util应用程序框架公共操作类(十二):Lambda表达式公共操作类(三)
今天在开发一个简单查询时,发现我的Lambda操作类的GetValue方法无法正确获取枚举类型值,以至查询结果错误. 我增加了几个单元测试来捕获错误,代码如下. /// <summary> ...
- Util应用程序框架公共操作类(七):Lambda表达式公共操作类
前一篇扩展了两个常用验证方法,本文将封装两个Lambda表达式操作,用来为下一篇的查询扩展服务. Lambda表达式是一种简洁的匿名函数语法,可以用它将方法作为委托参数传递.在Linq中,大量使用La ...
- Util应用程序框架公共操作类(四):验证公共操作类
为了能够验证领域实体,需要一个验证公共操作类来提供支持.由于我将使用企业库(Enterprise Library)的验证组件来完成这项任务,所以本文也将演示对第三方框架的封装要点. .Net提供了一个 ...
- Util应用程序框架公共操作类(九):Lambda表达式扩展
上一篇对Lambda表达式公共操作类进行了一些增强,本篇使用扩展方法对Lambda表达式进行扩展. 修改Util项目的Extensions.Expression.cs文件,代码如下. using Sy ...
- Util应用程序框架公共操作类(八):Lambda表达式公共操作类(二)
前面介绍了查询的基础扩展,下面准备给大家介绍一些有用的查询封装手法,比如对日期范围查询,数值范围查询的封装等,为了支持这些功能,需要增强公共操作类. Lambda表达式公共操作类,我在前面已经简单介绍 ...
- Util应用程序框架公共操作类(六):验证扩展
前面介绍了仓储的基本操作,下面准备开始扩展查询,在扩展查询之前,首先要增加两个公共操作类,一个是经常要用到的验证方法,另一个是Lambda表达式的操作类. 很多时候,我们会判断一个对象是否为null, ...
- Util应用程序框架公共操作类(三):数据类型转换公共操作类(扩展篇)
上一篇以TDD方式介绍了数据类型转换公共操作类的开发,并提供了单元测试和实现代码,本文将演示通过扩展方法来增强公共操作类,以便调用时更加简化. 下面以字符串转换为List<Guid>为例进 ...
- Util应用程序框架公共操作类(二):数据类型转换公共操作类(源码篇)
上一篇介绍了数据类型转换的一些情况,可以看出,如果不进行封装,有可能导致比较混乱的代码.本文通过TDD方式把数据类型转换公共操作类开发出来,并提供源码下载. 我们在 应用程序框架实战十一:创建VS解决 ...
随机推荐
- 懒加载lazyload
什么是懒加载 懒加载就是当你做滚动到页面某个位置,然后再显示当前位置的图片,这样做可以减少页面请求. 懒加载:主要目的是作为服务器前端的优化,减少请求数或延迟请求数,一些图片非常多的网站中非常有用,在 ...
- VMware创建Linux虚拟机并安装CentOS(一)
在VMware中新建虚拟机,在新建虚拟机向导中,选择“自定义(高级)”选项,鼠标单击“继续”按钮 选择VMware的版本workstation9.0(VMware版本对硬盘.内存.cpu等硬件的支持大 ...
- 设置Flush刷新模式setFlushMode()
参考 http://blog.csdn.net/superdog007/article/details/38852399 FlushMode的枚举值: FlushMode.ALWAYS:任务一条SQL ...
- 为什么commonjs不适合于浏览器端
有了服务器端模块以后,很自然地,大家就想要客户端模块.而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行. 但是,由于一个重大的局限,使得CommonJS规范不适用于浏览器环境.还是上 ...
- CSS基础篇之选择符3
border(边框) 如何用CSS调出边框 我们给p标签加一个边框试一下 p{ border:1px solid #ccc:/*这是缩写*/ } 第一个值是为边框的宽度 第二个值是为边框线样式为直线 ...
- Mvc form提交
在项目开发中,我们离不开表单提交,本篇主要记录mvc的Ajax.BeginForm提交方式. 需要用到的js @Url.Script("~/Content/Scripts/jquer ...
- 微信支付开发demo
微信支付的时候,发红包部分的demo,在curl部分还要加上 curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,true); curl_setopt($ch,CURLOPT ...
- mac远程桌面连接windows 8.1 update,提示: 远程桌面连接无法验证您希望连接的计算机的身份
在网上找到解决方案: SolutionEnable RDP security layer in Group Policy on the machine: Verify that the firewal ...
- Logging with Log4net (二)
log4net 是.net 的一款日志记录框架. 它提供了很多的方法来帮助记录日志: 使用起来也比较方便: 选中项目,点击右键,然后选择 Manage NuGet Packages... 安装log4 ...
- 使用GDB调试Go语言
用Go语言已经有一段时间了,总结一下如何用GDB来调试它! ps:网上有很多文章都有描述,但是都不是很全面,这里将那些方法汇总一下 GDB简介 GDB是GNU开源组织发布的⼀一个强⼤大的UNIX下的 ...