精简自己20%的代码

精简自己20%的代码

持续重构,其乐无穷。

一:发现问题

  先来说如何重构业务层的try{}catch{}finally{}代码块,我看过很多代码,异常处理这一块大致分为两种情况,一种是每个方法都大量的充斥着try{}catch{}finally{},这种方式的编程已经考虑到了异常处理,还有一种就是没有try{}catch{}finally{}的代码,因为根本就没有考虑代码的异常处理。每当我看到这样的代码,我都很忧伤。从程序的健壮性来看第一种还是要比第二种情况好,至少在编程意识中,随时想到了异常情况,有一种基本的编程思想。

  比如:一个业务单据的多表插入,关联修改,虚拟删除等都是一些基本的操作,但是又是比较容易引起错误的操作,在这些方法上都会加上try{}catch{}finally{}对代码进行有效的防错处理。早期的代码是这样的。

复制代码
public Boolean Save(AccountModel accountData)
{
    Boolean result = false;
    try
    {
        //TODO ...
        result = true;
    }
    catch
    {

    }
    finally
    { 
            
    }
    return result;
}

public Boolean Edit(AccountModel accountData)
{
    Boolean result = false;
    try
    {
        //TODO ...
        result = true;
    }
    catch
    {

    }
    finally
    {

    }
    return result;
}

public Boolean VirDelete(AccountModel accountData)
{
    Boolean result = false;
    try
    {
        //TODO ...
        result = true;
    }
    catch
    {

    }
    finally
    {

    }
    return result;
}
复制代码

仅仅定义了添加,修改,删除几个空方法,就写了三四十行代码,如果业务稍微复杂些,异常处理的代码很快就会突破百行大关。虽然复制,粘贴try{}catch{}finally{}很好使,但是业务逻辑代码大量充斥着这样的try{}catch{}finally{}代码,确实显得做事不够利落。

二:解决问题

  那怎样来解决这件棘手的事呢,首先定义一个公用的try{}catch{}finally{},如下如示:

复制代码
public class Process
{
    public static bool Execute(Action action)
    {
        try
        {
            action.Invoke();
            return true;
        }
        catch (Exception ex)
        {
            //1,异常隐藏
            //2,异常替换
            //3,异常封装

            //写日志
            return false;
        }
        finally
        {

        }
    }
}
复制代码

上边的代码定义了公用的try{}catch{}finally{},最关键是怎么运用起来,如下代码:

复制代码
protected void Page_Load(object sender, EventArgs e)
{
    AccountModel accountData = new AccountModel();     //准备传入的参数
    Boolean result = false;                            //接收返回的值
    Process.Execute(() => result = Save(accountData)); //执行方法
}

public Boolean Save(AccountModel accountData)
{
    Boolean result = false;
    //TODO ...
    result = true;
    return result;
}

public Boolean Edit(AccountModel accountData)
{
    Boolean result = false;
    //TODO ...
    result = true;
    return result;
}

public Boolean VirDelete(AccountModel accountData)
{
    Boolean result = false;
    //TODO ...
    result = true;
    return result;
}
复制代码

这样的精简过的代码,是不是感觉心情很舒畅。

三:提升与扩展

   对于知足者常乐的人来说,到第二个步骤就可以洗洗睡了。但是对于精益求精的人来说,问题仍然没有完。我们来说一个应用场景,在WCF中的应用,我们知道WCF服务端的异常,不经过<serviceDebug includeExceptionDetailInFaults="true"/>的设置,服务端的异常是无法抛到客户端的。但是在正式环境中,不可能对进行serviceDebug的配置。正确的处理是在服务端对异常进行隐藏,替换,或者封装。

  比如我们在服务端捕获了一个已知异常,但是这个异常会暴露一些敏感的信息,所以我们对异常进行替换,抛出新的异常后,我们还要把这个异常怎样传输给客户端。首先们要明确WCF中的一些基本常识,就是WCF中的数据传递要遵循WCF的数据契约,服务端抛到客户端的异常(异常其实也是数据),所以必须要给异常定义异常契约。

复制代码
    [DataContract(Name = "WCFException")]
    public class WCFException
    {
        [DataMember(Name = "Type")]
        public String Type { get; set; }

        [DataMember(Name = "StackTrace")]
        public String StackTrace { get; set; }

        [DataMember(Name = "Message")]
        public String Message { get; set; }
    }
