Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >如何编写没有Try/Catch的程序

如何编写没有Try/Catch的程序

作者头像
蒋金楠
发布于 2018-01-16 09:50:59
发布于 2018-01-16 09:50:59
8700
举报
文章被收录于专栏:大内老A大内老A

在上面一篇文章《谈谈关于MVP模式中V-P交互问题》中,我提到最近一直为一个项目进行Code Review的工作,从中发现了一些问题,同时也有了一些想法。上次谈到如何正确编写服务MVP规范的程序,这次我们来关注一个我们每天都会面对的问题:异常处理。

一、异常处理不简单

个人觉得,异常处理对于程序员来说,尤其是对于那些初级.NET程序员来说,是最为熟悉的同时也是最难掌握的。说它熟悉,因为仅仅就是Try/Catch而已。说它难以掌握,很多开发人员却说不清楚Try/Catch应该置于何处?什么情况下需要对异常进行日志记录?什么情况下需要对异常进行封装?什么情况下需要对异常进行替换?对于捕获的异常,在什么情况下需要将其再次抛出?什么情况下则不需要。总之,异常处理没有我们想象的那么简单。

无论对于何种类型的应用,异常处理都是必不可少的。合理的异常处理应该是场景驱动的,在不同的场景下,采用的异常处理策略往往是不同的。异常处理的策略应该是可配置的,因为应用程序出现怎样的异常往往是不可预测的,现有异常策略的不足往往需要在真正出现某种异常的时候才会体现出来,所以我们需要一种动态可配置的异常处理策略维护方式。目前有一些开源的异常处理框架提供了这种可配置的、场景驱动的异常处理方式,EnterLib的Exception Handling Application Block就是一个不错的选择。

二、异常处理对于最终的开发人员是透明的

“异常处理对于最终的开发人员是透明的”,可能这句话说得有点过头。但是,就我个人的项目经验来讲,这是一种理想的状态。由于异常策略是一般是通过配置动态配置的,不需要反映在代码上面。如果能够通过框架的方式提供异常处理的实现,使开发人员无需编写任何异常处理的代码,只需要关注业务流程的实现就可以了,这不仅能够提高开发的效率,也能够提高系统的可维护性。

我们目前的项目是一个典型的分布式应用,所有的业务流程的处理和数据访问都实现在服务端,最终以WCF服务的形式暴露给客户端(Smart Client)和第三方应用。所有客户端和服务端从逻辑上具有相应的层次划分,但是异常处理仅仅实现在两个地方,一个地方是WCF服务本身,另一个实现UI层。忘了说明一点,我们项目直接将EnterLib的Exception Handling Application Block作为我们的异常处理框架。对于服务端的异常处理来说,我们通过WCF与EHAB的集成来实现的(《WCF与Exception Handling AppBlock集成》),所以不需要开发人员添加任何一句Try/Catch代码。但是客户端来说,对于某个控件的事件来说,由于UI本身就是处于整个调用栈的最顶层,很难通过基于AOP的拦截机制来实现对异常处理的动态注入,所以客户端会出现非常类似于下面代码所示的Try/Catch。

代码语言:js
AI代码解释
复制
   1: private void buttonCalculate_Click(object sender, EventArgs e)
   2: {
   3:     try
   4:     {
   5:         //
   6:     }
   7:     catch (Exception ex)
   8:     {
   9:         if (ExceptionPolicy.HandleException(ex, "policyName"))
  10:         {
  11:             throw;
  12:         }
  13:     }
  14:  
  15: }    

我是一个对重复代码具有强迫症的人,看到两个相同的代码我都有对代码进行重构的冲动,何况如此众多的相同代码充斥在客户端。

三、通过编写公共方法的形式实现代码的重用

为了避免开发人员编写相同的Try/Catch,很多人首先想到的肯定是将重复代码定义在一个公共的方法上,以实现代码的复用。这个公共方法很简单,只需要如下几句代码即可。

代码语言:js
AI代码解释
复制
   1: public void Invoke(Action action)
   2: {
   3:     try
   4:     {
   5:         action();
   6:     }
   7:     catch (Exception ex)
   8:     {
   9:         if (ExceptionPolicy.HandleException(ex, "data access policy"))
  10:         {
  11:             throw;
  12:         }
  13:     }
  14: } 

在调用的时候,只需要将相应的操作以Action类型的Delegate的形式传入Invoke方法即可。但是这样,也会在所有控件处理事件中出现重复的Invoke调用,虽然重复的代码行数减少了,但是还是会出现大规模的重复。接下里我来介绍另一种解决方法。

四、对EventHandler进行封装

认真分析上面的需求,我们的根本目的就是让执行事件处理程序的时候在外面人为地套一个Try/Catch,并对捕获的异常进行相应的处理。从这个意义上讲,如果我们能够对EventHandler或者ExventHandler<TEventArgs>进行相应的封装,就能实现我们需要的目的。

可能我这样说,你不会太明白,我们还是通过代码来说话好了。在下面我创建了一个用于封装EventHandler对象的EventHandlerWrapper类型。我们知道EventHandler是一个Delegate,而Delegate由两部分组成:表示操作本身的MethodInfo和操作执行的目标对象,分别通过属性Method和Target表示。在执行EventHandler的时候,就是通过反射的方式调用MethodInfo的Invoke方法,并将目标对象和相应的参数传入该方法而已。

代码语言:js
AI代码解释
复制
   1: using System;
   2: using System.Diagnostics;
   3: using System.Reflection;
   4: using System.Text;
   5: using System.Windows.Forms;
   6: namespace ProgramingWithoutTryCatch
   7: {
   8:     public class EventHandlerWrapper
   9:     {
  10:         public object Target
  11:         { get; private set; }
  12:  
  13:         public MethodInfo Method
  14:         { get; private set; }
  15:  
  16:         public EventHandler Hander
  17:         { get; private set; }
  18:  
  19:         public EventHandlerWrapper(EventHandler eventHandler)
  20:         {
  21:             if (null == eventHandler)
  22:             {
  23:                 throw new ArgumentNullException("eventHandler");
  24:             }
  25:  
  26:             this.Target = eventHandler.Target;
  27:             this.Method = eventHandler.Method;
  28:             this.Hander += Invoke;
  29:         }
  30:  
  31:         public static implicit operator EventHandler (EventHandlerWrapper eventHandlerWrapper)
  32:         {
  33:             return eventHandlerWrapper.Hander;
  34:         }
  35:  
  36:         private void Invoke(object sender, EventArgs args)
  37:         {
  38:             try
  39:             {
  40:                 this.Method.Invoke(this.Target, new object[] { sender, args });
  41:             }
  42:             catch (TargetInvocationException ex)
  43:             {
  44:                 StringBuilder message = new StringBuilder();
  45:                 message.AppendLine(string.Format("Message: {0}", ex.InnerException.Message));
  46:                 message.AppendLine(string.Format("Exception Type: {0}", ex.InnerException.GetType().AssemblyQualifiedName));
  47:                 message.AppendLine(string.Format("Stack Trace: {0}", ex.InnerException.StackTrace));
  48:                 EventLog.WriteEntry("Application", message.ToString());
  49:                 MessageBox.Show(ex.InnerException.Message + Environment.NewLine + "For detailed information, please view event log", string.Empty, MessageBoxButtons.OK, MessageBoxIcon.Error);
  50:             }
  51:         }
  52:     }
  53: }

EventHandlerWrapper通过EventHandler对象创建,并将EventHandler的Target和Method赋值给EventHandlerWrapper的同名属性。此外,EventHandlerWrapper得Invoke方法中,将对Method的调用放在一个Try/Catch中,并对捕获的异常进行简单的处理:记录到EventLog中在通过MessageBox将相关异常信息显示出来。而EventHandlerWrapper的Handler属性就是对该Invoke方法的直接反映。最后定义了一个隐式类型转换将EventHandlerWrapper直接转换成EventHandler。转化后返回的就是反映Invoke方法的Handler属性。为了演示,我写了一个简单的计算器的应用。该应用运行后的界面如右图所示,这是一个进行简单除法运算的计算器。 下面是相关的代码:

代码语言:js
AI代码解释
复制
   1: using System;
   2: using System.Windows.Forms;
   3: namespace ProgramingWithoutTryCatch
   4: {
   5:     public partial class Form1 : Form
   6:     {
   7:         public Form1()
   8:         {
   9:             InitializeComponent();
  10:             this.buttonCalculate.Click += new EventHandlerWrapper(buttonCalculate_Click);
  11:         }
  12:  
  13:         private void buttonCalculate_Click(object sender, EventArgs e)
  14:         {
  15:             int op1 = int.Parse(this.textBoxOp1.Text);
  16:             int op2 = int.Parse(this.textBoxOp2.Text);
  17:             int result = op1 / op2;
  18:             this.textBoxResult.Text = result.ToString();
  19:         }     
  20:     }
  21: }

代码非常简单,需要注意的是在对Button的Click事件进行注册的时候,我们直接使用的时我们上面创建的EventHandlerWrapper,这和真正进行事件注册的方式几乎一致。当你输入非数字或者被除数设置为的时候,会抛出异常,异常的相关信息会直接写入EventLog,并将异常消息通过MessageBox显示出来,如下图所示:

五、通过EventHandlerWrapper的写法实现其他的功能

EventHandlerWrapper实际上为了展示了对EventHandler进行封装的方式,异常处理并非其独有的应用场景。如果你看过我的文章《事件 (Event),绝大多数内存泄漏(Memory Leak)的元凶(上篇)(下篇)》,你会发现我通过相同的方式解决了事件注册导致的内存泄露的问题。在这里我在介绍另外一种有趣的应用。

在进行Windows Forms开发中,相信你会经常要求实现这样的功能:如果点击某个按钮后,需要较长的反映时间,需要在点击之后将Form的光标设置成沙漏的形状(Wait Cursor),当整个处理结束后再将其回复。我们可以对EventHandlerWrapper的Invoke方法略加修改就能够实现这个功能:

代码语言:js
AI代码解释
复制
   1: private void Invoke(object sender, EventArgs args)
   2: {
   3:     if(null !=  Form.ActiveForm)
   4:     {
   5:         Form.ActiveForm.Cursor = Cursors.WaitCursor;
   6:     }
   7:     try
   8:     {
   9:         this.Method.Invoke(this.Target, new object[] { sender, args });
  10:     }
  11:     finally
  12:     {
  13:         if (null != Form.ActiveForm)
  14:         {
  15:             Form.ActiveForm.Cursor = Cursors.Default;
  16:         }
  17:     }
  18: }
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2010-03-26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
无需写try/catch,也能正常处理异常
对于企业应用的开发者来说,异常处理是一件既简单又复杂的事情。说其简单,是因为相关的编程无外乎try/catch/finally+throw而已;说其复杂,是因为我们往往很难按照我们真正需要的策略来处理异常。我一直有这样的想法,理想的企业应用开发中应该尽量让框架来完成对异常的处理,最终的开发人员在大部分的情况下无需编写异常处理相关的任何代码。在这篇文章中我们将提供一个解决方案来让ASP.NET应用利用EntLib的异常处理模块来实现自动化的异常处理。 源代码: Sample1[通过重写Page的OnL
蒋金楠
2018/01/15
1.1K0
无需写try/catch,也能正常处理异常
也谈事件(Event)
最近园子里发表了一些讨论“事件(Event)”的文章,我也来凑个热闹,谈谈我对事件的一些粗浅的认识。本文不谈设计模式(观察者模式),只从运行时的角度来分析事件这个对象到底是个什么东西,它有那么神秘吗?为了更好的分析事件,本文将会编写一些例子来模拟事件的订阅机制。本文对事件的分析可以概括为下面三句话: 事件本质上是一个MulticastDelegate对象; MulticastDelegate对象是多个Delegate对象的链表; Delegate = Object + MethodInfo,委托的执行最
蒋金楠
2018/02/08
5770
ASP.NET MVC集成EntLib实现“自动化”异常处理[实例篇]
个人觉得异常处理对于程序员来说是最为熟悉的同时也是最难掌握的。说它熟悉,因为仅仅就是try/catch/finally而已。说它难以掌握,则是因为很多开发人员却说不清楚try/catch/finally应该置于何处?什么情况下需要对异常进行日志记录?什么情况下需要对异常进行封装?什么情况下需要对异常进行替换?对于捕获的异常,在什么情况下需要将其再次抛出?什么情况下则不需要? 合理的异常处理应该是场景驱动的,在不同的场景下,采用的异常处理策略往往是不同的。异常处理的策略应该是可配置的,因为应用程序出现怎样的异
蒋金楠
2018/01/15
1.2K0
ASP.NET MVC集成EntLib实现“自动化”异常处理[实例篇]
Enterprise Library深入解析与灵活应用(6):自己动手创建迷你版AOP框架
基于Enterprise Library PIAB的AOP框架已经在公司项目开发中得到广泛的使用,但是最近同事维护一个老的项目,使用到了Enterprise Library 2,所以PIAB是在Enterprise Library 3.0中推出的,所以不同直接使用。为了解决这个问题,我写了一个通过方法劫持(Method Interception)的原理,写了一个简易版的AOP框架。(如果对PIAB不是很了解的读者,可以参阅我的文章MS Enterprise Library Policy Injection
蒋金楠
2018/01/16
6380
Enterprise Library深入解析与灵活应用(6):自己动手创建迷你版AOP框架
Enterprise Library深入解析与灵活应用(3):倘若将Unity、PIAB、Exception Handling引入MVP模式.. .. ..
最近在做一个Smart Client Software Factory的项目。熟悉SCSF或者CAB的都应该很清楚MVP这种设计模式。MVP是MVC的一种变体,View和Mode分别关注于UI的呈现和业务模型,View和Mode完全分离,View通过Presenter实现对业务模型的访问,Presenter“间接”地调用View实现对UI的操作。对于MVP中的异常处理,我们是直接通过Enterprise Library的Exception Handling Application Block来实现的。具体的
蒋金楠
2018/01/16
6590
Enterprise Library深入解析与灵活应用(3):倘若将Unity、PIAB、Exception Handling引入MVP模式.. .. ..
我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案
几个星期之前写了一篇关于如何通过WCF进行 双向通信的文章([原创]我的WCF之旅(3):在WCF中实现双向通信(Bi-directional Communication) ),在文章中我提供了一个如果在Console Application 调用Duplex WCF Service的Sample。前几天有个网友在上面留言说,在没有做任何改动得情况下,把 作为Client的Console Application 换成Winform Application,运行程序的时候总是出现Timeout的错误。我觉得这
蒋金楠
2018/01/16
6010
我的WCF之旅(6):在Winform Application中调用Duplex Service出现TimeoutException的原因和解决方案
浅入 ABP 系列(4):事件总线
这一篇将来学习 ABP 中的事件总线,然后结合在我们的基架项目中,逐渐构建一个完整的系统。
痴者工良
2021/04/26
1K0
进阶|C#利用反射方式动态调用类库里的方法(附源码)
做为2020年第一篇技术文章,我们就直接分享一个进阶的的东西,利用反射方式动态调用类库执行方法。
Vaccae
2020/01/13
2.5K0
进阶|C#利用反射方式动态调用类库里的方法(附源码)
Enterprise Library深入解析与灵活应用(8):WCF与Exception Handling AppBlock集成[上]
在《WCF技术剖析(卷1)》的最后一章,我给出了一个具体的应用WCF的分布式应用实例,我把这个实例命名为PetShop。在这个例子中,我利用WCF的扩展实现了一些设计、架构模式,比如AOP、IoC等。看过本书的读者,一定还记得我还通过WCF扩展实现了于微软企业库(Enterprise Library)异常处理应用块(Exception Handling Application Block:EHAB)的集成。当时由于缺乏相应的背景知识,不可能介绍具体的实现,现在我们可以详细来讲述这是如何实现的。 (Sourc
蒋金楠
2018/01/16
5480
Enterprise Library深入解析与灵活应用(8):WCF与Exception Handling AppBlock集成[上]
WinForm企业应用框架设计【五】系统登录以及身份验证+源码
WinForm企业应用框架设计【一】界限划分与动态创建WCF服务(no svc!no serviceActivations!)
liulun
2022/05/09
6650
WinForm企业应用框架设计【五】系统登录以及身份验证+源码
19-ESP8266 SDK开发基础入门篇--C# TCP客户端编写 , 连接和断开
https://www.cnblogs.com/yangfengwu/p/11130428.html
杨奉武
2019/07/16
1.2K0
19-ESP8266 SDK开发基础入门篇--C# TCP客户端编写  , 连接和断开
深入分析委托与事件—C#综合揭秘——细说多线程
本篇文章将为你介绍一下 Delegate 的使用方式,逐渐揭开 C# 当中事件(Event)的由来,它能使处理委托类型的过程变得更加简单。 还将为您解释委托的协变与逆变,以及如何使用 Delegate 使 Observer(观察者)模式的使用变得更加简单。 在事件的介绍上,会讲述事件的使用方式,并以ASP.NET的用户控件为例子,介绍一下自定义事件的使用。 最后一节,将介绍Predicate<T>、Action<T>、Func<T,TResult>多种泛型委托的使用和Lambda的发展过程与其使用方式。 因为时间仓促,文中有错误的地方敬请点评。
vv彭
2021/01/06
2.2K0
20-ESP8266 SDK开发基础入门篇--C# TCP客户端编写 , 加入数据通信
https://www.cnblogs.com/yangfengwu/p/11192594.html
杨奉武
2019/07/16
7290
20-ESP8266 SDK开发基础入门篇--C# TCP客户端编写 , 加入数据通信
事件(Event),绝大多数内存泄漏(Memory Leak)的元凶[下篇] (提供Source Code下载)
在上篇中我们谈到:将一个生命周期较短的对象(对象A)注册到一个生命周期较长(对象B)的某个事件(Event)上,两者便无形之间建立一个引用关系(B引用A)。这种引用关系导致GC在进行垃圾回收的时候不会将A是为垃圾对象,最终使其常驻内存(或者说将A捆绑到B上,具有了和B一样的生命周期)。这种让无用的对象不能被GC垃圾回收的现象,在托管环境下就是一种典型的内存泄漏问题。我们今天将会着重解释其背后的原因。[本篇文章的Source Code从这里下载) 一、CLR垃圾回收简介 在一个托管应用程序中,我们通过不同的方
蒋金楠
2018/01/16
9530
事件(Event),绝大多数内存泄漏(Memory Leak)的元凶[下篇] (提供Source Code下载)
WCF 入门 (17)
首先修改服务端的app.config 配置文件,先使用basicHttpBinding
_淡定_
2018/08/24
2100
WCF 入门 (17)
关于.NET异常处理的思考
彭泽0902
2018/01/04
9260
AOP编程
Aspect Oriented Programming(AOP),面向切面编程。AOP主要解决的问题是针对业务处理过程中对一些逻辑进行切面提取,它可以分散在处理过程中的不同的阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这样做可以提高程序的可重用性,同时提高了开发的效率。AOP编程一般会分离应用中的业务逻辑和通用系统级服务逻辑,可以让各自业务进行高内聚的开发,通用系统级服务也能得到很好的复用。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责其它的系统级关注点,例如日志或事务支持。AOP编程的主要场景是从业务逻辑里面提取日志记录,性能统计,安全控制,事务处理,异常处理等逻辑到独立的单元里。让负责业务逻辑的代码更加清晰和简单,从而更加容易维护,并且容易被复用。用一张图来看一下AOP编程的表现形式:
小蜜蜂
2019/07/15
5260
AOP编程
基于TCP通信的客户端断线重连
转载:http://www.cnblogs.com/networkcomms/p/4304362.html
跟着阿笨一起玩NET
2018/09/19
3.8K0
基于TCP通信的客户端断线重连
Caliburn.Micro学习笔记(五)----协同IResult
今天说一下协同IResult 看一下IResult接口 /// <summary> /// Allows custom code to execute after the return of a action. /// </summary> public interface IResult { /// <summary> /// Executes the result using the specified context. ///
lpxxn
2018/01/31
5360
C# 委托进阶
本文参考自:https://wenku.baidu.com/view/41ab91d3c1c708a1284a44d7.html?qq-pf-to=pcqq.c2c 1、为什么委托定义的返回值通常为v
郑小超.
2018/01/26
9000
推荐阅读
相关推荐
无需写try/catch,也能正常处理异常
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文