错误信息的一种定义方式
可恶的前言
话说最近开始写博客,折腾个人站点,突然也喜欢上了写点内容作为开发路上的一些记录。最近在开发公司的api,开发了几个之后,我意识到需要统一下错误信息,于是有了这篇文章,提交给小组review之后,觉得可行于是统一采取了这种处理方式。
何为错误信息
说道错误信息,大家应该都很熟悉,例如windows的错误代码,html的错误代码(404,403等等)。在.net里,Exception大家都很熟悉,对于一个完整的、健壮的程序,Exception应该不在运行时随便的报给用户。试想用户随便点击下就弹出个.NET Framework Error什么的,人家对这个产品的影响一定大打折扣,甚至可以说这个根本不是一个完整的产品。所以,错误信息应该是对各种异常的高可读、高度统一的概括,用更友好的方式让用户知道这个调用出了什么问题,是没权限、参数错误或者是其他的错误,进而可以反馈给开发者,进行什么操作的时候提示了404,403等等。说了一堆废话之后我们来看看错误信息如何定义比较好?
定义错误信息
首先我们来看看一般的错误信息定义:
1. 一般时候会把错误信息定义在资源文件(Resource)里,这样Framework会帮我们转换成xml,并且提供了提示信息,但是这种方式不够灵活,能自定义处理的东西有限。
2. 另外的方式是定义成枚举或者伪枚举的类,这种类可以提供高可扩展性,自定义起来很方便。这边篇文章,我就是采用这种方式。
首先定义错误信息的属性,一般的错误信息,应该拥有ErrorCode(错误代码,例如:404)和ErrorMessage(错误信息)两个属性,如下:
1 /// <summary> 2 /// 错误信息类,注意错误信息可以附带的参数 3 /// </summary> 4 public class CustomerError 5 { 6 7 /// <summary> 8 /// 错误代码 9 /// </summary> 10 public int ErrorCode { get; set; } 11 /// <summary> 12 /// 错误信息 13 /// </summary> 14 public string ErrorMessage { get; set; } 15 }
定义好了基本属性,我们来想错误信息一般都是固定的,不能改变的。所以我们应该把CustomerError改成值类型,如下:
1 /// <summary> 2 /// 错误信息类,注意错误信息可以附带的参数 3 /// </summary> 4 public class CustomerError 5 { 6 /// <summary> 7 /// 错误代码 8 /// </summary> 9 public int ErrorCode { get; private set; } 10 /// <summary> 11 /// 错误信息 12 /// </summary> 13 public string ErrorMessage { get; private set; } 14 15 public CustomerError(int code, string message) 16 { 17 this.ErrorCode = code; 18 this.ErrorMessage = message; 19 } 20 }
这里我把两个属性设置为私有的set,这样对外这两个属性就是只读的,并且定义一个两个参数的构造函数,这样CustomerError就必须要声明时就带上两个参数了,就是一个值类型对象。
然后,我们再来想想错误信息应该是预定义好的,程序一加载就全部加载进来了,最好像枚举类型一样。那么我们该怎么做呢?不知道大家是否对.NET里的Color对象有没有映像,起是Color对象和这里的Customer类很像,Color里面定义了很多颜色,就像枚举一样,可是起是它不是一个枚举,而是一个伪枚举类的实现。为了实现这样的效果,我们来进行修改,如下:
1 /// <summary> 2 /// 错误信息类,注意错误信息可以附带的参数 3 /// </summary> 4 public class CustomerError 5 { 6 /// <summary> 7 /// 错误代码 8 /// </summary> 9 public int ErrorCode { get; private set; } 10 /// <summary> 11 /// 错误信息 12 /// </summary> 13 public string ErrorMessage { get; private set; } 14 /// <summary> 15 /// 错误信息参数 16 /// </summary> 17 public int ParamLength { get; private set; } 18 private CustomerError(int code, string message) 19 { 20 this.ErrorCode = code; 21 this.ErrorMessage = message; 22 } 23 24 public static readonly CustomerError Success = new CustomerError(1, "操作成功"); 25 26 public static readonly CustomerError Failed = new CustomerError(0, "操作失败");
27 }
上面把属性的set置为私有,并且把构造函数也置为私有,以便在类外无法实例化这个类。而内部定义static静态的错误信息属性供外部调用。这样一个伪枚举类就实现了,并且可以自定义这个类附带的属性。
升级改造
上面的错误信息类,看起来应该满足了需求,但是仔细想来还差很多,错误信息还应该附带自定义的参数。类似 参数param不合法,这样的指定参数信息。那么我们再来改造一下:
1 /// <summary> 2 /// 缺少参数{0} 3 /// </summary> 4 public static readonly CustomerError Error10001 = new CustomerError(10001, "缺少参数{0}");
这里我们定义了一个带参数的错误信息,调用方式如下:
//先定义一个Response基类里的错误信息 public class ResponseBase { public int StateCode{get; set;} public string Message{get; set;} } public object action(xxx request) { var response = new ResponseBase{ StateCode = CustomerError.Error10001.ErrorCode, Message = string.format(CustomerError.Error10001.ErrorMessage, "param") }; return response; }
上面的定义之后,可能看起来这个转换稍微有点繁琐,能不能来点更简单的呢?这时候飘过一个扩展方法,.NET里的神器啊,那咱们就拿他来扩展下CustomerError:
首先我们把CustomerError稍微扩展下,加个参数长度:
1 /// <summary> 2 /// 错误信息参数 3 /// </summary> 4 public int ParamLength { get; private set; } 5 private CustomerError(int code, string message, int paramLength = 0) 6 { 7 this.ErrorCode = code; 8 this.ErrorMessage = message; 9 this.ParamLength = paramLength; 10 }
然后呢,我们修改下CustomerError枚举的定义:
1 /// <summary> 2 /// 缺少参数{0} 3 /// </summary> 4 public static readonly CustomerError Error10001 = new CustomerError(10001, "缺少参数{0}", 1);
接着我们在类外实现一个扩展函数:
1 public static class CustomerErrorExtends 2 { 3 /// <summary> 4 /// 转换指定的错误为ResponseBase 5 /// </summary> 6 /// <param name="error">错误信息</param> 7 /// <param name="param">错误信息参数</param> 8 /// <returns></returns> 9 public static ResponseBase ToResponse(this CustomerError error, params string[] param) 10 { 11 var msg = error.ErrorMessage; 12 if (param.Length == error.ParamLength) 13 msg = string.Format(error.ErrorMessage, param); 14 return new ResponseBase 15 { 16 State = error.ErrorCode, 17 ErrorMsg = msg 18 }; 19 } 20 21 /// <summary> 22 /// 转换指定的错误为ResponseBase 23 /// </summary> 24 /// <param name="error">错误信息</param> 25 /// <returns></returns> 26 public static ResponseBase ToResponse(this CustomerError error) 27 { 28 return error.ToResponse(""); 29 } 30 }
一个漂亮的扩展函数就实现了,我们回到action看下如何改造:
1 public object action(xxx request) 2 { 3 return CustomerError.Error10001.ToResponse("param"); 4 }
是不是一下子就简单了很多了?
一个使用例子
1 public object Get(DeleteCloudFileRequest request) 2 { 3 //检查id是否正确 4 int fileId = 0; 5 if (!int.TryParse(request.FileID, out fileId) || fileId <= 0) 6 return CustomerError.Error10002.ToResponse("FileID"); 7 8 //检查文件是否存在 9 var folder = FolderManager.GetFolder(fileId); 10 if (folder == null) 11 return CustomerError.Error10007.ToResponse(); 12 13 //检查当前用户 对该文件夹 是否有操作权限 14 if (!CheckUser(folder.CreatedID.ToString())) 15 return CustomerError.Error10003.ToResponse(); 16 17 //删除 18 try 19 { 20 FolderManager.DeleteCloudStorageFileByFolderID(fileId); 21 } 22 catch (Exception e) 23 { 24 //日志 25 26 return CustomerError.Error10005.ToResponse(); 27 } 28 29 //操作成功 30 return CustomerError.Success.ToResponse(); 31 }
Summary
上面就是我今天想到的一个错误信息类的定义过程,可能显得相当粗俗,在大拿面前班门弄斧了。希望大家看过之后觉得好的,点下推荐,觉得不好的请留言指教。我也希望不断改进我在代码中的不足。谢谢~