逸言

重构-勿以善小而不为

| Comments

重构最大的敌人不是技巧与能力,而是懒惰,或者说是态度。许多细小的重构看似无足轻重,例如方法重命名,提取方法,即使重构了,似乎对代码的结构也没有太大的影响,于是就决定淡然处之,心里想“事情还未到不可挽回的地步,实现功能要紧,至于重构,还是以后再做吧!”这样一想,于是就会滋生得过且过的想法,等到代码开始变得一团糟时,重构已经变得无比困难了。此时需要的重构技巧,将百倍难于发现坏味道之初。

在我参加的前一个项目中,我们定义了一个处理OrderSet的Controller。刚刚开始开发时,对于OrderSet的操作并不多,主要是Search与Count操作。OrderSet分为WithDetails与WithoutDetail两种类型。为了实现的简单,我们将这两种类型的操作都放在一个Controller中。随着操作的逐渐增多,这个Controller就变得越来越庞大,逐渐变得臃肿起来。每当我需要调用或者修改Controller时,我都在想:“嗯,这个代码太糟糕了,什么时候给它重构一下。”想是这么想,却一直扮演着说话的巨人,行动的矮子。即使兴起这样的念头,又因为其他的工作将此念头浇灭。直到有一天,这个Controller的代码已经到了忍无可忍的地步,我和我的Pair终于达成一致意见,决定对此代码进行手术。我们花费了近一天的时间对Controller以及相关的Repository进行了彻底的重构。重构前后的代码天差地别,我终于可以不用像吃了苍蝇那般看着代码恶心了。这种重构后体验到的愉悦简直无与伦比,最关键是结果令人满意,重构后的代码变得更可读,更简单,也更容易增加新的功能。

思考的乐趣

| Comments

无论诗人的意图是什么,我既不愿意去管窥蠡测,只愿意在诗意的文字中体会自己感悟的诗意就好。这有时候看起来仿佛是文字游戏,尤其是当诗歌经过文字的转换后,这层意思本身隔了一层,可只要我对这些文字产生了某种难以言说的感觉,又引我思考,我就觉得极好。因为这会让我感到自己似乎穿梭在幽玄奥妙的星空之路中,又仿佛是在小径幽深的花园,只有小径,却永远没有尽头,但四周的静谧包裹着我,不绝不尽的奥妙吸引着我,我能体会到自己在思考,并沉迷于这种思考的乐趣之中。

恩,是的,吾思故吾在。

GAE的设计

| Comments

说明:以下获得的设计思考,是阅读《GAE编程指南》时的一些简单体会。主要针对的是一些设计细节(非架构)的考量。

简单与灵活。在对API进行设计决策时,需充分考虑API的简单性和全面性。此二者都是站在调用者的角度来思考,即需要提供一种默认的简单调用形式,也可以根据实际情况提供可选的方式。这是简单性和灵活性的共同体现。在Google App Engine中,对数据存储区的实体存储,可以在创建实体对象时指定主键;如果不指定主键,就会生成唯一的ID。

Post数据到服务端生成文件

| Comments

我们希望将客户端(ExtJS 4.0)浏览器中选择的记录post到服务端(ASP.NET MVC 3.0),然后在服务端将这些数据导出到CSV文件中,并传到客户端进行保存。最初,我们希望在客户端采用AJAX请求,将json数据post到服务端。例如:

postSelectedItems: function(gridId, callback) {
    var me = this;
    Ext.Ajax.request({
        method: 'POST',
        jsonData: Ext.encode(
            Ext.getCmp(gridId).getSelectedRecords().map(function (item){
                return item.data;
                })
        ),
        success: function(){
            callback();
        }
    );
}

在服务端,Controller中的Action可以处理post过来的json数据,然后以FileResult的方式返回。为了更好地处理CSV文件,我们定义了一个CsvActionResult,它继承自FileResult,并重写了它的WriteFile()方法。浏览器就可以获得CsvActionResult返回的Stream,从而就可以完成文件的下载和保存了。

IsDefined的问题

| Comments

在.NET 4.0(当然也包括4.0以前的版本)下,用反射判断某个方法是否运用了自定义Attribute时,可以通过调用MethodInfo的IsDefined()方法进行确认。当然,IsDefined()方法事实上定义在MethodInfo的父类MemberInfo中,但它仅仅被定义为抽象方法,真正的实现是在MethodInfo的子类DynamicMethod中。调用方式如下所示:

methodInfo.IsDefined(typeof(MyAttribute), false)

然而,在实际开发中,我发现该方法有一个问题。如果获得MethodInfo的方式是通过加载程序集,然后利用反射方式获得的MethodInfo对象,即使该方法运用了自定义Attribute,返回的结果仍然是false。例如,我们将需要判断的方法所在的类定义到一个单独的Project中,并编译为单独的dll文件,然后,利用Assembly的LoadFile()方式获得程序集:

  var assembly = Assembly.LoadFile(assemblyPath);
  var types = assembly.GetExportedTypes();
  types.ToList().ForEach(
      type =>
      {
          var flag =
              type.GetMethods().Where(methodInfo => !methodInfo.IsAbstract).Any(
                  methodInfo => methodInfo.IsDefined(typeof(MyAttribute), false));
          Console.WriteLine("Flag of IsDefined is: {0}", flag);
      }
  );

打印出来的值为false。

设计恰如其分的架构

| Comments

Thoughtworks的Sam Newman在Mythoughtworks的Software Development小组中给出了Evolutionary Architecture的一些资源。其中一个是Martin Fowler与Rebecca Parsons在QCon SF 2009的一次演讲,题目为Agilists and Architects: Allies not Adversaries Presentation。演讲主要谈到了在敏捷方法中的架构活动(在Martin Fowler的演讲中,播放了黑客帝国中的一个片段,很有意思)。另一个资源则是同样作为thoughtworker的Neal Ford在IBM developerWorks发表的Evelutionary Architecture and Emergent Design(演进架构与紧急设计)系列。这是很棒的一个讲解演进架构的系列文章,谈到了TDD、代码复用、连贯接口、DSL、重构、惯用法模式、指标等与演进架构和紧急设计有关的内容。

处理遗留系统

| Comments

处理遗留系统,几乎是每个程序员都不可能绕过的一件麻烦事儿。因为时间压力,技能不足以及功能复杂等诸多原因,常常使得遗留系统的代码变得糟糕混乱,可读性与维护性差,无法保证功能的可测试性,纠缠不清的代码让类、方法之间紧紧耦合在一起。如果遗留系统能够正常工作,那么我们还可以置之不理,即使代码接近腐烂的边缘,我们还可以得过且过。倘若我们需要维护遗留系统,或者需要为它添加新的功能,又或者需要将新的系统与遗留系统进行集成,就必须正视遗留系统带来的问题了。

处理遗留系统,首先需要分析和了解遗留系统,尤其这个遗留系统并非你开发时,更需如此。我们可以考虑双管齐下的办法。一是从业务逻辑方面去了解。相比新系统而言,遗留系统的唯一好处就是它往往是可以运行可以使用的。因此,最好的办法是直接运行遗留系统,通过实际操作了解系统的各个功能点、业务流程。这样的直观感受可以最快地帮助你了解该系统:它能够做什么?它能达成什么目标?它的范围是什么?它存在什么问题?其二则需要从系统架构出发,了解遗留系统的逻辑结构和物理分布。可以阅读架构文档和源代码,如果能够咨询遗留系统的设计者或开发人员,就更好。尽快地描绘出遗留系统的轮廓图,可以帮助你从技术的宏观角度剖析遗留系统的结构与组成。再结合你对该系统业务的理解,快速地掌握遗留系统。如果需要阅读源代码,最好能够从主程序入口开始,找到一些主要的模块,了解其大体的设计方式与编码习惯。由于之前对系统架构已有了解,阅读代码时,不应在一开始就去理解代码实现的细节,而应结合架构文档,比对代码实现是否与文档的描述一致,并充分利用自己的技术与经验,找到阅读代码的终南捷径。例如,如果我们知道该系统采用了MVC架构,就可以很容易地根据Url找到对应的Controller对象,并在该对象中寻找业务功能实现的脉络。又例如我们知道系统采用了SSH框架,而我们又非常熟悉SSH框架,就可以基本忽略系统基础设施的部分,直接了解系统的业务实现。如果是Swing系统,而且在界面中混杂了大量的业务逻辑和界面逻辑,则需要找到系统实现的特点,譬如系统的业务都是通过菜单项进行操作,就可以在界面中找到相关的菜单对象,然后根据这些对象的Action操作,一步一步跟踪。甚至可以利用调试的方法,设置断点,来摸清楚系统的运行机制与执行顺序。

解依赖与接缝

| Comments

接缝(seam)是Michael C. Feathers提出的概念。Feathers在Working Effectively with Legacy Code一书中对接缝的定义如下:接缝,顾名思义,就是指程序中的一些特殊的点,在这些点上你无需作任何修改就可以达到改动程序行为的目的。

“接缝”这个词语不太好理解,根据我的理解,大约还是依赖点的含义。通过事先找到依赖点,并采取一定方式解除依赖,就能够改善代码质量,尤其是针对遗留代码而言。准确而言,我们寻找接缝以及解依赖,就是为了代码能够具有好的可重用性与可扩展性,尤其是当我们能解除对其他外部服务的依赖时,可以带来程序的可测试性。

最近项目组的同事和我讨论了这样一个满足可测试性的问题。项目中需要对返回的响应信息PlatformResponse进行处理,这些信息会根据不同的StatusCode,得到不同的提示或出错信息。为了避免分支语句的判断,同事利用hash table将StatusCode与提示(出错)信息进行了映射,然后根据当前的StatusCode就可以返回对应的结果。返回结果后,还需要调用外部服务对消息进行处理,例如消息的输出。由于之前相关的类PlatformResponse并没有提供这一逻辑,相关服务要返回消息时,直接返回了PlatformResponse对象,然后再由客户端根据当前的StatusCode来判断,输出相关的提示信息,所以同事将这些逻辑写到了扩展方法中,例如定义PlatformResponseHelper静态类: