分享Web应用运行的细节问题:预编译提高网站性能、跟踪用户习惯和解决线程同步

在这个文章里,我将分享一下在iOpenWorks.com这个网站试运行中碰到的若干问题和解决方案,这些问题包含了:(1)如果通过ASP.NET MVC预编译提高性能;(2)如果知道网站在运行中,用户响应速度、网站异常信息、用户操作习惯;(3)解决与DiscuzToolkit集成的线程同步问题。

 

1 ASP.NET MVC 3预编译支持

提高网站性能,除了我们常见的压缩、CDN、缓存之外,还有一个就是使用预编译。不管是ASP.NET WebForm,或者是ASP.NET MVC,这些页面在网站运行过程中,都是要先经过编译处理的。因此,如果能在网站运行前对其进行编译,那无疑能更好的提高网站的响应速度。因此,我们选择了一个RazorGenerator来对所有的ASP.NET MVC 3的视图进行编译,这样,在部署时仅需要将dll文件拷贝过去,而不再需要cshtml文件了。下面介绍如何使用它来实现预编译。

1.1 下载安装RazorGenerator

你可以在http://razorgenerator.codeplex.com/下载到RazorGenerator,这是一个VS 2010的扩展。下载完成后,就可以直接安装了。接着你还需要下载源代码,然后编译一下,获取编译的RazorGenerator.Mvc.dll程序集。

 

1.2 改变视图文件的生成方式

将所有的视图的BuildAction改成None,并且将CustomTool改成RazorGenerator,这时候,你可以看到一个关联的.generated.cs文件,这个文件就是预编译的源码文件了。

image

1.3 处理Helper

对于Helpr文件,处理方式有所不同。Helper文件一般放在App_Code文件夹里面。首先,你需要在Helper文件的第一行添加 @* Generator: MvcHelper *@ 来声明一下,接着将BuildAction改成None,并且将CustomTool改成RazorGenerator

image

下面,还需要额外一个步骤,这个非常重要,否则编译无法通过,那就是需要将.generated.cs文件的BuildAction由Content改为Compile。

image

 

1.4 注册PrecompiledMvcEngine

下面我们在ASP.NET MVC 3项目中引用RazorGenerator.Mvc.dll这个程序集,然后定义一个PreApplicationStartCode,并在AssemblyInfo.cs文件中注册这个PreApplicationStartCode。这样,我们就注册了PrecompiledMvcEngine了。

(1)在AssemblyInfo.cs注册

 

[assembly: PreApplicationStartMethod( 
     typeof(UIShell.iOpenWorks.PreApplicationStartCode),  " PreStart ")]

 

(2)PreApplicationStartCode定义

 

namespace UIShell.iOpenWorks 

     public  class PreApplicationStartCode 
    { 
         private  static  bool _isStarting;
         public  static  void PreStart() 
        { 
             if (!_isStarting) 
            { 
                _isStarting =  true;
                 var engine =  new PrecompiledMvcEngine( 
                     typeof(PreApplicationStartCode).Assembly);
                ViewEngines.Engines.Add(engine);
                VirtualPathFactoryManager.RegisterVirtualPathFactory(engine); 
            } 
        } 
    } 
}

 

 

1.5 部署

这时候,部署网站就不再需要将视图文件部署过去了,只需要拷贝dll文件和网站资源。注意,在Views下面已经没有.cshtml文件了,也没有App_Code文件,因为它们都被预编译到了UIShell.iOpenWorks.dll这个程序集了。接下来,你就可以测试一下网站,享受一下预编译带来的性能提升了。

imageimage

 

2 跟踪网站运行情况

网站在内测期间,会碰到较多的问题。但是,这时候,用户已经进来测试,你怎么能够及时发现用户响应速度、用户访问过程中网站异常信息以及用户是如何来使用你的网站。这里,我们使用了log4net这个日志组件,它用于记录:(1)用户访问了哪些页面;(2)用户在访问页面过程中,碰到了哪些异常;(3)每一个页面的响应速度。下面,我来介绍如何记录这些信息的。

2.1 在Global中,跟踪每个用户访问的页面,并且要记录用户响应的速度

 

[ThreadStatic] 
private  static Stopwatch _stopwatch;
protected  void Application_BeginRequest() 

    _stopwatch = Stopwatch.StartNew();   //  计时开始 
     if (DiscuzHelper.IsLoggedIn())  //  记录当前用户 
    { 
         try 
        { 
             var user = DiscuzHelper.LoggedUser(); 
             if(user !=  null
            { 
                ThreadContext.Properties[ " user "] = user.UserName; 
                 return
            } 
        } 
         catch (Exception ex) 
        { 
            _logger.Error( " Failed to get the user name though the user is logged in. ", ex); 
        } 
    } 
            
    ThreadContext.Properties[ " user "] =  string.Empty;
     if (Request !=  null//  记录当前用户的IP 
    { 
        ThreadContext.Properties[ " ipaddress "] = Request.ServerVariables[ " REMOTE_ADDR "]; 
    } 
     else 
    { 
        ThreadContext.Properties[ " ipaddress "] =  string.Empty; 
    } 
}
protected  void Application_EndRequest() 

     if (Request !=  null && _stopwatch !=  null && _logger !=  null)   //  计时结束,就用户响应时间和访问页面 
    { 
        _stopwatch.Stop(); 
        _logger.Debug( string.Format( " Accessed page 'Response time: {0} ms, Url: {1}'. ", _stopwatch.ElapsedMilliseconds, Request.Url)); 
    } 
}

 

2.2 在Global中,记录系统的异常

 

void Application_Error(Object sender, EventArgs ea) 

     if (Server !=  null
    { 
        Exception e; 
         for (e = Server.GetLastError(); e !=  null; e = e.InnerException) 
        { 
            _logger.Error( " Unhandled server exception thrown. ", e); 
        } 
    } 
}

 

 

2.3 处理关键方法

下面,我还在关键方法记录了用户的操作异常信息、响应速度。比如我必须记录了:(1)用户注册时响应速度、注册时发生的异常、用户登录时响应速度、用户登录时发生的异常;(2)用户在什么情况下尝试下载iOpenWorksSDK这个免费插件框架;(3)尝试下载时,会转到注册页面,这时候用户是否继续注册并下载,还是放弃。

对这些关键方法的记录,有助于提高应用系统的易用性。通过日志,我们修复了与Discuz集成的很多问题,并且提高了用户响应速度。

 

2.4 日志分析

下面,我们需要来看一下日志分析,这里我们在一个开源的LogViewer自定义了一下。通过对日志的分析,你就可以知道系统发生了什么异常、系统性能如何、用户操作习惯、关键方法的信息。当然,你也可以打开日志文件直接查看,只是,那样比较费劲。对了,在这里我们绝不记录用户的密码,这太不职业道德了,此外,所有密码都是加密的,避免“CSDN”!

(1)查看异常信息

image

(2)查看关键方法信息:用户访问习惯、响应性能等

image

 

3 解决DiscuzToolkit线程同步

网站的社区是与Discuz集成的,我们就用了DiscuzToolkit来集成。这是官方发布的类库,但是依然问题一堆。最严重的2个问题就是线程同步引起的,可见Discuz这帮人都ASP.NET多线程模型压根没有当一回事,或者连线程安全都没有注意到。下面就说一下碰到的2个线程安全问题。

(1)在注册用户时,碰到以下异常:当前会话所提交的call_id没有大于前一次的call_id

Failed to get the user name though the user is logged in.
Discuz.Toolkit.DiscuzException: Code: 103, Message: 当前会话所提交的call_id没有大于前一次的call_id
at Discuz.Toolkit.Util.GetResponse[T](String method_name, DiscuzParam[] parameters) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\DiscuzToolkit\Util.cs:line 97
at Discuz.Toolkit.DiscuzSession.GetUserInfo(Int64[] uids, String[] fields) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\DiscuzToolkit\DiscuzSession.cs:line 224
at Discuz.Toolkit.DiscuzSession.GetUserInfo(Int64 uid) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\DiscuzToolkit\DiscuzSession.cs:line 255

这个问题是由Discuz.Toolkit.Util的Sign方法引起的,在这里,它为每一个API请求生成一个call_id。

 

list.Add(DiscuzParam.Create( " call_id ", DateTime.Now.Ticks));

 

 