复制代码

然后处理异常的公共方法改写为:

复制代码
public static bool Execute(Action action)
{
    try
    {
        action.Invoke();
        return true;
    }
    catch (Exception ex)
    {
        //1,异常隐藏
        //2,异常替换
        //3,异常封装

        //写日志
        WCFException exception = new WCFException
        {
            Type = "Error"
            ,
            StackTrace = ex.StackTrace
            ,
            Message = ex.Message
        };
        throw new FaultException<WCFException>(exception
            , new FaultReason("服务端异常:" + ex.Message)
            , new FaultCode(ex.TargetSite.Name));
    }
    finally
    {

    }
}
复制代码

这样在服务端抛出的异常,就能在客户端捕捉到。现在是不是感觉自己又提升了一些,想成为编程高手是指日可待了。

四:举一反三

  异常的处理也不过如此,那是不是应该举一反三,看看事务的处理应该怎么办?比如现在大量的访求都用到了事务,如下代码:

复制代码
public Boolean Save(AccountModel accountData)
{
    OracleConnection conn = new OracleConnection("连接字符串");
    IDbTransaction trans = conn.BeginTransaction();
    Boolean result = false;
    try
    {
        //TODO ...
        trans.Commit();
        result = true;
    }
    catch
    {
        trans.Rollback();
    }
    finally
    {

    }
    return result;
}
复制代码

特别是 trans.Commit();  trans.Rollback(); 这样的代码出现在每个与事务相关的方法中, 让我感觉到代码的臃肿,以及隐陷约约的失望。
经过我几天的翻阅资料终于实现了事务的公用访求提取。使用方法如下代码所示:

复制代码
[TransactionAttribute]
[ExceptionAttribute]
public bool Save(DataContext dContext, Dictionary<string, string> dtoPara)
{
    Boolean returnVal = true;
    //TODO ...
    return returnVal;
}
复制代码

就是在一个方法上加[TransactionAttribute]就表示这个方法写在了事务中,反之,不在事务中,加[ExceptionAttribute]就表示这个方法作了异常处理,反之,不作异常处理。通过反射或者AOP都能实现Attribute编程的效果。最后,还有什么疑惑,可以在评论中给我留言,当然别忘记了点右下角的【推荐】。

 

 
 
 
绿色通道: 好文要顶 关注我 收藏该文与我联系 
75
2
 
(请您对文章做出评价)
 
« 上一篇:不仅要理论,而且要实践(开源下载)
posted on 2013-11-26 06:26 Sam Xiao 阅读(5964) 评论(49编辑 收藏

 

评论:
 
#1楼 2013-11-26 07:58 | LukyW  
楼主这种方式[TransactionAttribute]这种方式是怎么实现呢?
  回复引用
#2楼 2013-11-26 08:10 | objectboy  
是啊啊TransactionAttribute 怎么实现的啊啊 求教。。。
  回复引用
#3楼 2013-11-26 08:16 | 金色海洋(jyk)阳光男孩  
业务层为啥会有异常?

只是数据库操作引起的吗?

那么是不是应该由数据层自己搞定,而不要抛出给业务层呢。

另外如果转换成ajax的方式的话,
  回复引用
#4楼[楼主2013-11-26 08:19 | Sam Xiao  
@LukyW
引用楼主这种方式[TransactionAttribute]这种方式是怎么实现呢?

@objectboy
引用是啊啊TransactionAttribute 怎么实现的啊啊 求教。。。

首先定义TransactionAttribute属性
1
2
3
4
5
6
7
8
/// <summary>
/// 事务属性
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class TransactionAttribute : Attribute
{
     
}

然后通过entlib的unity进行拦截,如果没有用过unity的就自己写反射也行。
比如用反射来写:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public dynamic ExecuteSave<T>(Func<DataContext, Dictionary<string, string>, bool> saveMethod, dynamic paraData)
{
    Type t = typeof(T);
    MethodInfo methodItem = t.GetMethod(saveMethod.Method.Name);
    ///执行事务
    if (methodItem.GetCustomAttributes(typeof(TransactionAttribute), true).Length > 0)
    {
        using (DataContext dContext = new DataContext(TransactionState.NeedTransaction))
        {
                     
        }
    }
    ///不执行事务
    else
    {
        using (DataContext dContext = new DataContext(TransactionState.NoTransaction))
        {
                    
        }
    }
}
  回复引用
#5楼[楼主2013-11-26 08:32 | Sam Xiao  
@金色海洋(jyk)阳光男孩
引用业务层为啥会有异常?

只是数据库操作引起的吗?

那么是不是应该由数据层自己搞定,而不要抛出给业务层呢。

另外如果转换成ajax的方式的话,

业务层的异常比较多,比如上传的文件被用户独占,其它用户无法访问,数据库连接失败,都会出现异常。 我是把数据库持久化的异常拿到了业务层来处理。达到了在一个层中处理了所有异常的效果。如果在每个层中都处理异常,程序很显然会更加臃肿。
  回复引用
#6楼 2013-11-26 08:41 | objectboy  
@Sam Xiao
加入遇到那种嵌套事务也能满足需求?
  回复引用
#8楼[楼主2013-11-26 08:51 | Sam Xiao  
@objectboy
引用@Sam Xiao
加入遇到那种嵌套事务也能满足需求?

事务嵌套?我不太明白你说的事务嵌套是什么。如果你是说的分别有几个类中的某些方法,要写在一个事务中,可以单独搞一个事务协调器来管理事务,在事务协调器中,可以运用[TransactionAttribute]来对事务进行处理。
  回复引用
#9楼 2013-11-26 08:57 | 技术先锋  
说得不错,应该少用try,我是尽量不用的,而从代码本身上去避免错误!
  回复引用
#10楼 2013-11-26 08:59 | 永远的麦子  
兄弟,请教个问题。
问题1:
首先我比较赞同你将所有的异常集中在BLL层处理的做法。
我看到很多人写代码要不就是几乎不用try...catch,要不就是每个方法都try...catch。我的问题是,什么时候需要try...catch,什么时候我们不用try...catch?

问题2:
我有了解到异常的传递是冒泡机制,一层一层地往上传。一般我们捕获到异常后,也就是在catch代码块中,什么时候只是做写日志的动作,异常不继续抛出?什么时候需要再throw出来?有没有一条比较好的实践原则。
  回复引用
#11楼 2013-11-26 09:16 | KarasCanvas  
@金色海洋(jyk)阳光男孩
引用业务层为啥会有异常?

只是数据库操作引起的吗?

那么是不是应该由数据层自己搞定,而不要抛出给业务层呢。

另外如果转换成ajax的方式的话,


异常只会在最上层统一处理,而且业务层抛出异常也很正常,参考MSDN异常设计准则
  回复引用
#12楼 2013-11-26 09:23 | 金色海洋(jyk)阳光男孩  
数据库操作,文件操作,这一类的出不出异常不是由代码本身来决定的地方需要用try。

还有类型转换,不过这个应该先进行类型判断,然后在转换。


我是在数据操作的地方做try,然后通过属性 (errorMessage)来表达是否异常。

这样可以把详细信息放在errorMessage里面。

调用者判断属性的length来判断是否异常。

当然这么做也有缺点,如果调用者不判断属性的话,就有可能漏过异常。

判断属性的代码也要写四行,还是比较麻烦。
  回复引用
#13楼[楼主2013-11-26 09:25 | Sam Xiao  
@金色海洋(jyk)阳光男孩
引用数据库操作,文件操作,这一类的出不出异常不是由代码本身来决定的地方需要用try。

还有类型转换,不过这个应该先进行类型判断,然后在转换。


我是在数据操作的地方做try,然后通过属性 (errorMessage)来表达是否异常。

这样可以把详细信息放在errorMessage里面。

调用者判断属性的length来判断是否异常。

当然这么做也有缺点,如果调用者不判断属性的话,就有可能漏过异常。

判断属性的代码也要写四行,还是比较麻烦。

谢谢你的分享,经常翻阅你的博客!^_^
  回复引用
#14楼 2013-11-26 09:31 | 打醋的  
@永远的麦子
问题1 简单说用try就是想对异常处理(吞掉、日志、恢复等等)
下面一段是我在园子里看到过的,可以作为一些原则吧(问题2)
引用常见异常处理策略:
•让异常冒泡,你做出这种选择,一般是因为在调用层次的下端,此时你不知道如何从异常中恢复,你希望异常冒泡到上层某个集中的地方,在上层做统一的日志操作或其它操作。
•处理并吞掉异常,你做出这样选择,一般是因为在调用层次的最上端,此时你不希望将异常信息穿过系统边界直接显示在UI中或者导致应用程序终止,你希望在此处对异常做最终的特殊处理,或者将合适的异常信息提示给用户,或者将合适的异常信息记录到日志中。
•处理并重新抛出异常,你做出这样的选择,一般是因为在调用层次的中间,此时你试着对异常进行一些处理,或者是希望从异常中恢复,或者是希望记录一下异常日志,如果你不能从异常中恢复,最好要重新抛出改异常。
•处理并抛出新的异常,你做出这样的选择,一般是因为某些异常你希望提示给UI,但是UI中不能显示敏感的信息,因此你选择新建一个异常。
•处理并抛出包装的异常,你做出这样的选择,一般是为了整合不同的框架,如NHibernate和EntityFramework,你希望Domain层面对的是包装过的异常,这样Domain层的代码才能真正意义的和具体的数据库访问技术解耦。
•将合理的异常显示给UI,你做出这样的选择,一般是因为你接受了结构化异常处理这一新的理念,而不是采用状态码表示操作结果,此处可以采用AOP机制实现。
•最终要监控没有被处理的异常,你必须选择这一选项,每种Application类型都提供了自己的选项,请使用。
  回复引用
#15楼 2013-11-26 09:32 | KarasCanvas  
@金色海洋(jyk)阳光男孩
引用
我是在数据操作的地方做try,然后通过属性 (errorMessage)来表达是否异常。
这样可以把详细信息放在errorMessage里面。
调用者判断属性的length来判断是否异常。


http://msdn.microsoft.com/zh-cn/library/ms229014%28v=vs.100%29.aspx

:)
  回复引用
#16楼 2013-11-26 09:47 | 麦克默菲  
好吧,我的作法是统一在全局某一个地方捕获异常。比如在Global.asax的Application_Error中,或者在Web.Config文件中注册异常处理程序。在一定程度上会影响系统性能,但我看好多大型网站和应用程序都是这么搞的,所以我也这么搞了。
如果是给第三方开发调用模块,还是老老实实的对异常进行封装,然后抛出。我的想法是调用者有权知道调用之后的结果,包括错误信息。而不应该依赖于去判断某个属性定性结果的正确与否。
多谢作者的分享!
  回复引用
#17楼 2013-11-26 09:48 | 搂着鱼睡的猫  
我现在的项目中就是采取的第一种方法来处理异常的。一直在给项目组提意见要通过AOP的方式来处理,一直被拒绝。
我觉得对于异常以及事务等非业务相关的逻辑,可以使用微软企业库通过AOP的方式统一处理。那样既简化了代码,也可让开发人员专注到业务相关的开发上来。
  回复引用
#18楼 2013-11-26 09:49 | ymnets  
#19楼 2013-11-26 10:02 | 淡抹眼线  
评论很有油水!
  回复引用
#20楼 2013-11-26 10:07 | KMSFan  
顶LZ,虽然不太明白。。。我还是菜鸟
  回复引用
#21楼 2013-11-26 10:08 | Allen Zhang  
用属性TransactionAttribute的方式,代码是够简洁,我有一个疑问:
如果我有多个不同类里的方法,有些特殊殊场合,需要多个method一起在一个事务里执行,一个方法失败,所有都在回滚,这种情况下我们应怎么做。按你的每个方法上面都定义了TransactionAttribute属性,每个方法自己内部在事务中执行了,单个方法错了,自己会回滚,但别的方法怎么处理?
  回复引用
#22楼 2013-11-26 10:10 | 小彬  
业务层应抛出异常,而不是捕获吧?
  回复引用
#23楼[楼主2013-11-26 10:11 | Sam Xiao  
@小彬
引用业务层应抛出异常,而不是捕获吧?

业务层有可能做:
//1,异常隐藏
//2,异常替换
//3,异常封装
  回复引用
#24楼 2013-11-26 10:22 | 萧秦  
支持下
我现在的代码也是这么处理的
1
2
3
4
5
6
public List<T> GetModelList(ParamQuery param = null)
{
    var result = new List<T>();
    Logger("获取实体列表", () =>result = BuilderParse(param).QueryMany());
    return result;
}
  回复引用
#25楼 2013-11-26 11:17 | 金色海洋(jyk)阳光男孩  
@Sam Xiao
引用@金色海洋(jyk)阳光男孩
引用引用数据库操作,文件操作,这一类的出不出异常不是由代码本身来决定的地方需要用try。

还有类型转换,不过这个应该先进行类型判断,然后在转换。


我是在数据操作的地方做try,然后通过属性 (errorMessage)来表达是否异常。

这样可以把详细信息放在errorMessage里面。

调用者判断属性的length来判断是否异常。

当然这么做也有缺点,如果调用者不判断属性的话,就有可能漏过异常。

判断属性的代码也要写四行,还是比较麻烦。

谢谢你的分享,经常翻阅你的博客!^_^


十分抱歉,最近一直没写博客。
比较懒了
  回复引用
#26楼 2013-11-26 11:20 | 金色海洋(jyk)阳光男孩  
@KarasCanvas
引用@金色海洋(jyk)阳光男孩
引用引用
我是在数据操作的地方做try,然后通过属性 (errorMessage)来表达是否异常。
这样可以把详细信息放在errorMessage里面。
调用者判断属性的length来判断是否异常。


http://msdn.microsoft.com/zh-cn/library/ms229014%28v=vs.100%29.aspx

:)


每个人都有自己的需求。

对于我来说,数据操作的地方如果抛出异常,那么调用的地方是不知道要如何处理的。
  回复引用
#27楼 2013-11-26 11:20 | 匹配度  
#29楼 2013-11-26 11:32 | 深蓝医生  
在方法上加一个事务的特性声明不太好,如果我的这个方法不需要事务也能调用怎么办?
所以,还是根据连接的事务上下文来执行比较好,这个连接是事务的连接,那么当前方法就是在事务中执行,否则,在非事务中执行 。
  回复引用
#30楼 2013-11-26 11:50 | KarasCanvas  
@金色海洋(jyk)阳光男孩
引用@KarasCanvas
引用引用@金色海洋(jyk)阳光男孩
引用引用
我是在数据操作的地方做try,然后通过属性 (errorMessage)来表达是否异常。
这样可以把详细信息放在errorMessage里面。
调用者判断属性的length来判断是否异常。


http://msdn.microsoft.com/zh-cn/library/ms229014%28v=vs.100%29.aspx

:)

每个人都有自己的需求。

对于我来说,数据操作的地方如果抛出异常,那么调用的地方是不知道要如何处理的。


如果调用地方不知道异常,那怎么知道怎么调用有没有成功?
  回复引用
#31楼 2013-11-26 11:55 | draculav  
这个别的倒是没什么,就只担心一点,如果两个相同类型的异常,在两个不同场景下,要用不同的处理方式,该怎么解决
  回复引用
#32楼 2013-11-26 12:12 | 海华  
一般 AOP 框架都支持 Attribute 配置(一般就在 facade 层统一配置)。这种代码风格简洁容易维护。
带来新的问题是,横切处理逻辑的配置范围(粒度)不好控制,尤其是事务。
如果是小项目,大事务中带来的性能问题倒是可以直接无视。
  回复引用
#33楼 2013-11-26 12:26 | 爱爱春春  
不错,茅塞顿开···
  回复引用
#34楼 2013-11-26 12:57 | 程序诗人  
我基本不用try,而是一进入函数,就开始判断可能出现的错误,比如null,空值等等,然后直接给return掉。 在类创建密集的场合,总是感觉try会让一切变慢。
  回复引用
#35楼 2013-11-26 12:59 | 程序诗人  
@麦克默菲
引用好吧,我的作法是统一在全局某一个地方捕获异常。比如在Global.asax的Application_Error中,或者在Web.Config文件中注册异常处理程序。在一定程度上会影响系统性能,但我看好多大型网站和应用程序都是这么搞的,所以我也这么搞了。
如果是给第三方开发调用模块,还是老老实实的对异常进行封装,然后抛出。我的想法是调用者有权知道调用之后的结果,包括错误信息。而不应该依赖于去判断某个属性定性结果的正确与否。
多谢作者的分享!

+1,我也喜欢这么做。
  回复引用
#36楼 2013-11-26 13:01 | 程序诗人  
@Sam Xiao
引用@LukyW

引用楼主这种方式[TransactionAttribute]这种方式是怎么实现呢?
@objectboy

引用是啊啊TransactionAttribute 怎么实现的啊啊 求教。。。
首先定义TransactionAttribute属性


然后通过entlib的unity进行拦截,如果没有用过unity的就自己写反射也行。
比如用反射来写:


不错,也可以这么搞:http://stackoverflow.com/questions/1054980/how-to-use-transactions-with-the-entity-framework
  回复引用
#37楼 2013-11-26 13:36 | wdwwtzy  
#38楼 2013-11-26 14:50 | relax  
Process.Execute(() => result = Save(accountData)); 
好方法~下个项目里用上~谢谢分享
  回复引用
#39楼 2013-11-26 14:53 | 油纸伞  
你好,关于你的bool Execute(Action action)设计,请问如果我需要Action的返回值,这种情况,怎么做的,谢谢
  回复引用
#40楼 2013-11-26 14:53 | zagelover  
不错,文章和评论都有很多知识,值得学习~
  回复引用
#41楼 2013-11-26 15:37 | john23.net  
#42楼 2013-11-26 16:10 | 键盘下的艺术  
WCF那个 我试了试 为啥 返回的还是错误404 ,新手求解释 用的 rest
数据用的json格式
  回复引用
#43楼 2013-11-26 16:14 | 冰麟轻武  
需要评估牺牲的性能是否值得

引用异常只会在最上层统一处理,而且业务层抛出异常也很正常,参考MSDN异常设计准则

关于这个我个人觉得 思路是对的,但是方法欠妥
应该重构代码达到统一处理异常的目的,而不是只图简单,全局增加一个Invoke方法
很多方法的参数都不一样,难道要全部使用匿名委托?
还有很多异常的处理方式都是不一样的,不能一概trycatch
还有很多其他的因素,总之统一Invoke不是最优方案
  回复引用
#44楼 2013-11-26 19:09 | KarasCanvas  
@冰麟轻武
引用需要评估牺牲的性能是否值得

引用引用异常只会在最上层统一处理,而且业务层抛出异常也很正常,参考MSDN异常设计准则

关于这个我个人觉得 思路是对的,但是方法欠妥
应该重构代码达到统一处理异常的目的,而不是只图简单,全局增加一个Invoke方法
很多方法的参数都不一样,难道要全部使用匿名委托?
还有很多异常的处理方式都是不一样的,不能一概trycatch
还有很多其他的因素,总之统一Invoke不是最优方案


你说的委托那是只楼主的方案.
.net framework就是这样处理的,要是觉得有问题,完全可以抛弃掉它
  回复引用
#45楼 2013-11-27 01:29 | vamp  
嘿嘿,我这几天也在思考这方面的问题。LZ的文章给了不少有用的建议啊。
  回复引用
#46楼 2013-11-27 09:44 | RyanCheng  
@Sam Xiao
为什么要在业务层去处理异常呢,我还是没懂
  回复引用
#47楼[楼主2013-11-27 09:50 | Sam Xiao  
@RyanCheng
引用@Sam Xiao
为什么要在业务层去处理异常呢,我还是没懂

原则上,在应用程序中,是在程序的最上层(即UI层)处理异常。但是UI层只有绑定数据,收集数据这种很简单的操作,出现异常的可能性比较小。而一般的UI层,基本上都是与业务逻辑层打交道。但是业务层出现异常的可能性比较大,所以把异常拿到了业务逻辑层来处理。
  回复引用
#48楼 2013-11-27 11:14 | 王大明  
越搞越复杂。。。
我的做法是统一由最上层处理异常
  回复引用
#49楼 2013-11-27 12:13 | 小坦克  
学习了, 先收藏, 慢慢看。
posted @ 2013-11-27 13:38  小马科技团队博客  阅读(203)  评论(0)    收藏  举报