posts - 176, comments - 240, trackbacks - 0, articles - 7

    据说"Less is more"是1961年宝姿品牌提出的设计理念,它开创了简约优雅的时尚风格。Unix系统的设计可以说也是这一设计理念的最佳体现。使用Unix工具, 读Unix系统源码,我们时时都能体会到一种简约之美。而Microsoft发放出来的源代码一般都相当冗长,变量名也长,这一度让我很反感。为了简化C ++中的COM编程,我不得不专门写了一个封装框架。
    简约并不简单。例如,注释一般都是期望能够帮助理解的,应该是有益的。但注释太多就会干扰对程序的理解。这里关键就是能否提供有效的信息量。利用这一点,我们换一个角度看,多未必意味着复杂。例如,这样的一个函数调用
          StupidFunc(arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8)
    显然不如
          BetterFunc(userName:arg1, userMail:arg2, ...)
    非常紧凑的调用可能需要极端强度的注意力和思考力的投入,即要求我们付出的更多才能够理解。Perl这样的脚本是可以紧致到变态的一种语言。 Microsoft的自动化组件支持命名参数,则大大简化了OLE编程。与此类似,xml动态脚本中的函数调用也是比较容易理解的,甚至比java函数调 用更容易理解。
       <sendMail from="a@b.com" to="c@d.com" text="hello" />
    使用witrix平台中的SQL类写起SQL语句来虽然语句很长,但参数意义明显,并不容易出错。
    很多时候都存在着某个最适尺度,偏离了这个尺度,我们就会遇上各种各样的"More"。不要画蛇添足,也不要削足适履。

posted @ 2005-12-12 22:46 canonical 阅读(670) | 评论 (1)编辑 收藏

    在witrix平台中,validate.js提供了完整的客户端输入校验框架。其基本思想是为每个输入控件指定验证函数(validator属性),在提交Form的时候自动调用该验证函数即可。
<form action="test.jsp">
<input type="text" name="userName" validator="js.validate.checkNotEmpty(value,'用户名')" />
<input type="button" value="submit" onclick="js.validate.submitForm(this.form)" />
</form>
    witrix平台的一个基本设计原则是模块的独立性,不仅各个模块之间的耦合很少,我们还尽量避免使用配置文件。与struts等web框架不同, witrix的输入校验不依赖于外部配置文件,可以完全独立的使用。虽然jsplet框架也提供了服务器端校验的支持,但在实际使用过程中却很少使用。客 户端校验提供了更好的用户体验。而如果我们需要进一步确保业务逻辑的稳定性,例如避免用户伪造客户端url请求,数据校验需要在业务逻辑对象层进行而不应 是在解析用户请求的时候。针对每个form所写的配置文件有很多不方便的地方,例如witrix平台支持从数据库描述文件直接生成操作界面的快速开发,校 验规则在数据描述文件中指定,而同一个字段可能在多个界面中出现,如果针对Form写校验配置文件,就会出现冗余,而难以保证结构的同步。实际上,一个结 构在界面上表现一次,又在校验配置文件中表现一次,就必然会出现同步问题,解决的方法就是面向对象设计中的对象化,局部化,而不是一个个分离的处理层。

    很多客户端的校验框架使用的是一个万能的校验函数,通过参数不同来实现不同校验。例如
<input type="text" name="userName" validateType="required" />
这种方式的扩展性不好。正如面向对象设计中的通常做法,我们通过使用回调函数(虚拟函数)来实现可扩展的设计。