如果你在当前线程API调用太勤快的话,DateTime.Now.Ticks会生成一样的值,从而引发异常。因此,官方提议可以Sleep一下。因此,我们就需要改成如下:

 

list.Add(DiscuzParam.Create( " call_id ", DateTime.Now.Ticks)); 
//  Avoid to generate same 'call_id' and throws an exception on '当前会话所提交的call_id没有大于前一次的call_id'. 
Thread.Sleep( 50);

 

 

但是这样,依然是不过的,这个异常只是变得更加诡异了,让你碰到机会少一点而已。你别忘了ASP.NET应用程序是多线程的,当两个线程同时访问时,依然可能获得同一个call_id,于是,在碰到若干次这个问题后,我用以下方法来修复。

 

lock (_syncRoot) 

    list.Add(DiscuzParam.Create( " call_id ", DateTime.Now.Ticks)); 
     //  Avoid to generate same 'call_id' and throws an exception on '当前会话所提交的call_id没有大于前一次的call_id'. 
    Thread.Sleep( 50); 
}

 

 

(2)注册用户时,碰到以下异常:An item with the same key has already been added.

[2012-04-07 17:11:30,818] [7] [ERROR] [AccountController] [49.72.46.135] []: System.ArgumentException: An item with the same key has already been added.
at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
at Discuz.Toolkit.Util.GetSerializer(Type t) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\DiscuzToolkit\Util.cs:line 157
at Discuz.Toolkit.Util.GetResponse[T](String method_name, DiscuzParam[] parameters) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\DiscuzToolkit\Util.cs:line 88
at Discuz.Toolkit.DiscuzSession.GetUserID(String username) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\DiscuzToolkit\DiscuzSession.cs:line 243
at UIShell.iOpenWorks.Controllers.AccountController.Register(DiscuzNewUser newUser, String returnUrl) in E:\Work\Design\Core\milestore 1\osgi\m10\uishell.iopenworks\UIShell.iOpenWorks\Controllers\AccountController.cs:line 53

 

你想想,要是用户注册时,动不动碰到注册不成功,是多么窝火!!所以,我根据日志再次调查发现,DiscuzToolkit在使用静态变量保存数据时,竟然不加锁,太不拿Thread-Safe当回事了。这会异常也发生在Util类里,代码如下,其中serializer_dict是静态全局变量。

 

serializer_dict.Add(type_hash,  new XmlSerializer(t));

 

于是,我修改如下。这样,彻底解决了和Discuz的集成了。

 

private  static Dictionary< int, XmlSerializer> serializer_dict =  new Dictionary< int, XmlSerializer>(); 
private  static ReaderWriterLock _lock =  new ReaderWriterLock(); 
public  static XmlSerializer GetSerializer(Type t) 

     int type_hash = t.GetHashCode(); 
     const  int timeout =  5000;
     try 
    { 
        _lock.AcquireReaderLock(timeout); 
         if (!serializer_dict.ContainsKey(type_hash)) 
        { 
            _lock.UpgradeToWriterLock(timeout); 
             if (!serializer_dict.ContainsKey(type_hash)) 
            { 
                serializer_dict.Add(type_hash,  new XmlSerializer(t)); 
            } 
        }
         return serializer_dict[type_hash]; 
    } 
     catch (ApplicationException ex) 
    { 
         throw  new Exception( " Accquire lock failed. ", ex); 
    } 
     finally 
    { 
         if (_lock.IsReaderLockHeld) 
        { 
            _lock.ReleaseReaderLock(); 
        } 
         else  if (_lock.IsWriterLockHeld) 
        { 
            _lock.ReleaseWriterLock(); 
        } 
    } 
}

 

 

OK,关于网站试运行中,最重要的几点分享描述完了。顺道介绍一下什么是iOpenWorks.com。iOpenWorks.com是一个免费工厂的开放仓库,旨在向开发人员提供完全免费的标准化的OSGi.NET面向服务插件框架以及共享的插件仓库,这样,你既可以从插件仓库使用别人插件,也可以共享自己的插件,互利共赢!

 

你也可以加入iOpenWorks交流群:121369588,Thanks。

 

本文基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 道法自然(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。
健壮的程序并不是没有异常,而是能够处理异常和定位异常!

 

去年给客户咨询的时候,客户提出一个非常有趣的问题:他们做的一个应用系统经常动不动就挂掉,而且最主要的是,挂掉后无法找出问题的原因。

 
后面查看代码时,发现用户在编写代码中,出现了类似以下的代码。
1 IPageFlowService pageFlowService = 
2     bundleRuntime.GetFirstOrDefaultService<IPageFlowService>();
3  //  Redirect to first node.
4 Response.Redirect(pageFlowService.FirstPageNodeValue);
 
 

这段代码在正常情况下,没有任何问题,也一定能够通过正常的功能测试。但是,用户思维和程序员不同,因此,稀奇古怪的问题都是有可能的。可以这么说,在用户这里,“Nothing is impossible!”,:)。

 

 
那我来说明一下,这段代码可能出现的问题。
(1)有可能pageFlowService没有获取到,这时程序会抛出NullReferenceException;
(2)pageFlowService.FirstPageNodeValue有可能为空,这时候程序也会抛出NullReferenceException。

 

 
当以上任意一种情况出现时,系统都将崩溃,或许有的程序员已经注意到可能出现NullReferenceException异常,于是将程序更改成如下。
1 IPageFlowService pageFlowService = 
2     bundleRuntime.GetFirstOrDefaultService<IPageFlowService>();
3  //  Redirect to first node.
4  if(pageFlowService !=  null && ! string.IsNullOrEmpty(pageFlowService.FirstPageNodeValue))
5 {
6   Response.Redirect(pageFlowService.FirstPageNodeValue);
7 }
 
这段代码出现NullReferenceException的几率几乎为零了,也就意味着上面的程序在运行过程中不会再出现异常情况了。似乎是健壮很多了。
 
 

不过,恰恰相反,这次更加悲剧了,因为一旦pageFlowService为null,或者pageFlowService.FirstPageNodeValue为空时,则上面的程序不会出现任何的异常,但是却使得系统出现问题时,我们很难定位出到底哪出错了。

 

 
因此,正确的做法,应该是对于反面情况,我们的程序也必须处理,并且需要抛出良好的异常信息,如下所示。
1  //  Get PageFlowService.
2 IPageFlowService pageFlowService = 
3     BundleRuntime.Instance.GetFirstOrDefaultService<IPageFlowService>();
4  if (pageFlowService ==  null)
5 {
6    throw  new ServiceNotAvailableException(
7      typeof(IPageFlowService).FullName, Properties.Resources.IOpenWorksWebShellName);
8 }
9 
10  if ( string.IsNullOrEmpty(pageFlowService.FirstPageNodeValue))
11 {
12      throw  new PageNodeException(Properties.Resources.CanNotFindAnAvailablePageNode);
13 }
14  //  Redirect to first node.
15 Response.Redirect(pageFlowService.FirstPageNodeValue);
 
在上述代码中,如果pageFlowService为null,则抛出一个ServiceNotAvailableException,来描述服务不存在的异常,保证异常情况出现时,我们就可以快速判断问题的根源;在pageFlowService.FirstPageNodeValue为空时,抛出PageNodeException。这样,当异常出现时,我们就可以很快的定位出问题了。
 
在上面的例子中,你也可以引入断言达到相同的效果。这里,我仅是做一个例子来描述而已。

 

为了提供系统的健壮性,我们在写程序时,必须同时关注提供的正面行为和反面行为,并给出不同的处理方法! 要学会习惯性的处理异常情况!!

 

以下是一点点建议,欢迎补充。

 

1 不要吞掉异常情况,要学会习惯处理异常情况,即要考虑正面和负面情况
绝对不要吞掉异常情况,虽然这样子写程序很省事,但是,一旦问题出现了,也让你很麻烦。吞掉异常的情况一般有:
(1)使用try…catch…,然后不做任何异常处理,这种情况下,所有异常都被忽视掉了,对于写代码来说是非常省事,但是问题出现后,压根无法找到那出错了。
 
try
{
}
catch
{
     //  Ignore the exceptions
}
 
(2)使用try…catch,只是笼统处理,这里处理方式要好于第一种,但是程序并没有根据捕捉的不同异常类型而给出不同处理方法,虽然能够定位大概原因,但是无法精确,因此这个方法还是存在问题。
try
{
}
catch(Exception ex)
{
     //  Show the ex.Message here.
}
(3)只处理正面条件,这种方法和(1)情况类似,当然动机不一样,在这种情况下,写程序时是做了程序正常的假设,而不考虑异常。
if(pageFlowService !=  null && ! string.IsNullOrEmpty(pageFlowService.FirstPageNodeValue))
{
  Response.Redirect(pageFlowService.FirstPageNodeValue);
}
 
2 引入日志
这里日志,最好不要使用Log.Message/Error(sting message)的处理方法,因为这样的日志并没有办法准确描述是在那个线程、那个类、那个方法产生的结果。因此,我提倡直接使用log4net的日志。
 
3 程序进行模块化分解
这种方法可以把依赖限制在一个功能模块里面,因此,异常的范围也可以进一步限制起来,从而有助于更好的定位问题的根源。
 
欢迎大家多多补充。
 

 

本文基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 道法自然(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。

 

专业的程序员需要具备的思考能力:写一个程序需要注意多少细节问题

 

我曾经听过一个老师对一个做的不错的程序员的一句评论:“某某某做程序确实很快,但就是都不能用”。事实上,直到今日,我们中很多人仍然在写这样的程序。不能用的程序一般都有几个特征,比如当程序出现问题时,总是不知道该怎么处理,甚至是在正常情况下都会出现问题,但归根结底就是压根就没有认真用户的角度来考虑用户到底怎么来使用。如果用户输入了程序接受的输入,那么必然该给出正确的结果,可悲剧的是,就是有那么多的意外,因为客户行为总是在很多程序员意料之外,因此,当用户执行了某个程序员认为是非常规的操作的话,运行结果可能是用户无法得到任何问题的解释,然后用户下定一个结论就是:“这是一个什么破软件,真没用!”。

举个例子,民生网银就是一个巨大的悲剧,我上网银想要做一个操作,就是查询我的信用卡本期该还多少钱,但是我始终找不到那个菜单,原因在于:1 菜单字体太小,找起来真费劲;2 前几项看起来像是查询账户的,但点击后,悲剧的结果是提示“该卡为非储蓄卡”,我就纳闷那你为什么不提示我信用卡该做那个操作呢。于是我就找了客服问她如何查询,并向她抱怨网银字体太小,无用菜单太多,于是客服MM告诉我可以自己设置菜单的现实。可是,我哪知道该显示/隐藏那些一级菜单呢?最后我费了很多力气总算找到菜单,然后点击查询后,竟然要让我输入日期,我就寻思,为什么不直接显示最近一期的呢?这么麻烦我干么呢?最后越想越气,因为:1 这个操作过程非常不爽;2 做网银软件的这帮人真是坏了我们程序员的名声,怎么搞出这么难用的软件呢?要是逼这帮程序员天天用民生网银软件,估计也让他们折寿不少; 3 民生银行好歹算不错的公司了,怎么会上这么难用的软件呢?于是,我就很自然的、“邪恶”的联想到“特色”的社会主义。后来,我觉得我做为一个社会主义合法公民,我该“为中国的软件事业做点贡献”,给民生银行提点建议,于是我就想注册民生论坛发表一下意见。不幸的是,悲剧又来了,需要填写的注册信息真多啊,但更悲剧的是,在我填写完所有详细信息后,IE 9 下无法注册,总提示验证码不对。还好,我是程序员,我机器上有3个浏览器,于是,换了Chrome,注册了,但修改帖子不成功;淡定,我还有Firefox,结果出现了乱码。于是,我再次发挥程序员的职业素质,清理了Cookie,用注册好的账户,换回Chrome来发帖子,告诉民生网银有哪些易用性问题。经过一个小时痛苦折腾,算是OK了。要不是为了出心里的这口气,我才不会半夜1点钟忙到2点钟给民生银行反应问题呢(需要说明一下,客户MM态度很好,另外,半夜还要伺候我这样的人,还真难为她了。在她给我道歉时,我就回复她,这不是你的问题,然后转到论坛抱怨来了)。在我印象中,我活到现在半夜发抱怨的帖子仅有2次,一次是在电影院看了“要拍给下一代人看的”田壮壮导演亲自执导的《狼灾记》电影,另一次就是使用了民生网银。你也可以看到,程序员不替用户考虑最终会给用户带来多大的麻烦,也可想而知,如果这事发生在作为程序员的你身上或者你所在公司身上,会给你带来多大的信誉损失。

这类的悲剧也曾出现在我们团队身上,比如,我们的SaaS应用商店开放平台的应用让用户试用了应用之后,用户觉得不错后要继续使用,当用户购买以后却发现怎么也无法激活应用了。这对用户和我们来说,绝对是一个巨大的打击,先不说用户有多么恼火,就问你,你怎么给用户解释出现的问题呢。你总不能告诉用户,我们的程序员忽略了一个情况,“当应用过期时,应用不能激活了,因为要防止用户重复使用试用的应用”。这个问题让我思索了很久,因为我不能单纯的去怪罪我的程序员,因为毕竟在设计许可证授权这个模块时,由于我忙于别的任务,我没有去努力把好关,没有让他严格走我们的“概念设计->功能规范->设计规范->实现->QA”标准软件产品生产路线,但更重要的是,我没有把他们培养的和我一样,无论做什么东西,都必须以“用户场景驱动”来设计产品。因此,他们很容易忽视软件在非常规操作下的处理,以及一些很细节的问题。最后这件事情是在我用1个小时的时间给他解决好问题后,客户才最终释怀并表示理解和谢意。

我这篇文章想以2个例子来解释程序需要注意的细节和非常规操作下的软件处理。关于“用户场景驱动”设计,我在本文不做详述,大家可以查看《Framework Design GuideLine》这本书。

第一个实例:我们给一个客户提供尤埃开放服务平台(UIOSP) OEM版的激活工具。这个工具在正常运行下的界面如下:

initialized

(1)运行初始界面。

activating

(2)输入序列号后,正在激活。

activated

(3)激活成功,显示一些信息。

在这个小程序里面,需要考虑一下的小细节:

(1)界面不能被Resize,因为一旦Resize,将会变得非常丑陋;

(2)背景图片不能随着分辨率的变更而出现一些空白或者遮挡,这也非常的丑陋;

(3)可以支持“ESC”或者“Enter”按钮,从而避免用户使用鼠标操作;

(4)支持使用“Alt + <Key>”的组合键,这样方便用户使用键盘操作;

(5)当用户点击“激活”按钮时,要将所有的按钮变成Disabled,避免用户重复点击;

(6)激活过程要避免界面假死,就是当此时点击界面,界面能及时刷新;

(7)激活是要显示激活进程,提示用户,现在正在操作;

(8)界面的字体,要让用户能看清楚;

(9)异常处理:A)如果网络不畅通,用提示用户检查网络;B)如果许可证输入为空,提示用户输入;C)如果许可证无效,提示用户输入正确许可证;D)如果许可证已经超过使用限制,提示用户购买更多授权;E)如果激活成功,但不是使用管理员用户,则许可证在Win7下可能无法正确保存,那么必须提示用户设置权限或者使用管理员身份。

emptyerrorkeynetworkerror

(10)如果用户点击取消或者关闭,要提示用户是否确定取消。

existconfirm

您或许还需要考虑国际化与本地化支持,此外还需要考虑是否遵循Section 508规范来支持更广泛的易用性规范,以及其它问题。不过,一个真正合格的软件需要考虑的细节确实非常的多,我也没有完全做到正确。下面,我用一个示例来介绍一下关于异常处理的实例。

 

第二个实例:许可证辅助工具,这是一个控制台程序,支持对SaaS应用商店开放平台应用许可证的操作。目前它提供了“-help”、“-alterlic”、“-alterconstraint”、“-listconstraint”、“-showmac”、“-listlicattr”和“-showlic”命令,分别用于获取帮助信息、变更许可属性、变更许可约束、列出可用的许可约束、查看本地Mac、列出许可证属性和查看一个许可证的信息。默认的是,让你将一个许可证拖拽到这个程序,自动显示该许可证的信息。这个程序采用了“管道-过滤器”体系结构来设计,我仅以“-alterlic”命令来展示如何进行异常情况处理。

image

 

1  namespace UIShell.OSGi.LicenseUtility 
2 { 
3      class Program 
4     { 
5          static  void Main( string[] args) 
6         { 
7             CmdLinePipeLine pipeLine =  new CmdLinePipeLine();  
 
// Pipeline-Filter Architecture。
8             var showLicense =  new ShowLicenseDetailsFilter(); 
9             pipeLine.RegisterFilter( new AlterLicenseAttributesFilter()); 
10             pipeLine.RegisterFilter( new AlterConstraintFilter()); 
11             pipeLine.RegisterFilter( new ListConstraintsFilter()); 
12             pipeLine.RegisterFilter( new ShowMacFilter()); 
13             pipeLine.RegisterFilter( new ListLicenseAttributesFilter()); 
14             pipeLine.RegisterFilter(showLicense);
15              if (args.Length ==  1 && 
16                 File.Exists(args[ 0])  /* && 
17                  Path.GetExtension(args[0]).ToLower().Equals(".lic") */
18             { 
19                  try 
20                 { 
21                     showLicense.ShowLicenseDetails(args[ 0],  string.Empty); 
22                 } 
23                  catch (Exception ex)  //  There is details exception in ex.Message. Thus, just show it directly. 
24                 { 
25                     System.Console.WriteLine(ex.Message); 
26                 } 
27                 System.Console.WriteLine(); 
28                 System.Console.Write( " Press any key to exit. "); 
29                 System.Console.Read(); 
30             } 
31              else 
32             { 
33                 pipeLine.Handle(args); 
34             } 
35         } 
36     } 
37 }

 

该命令的格式为“-alterlic <File or Directory> [-deep true/false] [-lictype fx/bundle] <attribute>=<value>”。

用户输入异常可能为:

(1)File or Directory没有指定;

(2)File or Directory不存在,必须告诉用户输入正确的文件/文件夹;

(3)File不可读取,必须告诉用户更改权限;

(4)File格式不正确,必须告诉用户程序无法处理;

(5)如果是Directory,则是遍历所有的License File,那么,如果其中一个LicenseFile处理失败,则不能影响其它LicenseFile,但必须记录失败的License;

(6)如果用户输入的属性设置“<attribute>=<value>”是一个“aaa”格式的字符串,则必须提示用户,并且忽略掉;

(7)如果用户输入的属性设置“<attribute>=<value>”中attribute不存在,则必须提示用户,并且抛出异常,终止变更;

(8)如果用户输入的属性设置“<attribute>=<value>”中value与目标类型不匹配,则必须提示用户,并且抛出异常,终止更新;

(9)对许可证变更之前必须备份,以做回滚操作。

(10)其它细节或者异常处理。

在这里面,我们需要考虑的细节问题和异常处理问题比较多,我无法一一细述,仅供参考,希望能给大家一些思考。关于异常,我想强调一下,异常处理就是你需要提前考虑用户的非常规操作,并对用户的这类操作给出错误提示,并告诉用户如何去纠正。不过,我想,你也应该发现了,做一个在正常情况下能够工作的软件和一个在任何情况下都能工作很好的软件是有非常大的区别的,后则需要我们付出更多的努力和更专业的技能。 

 

本文基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 道法自然(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。

当前标签: OSGi

 
道法自然 2011-06-13 00:46 阅读:2528 评论:14
 
道法自然 2011-06-10 12:48 阅读:3486 评论:19
 
道法自然 2011-05-22 19:06 阅读:2162 评论:10
 
道法自然 2011-01-20 22:17 阅读:2123 评论:4
 
道法自然 2010-11-26 15:23 阅读:2468 评论:10
 
道法自然 2010-11-01 14:45 阅读:2210 评论:18
 
道法自然 2010-10-26 14:33 阅读:2888 评论:56
 
道法自然 2010-10-18 12:28 阅读:2166 评论:17
 
道法自然 2010-10-07 14:08 阅读:1576 评论:47
 
道法自然 2010-09-17 23:05 阅读:1902 评论:2
 
道法自然 2010-07-31 11:53 阅读:3574 评论:70
 
道法自然 2010-05-05 00:44 阅读:2641 评论:28
 
道法自然 2009-11-15 23:36 阅读:2150 评论:7
 
道法自然 2009-09-19 22:04 阅读:2179 评论:21
 
道法自然 2009-08-31 00:46 阅读:3627 评论:10

你可能感兴趣的:(Web应用)