posted @ 2005-12-12 22:26 canonical 阅读(927) | 评论 (0)编辑 收藏

    守破离(Shu Ha Ri)是日本剑道(Kendo)的哲学。http://c2.com/cgi/wiki?ShuHaRi  (日本人很善于推销自己传统的思想,而中国的传统却似乎在盲目自卑和盲目自大两个极端之间徘徊)
    守是模仿(Imitate),遵循,是无我的过程。在日本的传统心性中,守的阶段需要完全开放心志,全盘接受导师的教导。此时应该学习唯一的真理,知道唯 一正确的方法,分清对与错。通过长期不辍的练习,将对规则的记忆固化在自己的身体中。初学者看似是自由的,但也是不明智的(unwisely),我们总是 倾向于采用错误的方式。
    beginners are very hard to fight... they don't do anything you expect them to do. They move freely, and randomly. Only by returning to very fundamental principles, can one uncover the faults (unwisdom) in their actions and defeat them. 简言之,就是没有结构。

    破是变(diverge),是自我意识逐渐增强,心智逐渐收缩的过程。Just 'winning' is not enough, you must win well. 这个阶段我们需要区分出好与坏而不仅仅是两分的对与错。
    离是返朴归真, 是忘我的过程。最终我们得到行为和思想上的自由(freedom)。离看似是随机的,但实际上是混沌的(chaos)。
    It's being good irrespective of whether you are right or wrong.看似打破常规,举手投足却都遵循着道(Tao),这是持续而自由的创造。
    传统上守破离这三个递进的阶段是在导师监护下完成的,导师决定你是否进入下一阶段。而在没有导师的情况下,我们需要增加一个步骤:检验(Test),即我 们需要缩短每个阶段的时间,对我们的修行成果进行检验,通过迭代循环来自我实现阶段跨越。(http://www.aikidofaq.com/essays/tin/shuhari.html 提到Test, 大概是这篇文章在agile社区流传的原因之一吧,呵呵)。
     我们无法跨越守的阶段。敏捷编程绝不意味着没有design pattern。没有良好的基本功,一切都是空谈。

   破,首先是破除权威。小的时候我们喜欢引用大师,喜欢谈论他们的轶事,现在也是引用,但却经常略带狡黠的歪曲他们的原意。大学以后应该少去阅读大部头的 书,读薄的书,并把书读薄。真正的思想并不是很多,当大量的细节都成为背景知识以后,我们需要进行思考的内容并不是很多。避免重复书中的原话,因为那是别 人的思想。广为涉猎,多做比较。换个角度看一看,或通过类比,尽力建立事物之间的关联,同中求异,异中求同。我以物理学的观点来看待软件,这是我采取的破 的方式。

posted @ 2005-12-12 22:24 canonical 阅读(1362) | 评论 (0)编辑 收藏

witrix平台中的tpl模板技术是一种通用的xml动态标签技术,不仅可以用于文本生成,而且可以用于任何需要动态标签的地方,例如工作流引擎 的配置和执行脚本。tpl模板引擎采用的不是jsp tag的标准机制,而是重新设计并实现的。在开发的后期,因为jstl标准出现,我们对标签的命名作了一定的修改,以尽量符合标准的调用接口。tpl模板 语言完全符合xml规范,其标签定义都是完全独立开发的。在开发tpl的时候,我们甚至没有看到任何类似于c:forEach和c:if的标签设计。但是 我们发现,tpl的动态处理功能与jstl虽然命名不同,但是基本是等价的,所以修改是非常直接的过程。

FreeMarker是一种流 行的文本模板语言,其语法类似于xml tag,但是命名规则不同。这实在是一种令人费解的设计。有意思的是,我们发现tpl的功能集也包含了FreeMarker的功能集。这实际上表明了一件 事情,xml动态标签存在一些必然的功能需求,无论是jsp tag, FreeMarker还是tpl, 所不同的只是表现形式而已。但这种表现形式的差异却又造成了实际功能上的巨大差异。

tpl与FreeMarker具体对比如下。

宏定义
<#macro greet person>
<font size="+2">Hello ${person}</font>
</#macro>]]>

<c:lib namespace="test">
<greet demandArgs="person">
<font size="+2">Hello ${person}</font>
</greet>
</c:lib>

tpl具有更加强大的变量域控制手段,可以通过importVars参数来指定是否使用调用环境中的变量作为缺省参数。另一方面,tpl具有更加灵活的参数校验规则,可以通过demandArgs, otherArgs等参数来指定对自定义标签参数的校验规则。
调用宏
<@greet person="Fred" />
<test:greet person="Fred" />

嵌套内容
<#macro border>
<table border="4" cellspacing="0" cellpadding="4"><tr><td>
<#nested>
<#nested>
</tr></td></table>
</#macro>
<c:lib namespace="test">
<border type="bodyTag">
<table border="4" cellspacing="0" cellpadding="4"><tr><td>
<cp:compile src="${tagBody}" />
</tr></td></table>
</border>
</c:lib>

tpl的<cp:compile>指令在执行时可以指定xslt参数,从而在编译tagBody之前应用xslt变换。
复杂嵌套
与FreeMark一样,嵌套内容可以是复杂内容

<@border>
<ul>
<@do_thrice>
<li><@greet person="Joe"/>
/@do_thrice
</ul>
/@border
<test:border>
<ul>
<test:do_thrice>
<li><test:greet person="Joe" /></li>
</test:do_thrice>
</ul>
</test:border>

导入库
<#import "/lib/my_test.ftl" as my>
<c:lib src="/lib/my_test.ftl" namespace="my" />

创建或替代变量
<#assign mail="jsmith@other.com" />
<c:set var="mail" value="jsmith@other.com" default="xx"/>

判断
<#if animals.python.price < animals.elephant.price>
Pythons are cheaper than elephants today.
</#if>
<c:if test="${lt(animals.python.price,animals.elephant.price)}">
Pythons are cheaper than elephants today.
</c:if>

tpl因为是xml语法,算术操作符<和>必须转义后才能使用,使用起来很不方便,因而最终决定tpl不支持操作符,通过lt(), gt()等函数来实现功能。
循环
<#list animals as being>
<tr><td>${being.name}<td>${being.price} Euros
</#list>
<c:forEach var="being" items="${animals}" >
<tr><td>${being.name}<td>${being.price} Euros
</c:forEach>

tpl提供<c:tile>等多种循环方式
include指令
<#include "/copyright_footer.html">
<c:include src="/copyright_footer.html" />

tpl强大的模板功能加上jsplet框架面向对象的能力,使得我们可以轻易的封装复杂的界面组件。而且这种封装能力还不需要Tapestry那种复杂的配置文件。tpl对portal应用的支持也是一个自然的发展过程。

posted @ 2005-12-12 22:18 canonical 阅读(1754) | 评论 (2)编辑 收藏

    http://www.ps.uni-sb.de/~duchier/python/continuations.html
    A continuation is a procedure that takes the value of the current expression and computes the rest of the computation.

    Continuation是一种非常古老的程序结构,关于它的理论分析可谓渊源流长,参见http://library.readscheme.org/page6.html
    continuation简单的说起来就是entire default future of a computation, 即对程序"接下来要做的事情"所进行的一种建模. 这一概念在理论上当然存在着巨大的价值, 至少它使得我们有一种手段把程序未来的运行过程明确的表达出来(给它取了个名字), 从而有可能对之作进一步的分析. continuation是对未来的完整描述, 这对于理论分析而言是有很多方便之处的, 正如统计学中最常见的分析工具是分布函数而不是密度函数一样. 实际上任何程序都可以通过所谓的CPS(Continuation Passing Style)变换而转换为使用continuation结构, 例如
    int foo(int x){
        return x+1;
    }
     ==>
    void foo(int x,Continuation c){
        c.continueWith(x+1);
    }   
    
    使用continuation的函数不"返回"值,而是把值作为一个参数传递给continuation从而"继续"处理值. 在传统的软件理论中, 程序本身在运行期是固定不变的, 我们只需要记录下执行点(excution point)的信息(例如指针位置和堆栈内容)即足以完整的描述程序未来的运行情况, 因此continuation有时也被看作是"带参数的goto", 是goto语句的一种函数形式.
    在函数式语言中, continuation的引入是非常自然的过程, 考察如下函数调用
         h(g(k(arg)))
    根据函数的结合律, 我们可以定义复合函数 m = h(g(.)), 它自然的成为 k(arg)的continuation. 在理论上我们有可能利用泛函分析的一些技术实现对于continuation(复合函数)的化简, 但实践已经证明这是极为艰难的, 主要是我们的程序不可避免的要涉及到程序与数据的纠缠.
    在引入continuation概念之后, 程序运行的表述是非常简单的:
        continuation.proceed();

    针对串行程序,我们可以建立更加精细的运行模型。
        while(continuation.hasNextStep())
            continuation.proceedOneStep();
   
    只要以某种方式构造出一种continuation closure(这意味着我们能够通过单一变量来表示程序未来的运行结构), 我们就有可能在某个层面上以如上方式实现对程序的一种简洁的描述.
    如果我们的眼界开阔一些, 不拘泥于构造语言级别通用的continuation结构(这需要以抽象的方式定义并保存任意程序的完整运行状态), 而是考察"对程序未来运行的整体结构进行建模"这一更宽广的命题, 我们很快就能发现大量对于continuation概念的应用. 例如实现AOP(Aspect Oriented Programming)的interceptor时所经常使用的MethodInvocation对象.
        class MyInterceptor implements MethodInterceptor{
            public Object invoke(MethodInvocation invocation){
                doSomeThingBeforeRawMethodCall();
                return invocation.proceed();
            }
        }
    
     在网络编程中, 一种常用的设计模式是Observer模式, 即注册监听器(listener)来处理接收到的网络指令. 在一些比较复杂的网络协议中, 网络指令之间往往存在一定的关联, 我们可以通过建立一个庞大的有限自动机来描述所有指令之间的关联规则, 也可以采用如下方式动态的实现对于监听器的调整.
        class ACommandListener{
            public void onEvent(Event event, FutureListeners futureListeners){
                handleEvent(event);
                futureListeners.clear();
                futureListeners.add("BCommand", new BCommandListener());
                futureListeners.add("CCommand", new CCommandListener());
            }
        }
    这种方式可以看作是对程序未来运行结构的一种动态调整. 实际上沿着这种方式深入下去, 我们甚至可以建立一种完整的动态工作流(workflow)机制.

     最近struts和webwork步cocoon和rife的后尘, 相继引入了对web continuation的支持, 在后台程序中实现了对于page flow的完整描述, 这无疑是一些非常有趣的工作. 例如现在我们可以编写
        void onRequest(){
            funcA();
            Input input = sendPageAndWait("collectionInfoFromUser.jsp");
            handleInput(input);
        }
     在调用sendPageAndWait的时候, web框架会保存当前函数调用的continuation, 向用户返回页面collectionInfoFromUser.jsp, 等待用户提交表单之后, web框架重新激活我们所保存的continuation, 继续执行我们的函数. 这种做法与系统调用和线程调度等机制是非常类似的.  
     有些人认为这种基于continuation的方式可以自然的解决在session中保存并清理变量的问题, 这显然是一种大材小用的做法, 而且事实上使用一种通用的continuation 实现很有可能在无意中保存了过多的临时变量, 从而对系统性能造成极大的损害. 有趣的是, 在Mach3.0中对系统线程所作的一项改进即称为continuation, 其动因恰在于避免保留线程堆栈,希望使用完全无状态的continuation函数.(参见Uresh Vahalia的经典著作"UNIX Internals" http://www.china-pub.com/computers/common/info.asp?id=12731).
    在传统的系统调用实现中
    syscall_l(argl)
    {
        ...
        thread_block();
        f2(arg);
        return;
    }
   
    f2(arg){
        ...
        return;
    }

    thread_block()函数会阻塞住当前系统调用过程, 并自动保存所有堆栈变量, 等待内核重新把控制权返回给调用函数. 在使用continuation函数的方式中, 我们需要显式的存取状态变量,
    syscall_1(arg1)
    {
        ...
        save arg1 and any other state information;
        thread_block(f2);  // thread_block(void * (contiuationFunc));
        /* not reached */
    }
   
    f2()
    {
        restore argl and any other state information;
        ...
        thread_syscall_return(status);
    }
    在这种方式中thread_block()并不返回到调用者。线程恢复执行时,内核把控制权传递给f2(). 函数thread_syscall_return()用来从系统调用返回到用户级。"整个过程对用户是透明的,用户所看到的只是从系统调用一个同步返回 ". 在Linux系统内核中所使用的bottom_half机制也是基于类似的原理.

posted @ 2005-12-12 00:58 canonical 阅读(2311) | 评论 (1)编辑 收藏

    现在很多设计中推崇接口和依赖注入(dependency injection),而不倾向于采用继承机制来构造程序结构。但很多时候作为一种简便而廉价的封装方法,继承仍然是不可或缺的. 例如与一些Engine打交道的时候,需要实现某些特定的接口. 在osworkflow中, 我们需要实现FunctionProvider接口,
     interface FunctionProvider{
        void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException;
     }
    在Quartz中需要实现Job接口
      interface Job{
          public void execute(JobExecutionContext context) throws JobExecutionException;
      }
    这些接口是一种技术性的要求, 它们表示了代码生存所依赖的技术环境. 为了屏蔽这种对于外部引擎的依赖, 我们可以简单的选择实现一个基类,
    abstract class AbstractFunction implements FunctionProvider,Runnable{
        Map transientVars;
        Map args;
        PropertySet ps;

        public final void execute(Map transientVars, Map args, PropertySet ps){
            this.transientVars = transientVars;
            this.args = args;
            this.ps = ps;
            run();
        }

        public Object getPersistVar(String name){
           return ps.getAsActualType(name);
        }

        public void setPersistVar(String name, Object value){
           ps.setAsActualType(name,value);
        }

        public void removePersistVar(String name){
           ps.remove(name);
        }
    }
    在派生类中我们只要使用getPersistVar等函数就可以回避对于osworkflow特有的PropertySet类的依赖,而只在概念上需要一 个对象的持久化机制.当我们把业务代码从osworkflow移植到其他工作流引擎的时候, 只需要改变一下基类即可.我们可以在基类中引入更加特殊的假 设,
    abstract AbstractBusinessFunction extends AbstractFunction{
        public BusinessObject getBusinessObject(){
            return transientVars.get("businessObject");
        }

        public void commonBusinessOp(){ ... }
    }

    AbstractBusinessFunction提供的可以是一个完整的业务对象环境, 我们在派生类中的所有代码都可以是与业务直接相关的,而与具体 的技术实现无关(例如业务变量是存放在transientVars中还是存放在args中)

    class BusinessFunction extends AbstractBusinessFunction{
        public void run(){
            BusinessObject bo = getBusinessObject();
            bo.methodA();
            commonBusinessOp();
        }
    }
    对于我们来说实际有意义的是在派生类中所书写的代码,基类仅仅提供一个环境而已.无论我们使用Ioc注入业务变量还是从transientVars中主动 获取业务变量,都是与我们的业务操作无关的. 实际上在理论上我们希望整个基类都可以是注入的(包括业务变量和业务操作),在泛型编程中这对应于所谓的 policy class.

posted @ 2005-12-06 22:33 canonical 阅读(502) | 评论 (0)编辑 收藏

    Six Learning Barriers in End-User Programming Systems http://www.cs.cmu.edu/~ajko/LearningBarriers.pdf

    学习在传统上被认为是人类特有的活动, 怎样降低学习的难度一直是理论上非常令人迷惑的问题, 同时它也是人类所面临的最大的实际困难之一. 在软件的世界中, 关于学习的研究也是一个非常重要的领域, 例如所谓的可用性(usability)和用户友好设计,其核心问题就是如何降低用户学习的难度. 我们目前的了解多半是一些经验的总结,例如Pane所总结的,
    . Use signaling to highlight important information.
    . Support incremental testing and feedback.
    . Choose an appropriate computational metaphor.
    . Help detect, diagnose, and recover from errors.
    . Provide guiding knowledge through online help.
    . Support recognition rather than recall.

    Andrew J. Ko等人作了更加严谨一些的研究, 试图对Learning Barriers作出一个基本的分类(classification),

1. Design barriers: I don't know what I want the computer to do, 需要mapping a desired program behavior to an
abstract description of a solution.
2. Selection barriers: I think I know what I want the computer to do, but I don't know what to use, 需要mapping a behavior to appropriate search terms for use in help or web search engines, and interpreting the relevance of the results.
3. Coordination barriers : I think I know what things to use, but I don't know how to make them work together, 需要mapping a desired behavior to a computational pattern that obeys “invisible rules."
4. Use barriers: I think I know what to use, but I don't know how to use it, 需要mapping a desired behavior to a
programming interface’s available parameters.
5. Understatnding barriers: I thougtht I knew how to use this, but it didn't do what I expected, 需要interpreting the external behavior of a program to determine what it accomplished at runtime
6. Information barriers: I think I know why it didn't do what I expected, but I don't know how to check,需要mapping a hypothesis about a
program to the environment’s available tools, and interpreting the tool’s feedback.

    其中design, coordination和use的障碍体现了所谓的gulf of execution(the difference between users' intensions and the available actions), understanding的障碍则体现了所谓的gulf of evaluation(the effort of deciding if expectation have been met), 而selection和information的障碍则同时体现了gulf of execution和gulf of evaluation.
    
    关于以上分类的一件有趣的事情是它们之间的相互关系, 经常出现的情况是我们通过一些不正确的假定(invalid assumption)暂时克服了当前的困难,但是很快又遇上了其他不可克服的困难. 例如design barrier经常导向seletion barrier, 而coordination barrier和use barrier经常导向understanding barrier.

    对于如何克服这些学习上的障碍, Andrew J.Ko等人通过Factory的隐喻,提出了一些具体的建议, 但是实用意义不是很大.

posted @ 2005-12-03 21:49 canonical 阅读(438) | 评论 (0)编辑 收藏

    在witrix平台中,异常处理没有采用java语法支持的checked exception, 也不提倡使用自定义的异常类, 而是定义了少数几个RuntimeException基类,一般是CommonException(RuntimeException的派生类)。
    在我自己的经验中,checked exception从未发挥过实质性的作用。checked exception在某种程度上破坏了封装性原则。我们一般不会在最细的粒度上处理异常,而是在某个统一的模块节点处进行。如果使用checked exception, 则从最底层的调用到具体异常处理层的整个调用堆栈上的函数都必须明确标记自己不处理该异常,这是完全不必要的负担。这种细粒度上的负担往往将程序员引导到 错误的方向上去,例如编写catch块直接捕获异常
  try{
     ...
  }catch(MyException e){
     e.printStackTrace();
  }
在witrix平台中通过包装类来将checked exception包装为RuntimeException, 而且除了在最终代码处理模块决不屏蔽异常。
 try{
    ...
 }catch(IOException e){
 throw Exceptions.source(e); // 此时会自动trace异常堆栈及异常消息
 }

(后来看到Bruce Eckel的文章Does Java need Checked Exception,发现大家在对待checked exception的态度上倒是心有戚戚焉。)

     一般使用自定义的异常类似乎是要将类名作为错误返回码使用,利用java编译器可以做所谓的强类型检查,这实在是一种概念上的浪费。毕竟创建并维护一个 java类还是有一定的代价的,特别是错误码经常变动而且数量不菲。实际上,java类库的设计中也是尽量重用已有的异常类,例如整个jdbc包只抛出 SQLException异常,xml包只抛出SAXException异常。

     使用异常,常见的方法是抛出一个字符串消息,例如 throw new MyException("the object manager does not contains the object :" + objectName);
这种做法的主要问题是,字符串异常消息无法进行进一步的处理,因而只能直接显示给最终用户,这一方面限制了错误显示的格式和方式,另一方面也不利于程序的多语言支持。
     witrix平台中抛出异常的标准方法为
 throw Exceptions.code(errorCode).param(paramValue).param(paramName,paramValue);
例如
    throw Exceptions.code("web.CAN_err_missing_object_in_manager").param(objectName).param(objectManager);

class Exceptions{
    public static CommonException code(String errorCode){
  return new CommonException(code);
 }
}

class CommonException extends RuntimeException{
 public CommonException param(Object paramValue){
  ...
  return this;
 }
}
      Exceptions规定只使用规范格式的错误码而不是任意格式的异常消息。这样在捕获异常之后,就可以根据错误码和当时的语言Locale设置来决定最终显示的消息格式。
      同时CommonException采用流式设计来支持任意数量的自定义参数。这一方面减少了自定义异常类的需求,另一方面也避免了将参数与错误码混合的倾向,即我们就不会倾向于
使用 throw Exceptions.code("the object manager does not contains the object :" + objectName);

posted @ 2005-12-02 23:00 canonical 阅读(1049) | 评论 (0)编辑 收藏

    tag在国内java社区并不算流行,这在很大程度上是因为jsp tag的设计失误造成的。但在整个开发业界内,tag已经成为一种广泛应用的技术。微软的dotNet服务器端极端依赖tag技术,而在浏览器端IE的 behaviour, htc也独立的发展起来。Longhorn的XAML, Firefox的XUL无一例外的依赖于可自定义的tag。java社区的JSF, SiteMesh, Tiles 等等,不可尽数。有些人在前台通过给html原有元素增加自定义属性,然后通过javascript去解释的做法,也是一种element enhance概念的变种。至于FreeMarker这种模板语言,明明类似于tag技术,偏偏不采用xml语法,简直是自找麻烦。
    这里最关键的地方就是自定义tag可以实现抽象层次的提升,是一种类似于函数封装的机制,从而实现概念的分离和明确化。基于tag可以实现页面元素的组件 化,加上xml语法的可理解性,表达能力以及无与伦比的集成能力,使得tag技术可以超越VB等组件开发环境(想想集成别人的组件代码难还是集成别人的 xml描述文件难)。自定义tag提供的抽象能力不仅仅是面向对象的,而且是类似AOP的,这些都极大的辅助了我们的思考和设计。

    cocoon使用管道技术也构造了某种tag机制,但是它的效率很成问题。从数学上说多个处理函数 g, h, k可以通过函数组合(composition)构成新的函数f

    f(data) = g * h * k(data) 

这是所谓函数式语言强大能力的源泉。cocoon处理的时候从k(data)开始,处理完毕之后调用h, 即函数是从右向左结合的。如果我们保证处理函数满足左结合律,则g*h*k就可以预编译为f, 从而解决性能问题,这正是witrix平台中tpl技术所采用的方案。

posted @ 2005-12-02 22:59 canonical 阅读(841) | 评论 (2)编辑 收藏

    AOP作为一种新的分解与合成技术,除了性能问题之外,仍有一些概念层面上的细节问题需要解决。最近Stoerzer的一篇论文AOP Considered harmful因为与Dijkstra的经典论文Go To Statement Considered Harmful  进行对比而引起了广泛的讨论。

    Dijkstra认为程序运行时的指令序列是我们最终想要的东西,而这一序列是运行时根据源代码的描述在时间轴上展开的(串行结构)。因为人们更容易理解 静态关系而不是随时间演化的过程,所以我们应该尽量缩小静态程序(spread out in text space)和动态过程(spread out in time)的逻辑差距,因而我们需要使它们能够在一个固定的坐标系统(coordinate system)下形成对应。对于包括条件和分支语句的串行程序,我们只需要源代码的行号(line number)即可确定一个单一位置。在循环的情况下,我们只需要增加一个额外的循环计数器(loop counter)即可保证可理解性。而对于子例程(procedure)调用,我们可以认为整个调用堆栈(call stack)也构成坐标系统的一部分。goto导致一种非结构化的控制流,因而破坏了这种理解上所必需的独立坐标系统。例如,如果一个循环中充满了自由的 goto调转(可能跳出循环又跳回),我们就很难确定循环变量的值到底是怎么增加的,除非我们在脑海中把源代码运行一遍!
  仿照Dijkstra的分析,Stoerzer指出AOP Advice虽然类似于procedure,但存在如下重要区别: 1. 与方法调用不同,advice执行位置在基础源代码中没有标识(obliviousness of application), advice有可能在任何位置插入并改变现场变量的值 2. pointcut可能依赖运行时变量值而无法静态计算得出(non-certainty of application)。
    第一点是由AOP技术的开放性造成的,但正如面向对象中的原则: open to extension but close to modification,我们需要遵循一些原则来避免破坏原有的结构。当然,AOP应用的场景可能确实只存在着某种弱可分性,advice需要深度依赖base code中的一些特性,可能应用类似模板(template)的技术会在一定程度上缓解encapsulation breaking. AOP的开放性造成的更严重的问题是pointcut在演化过程中的不确定性。只有在拥有全局知识的情况下才能确认pointcut的结果正是我们所期望的。特别是重构造成方法名改变之后,pointcut无法监测这种变化。当base code修改之后,我们可能没有意识到缺省影响到很多的aspect, 即完全理解base code变得非常困难。这种困境有一部分的原因是方法名同时作为调用标记和pointcut标记,责任过重造成的。参考一下css的选择符
   selector { property: value }

              \_declaration_/
   \___________ rule _________/
css可以通过选择符应用,也可以通过指定标签的class属性来应用,选择符所依靠的选择特征也可以不仅仅是标签名而包含属性名等。Java最近增加了与dotNet类似的meta attribute的支持,pointcut所依赖的元数据与方法名分离之后应该可以提高pointcut的稳定性。
  关于第二点,实际上OOP中的Dynamic Dispatch在某种程度上也是需要动态计算决定的,但因为接口具有明确的概念含义(an overriding method should only expect less and provide more, by maintaining all invariants),我们可以在更高的层次上理解代码,而不需要具体到特定的实现。AOP目前可能是缺乏一些指导性的设计原则。
  相对OOP而言,AOP影响到大范围内的对象及系统的一些整体特性,因而更加需要工具的支持。

posted @ 2005-12-02 22:50 canonical 阅读(670) | 评论 (1)编辑 收藏

仅列出标题
共18页: First 上一页 9 10 11 12 13 14 15 16 17 下一页 Last