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

2005年11月19日

谨以此文庆祝清华建校108周年,及5字班本科毕业20周年

作者: Canonical

众所周知,计算机科学得以存在的基石是两个基本理论:图灵于1936年提出的图灵机理论和丘奇同年早期发表的Lambda演算理论。这两个理论奠定了所谓通用计算(Universal Computation)的概念基础,描绘了具有相同计算能力(图灵完备),但形式上却南辕北辙、大相径庭的两条技术路线。如果把这两种理论看作是上帝所展示的世界本源面貌的两个极端,那么是否存在一条更加中庸灵活的到达通用计算彼岸的中间路径?

自1936年以来,软件作为计算机科学的核心应用,一直处在不间断的概念变革过程中,各类程序语言/系统架构/设计模式/方法论层出不穷,但是究其软件构造的基本原理,仍然没有脱出两个基本理论最初所设定的范围。如果定义一种新的软件构造理论,它所引入的新概念本质上能有什么特异之处?能够解决什么棘手的问题?

本文中笔者提出在图灵机和lambda演算的基础上可以很自然的引入一个新的核心概念--可逆性,从而形成一个新的软件构造理论--可逆计算(Reversible Computation)。可逆计算提供了区别于目前业内主流方法的更高层次的抽象手段,可以大幅降低软件内在的复杂性,为粗粒度软件复用扫除了理论障碍。

可逆计算的思想来源不是计算机科学本身,而是理论物理学,它将软件看作是处于不断演化过程中的抽象实体, 在不同的复杂性层次上由不同的运算规则所描述,它所关注的是演化过程中产生的微小差量如何在系统内有序的传播并发生相互作用。

本文第一节将介绍可逆计算理论的基本原理与核心公式,第二节分析可逆计算理论与组件和模型驱动等传统软件构造理论的区别和联系,并介绍可逆计算理论在软件复用领域的应用,第三节从可逆计算角度解构Docker、React等创新技术实践。

一. 可逆计算的基本原理

可逆计算可以看作是在真实的信息有限的世界中,应用图灵计算和lambda演算对世界建模的一种必然结果,我们可以通过以下简单的物理图像来理解这一点。

首先,图灵机是一种结构固化的机器,它具有可枚举的有限的状态集合,只能执行有限的几条操作指令,但是可以从无限长的纸带上读取和保存数据。例如我们日常使用的电脑,它在出厂的时候硬件功能就已经确定了,但是通过安装不同的软件,传入不同的数据文件,最终它可以自动产生任意复杂的目标输出。图灵机的计算过程在形式上可以写成

目标输出 = 固定机器(无限复杂的输入)\\

与图灵机相反的是,lambda演算的核心概念是函数,一个函数就是一台小型的计算机器,函数的复合仍然是函数,也就是说可以通过机器和机器的递归组合来产生更加复杂的机器。lambda演算的计算能力与图灵机等价,这意味着如果允许我们不断创建更加复杂的机器,即使输入一个常数0,我们也可以得到任意复杂的目标输出。lambda演算的计算过程在形式上可以写成

目标输出 = 无限复杂的机器(固定输入)\\

可以看出,以上两种计算过程都可以被表达为Y=F(X) 这样一种抽象的形式。如果我们把Y=F(X)理解为一种建模过程,即我们试图理解输入的结构以及输入和输出之间的映射关系,采用最经济的方式重建输出,则我们会发现图灵机和lambda演算都假定了现实世界中无法满足的条件。在真实的物理世界中,人类的认知总是有限的,所有的量都需要区分已知的部分和未知的部分,因此我们需要进行如下分解:

\begin{align} Y &= F(X) \\   &= (F_0+F_1)(X_0+X_1)  \tag{拆分已知和未知} \\   &= F_0(X_0)+ △      \tag{类似Taylor级数展开} \end{align}

重新整理一下符号,我们就得到了一个适应范围更加广泛的计算模式

Y=F(X)\oplus△\\

除了函数运算F(X)之外,这里出现了一个新的结构运算符⊕,它表示两个元素之间的合成运算,并不是普通数值意义上的加法,同时引出了一个新的概念:差量△。△的特异之处在于,它必然包含某种负元素,F(X)与△合并在一起之后的结果并不一定是“增加”了输出,而完全可能是“减少”。

在物理学中,差量△存在的必然性以及△包含逆元这一事实完全是不言而喻的,因为物理学的建模必须要考虑到两个基本事实:

  1. 世界是“测不准”的,噪声永远存在
  2. 模型的复杂度要和问题内在的复杂度相匹配,它捕获的是问题内核中稳定不变的趋势及规律。

例如,对以下的数据 ​ ​

我们所建立的模型只能是类似图(a)中的简单曲线,图(b)中的模型试图精确拟合每一个数据点在数学上称之为过拟合,它难以描述新的数据,而图(c)中限制差量只能为正值则会极大的限制模型的描述精度。

以上是对Y=F(X)⊕△这一抽象计算模式的一个启发式说明,下面我们将介绍在软件构造领域落实这一计算模式的一种具体技术实现方案,笔者将其命名为可逆计算。 所谓可逆计算,是指系统化的应用如下公式指导软件构造的一种技术路线

App = Biz \quad aop\_extends \quad Generator\langle DSL\rangle \\

App : 所需要构建的目标应用程序
DSL: 领域特定语言(Domain Specific Language),针对特定业务领域定制的业务逻辑描述语言,也是所谓领域模型的文本表示形式
Generator : 根据领域模型提供的信息,反复应用生成规则可以推导产生大量的衍生代码。实现方式包括独立的代码生成工具,以及基于元编程(Metaprogramming)的编译期模板展开
Biz : 根据已知模型推导生成的逻辑与目标应用程序逻辑之间的差异被识别出来,并收集在一起,构成独立的差量描述
aop_extends: 差量描述与模型生成部分通过类似面向切面编程(Aspect Oriented Programming)的技术结合在一起,这其中涉及到对模型生成部分的增加、修改、替换、删除等一系列操作

DSL是对关键性领域信息的一种高密度的表达,它直接指导Generator生成代码,这一点类似于图灵计算通过输入数据驱动机器执行内置指令。而如果把Generator看作是文本符号的替换生成,则它的执行和复合规则完全就是lambda演算的翻版。差量合并在某种意义上是一种很新奇的操作,因为它要求我们具有一种细致入微、无所不达的变化收集能力,能够把散布系统各处的同阶小量分离出来并合并在一起,这样差量才具有独立存在的意义和价值。同时,系统中必须明确建立逆元和逆运算的概念,在这样的概念体系下差量作为“存在”与“不存在”的混合体才可能得到表达。

现有的软件基础架构如果不经过彻底的改造,是无法有效的实施可逆计算的。正如图灵机模型孕育了C语言,Lambda演算促生了Lisp语言一样,为了有效支持可逆计算,笔者提出了一种新的程序语言X语言,它内置了差量定义、生成、合并、拆分等关键特性,可以快速建立领域模型,并在领域模型的基础上实现可逆计算。

为了实施可逆计算,我们必须要建立差量的概念。变化产生差量,差量有正有负,而且应该满足下面三条要求:

  1. 差量独立存在
  2. 差量相互作用
  3. 差量具有结构

在第三节中笔者将会以Docker为实例说明这三条要求的重要性。

可逆计算的核心是“可逆”,这一概念与物理学中熵的概念息息相关,它的重要性其实远远超出了程序构造本身,在可逆计算的方法论来源一文中,笔者会对它有更详细的阐述。

正如复数的出现扩充了代数方程的求解空间,可逆计算为现有的软件构造技术体系补充了“可逆的差量合并”这一关键性技术手段,从而极大扩充了软件复用的可行范围,使得系统级的粗粒度软件复用成为可能。同时在新的视角下,很多原先难以解决的模型抽象问题可以找到更加简单的解决方案,从而大幅降低了软件构造的内在复杂性。在第二节中笔者将会对此进行详细阐述。

软件开发虽然号称是知识密集性的工作,但到目前为止,众多一线程序员的日常中仍然包含着大量代码拷贝/粘贴/修改的机械化手工操作内容,而在可逆计算理论中,代码结构的修改被抽象为可自动执行的差量合并规则,因此通过可逆计算,我们可以为软件自身的自动化生产创造基础条件。笔者在可逆计算理论的基础上,提出了一个新的软件工业化生产模式NOP(Nop is nOt Programming),以非编程的方式批量生产软件。NOP不是编程,但也不是不编程,它强调的是将业务人员可以直观理解的逻辑与纯技术实现层面的逻辑相分离,分别使用合适的语言和工具去设计,然后再把它们无缝的粘接在一起。笔者将在另一篇文章中对NOP进行详细介绍。

可逆计算与可逆计算机有着同样的物理学思想来源,虽然具体的技术内涵并不一致,但它们目标却是统一的。正如云计算试图实现计算的云化一样,可逆计算和可逆计算机试图实现的都是计算的可逆化。

二. 可逆计算对传统理论的继承和发展

(一)组件(Component)

软件的诞生源于数学家研究希尔伯特第十问题时的副产品,早期软件的主要用途也是数学物理计算,那时软件中的概念无疑是抽象的、数学化的。随着软件的普及,越来越多应用软件的研发催生了面向对象和组件化的方法论,它试图弱化抽象思维,转而贴近人类的常识,从人们的日常经验中汲取知识,把业务领域中人们可以直观感知的概念映射为软件中的对象,仿照物质世界的生产制造过程从无到有、从小到大,逐步拼接组装实现最终软件产品的构造。

像框架、组件、设计模式、架构视图等软件开发领域中耳熟能详的概念,均直接来自于建筑业的生产经验。组件理论继承了面向对象思想的精华,借助可复用的预制构件这一概念,创造了庞大的第三方组件市场,获得了空前的技术和商业成功,即使到今天仍然是最主流的软件开发指导思想。但是,组件理论内部存在着一个本质性的缺陷,阻碍了它把自己的成功继续推进到一个新的高度。

我们知道,所谓复用就是对已有的制成品的重复使用。为了实现组件复用,我们需要找到两个软件中的公共部分,把它分离出来并按照组件规范整理成标准形式。但是,A和B的公共部分的粒度是比A和B都要小的,大量软件的公共部分是比它们中任何一个的粒度都要小得多的。这一限制直接导致越大粒度的软件功能模块越难以被直接复用,组件复用存在理论上的极限。可以通过组件组装复用60%-70%的工作量,但是很少有人能超过80%,更不用说实现复用度90%以上的系统级整体复用了。

为了克服组件理论的局限,我们需要重新认识软件的抽象本质。软件是在抽象的逻辑世界中存在的一种信息产品,信息并不是物质。抽象世界的构造和生产规律与物质世界是有着本质不同的。物质产品的生产总是有成本的,而复制软件的边际成本却可以是0。将桌子从房间中移走在物质世界中必须要经过门或窗,但在抽象的信息空间中却只需要将桌子的坐标从x改为-x而已。抽象元素之间的运算关系并不受众多物理约束的限制,因此信息空间中最有效的生产方式不是组装,而是掌握和制定运算规则。

如果从数学的角度重新去解读面向对象和组件技术,我们会发现可逆计算可以被看作是组件理论的一个自然扩展。

  • 面向对象 : 不等式 A > B
  • 组件 : 加法 A = B + C
  • 可逆计算 : 差量 Y = X + △Y

面向对象中的一个核心概念是继承:派生类从基类继承,自动具有基类的一切功能。例如老虎是动物的一种派生类,在数学上,我们可以说老虎(A)这个概念所包含的内容比动物(B)这个概念更多,老虎>动物(即A>B)。据此我们可以知道,动物这个概念所满足的命题,老虎自然满足, 例如动物会奔跑,老虎必然也会奔跑( P(B) -> P(A) )。程序中所有用到动物这一概念的地方都可以被替换为老虎(Liscov代换原则)。这样通过继承就将自动推理关系引入到软件领域中来,在数学上这对应于不等式,也就是一种偏序关系。

面向对象的理论困境在于不等式的表达能力有限。对于不等式A > B,我们知道A比B多,但是具体多什么,我们并没有办法明确的表达出来。而对于 A > B, D > E这样的情况,即使多出来的部分一模一样,我们也无法实现这部分内容的重用。组件技术明确指出"组合优于继承",这相当于引入了加法

A=B+C\\ D=E+C

这样就可以抽象出组件C进行重用。

沿着上述方向推演下去,我们很容易确定下一步的发展是引入“减法”,这样才可以把 A = B + C看作是一个真正的方程,通过方程左右移项求解出

B=A-C=A+(-C)\\

通过减法引入的“负组件”是一个全新的概念,它为软件复用打开了一扇新的大门。

假设我们已经构建好了系统 X = D + E + F, 现在需要构建 Y = D + E + G。如果遵循组件的解决方案,则需要将X拆解为多个组件,然后更换组件F为G后重新组装。而如果遵循可逆计算的技术路线,通过引入逆元 -F, 我们立刻得到

Y = X-F+G=X+(-F+G)=X+△Y\\

在不拆解X的情况下,通过直接追加一个差量△Y,即可将系统X转化为系统Y。

组件的复用条件是“相同方可复用”,但在存在逆元的情况下,具有最大颗粒度的完整系统X在完全不改的情况下直接就可以被复用,软件复用的范围被拓展为“相关即可复用”,软件复用的粒度不再有任何限制。组件之间的关系也发生了深刻的变化,不再是单调的构成关系,而成为更加丰富多变的转化关系

Y = X + △Y 这一物理图像对于复杂软件产品的研发具有非常现实的意义。X可以是我们所研发的软件产品的基础版本或者说主版本,在不同的客户处部署实施时,大量的定制化需求被隔离到独立的差量△Y中,这些定制的差量描述单独存放,通过编译技术与主版本代码再合并到一起。主版本的架构设计和代码实现只需要考虑业务领域内稳定的核心需求,不会受到特定客户处偶然性需求的冲击,从而有效的避免架构腐化。主版本研发和多个项目的实施可以并行进行,不同的实施版本对应不同的△Y,互不影响,同时主版本的代码与所有定制代码相互独立,能够随时进行整体升级。

(二)模型驱动架构(Model Driven Architecture)

模型驱动架构(MDA)是由对象管理组织(Object Management Group,OMG)在2001年提出的软件架构设计和开发方法,它被看作是软件开发模式从以代码为中心向以模型为中心转变的里程碑,目前大部分所谓软件开发平台的理论基础都与MDA有关。

MDA试图提升软件开发的抽象层次,直接使用建模语言(例如Executable UML)作为编程语言,然后通过使用类似编译器的技术将高层模型翻译为底层的可执行代码。在MDA中,明确区分应用架构和系统架构,并分别用平台无关模型PIM(Platform Independent Model)和平台相关模型PSM(Platform Specific Model)来描述它们。PIM反映了应用系统的功能模型,它独立于具体的实现技术和运行框架,而PSM则关注于使用特定技术(例如J2EE或者dotNet)实现PIM所描述的功能,为PIM提供运行环境。

使用MDA的理想场景是,开发人员使用可视化工具设计PIM,然后选择目标运行平台,由工具自动执行针对特定平台和实现语言的映射规则,将PIM转换为对应的PSM,并最终生成可执行的应用程序代码。基于MDA的程序构造可以表述为如下公式

App=Transformer(PIM)\\

MDA的愿景是像C语言取代汇编那样最终彻底消灭传统编程语言。但经历了这么多年发展之后,它仍未能够在广泛的应用领域中展现出相对于传统编程压倒性的竞争优势。

事实上,目前基于MDA的开发工具在面对多变的业务领域时,总是难掩其内在的不适应性。根据本文第一节的分析,我们知道建模必须要考虑差量。而在MDA的构造公式中,左侧的App代表了各种未知需求,而右侧的Transformer和PIM的设计器实际上都主要由开发工具厂商提供,未知=已知这样一个方程是无法持久保持平衡的。

目前,工具厂商的主要做法是提供大而全的模型集合,试图事先预测用户所有可能的业务场景。但是,我们知道“天下没有免费的午餐”,模型的价值在于体现了业务领域中的本质性约束,没有任何一个模型是所有场景下都最优的。预测需求会导致出现一种悖论: 模型内置假定过少,则无法根据用户输入的少量信息自动生成大量有用的工作,也无法防止用户出现误操作,模型的价值不明显,而如果反之,模型假定很多,则它就会固化到某个特定业务场景,难以适应新的情况。

打开一个MDA工具的设计器,我们最经常的感受是大部分选项都不需要,也不知道是干什么用的,需要的选项却到处找也找不到。

可逆计算对MDA的扩展体现为两点:

  1. 可逆计算中Generator和DSL都是鼓励用户扩充和调整的,这一点类似于面向语言编程(Language-oriented programming)
  2. 存在一个额外的差量定制机会,可以对整体生成结果进行精确的局部修正。

在笔者提出的NOP生产模式中,必须要包含一个新的关键组件:设计器的设计器。普通的程序员可以利用设计器的设计器快速设计开发自己的领域特定语言(DSL)及其可视化设计器,同时可以通过设计器的设计器对系统中的任意设计器进行定制调整,自由的增加或者删除元素。

(三)面向切面编程(Aspect Oriented Programming)

面向切面(AOP)是与面向对象(OOP)互补的一种编程范式,它可以实现对那些横跨多个对象的所谓横切关注点(cross-cutting concern)的封装。例如,需求规格中可能规定所有的业务操作都要记录日志,所有的数据库修改操作都要开启事务。如果按照面向对象的传统实现方式,需求中的一句话将会导致众多对象类中陡然膨胀出现大量的冗余代码,而通过AOP, 这些公共的“修饰性”的操作就可以被剥离到独立的切面描述中。这就是所谓纵向分解和横向分解的正交性。


AOP本质上是两个能力的组合:

  1. 在程序结构空间中定位到目标切点(Pointcut)
  2. 对局部程序结构进行修改,将扩展逻辑(Advice)编织(Weave)到指定位置。

定位依赖于存在良好定义的整体结构坐标系(没有坐标怎么定位?),而修改依赖于存在良好定义的局部程序语义结构。目前主流的AOP技术的局限性在于,它们都是在面向对象的语境下表达的,而领域结构与对象实现结构并不总是一致的,或者说用对象体系的坐标去表达领域语义是不充分的。例如,申请人和审批人在领域模型中是需要明确区分的不同的概念,但是在对象层面却可能都对应于同样的Person类,使用AOP的很多时候并不能直接将领域描述转换为切点定义和Advice实现。这种限制反映到应用层面,结果就是除了日志、事务、延迟加载、缓存等少数与特定业务领域无关的“经典”应用之外,我们找不到AOP的用武之地。

可逆计算需要类似AOP的定位和结构修正能力,但是它是在领域模型空间中定义这些能力的,因而大大扩充了AOP的应用范围。特别是,可逆计算中领域模型自我演化产生的结构差量△能够以类似AOP切面的形式得到表达。

我们知道,组件可以标识出程序中反复出现的“相同性”,而可逆计算可以捕获程序结构的“相似性”。相同很罕见,需要敏锐的甄别,但是在任何系统中,有一种相似性都是唾手可得的,即动力学演化过程中系统与自身历史快照之间的相似性。这种相似性在此前的技术体系中并没有专门的技术表达形式。

通过纵向和横向分解,我们所建立的概念之网存在于一个设计平面当中,当设计平面沿着时间轴演化时,很自然的会产生一个“三维”映射关系:后一时刻的设计平面可以看作是从前一时刻的平面增加一个差量映射(定制)而得到,而差量是定义在平面的每一个点上的。这一图像类似于范畴论(Category Theory)中的函子(Functor)概念,可逆计算中的差量合并扮演了函子映射的角色。因此,可逆计算相当于扩展了原有的设计空间,为演化这一概念找到了具体的一种技术表现形式。

(四)软件产品线(Software Product Line)

软件产品线理论源于一个洞察,即在一个业务领域中,很少有软件系统是完全独特的,大量的软件产品之间存在着形式和功能的相似性,可以归结为一个产品家族,把一个产品家族中的所有产品(已存在的和尚未存在的)作为一个整体来研究、开发、演进,通过科学的方法提取它们的共性,结合有效的可变性管理,就有可能实现规模化、系统化的软件复用,进而实现软件产品的工业化生产。

软件产品线工程采用两阶段生命周期模型,区分领域工程应用工程。所谓领域工程,是指分析业务领域内软件产品的共性,建立领域模型及公共的软件产品线架构,形成可复用的核心资产的过程,即面向复用的开发(development for reuse)。而应用工程,其实质是使用复用来开发( development with reuse),也就是利用已经存在的体系架构、需求、测试、文档等核心资产来制造具体应用产品的生产活动。

卡耐基梅隆大学软件工程研究所(CMU-SEI)的研究人员在2008年的报告中宣称软件产品线可以带来如下好处:

  1. 提升10倍以上生产率
  2. 提升10倍以上产品质量
  3. 缩减60%以上成本
  4. 缩减87%以上人力需求
  5. 缩减98%以上产品上市时间
  6. 进入新市场的时间以月计,而不是年

软件产品线描绘的理想非常美好:复用度90%以上的产品级复用、随需而变的敏捷定制、无视技术变迁影响的领域架构、优异可观的经济效益等等。它所存在的唯一问题就是如何才能做到?尽管软件产品线工程试图通过综合利用所有管理的和技术的手段,在组织级别策略性的复用一切技术资产(包括文档、代码、规范、工具等等),但在目前主流的技术体制下,发展成功的软件产品线仍然面临着重重困难。

可逆计算的理念与软件产品线理论高度契合,它的技术方案为软件产品线的核心技术困难---可变性管理带来了新的解决思路。在软件产品线工程中,传统的可变性管理主要是适配、替换和扩展这三种方式:


这三种方式都可以看作是向核心架构补充功能。但是可复用性的障碍不仅仅是来自于无法追加新的功能,很多时候也在于无法屏蔽原先已经存在的功能。传统的适配技术等要求接口一致匹配,是一种刚性的对接要求,一旦失配必将导致不断向上传导应力,最终只能通过整体更换组件来解决问题。可逆计算通过差量合并为可变性管理补充了“消除”这一关键性机制,可以按需在领域模型空间中构建出柔性适配接口,从而有效的控制变化点影响范围。

可逆计算中的差量虽然也可以被解释为对基础模型的一种扩展,但是它与插件扩展技术之间还是存在着明显的区别。在平台-插件这样的结构中,平台是最核心的主体,插件依附于平台而存在,更像是一种补丁机制,在概念层面上是相对次要的部分。而在可逆计算中,通过一些形式上的变换,我们可以得到一个对称性更高的公式:

 A = B \oplus G(D) \equiv (B,D) \\

如果把G看作是一种相对不变的背景知识,则形式上我们可以把它隐藏起来,定义一个更加高级的“括号”运算符,它类似于数学中的“内积”。在这种形式下,B和D是对偶的,B是对D的补充,而D也是对B的补充。同时,我们注意到G(D)是模型驱动架构的体现,模型驱动之所以有价值就在于模型D中发生的微小变化,可以被G放大为系统各处大量衍生的变化,因此G(D)是一种非线性变换,而B是系统中去除D所对应的非线性因素之后剩余的部分。当所有复杂的非线性影响因素都被剥离出去之后,最后剩下的部分B就有可能是简单的,甚至能够形成一种新的可独立理解的领域模型结构(可以类比声波与空气的关系,声波是空气的扰动,但是不用研究空气本体,我们就可以直接用正弦波模型来描述声波)。

A = (B,D)的形式可以直接推广到存在更多领域模型的情况

 A = (B,D,E,F, ...) \\

因为B、D、E等概念都是某种DSL所描述的领域模型,因此它们可以被解释为A投影到特定领域模型子空间所产生的分量,也就是说,应用A可以被表示为一个“特征向量”(Feature Vector), 例如

应用A = (Biz,权限,流程,报表,...) \\

与软件产品线中常用的面向特征编程(Feature Oriented Programming)相比,可逆计算的特征分解方案强调领域特定描述,特征边界更加明确,特征合成时产生的概念冲突更容易处理。

特征向量本身构成更高维度的领域模型,它可以被进一步分解下去,从而形成一个模型级列,例如定义

D' \equiv [B,D]\ G'(D')\equiv B\oplus G(D)\\

, 并且假设D'可以继续分解

D'=V\oplus M(U)=M'(U') \\

,则可以得到

\begin{align} A &= B ⊕ G(D) \\   &= G'(D') \\  &= G'(M'(U'))\\  &= G'(M'([V,U])) \\  \end{align} \\

最终我们可以通过领域特征向量U'来描述D’,然后再通过领域特征向量D‘来描述原有的模型A。

可逆计算的这一构造策略类似于深度神经网络,它不再局限于具有极多可调参数的单一模型,而是建立抽象层级不同、复杂性层级不同的一系列模型,通过逐步求精的方式构造出最终的应用。

在可逆计算的视角下,应用工程的工作内容变成了使用特征向量来描述软件需求,而领域工程则负责根据特征向量描述来生成最终的软件。

三. 初露端倪的差量革命

(一)Docker

Docker是2013年由创业公司dotCloud开源的应用容器引擎,它可以将任何应用及其依赖的环境打包成一个轻量级、可移植、自包含的容器(Container),并据此以容器为标准化单元创造了一种新型的软件开发、部署和交付形式。

Docker一出世就秒杀了Google的亲儿子lmctfy (Let Me Contain That For You)容器技术,同时也把Google的另一个亲儿子Go语言迅速捧成了网红,之后Docker的发展 更是一发而不可收拾。2014年开始一场Docker风暴席卷全球,以前所未有的力度推动了操作系统内核的变革,在众多巨头的跟风造势下瞬间引爆容器云市场,真正从根本上改变了企业应用从开发、构建到部署、运行整个生命周期的技术形态。

Docker技术的成功源于它对软件运行时复杂性的本质性降低,而它的技术方案可以看作是可逆计算理论的一种特例。Docker的核心技术模式可以用如下公式进行概括

App = Docker\langle Dockerfile\rangle \quad unionfs \quad BaseImage\\

Dockerfile是构建容器镜像的一种领域特定语言,例如

FROM ubuntu:16.04 
RUN useradd --user-group --create-home --shell /bin/bash work
RUN apt-get update -y && apt-get install -y python3-dev
COPY . /app RUN make /app
ENV PYTHONPATH /FrameworkBenchmarks
CMD python /app/app.py
EXPOSE 8088

通过Dockerfile可以快速准确的描述容器所依赖的基础镜像,具体的构建步骤,运行时环境变量和系统配置等信息。

Docker应用程序扮演了可逆计算中Generator的角色,负责解释Dockerfile,执行对应的指令来生成容器镜像。

创造性的使用联合文件系统(Union FS),是Docker的一个特别的创新之处。这种文件系统采用分层的构造方式,每一层构建完毕后就不会再发生改变,在后一层上进行的任何修改都只会记录在自己这一层。例如,修改前一层的文件时会通过Copy-On-Write的方式复制一份到当前层,而删除前一层的文件并不会真的执行删除操作,而是仅在当前层标记该文件已删除。Docker利用联合文件系统来实现将多个容器镜像合成为一个完整的应用,这一技术的本质正是可逆计算中的aop_extends操作。

Docker的英文是码头搬运工人的意思,它所搬运的容器也经常被人拿来和集装箱做对比:标准的容器和集装箱类似,使得我们可以自由的对它们进行传输/组合,而不用考虑容器中的具体内容。但是这种比较是肤浅的,甚至是误导性的。集装箱是静态的、简单的、没有对外接口的,而容器则是动态的、复杂的、和外部存在着大量信息交互的。这种动态的复杂结构想和普通的静态物件一样封装成所谓标准容器,其难度不可同日而语。如果没有引入支持差量的文件系统,是无法构建出一种柔性边界,实现逻辑分离的。

Docker所做的标准封装其实虚拟机也能做到,甚至差量存储机制在虚拟机中也早早的被用于实现增量备份,Docker与虚拟机的本质性不同到底在什么地方?回顾第一节中可逆计算对差量三个基本要求,我们可以清晰的发现Docker的独特之处。

  1. 差量独立存在:Docker最重要的价值就在于通过容器封装,抛弃了作为背景存在(必不可少,但一般情况下不需要了解),占据了99%的体积和复杂度的操作系统层。应用容器成为了可以独立存储、独立操作的第一性的实体。轻装上阵的容器在性能、资源占用、可管理性等方面完全超越了虚胖的虚拟机。
  2. 差量相互作用:Docker容器之间通过精确受控的方式发生相互作用,可通过操作系统的namespace机制选择性的实现资源隔离或者共享。而虚拟机的差量切片之间是没有任何隔离机制的。
  3. 差量具有结构:虚拟机虽然支持增量备份,但是人们却没有合适的手段去主动构造一个指定的差量切片出来。归根结底,是因为虚拟机的差量定义在二进制字节空间中,而这个空间非常贫瘠,几乎没有什么用户可以控制的构造模式。而Docker的差量是定义在差量文件系统空间中,这个空间继承了Linux社区最丰富的历史资源。每一条shell指令的执行结果最终反映到文件系统中都是增加/删除/修改了某些文件,所以每一条shell指令都可以被看作是某个差量的定义。差量构成了一个异常丰富的结构空间,差量既是这个空间中的变换算符(shell指令),又是变换算符的运算结果。差量与差量相遇产生新的差量,这种生生不息才是Docker的生命力所在。

(二)React

2013年,也就是Docker发布的同一年,Facebook公司开源了一个革命性的Web前端框架React。React的技术思想非常独特,它以函数式编程思想为基础,结合一个看似异想天开的虚拟DOM(Virtual DOM)概念,引入了一整套新的设计模式,开启了前端开发的新大航海时代。

class HelloMessage extends React.Component {
  constructor(props) {
    super(props);
    
this.state = { count: 0 };
    
this.action = this.action.bind(this);
  }
  
  action(){
    
this.setState(state => ({
      count: state.count 
+ 1
    }));
  },
  
  render() {
    
return (
      
<button onClick={this.action}>
        Hello {
this.props.name}:{this.state.count}
      
</button>
    );
  }
}

ReactDOM.render(
  
<HelloMessage name="Taylor" />,
  mountNode
);

React组件的核心是render函数,它的设计参考了后端常见的模板渲染技术,主要区别在于后端模板输出的是HTML文本,而React组件的Render函数使用类似XML模板的JSX语法,通过编译转换在运行时输出的是虚拟DOM节点对象。例如上面HelloMessage组件的render函数被翻译后的结果类似于

render(){
   
return new VNode("button", {onClick: this.action, 
          content: 
"Hello "+ this.props.name + ":" + this.state.count });
}
可以用以下公式来描述React组件:  VDom = render(state)
当状态发生变化以后,只要重新执行render函数就会生成新的虚拟DOM节点,虚拟DOM节点可以被翻译成真实的HTML DOM对象,从而实现界面更新。这种根据状态重新生成完整视图的渲染策略极大简化了前端界面开发。例如对于一个列表界面,传统编程需要编写新增行/更新行/删除行等多个不同的DOM操作函数,而在React中只要更改state后重新执行唯一的render函数即可。

每次重新生成DOM视图的唯一问题是性能很低,特别是当前端交互操作众多、状态变化频繁的时候。React的神来之笔是提出了基于虚拟DOM的diff算法,可以自动的计算两个虚拟DOM树之间的差量,状态变化时只要执行虚拟Dom差量对应的DOM修改操作即可(更新真实DOM时会触发样式计算和布局计算,导致性能很低,而在JavaScript中操作虚拟DOM 的速度是非常快的)。整体策略可以表示为如下公式

state_1=state_0\oplus action\\ \Delta VDom=render(state_1)-render(state_0)\\ \Delta Dom =Translater(\Delta VDom) \\

显然,这一策略也是可逆计算的一种特例。

只要稍微留意一下就会发现,最近几年merge/diff/residual/delta等表达差量运算的概念越来越多的出现在软件设计领域中。比如大数据领域的流计算引擎中,流与表之间的关系可以表示为

Table=\int \Delta Stream \\

对表的增删改查操作可以被编码为事件流,而将表示数据变化的事件累积到一起就形成了数据表。

现代科学发端于微积分的发明,而微分的本质就是自动计算无穷小差量,而积分则是微分的逆运算,自动对无穷小量进行汇总合并。19世纪70年代,经济学经历了一场边际革命,将微积分的思想引入经济分析,在边际这一概念之上重建了整个经济学大厦。软件构造理论发展到今天,已经进入一个瓶颈,也到了应该重新认识差量的时候。

四. 结语

笔者的专业背景是理论物理学,可逆计算源于笔者将物理学和数学的思想引入软件领域的一种尝试,它最早由笔者在2007年左右提出。一直以来,软件领域对于自然规律的应用一般情况下都只限于"模拟"范畴,例如流体动力学模拟软件,虽然它内置了人类所认知的最深刻的一些世界规律,但这些规律并没有被用于指导和定义软件世界自身的构造和演化,它们的指向范围是软件世界之外,而不是软件世界自身。在笔者看来,在软件世界中,我们完全可以站在“上帝的视角”,规划和定义一系列的结构构造规律,辅助我们完成软件世界的构建。而为了完成这一点,我们首先需要建立程序世界中的“微积分”。

类似于微积分,可逆计算理论的核心是将“差量”提升为第一性的概念,将全量看作是差量的一种特例(全量=单位元+全量)。传统的程序世界中我们所表达的都只是“有”,而且是“所有”,差量只能通过全量之间的运算间接得到,它的表述和操纵都需要特殊处理,而基于可逆计算理论,我们首先应该定义所有差量概念的表达形式,然后再围绕这些概念去建立整个领域概念体系。为了保证差量所在数学空间的完备性(差量之间的运算结果仍然需要是合法的差量),差量所表达的不能仅仅是“有”,而必须是“有”和“没有”的一种混合体。也就是说差量必须是“可逆的”。可逆性具有非常深刻的物理学内涵,在基本的概念体系中内置这一概念可以解决很多非常棘手的软件构造问题。

为了处理分布式问题,现代软件开发体系已经接受了不可变数据的概念,而为了解决大粒度软件复用问题,我们还需要接受不可变逻辑的概念(复用可以看作是保持原有逻辑不变,然后增加差量描述)。目前,业内已经逐步出现了一些富有创造性的主动应用差量概念的实践,它们都可以在可逆计算的理论框架下得到统一的诠释。笔者提出了一种新的程序语言X语言,它可以极大简化可逆计算的技术实现。目前笔者已经基于X语言设计并实现了一系列软件框架和生产工具,并基于它们提出了一种新的软件生产范式(NOP)。

posted @ 2019-04-29 10:18 canonical 阅读(699) | 评论 (0)编辑 收藏

  浏览器前端编程的面貌自2005年以来已经发生了深刻的变化,这并不简单的意味着出现了大量功能丰富的基础库,使得我们可以更加方便的编写业务代码,更重要的是我们看待前端技术的观念发生了重大转变,明确意识到了如何以前端特有的方式释放程序员的生产力。本文将结合jQuery源码的实现原理,对javascript中涌现出的编程范式和常用技巧作一简单介绍。
 
1. AJAX: 状态驻留,异步更新
  首先来看一点历史。
A. 1995年Netscape公司的Brendan Eich开发了javacript语言,这是一种动态(dynamic)、弱类型(weakly typed)、基于原型(prototype-based)的脚本语言。
B. 1999年微软IE5发布,其中包含了XMLHTTP ActiveX控件。
C. 2001年微软IE6发布,部分支持DOM level 1和CSS 2标准。
D. 2002年Douglas Crockford发明JSON格式。
至此,可以说Web2.0所依赖的技术元素已经基本成形,但是并没有立刻在整个业界产生重大的影响。尽管一些“页面异步局部刷新”的技巧在程序员中间秘密的流传,甚至催生了bindows这样庞大臃肿的类库,但总的来说,前端被看作是贫瘠而又肮脏的沼泽地,只有后台技术才是王道。到底还缺少些什么呢?
  当我们站在今天的角度去回顾2005年之前的js代码,包括那些当时的牛人所写的代码,可以明显的感受到它们在程序控制力上的孱弱。并不是说2005年之前的js技术本身存在问题,只是它们在概念层面上是一盘散沙,缺乏统一的观念,或者说缺少自己独特的风格, 自己的灵魂。当时大多数的人,大多数的技术都试图在模拟传统的面向对象语言,利用传统的面向对象技术,去实现传统的GUI模型的仿制品。
  2005年是变革的一年,也是创造概念的一年。伴随着Google一系列让人耳目一新的交互式应用的发布,Jesse James Garrett的一篇文章《Ajax: A New Approach to Web Applications》被广为传播。Ajax这一前端特有的概念迅速将众多分散的实践统一在同一口号之下,引发了Web编程范式的转换。所谓名不正则言不顺,这下无名群众可找到组织了。在未有Ajax之前,人们早已认识到了B/S架构的本质特征在于浏览器和服务器的状态空间是分离的,但是一般的解决方案都是隐藏这一区分,将前台状态同步到后台,由后台统一进行逻辑处理,例如ASP.NET。因为缺乏成熟的设计模式支持前台状态驻留,在换页的时候,已经装载的js对象将被迫被丢弃,这样谁还能指望它去完成什么复杂的工作吗?
  Ajax明确提出界面是局部刷新的,前台驻留了状态,这就促成了一种需要:需要js对象在前台存在更长的时间。这也就意味着需要将这些对象和功能有效的管理起来,意味着更复杂的代码组织技术,意味着对模块化,对公共代码基的渴求。
  jQuery现有的代码中真正与Ajax相关(使用XMLHTTP控件异步访问后台返回数据)的部分其实很少,但是如果没有Ajax, jQuery作为公共代码基也就缺乏存在的理由。

2. 模块化:管理名字空间
  当大量的代码产生出来以后,我们所需要的最基础的概念就是模块化,也就是对工作进行分解和复用。工作得以分解的关键在于各人独立工作的成果可以集成在一起。这意味着各个模块必须基于一致的底层概念,可以实现交互,也就是说应该基于一套公共代码基,屏蔽底层浏览器的不一致性,并实现统一的抽象层,例如统一的事件管理机制等。比统一代码基更重要的是,各个模块之间必须没有名字冲突。否则,即使两个模块之间没有任何交互,也无法共同工作。
  jQuery目前鼓吹的主要卖点之一就是对名字空间的良好控制。这甚至比提供更多更完善的功能点都重要的多。良好的模块化允许我们复用任何来源的代码,所有人的工作得以积累叠加。而功能实现仅仅是一时的工作量的问题。jQuery使用module pattern的一个变种来减少对全局名字空间的影响,仅仅在window对象上增加了一个jQuery对象(也就是$函数)。
   所谓的module pattern代码如下,它的关键是利用匿名函数限制临时变量的作用域。
  var feature =(function() {

// 私有变量和函数
var privateThing = 'secret',
    publicThing = 'not secret',

    changePrivateThing = function() {
        privateThing = 'super secret';
    },

    sayPrivateThing = function() {
        console.log(privateThing);
        changePrivateThing();
    };

// 返回对外公开的API
return {
    publicThing : publicThing,
    sayPrivateThing :  sayPrivateThing
}
})();
  js本身缺乏包结构,不过经过多年的尝试之后业内已经逐渐统一了对包加载的认识,形成了RequireJs库这样得到一定共识的解决方案。jQuery可以与RequireJS库良好的集成在一起, 实现更完善的模块依赖管理。http://requirejs.org/docs/jquery.html
 
  require(["jquery", "jquery.my"], function() {
    //当jquery.js和jquery.my.js都成功装载之后执行
    $(function(){
      $('#my').myFunc();
    });
  });
 
  通过以下函数调用来定义模块my/shirt, 它依赖于my/cart和my/inventory模块,
  require.def("my/shirt",
    ["my/cart", "my/inventory"],
    function(cart, inventory) {
        // 这里使用module pattern来返回my/shirt模块对外暴露的API
        return {
            color: "blue",
            size: "large"
            addToCart: function() {
                // decrement是my/inventory对外暴露的API
                inventory.decrement(this);
                cart.add(this);
            }
        }
    }
  );

3. 神奇的$:对象提升
  当你第一眼看到$函数的时候,你想到了什么?传统的编程理论总是告诉我们函数命名应该准确,应该清晰无误的表达作者的意图,甚至声称长名字要优于短名字,因为减少了出现歧义的可能性。但是,$是什么?乱码?它所传递的信息实在是太隐晦,太暧昧了。$是由prototype.js库发明的,它真的是一个神奇的函数,因为它可以将一个原始的DOM节点提升(enhance)为一个具有复杂行为的对象。在prototype.js最初的实现中,$函数的定义为
  var $ = function (id) {
    return "string" == typeof id ? document.getElementById(id) : id;
  };
  这基本对应于如下公式
      e = $(id)
  这绝不仅仅是提供了一个聪明的函数名称缩写,更重要的是在概念层面上建立了文本id与DOM element之间的一一对应。在未有$之前,id与对应的element之间的距离十分遥远,一般要将element缓存到变量中,例如
  var ea = docuement.getElementById('a');
  var eb = docuement.getElementById('b');
  ea.style....
但是使用$之后,却随处可见如下的写法
  $('header_'+id).style...
  $('body_'+id)....
id与element之间的距离似乎被消除了,可以非常紧密的交织在一起。
  prototype.js后来扩展了$的含义,
  function $() {
    var elements = new Array();
    
    for (var i = 0; i < arguments.length; i++) {
        var element = arguments[i];
        if (typeof element == 'string')
          element = document.getElementById(element);
    
        if (arguments.length == 1)
          return element;
    
        elements.push(element);
    }
    
    return elements;
  }
  这对应于公式
    [e,e] = $(id,id)
  很遗憾,这一步prototype.js走偏了,这一做法很少有实用的价值。
  真正将$发扬光大的是jQuery, 它的$对应于公式
    [o] = $(selector)
  这里有三个增强
  A. selector不再是单一的节点定位符,而是复杂的集合选择符
  B. 返回的元素不是原始的DOM节点,而是经过jQuery进一步增强的具有丰富行为的对象,可以启动复杂的函数调用链。
  C. $返回的包装对象被造型为数组形式,将集合操作自然的整合到调用链中。

  当然,以上仅仅是对神奇的$的一个过分简化的描述,它的实际功能要复杂得多. 特别是有一个非常常用的直接构造功能.
   $("<table><tbody><tr><td>...</td></tr></tbody></table>")....
  jQuery将根据传入的html文本直接构造出一系列的DOM节点,并将其包装为jQuery对象. 这在某种程度上可以看作是对selector的扩展: html内容描述本身就是一种唯一指定.
  $(function{})这一功能就实在是让人有些无语了, 它表示当document.ready的时候调用此回调函数。真的,$是一个神奇的函数, 有任何问题,请$一下。
  总结起来, $是从普通的DOM和文本描述世界到具有丰富对象行为的jQuery世界的跃迁通道。跨过了这道门,就来到了理想国。
   
4. 无定形的参数:专注表达而不是约束
  弱类型语言既然头上顶着个"弱"字, 总难免让人有些先天不足的感觉. 在程序中缺乏类型约束, 是否真的是一种重大的缺憾? 在传统的强类型语言中, 函数参数的类型,个数等都是由编译器负责检查的约束条件, 但这些约束仍然是远远不够的. 一般应用程序中为了加强约束, 总会增加大量防御性代码, 例如在C++中我们常用ASSERT, 而在java中也经常需要判断参数值的范围
    if (index < 0 || index >= size)
        throw new IndexOutOfBoundsException(
            "Index: "+index+", Size: "+size);            
  很显然, 这些代码将导致程序中存在大量无功能的执行路径, 即我们做了大量判断, 代码执行到某个点, 系统抛出异常, 大喊此路不通. 如果我们换一个思路, 既然已经做了某种判断,能否利用这些判断的结果来做些什么呢? javascript是一种弱类型的语言,它是无法自动约束参数类型的, 那如果顺势而行,进一步弱化参数的形态, 将"弱"推进到一种极致, 在弱无可弱的时候, weak会不会成为标志性的特点?
  看一下jQuery中的事件绑定函数bind,
   A. 一次绑定一个事件 $("#my").bind("mouseover", function(){});
   B. 一次绑定多个事件 $("#my").bind("mouseover mouseout",function(){})
   C. 换一个形式, 同样绑定多个事件
      $("#my").bind({mouseover:function(){}, mouseout:function(){});
   D. 想给事件监听器传点参数
      $('#my').bind('click', {foo: "xxxx"}, function(event) { event.data.foo..})
   E. 想给事件监听器分个组
      $("#my").bind("click.myGroup″, function(){});
   F. 这个函数为什么还没有疯掉???
   
   就算是类型不确定, 在固定位置上的参数的意义总要是确定的吧? 退一万步来说, 就算是参数位置不重要了,函数本身的意义应该是确定的吧? 但这是什么?
      取值 value = o.val(), 设置值 o.val(3)
      
   一个函数怎么可以这样过分, 怎么能根据传入参数的类型和个数不同而行为不同呢? 看不顺眼是不是? 可这就是俺们的价值观. 既然不能防止, 那就故意允许. 虽然形式多变, 却无一句废话. 缺少约束, 不妨碍表达(我不是出来吓人的).
   
5. 链式操作: 线性化的逐步细化
  jQuery早期最主要的卖点就是所谓的链式操作(chain).
 
  $('#content') // 找到content元素
    .find('h3') // 选择所有后代h3节点
    .eq(2)      // 过滤集合, 保留第三个元素
        .html('改变第三个h3的文本')
    .end()      // 返回上一级的h3集合
    .eq(0)
        .html('改变第一个h3的文本');

在一般的命令式语言中, 我们总需要在重重嵌套循环中过滤数据, 实际操作数据的代码与定位数据的代码纠缠在一起. 而jQuery采用先构造集合然后再应用函数于集合的方式实现两种逻辑的解耦, 实现嵌套结构的线性化. 实际上, 我们并不需要借助过程化的思想就可以很直观的理解一个集合, 例如 $('div.my input:checked')可以看作是一种直接的描述,而不是对过程行为的跟踪.
   循环意味着我们的思维处于一种反复回绕的状态, 而线性化之后则沿着一个方向直线前进, 极大减轻了思维负担, 提高了代码的可组合性. 为了减少调用链的中断, jQuery发明了一个绝妙的主意: jQuery包装对象本身类似数组(集合). 集合可以映射到新的集合, 集合可以限制到自己的子集合,调用的发起者是集合,返回结果也是集合,集合可以发生结构上的某种变化但它还是集合, 集合是某种概念上的不动点,这是从函数式语言中吸取的设计思想。集合操作是太常见的操作, 在java中我们很容易发现大量所谓的封装函数其实就是在封装一些集合遍历操作, 而在jQuery中集合操作因为太直白而不需要封装.
   链式调用意味着我们始终拥有一个“当前”对象,所有的操作都是针对这一当前对象进行。这对应于如下公式
     x += dx
调用链的每一步都是对当前对象的增量描述,是针对最终目标的逐步细化过程。Witrix平台中对这一思想也有着广泛的应用。特别是为了实现平台机制与业务代码的融合,平台会提供对象(容器)的缺省内容,而业务代码可以在此基础上进行逐步细化的修正,包括取消缺省的设置等。
  话说回来, 虽然表面上jQuery的链式调用很简单, 内部实现的时候却必须自己多写一层循环, 因为编译器并不知道"自动应用于集合中每个元素"这回事.
  $.fn['someFunc'] = function(){
    return this.each(function(){
      jQuery.someFunc(this,...);
    }
  }
 
6. data: 统一数据管理
  作为一个js库,它必须解决的一个大问题就是js对象与DOM节点之间的状态关联与协同管理问题。有些js库选择以js对象为主,在js对象的成员变量中保存DOM节点指针,访问时总是以js对象为入口点,通过js函数间接操作DOM对象。在这种封装下,DOM节点其实只是作为界面展现的一种底层“汇编”而已。jQuery的选择与Witrix平台类似,都是以HTML自身结构为基础,通过js增强(enhance)DOM节点的功能,将它提升为一个具有复杂行为的扩展对象。这里的思想是非侵入式设计(non-intrusive)和优雅退化机制(graceful degradation)。语义结构在基础的HTML层面是完整的,js的作用是增强了交互行为,控制了展现形式。
  如果每次我们都通过$('#my')的方式来访问相应的包装对象,那么一些需要长期保持的状态变量保存在什么地方呢?jQuery提供了一个统一的全局数据管理机制。
  获取数据 $('#my').data('myAttr')   设置数据 $('#my').data('myAttr',3);
这一机制自然融合了对HTML5的data属性的处理
   <input id="my" data-my-attr="4" ... />
 通过 $('#my').data('myAttr')将可以读取到HTML中设置的数据。
 
 第一次访问data时,jQuery将为DOM节点分配一个唯一的uuid, 然后设置在DOM节点的一个特定的expando属性上, jQuery保证这个uuid在本页面中不重复。
   elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
 以上代码可以同时处理DOM节点和纯js对象的情况。如果是js对象,则data直接放置在js对象自身中,而如果是DOM节点,则通过cache统一管理。
 因为所有的数据都是通过data机制统一管理的,特别是包括所有事件监听函数(data.events),因此jQuery可以安全的实现资源管理。在clone节点的时候,可以自动clone其相关的事件监听函数。而当DOM节点的内容被替换或者DOM节点被销毁的时候,jQuery也可以自动解除事件监听函数, 并安全的释放相关的js数据。
 
7. event:统一事件模型
  "事件沿着对象树传播"这一图景是面向对象界面编程模型的精髓所在。对象的复合构成对界面结构的一个稳定的描述,事件不断在对象树的某个节点发生,并通过冒泡机制向上传播。对象树很自然的成为一个控制结构,我们可以在父节点上监听所有子节点上的事件,而不用明确与每一个子节点建立关联。
  jQuery除了为不同浏览器的事件模型建立了统一抽象之外,主要做了如下增强:
  A. 增加了自定制事件(custom)机制. 事件的传播机制与事件内容本身原则上是无关的, 因此自定制事件完全可以和浏览器内置事件通过同一条处理路径, 采用同样的监听方式. 使用自定制事件可以增强代码的内聚性, 减少代码耦合. 例如如果没有自定制事件, 关联代码往往需要直接操作相关的对象
  $('.switch, .clapper').click(function() {
    var $light = $(this).parent().find('.lightbulb');
    if ($light.hasClass('on')) {
        $light.removeClass('on').addClass('off');
    } else {
        $light.removeClass('off').addClass('on');
    }
  });
而如果使用自定制事件,则表达的语义更加内敛明确,
  $('.switch, .clapper').click(function() {
    $(this).parent().find('.lightbulb').trigger('changeState');
  });
  B. 增加了对动态创建节点的事件监听. bind函数只能将监听函数注册到已经存在的DOM节点上. 例如
    $('li.trigger').bind('click',function(){}}
  如果调用bind之后,新建了另一个li节点,则该节点的click事件不会被监听.
  jQuery的delegate机制可以将监听函数注册到父节点上, 子节点上触发的事件会根据selector被自动派发到相应的handlerFn上. 这样一来现在注册就可以监听未来创建的节点.
    $('#myList').delegate('li.trigger', 'click', handlerFn);
  最近jQuery1.7中统一了bind, live和delegate机制, 天下一统, 只有on/off.
    $('li.trigger’).on('click', handlerFn);  // 相当于bind
    $('#myList’).on('click', 'li.trigger', handlerFn);  // 相当于delegate
    
8. 动画队列:全局时钟协调
  抛开jQuery的实现不谈, 先考虑一下如果我们要实现界面上的动画效果, 到底需要做些什么? 比如我们希望将一个div的宽度在1秒钟之内从100px增加到200px. 很容易想见, 在一段时间内我们需要不时的去调整一下div的宽度, [同时]我们还需要执行其他代码. 与一般的函数调用不同的是, 发出动画指令之后, 我们不能期待立刻得到想要的结果, 而且我们不能原地等待结果的到来. 动画的复杂性就在于:一次性表达之后要在一段时间内执行,而且有多条逻辑上的执行路径要同时展开, 如何协调?
  伟大的艾萨克.牛顿爵士在《自然哲学的数学原理》中写道:"绝对的、真正的和数学的时间自身在流逝着". 所有的事件可以在时间轴上对齐, 这就是它们内在的协调性. 因此为了从步骤A1执行到A5, 同时将步骤B1执行到B5, 我们只需要在t1时刻执行[A1, B1], 在t2时刻执行[A2,B2], 依此类推.
    t1 | t2 | t3 | t4 | t5 ...
    A1 | A2 | A3 | A4 | A5 ...
    B1 | B2 | B3 | B4 | B5 ...
  具体的一种实现形式可以是
  A. 对每个动画, 将其分装为一个Animation对象, 内部分成多个步骤.
      animation = new Animation(div,"width",100,200,1000,
                  负责步骤切分的插值函数,动画执行完毕时的回调函数);
  B. 在全局管理器中注册动画对象
      timerFuncs.add(animation);
  C. 在全局时钟的每一个触发时刻, 将每个注册的执行序列推进一步, 如果已经结束, 则从全局管理器中删除.
     for each animation in timerFuncs
        if(!animation.doOneStep())
           timerFuncs.remove(animation)

  解决了原理问题,再来看看表达问题, 怎样设计接口函数才能够以最紧凑形式表达我们的意图? 我们经常需要面临的实际问题:
  A. 有多个元素要执行类似的动画
  B. 每个元素有多个属性要同时变化
  C. 执行完一个动画之后开始另一个动画
jQuery对这些问题的解答可以说是榨尽了js语法表达力的最后一点剩余价值.
   $('input')
     .animate({left:'+=200px',top:'300'},2000)
     .animate({left:'-=200px',top:20},1000)
     .queue(function(){
       // 这里dequeue将首先执行队列中的后一个函数,因此alert("y")
       $(this).dequeue();
       alert('x');
      })
     .queue(function(){
        alert("y");
        // 如果不主动dequeue, 队列执行就中断了,不会自动继续下去.
        $(this).dequeue();
      });

  A. 利用jQuery内置的selector机制自然表达对一个集合的处理.
  B. 使用Map表达多个属性变化
  C. 利用微格式表达领域特定的差量概念. '+=200px'表示在现有值的基础上增加200px
  D. 利用函数调用的顺序自动定义animation执行的顺序: 在后面追加到执行队列中的动画自然要等前面的动画完全执行完毕之后再启动.
   
  jQuery动画队列的实现细节大概如下所示,
   A. animate函数实际是调用queue(function(){执行结束时需要调用dequeue,否则不会驱动下一个方法})
      queue函数执行时, 如果是fx队列, 并且当前没有正在运行动画(如果连续调用两次animate,第二次的执行函数将在队列中等待),则会自动触发dequeue操作, 驱动队列运行.
      如果是fx队列, dequeue的时候会自动在队列顶端加入"inprogress"字符串,表示将要执行的是动画.
   B. 针对每一个属性,创建一个jQuery.fx对象。然后调用fx.custom函数(相当于start)来启动动画。
   C. custom函数中将fx.step函数注册到全局的timerFuncs中,然后试图启动一个全局的timer.
       timerId = setInterval( fx.tick, fx.interval );
   D. 静态的tick函数中将依次调用各个fx的step函数。step函数中通过easing计算属性的当前值,然后调用fx的update来更新属性。
   E. fx的step函数中判断如果所有属性变化都已完成,则调用dequeue来驱动下一个方法。

  很有意思的是, jQuery的实现代码中明显有很多是接力触发代码: 如果需要执行下一个动画就取出执行, 如果需要启动timer就启动timer等. 这是因为js程序是单线程的,真正的执行路径只有一条,为了保证执行线索不中断, 函数们不得不互相帮助一下. 可以想见, 如果程序内部具有多个执行引擎, 甚至无限多的执行引擎, 那么程序的面貌就会发生本质性的改变. 而在这种情形下, 递归相对于循环而言会成为更自然的描述.
 
9. promise模式:因果关系的识别
  现实中,总有那么多时间线在独立的演化着, 人与物在时空中交错,却没有发生因果. 软件中, 函数们在源代码中排着队, 难免会产生一些疑问, 凭什么排在前面的要先执行? 难道没有它就没有我? 让全宇宙喊着1,2,3齐步前进, 从上帝的角度看,大概是管理难度过大了, 于是便有了相对论. 如果相互之间没有交换信息, 没有产生相互依赖, 那么在某个坐标系中顺序发生的事件, 在另外一个坐标系中看来, 就可能是颠倒顺序的. 程序员依葫芦画瓢, 便发明了promise模式.
  promise与future模式基本上是一回事,我们先来看一下java中熟悉的future模式.
  futureResult = doSomething();
  ...
  realResult = futureResult.get();
  发出函数调用仅仅意味着一件事情发生过, 并不必然意味着调用者需要了解事情最终的结果. 函数立刻返回的只是一个将在未来兑现的承诺(Future类型), 实际上也就是某种句柄. 句柄被传来传去, 中间转手的代码对实际结果是什么,是否已经返回漠不关心. 直到一段代码需要依赖调用返回的结果, 因此它打开future, 查看了一下. 如果实际结果已经返回, 则future.get()立刻返回实际结果, 否则将会阻塞当前的执行路径, 直到结果返回为止. 此后再调用future.get()总是立刻返回, 因为因果关系已经被建立, [结果返回]这一事件必然在此之前发生, 不会再发生变化.
  future模式一般是外部对象主动查看future的返回值, 而promise模式则是由外部对象在promise上注册回调函数.
  function getData(){
   return $.get('/foo/').done(function(){
      console.log('Fires after the AJAX request succeeds');
   }).fail(function(){
      console.log('Fires after the AJAX request fails');
   });
  }
 
  function showDiv(){
    var dfd = $.Deferred();
    $('#foo').fadeIn( 1000, dfd.resolve );
    return dfd.promise();
  }
 
  $.when( getData(), showDiv() )
    .then(function( ajaxResult, ignoreResultFromShowDiv ){
        console.log('Fires after BOTH showDiv() AND the AJAX request succeed!');
        // 'ajaxResult' is the server’s response
    });
  jQuery引入Deferred结构, 根据promise模式对ajax, queue, document.ready等进行了重构, 统一了异步执行机制. then(onDone, onFail)将向promise中追加回调函数, 如果调用成功完成(resolve), 则回调函数onDone将被执行, 而如果调用失败(reject), 则onFail将被执行. when可以等待在多个promise对象上. promise巧妙的地方是异步执行已经开始之后甚至已经结束之后,仍然可以注册回调函数
  someObj.done(callback).sendRequest() vs. someObj.sendRequest().done(callback)
 callback函数在发出异步调用之前注册或者在发出异步调用之后注册是完全等价的, 这揭示出程序表达永远不是完全精确的, 总存在着内在的变化维度. 如果能有效利用这一内在的可变性, 则可以极大提升并发程序的性能.
   promise模式的具体实现很简单. jQuery._Deferred定义了一个函数队列,它的作用有以下几点:
   A. 保存回调函数。
   B. 在resolve或者reject的时刻把保存着的函数全部执行掉。
   C. 已经执行之后, 再增加的函数会被立刻执行。
 
   一些专门面向分布式计算或者并行计算的语言会在语言级别内置promise模式, 比如E语言.
     def carPromise := carMaker <- produce("Mercedes");
     def temperaturePromise := carPromise <- getEngineTemperature()
     ...
     when (temperaturePromise) -> done(temperature) {
       println(`The temperature of the car engine is: $temperature`)
     } catch e {
       println(`Could not get engine temperature, error: $e`)
     }
  在E语言中, <-是eventually运算符, 表示最终会执行, 但不一定是现在. 而普通的car.moveTo(2,3)表示立刻执行得到结果. 编译器负责识别所有的promise依赖, 并自动实现调度.
 
10. extend: 继承不是必须的
  js是基于原型的语言, 并没有内置的继承机制, 这一直让很多深受传统面向对象教育的同学们耿耿于怀. 但继承一定是必须的吗? 它到底能够给我们带来什么? 最纯朴的回答是: 代码重用. 那么, 我们首先来分析一下继承作为代码重用手段的潜力.
  曾经有个概念叫做"多重继承", 它是继承概念的超级赛亚人版, 很遗憾后来被诊断为存在着先天缺陷, 以致于出现了一种对于继承概念的解读: 继承就是"is a"关系, 一个派生对象"is a"很多基类, 必然会出现精神分裂, 所以多重继承是不好的.
   class A{ public: void f(){ f in A } }
   class B{ public: void f(){ f in B } }
   class D: public A, B{}
 如果D类从A,B两个基类继承, 而A和B类中都实现了同一个函数f, 那么D类中的f到底是A中的f还是B中的f, 抑或是A中的f+B中的f呢? 这一困境的出现实际上源于D的基类A和B是并列关系, 它们满足交换律和结合律, 毕竟,在概念层面上我们可能难以认可两个任意概念之间会出现从属关系. 但如果我们放松一些概念层面的要求, 更多的从操作层面考虑一下代码重用问题, 可以简单的认为B在A的基础上进行操作, 那么就可以得到一个线性化的结果. 也就是说, 放弃A和B之间的交换律只保留结合律, extends A, B 与 extends B,A 会是两个不同的结果, 不再存在诠释上的二义性. scala语言中的所谓trait(特性)机制实际上采用的就是这一策略.
  面向对象技术发明很久之后, 出现了所谓的面向方面编程(AOP), 它与OOP不同, 是代码结构空间中的定位与修改技术. AOP的眼中只有类与方法, 不知道什么叫做意义. AOP也提供了一种类似多重继承的代码重用手段, 那就是mixin. 对象被看作是可以被打开,然后任意修改的Map, 一组成员变量与方法就被直接注射到对象体内, 直接改变了它的行为.
  prototype.js库引入了extend函数,
  Object.extend = function(destination, source) {
    for (var property in source) {
      destination[property] = source[property];
    }
    return destination;
  }
  就是Map之间的一个覆盖运算, 但很管用, 在jQuery库中也得到了延用. 这个操作类似于mixin, 在jQuery中是代码重用的主要技术手段---没有继承也没什么大不了的.

11. 名称映射: 一切都是数据
  代码好不好, 循环判断必须少. 循环和判断语句是程序的基本组成部分, 但是优良的代码库中却往往找不到它们的踪影, 因为这些语句的交织会模糊系统的逻辑主线, 使我们的思想迷失在疲于奔命的代码追踪中. jQuery本身通过each, extend等函数已经极大减少了对循环语句的需求, 对于判断语句, 则主要是通过映射表来处理. 例如, jQuery的val()函数需要针对不同标签进行不同的处理, 因此定义一个以tagName为key的函数映射表
   valHooks: { option: {get:function(){}}}
这样在程序中就不需要到处写
   if(elm.tagName == 'OPTION'){
     return ...;
   }else if(elm.tagName == 'TEXTAREA'){
     return ...;
   }
可以统一处理
   (valHooks[elm.tagName.toLowerCase()] || defaultHandler).get(elm);
   
  映射表将函数作为普通数据来管理, 在动态语言中有着广泛的应用. 特别是, 对象本身就是函数和变量的容器, 可以被看作是映射表. jQuery中大量使用的一个技巧就是利用名称映射来动态生成代码, 形成一种类似模板的机制. 例如为了实现myWidth和myHeight两个非常类似的函数, 我们不需要
  jQuery.fn.myWidth = function(){
      return parseInt(this.style.width,10) + 10;
    }
    
    jQuery.fn.myHeight = function(){
      return parseInt(this.style.height,10) + 10;
    }
而可以选择动态生成
    jQuery.each(['Width','Height'],function(name){
      jQuery.fn['my'+name] = function(){
        return parseInt(this.style[name.toLowerCase()],10) + 10;
      }
    });
 
12. 插件机制:其实我很简单    
  jQuery所谓的插件其实就是$.fn上增加的函数, 那这个fn是什么东西?
  (function(window,undefined){
    // 内部又有一个包装
    var jQuery = (function() {
      var jQuery = function( selector, context ) {
            return new jQuery.fn.init( selector, context, rootjQuery );
        }
       ....
      // fn实际就是prototype的简写
      jQuery.fn = jQuery.prototype = {
          constructor: jQuery,
          init: function( selector, context, rootjQuery ) {...  }
      }
    
      // 调用jQuery()就是相当于new init(), 而init的prototype就是jQuery的prototype
      jQuery.fn.init.prototype = jQuery.fn;
    
      // 这里返回的jQuery对象只具备最基本的功能, 下面就是一系列的extend
      return jQuery;
    })();  
    ...
     // 将jQuery暴露为全局对象
    window.jQuery = window.$ = jQuery;
  })(window);
  显然, $.fn其实就是jQuery.prototype的简写.
 
  无状态的插件仅仅就是一个函数, 非常简单.
  // 定义插件
  (function($){
      $.fn.hoverClass = function(c) {
          return this.hover(
              function() { $(this).toggleClass(c); }
          );
      };
  })(jQuery);
 
  // 使用插件
  $('li').hoverClass('hover');
 
 对于比较复杂的插件开发, jQuery UI提供了一个widget工厂机制,
 $.widget("ui.dialog", {
   options: {
        autoOpen: true,...
     },
     _create: function(){ ... },
     _init: function() {
        if ( this.options.autoOpen ) {
            this.open();
        }
     },
     _setOption: function(key, value){ ... }
     destroy: function(){ ... }
 });
 
 调用 $('#dlg').dialog(options)时, 实际执行的代码基本如下所示:
  this.each(function() {
        var instance = $.data( this, "dialog" );
        if ( instance ) {
            instance.option( options || {} )._init();
        } else {
            $.data( this, "dialog", new $.ui.dialog( options, this ) );
        }
    }
 可以看出, 第一次调用$('#dlg').dialog()函数时会创建窗口对象实例,并保存在data中, 此时会调用_create()和_init()函数, 而如果不是第一次调用, 则是在已经存在的对象实例上调用_init()方法. 多次调用$('#dlg').dialog()并不会创建多个实例.

13. browser sniffer vs. feature detection
  浏览器嗅探(browser sniffer)曾经是很流行的技术, 比如早期的jQuery中
  jQuery.browser = {
        version:(userAgent.match(/.+(?:rv|it|ra|ie)[/: ]([d.]+)/) || [0,'0'])[1],
        safari:/webkit/.test(userAgent),
        opera:/opera/.test(userAgent),
        msie:/msie/.test(userAgent) && !/opera/.test(userAgent),
        mozilla:/mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent)
  };
  在具体代码中可以针对不同的浏览器作出不同的处理
  if($.browser.msie) {
      // do something
  } else if($.browser.opera) {
      // ...
  }  

  但是随着浏览器市场的竞争升级, 竞争对手之间的互相模仿和伪装导致userAgent一片混乱, 加上Chrome的诞生, Safari的崛起, IE也开始加速向标准靠拢, sniffer已经起不到积极的作用. 特性检测(feature detection)作为更细粒度, 更具体的检测手段, 逐渐成为处理浏览器兼容性的主流方式.
  jQuery.support = {
        // IE strips leading whitespace when .innerHTML is used
        leadingWhitespace: ( div.firstChild.nodeType === 3 ),
        ...
    }
    只基于实际看见的,而不是曾经知道的, 这样更容易做到兼容未来.

14. Prototype vs. jQuery
  prototype.js是一个立意高远的库, 它的目标是提供一种新的使用体验,参照Ruby从语言级别对javascript进行改造,并最终真的极大改变了js的面貌。$, extends, each, bind...这些耳熟能详的概念都是prototype.js引入到js领域的. 它肆无忌惮的在window全局名字空间中增加各种概念, 大有谁先占坑谁有理, 舍我其谁的气势. 而jQuery则扣扣索索, 抱着比较实用化的理念, 目标仅仅是write less, do more而已.  
  不过等待激进的理想主义者的命运往往都是壮志未酬身先死. 当prototype.js标志性的bind函数等被吸收到ECMAScript标准中时, 便注定了它的没落. 到处修改原生对象的prototype, 这是prototype.js的独门秘技, 也是它的死穴. 特别是当它试图模仿jQuery, 通过Element.extend(element)返回增强对象的时候, 算是彻底被jQuery给带到沟里去了. prototype.js与jQuery不同, 它总是直接修改原生对象的prototype, 而浏览器却是充满bug, 谎言, 历史包袱并夹杂着商业阴谋的领域, 在原生对象层面解决问题注定是一场悲剧. 性能问题, 名字冲突, 兼容性问题等等都是一个帮助库的能力所无法解决的. Prototype.js的2.0版本据说要做大的变革, 不知是要与历史决裂, 放弃兼容性, 还是继续挣扎, 在夹缝中求生.

posted @ 2011-12-25 21:23 canonical 阅读(2323) | 评论 (5)编辑 收藏

1. C语言抽象出了软件所在的领域(domain): 由变量v1,v2,...和函数f1,f2,...组成的空间

2. 面向对象(OOP)指出,在这一领域上可以建立分组(group)结构:一组相关的变量和函数构成一个集合,我们称之为对象(Object)。同时在分组结构上可以定义一个运算(推理)关系:  D > B, 派生类D从基类B继承(inheritance),相应的派生对象符合基类对象所满足的所有约束。推理是有价值的,因为根据 D > B, B > A 可以自动推导出 D > A,所有针对A的断言在理论上对D都成立(这也就是我们常说的“派生对象 is a 基类对象”)。编译器也能有点智能了。
   一个有趣的地方是,D > B意味着在D和B之间存在着某种差异,但是我们却无法把它显式的表达出来!也就是说在代码层面上我们无法明确表达 D - B是什么。为了把更多的信息不断的导入到原有系统中,面向对象内置提供的方法是建立不断扩展的类型树,类型树每增长一层,就可以多容纳一些新的信息。这是一种金字塔式的结构,只不过是一种倒立的金字塔,最终基点会被不断增长的结构压力所压垮。

3. 组件技术(Component)本质上是在提倡面向接口(interface),然后通过接口之间的组合(Composition)而不是对象之间的继承(inheritance)来构造系统。基于组合的观念相当于是定义了运算关系:D = B + C。终于,我们勉强可以在概念层面上做加法了。
   组件允许我们随意的组合,按照由简单到复杂的方向构造系统,但是组件构成的成品之间仍然无法自由的建立关系。这意味着组件组装得到的成品只是某种孤立的,偶然的产物。
   F = A + B + C  ? G = A + D + C。

4. 在数学上,配备了加法运算的集合构成半群,如果要成为群(Group),则必须定义相应的逆运算:减法。 群结构使得大粒度的结构变换成为可能。
   F = A + B + C = A + D - D + B + C = (A + D + C) - D + B = G - D + B
   在不破坏原有代码的情况下,对原有系统功能进行增删,这就是面向切面(AOP)技术的全部价值。


posted @ 2011-05-08 12:07 canonical 阅读(3632) | 评论 (1)编辑 收藏

   业务架构平台的设计与实现要比普通业务系统困难很多。一个核心难点在于如何建立普遍有效的应用程序模型,如何控制各种偶然性的业务需求对系统整体架构的冲击。大多数现有的业务架构平台都是提供了一个庞大的万能性产品,它预料到了所有可能在业务系统开发中出现的可能性,并提供了相应的处理手段。业务系统开发人员的能力被限定在业务架构平台所允许的范围之内。如果业务架构平台的复杂度为A+,则我们最多只能用它来开发复杂度为A的业务系统。一个典型的特征就是使用业务架构平台的功能配置非常简单,但是要开发相应的功能特性则非常困难,而且必须采用与业务系统开发完全不同的技术手段和开发方式。
   采用业务架构平台来开发业务系统,即使看似开发工作量小,最终产生的各类配置代码量也可能会大大超过普通手工编程产生的代码量,这意味着平台封装了业务内在的复杂性,还是意味着平台引入了不必要的复杂性?很多业务架构平台的卖点都是零代码的应用开发,低水平的开发人员也可以主导的开发,但是为什么高水平的程序员不能借助于这些开发平台极大的提高生产率?
   一般的业务架构平台无法回答以下问题:
1) 业务系统可以通过使用设计工具来重用业务架构平台已经实现的功能,但是业务系统内部大量相似的模型配置如何才能够被重用?
2) 特定的业务领域中存在着大量特殊的业务规则,例如“审批串行进行,每一步都允许回退到上一步,而且允许选择跳转到任意后一步”。这些规则如何才能够被引入设计工具,简化配置过程?
3) 已经开发好的业务系统作为产品来销售的时候,如何应对具体客户的定制化?如果按照客户要求修改配置,则以后业务系统自身是否还能够实现版本升级?
  
   Witrix平台提供的基本开发模型为
          App = Biz aop-extends Generator<DSL>
在这一图景下,我们就可以回答以上三个问题:
1) 业务模型通过领域特定语言(DSL)来表达,因此可以使用语言中通用的继承或者组件抽象机制来实现模型重用。
2) 推理机对于所有推理规则一视同仁,特殊的业务规则与通用的业务规则一样都可以参与推理过程,并且一般情况下特殊的业务规则更能够大幅简化系统实现结构。
3) 相对于原始模型的修改被独立出来,然后应用面向切面(AOP)技术将这些特定代码织入到原始模型中。原始模型与差异修改相互分离,因此原始模型可以随时升级。

  Witrix平台所强调的不是强大的功能,而是一切表象之后的数学规律。Witrix平台通过少数基本原理的反复应用来构造软件系统,它本身就是采用平台技术构造的产物。我们用复杂度为A的工具制造复杂度为A+的产品,然后进一步以这个复杂度为A+的产品为工具来构造复杂度为A++的产品。

posted @ 2011-02-11 14:02 canonical 阅读(1532) | 评论 (0)编辑 收藏

    一种技术思想如果确实能够简化编程,有效降低系统构造的复杂性,那么它必然具有某种内在的数学解释。反之,无论一种技术机制显得如何华丽高深,如果它没有 清晰的数学图象,那么就很难证明自身存在的价值。对于模型驱动架构(MDA),我长期以来一直都持有一种批判态度。(Physical Model Driven http://canonical.javaeye.com/blog/29412 )。原因就在于“由工具自动实现从平台无关模型(PIM)向平台相关模型(PSM)的转换”这一图景似乎只是想把系统从实现的泥沼中拯救出来,遮蔽特定语 言,特定平台中的偶然的限制条件,并没有触及到系统复杂性这一核心问题。而所谓的可视化建模充其量不过是说明人类超强的视觉模式识别能力使得我们可以迅速 识别系统全景图中隐含的整体结构,更快的实现对系统结构的理解,并没有证明系统复杂性有任何本质性的降低。不过如果我们换一个视角, 不把模型局限为某种可视化的结构图,而将它定义为某种高度浓缩的领域描述, 则模型驱动基本上等价于根据领域描述自动推导得到最终的应用程序。沿着这一思路,Witrix平台中的很多设计实际上可以被解释为模型定义,模型推导以及 模型嵌入等方面的探索。这些具体技术的背后需要的是比一般MDA思想更加精致的设计原理作为支撑。我们可以进行如下抽象分析。(Witrix架构分析 http://canonical.javaeye.com/blog/126467

1. 问题复杂?线性切分是削减问题规模(从而降低问题复杂性)的通用手段,例如模块(Module)。(软件中的分析学 http://canonical.javaeye.com/blog/33885

App = M1 + M2 + M3 +    


2. 分块过多?同态映射是系统约化的一般化策略,例如多态(polymorphism)。(同构与同态:认识同一性 http://canonical.javaeye.com/admin/blogs/340704

(abc,abb,ade,-> [a],   (bbb, bcd,bab,-> [b]


3. 递归使用以上两种方法,将分分合合的游戏进行到底,推向极致。

4. 以少控多的终极形态?如果存在,则构成输入与输出之间的非线性变换(输入中局部微小变化将导致输出中大范围明显的变化)。

App = F(M)


5. 变换函数F可以被诠释为解释器(Interpreter)或者翻译机,例如工作流引擎将工作流描述信息翻译为一步步的具体操作,工作流描述可以看作是由底 层引擎支撑的,在更高的抽象层面上运行的领域模型。但是在这种观点下,变换函数F似乎是针对某种特定模型构造的,引擎内部信息传导的途径是确定的,关注的 重点始终在模型上,那么解释器自身应该如何被构造出来呢?

App ~ M


6. 另外一种更加开放的观点是将变换函数F看作是生成器(Generator)或者推理机。F将根据输入的信息,结合其他知识,推理生成一系列新的命题和断 言。模型的力量源于推导。变换函数F本身在系统构造过程中处于核心地位,M仅仅是触发其推理过程的信息源而已。F将榨干M的最后一点剩余价值,所有根据M 能够确定的事实将被自动实现,而大量单靠M自身的信息无法判定的命题也可以结合F内在的知识作出判断。生成器自身的构造过程非常简单--只要不断向推理系 统中增加新的推理规则即可。语言内置的模板机制(template)及元编程技术(meta programming),或者跨越语言边界的代码生成工具都可以看作是生成器的具体实例。(关于代码生成和DSL http://canonical.javaeye.com/blog/275015 )

App = G<M>


7. 生成器G之所以可以被独立实现,是因为我们可以实现相对知识与绝对知识的分离, 这也正是面向对象技术的本质所在。(面向对象之形式系统 http://canonical.javaeye.com/blog/37064

G<M> ==> G = {m => m.x(a,b,c);m.y();  }


8. 现实世界的不完美,就在于现实决不按照我们为其指定的理想路线前进。具体场景中总是存在着大量我们无法预知的“噪声”,它们使得任何在“过去”确立的方程 都无法在“未来”保持持久的平衡。传统模型驱动架构的困境就在于此。我们可以选择将模型M和生成器G不断复杂化,容纳越来越多的偶然性,直至失去对模型整 体结构的控制力。另外一种选择是模型在不断膨胀,不断提高覆盖能力的过程中,不断的空洞化,产生大量可插入(plugin)的接入点,最终丧失模型的推理 能力,退化成为一种编码规范。Witrix平台中采用的是第三种选择:模型嵌入--模型中的多余信息被不断清洗掉,模型通过精炼化来突出自身存在的合理 性,成为更广泛的运行环境中的支撑骨架。(结构的自足性 http://canonical.javaeye.com/blog/482620

App != G0<M0>  , App != G0<M1>, App = G1<M1>


9. 现在的问题是:如何基于一个已经被完美解决的重大问题,来更有效率的搞定不断出现但又不是重复出现的小问题。现在我们所需要的不是沿着某个维度进行均匀的 切分,而必须是某种有效的降维手段。如果我们可以定义一种投影算子P, 将待解决的问题投射到已经被解决的问题域中,则剩下的补集往往可以被简化。(主从分解而不是正交分解 http://canonical.javaeye.com/blog/196826

dA = App - P[App]  = App - G0<M0>


10. 要实现以上微扰分析策略,前提条件是可以定义逆元,并且需要定义一种精细的粘结操作,可以将分散的扰动量极为精确的应用到基础系统的各处。Witrix平台的具体实现类似于某种AOP(面向切面编程)技术。(逆元:不存在的真实存在 http://canonical.javaeye.com/blog/325051

App = A + D + B = (A + B + C) - C + D = App0 + (-+ D) = G0<M0> + dA


11. 模型驱动并不意味着一个应用只能由唯一的一个模型来驱动,但是如果引入多个不同形式的模型,则必须为如下推理提供具体的技术路径:
  A. 将多个模型变换到共同的描述域
  B. 实现多个模型的加和
  C. 处理模型之间的冲突并填补模型之间的空白
在Witrix平台中模型嵌入/组合主要依赖于文本化及编译期运行等机制。(文本化 http://canonical.javaeye.com/blog/309395

App = Ga<Ma> + Gb<Mb> + dA


12. 系统的开发时刻t0和实施时刻t1一般是明确分离的,因此如果我们要建立一个包含开发与实施时刻信息的模型,则这一模型必须是含时的,多阶段的。关于时 间,我们所知道的最重要的事实之一是“未来是不可预知的”。在t0时刻建立的模型如果要涵盖t1时刻的所有变化,则必须做出大量猜测,而且t1时刻距离 t0时刻越远,猜测的量越大,“猜测有效”这一集合的测度越小,直至为0。延迟选择是实现含时系统控制的不二法门。
   在Witrix平台中,所有功能特性的实现都包含某种元数据描述或者定制模板,因此结合配置机制以及动态编译技术既可实现多阶段模型。例如对于一个在未来 才能确定的常量数组,我们可以定义一个Excel文件来允许实施人员录入具体的值,然后通过动态编译技术在编译期解析Excel文件,并完成一系列数值映 射运算,最终将其转化为编译期存在的一个常量。这一过程不会引入任何额外的运行成本,也不要求任何特定的缓存机制,最终的运行结构与在未来当所有信息都在 位之后再手写代码没有任何区别。(D语言与tpl之编译期动作 http://canonical.javaeye.com/blog/57244

App(t1) = G(t0,t1)<M(t0,t1)> + dA(t0,t1)


13. 级列理论提供了一个演化框架,它指出孤立的模型必须被放在模型级列中被定义,被解释。(关于级列设计理论 http://canonical.javaeye.com/blog/33824

M[n] = G<M[n-1]> + dMn


14. 推理的链条会因为任何局部反例的出现而中断。在任意时空点上,我们能够断言的事实有哪些?信息越少,我们能够确定的事实越少,能够做出的推论也就越少。现 在流行的很多设计实质上是在破坏系统的对称性,破坏系统大范围的结构。比如明明ORM容器已经实现所有数据对象的统一管理,非要将其拆分为每个业务表一个 的DAO接口。很多对灵活性的追求完全没有搞清楚信息是对不确定性的消除,而不确定性的减少意味着限制的增加,约束的增加。(From Local To Global http://canonical.javaeye.com/blog/42874

   组件/构件技术的宣言是生产即组装,但是组装有成本,有后遗症(例如需要额外的胶水或者螺钉)。软件的本质并不是物质,而是信息,而信息的本质是抽象的规 律。在抽象世界中最有效的生产方式是抽象的运算,运算即生产。组件式开发意味着服从现有规律,熟练应用,而原理性生产则意味着不断创造新的规律。功能模 块越多,维护的成本越高,是负担,而推理机制越多,生产的成本越低,是财富。只有恢复软件的抽象性,明确把握软件构造过程内在的数学原理,才能真正释放软 件研发的生产力。(从编写代码到制造代码 http://canonical.javaeye.com/blog/333167


注解1:很多设计原则其实是在强调软件由人构造由人理解,软件开发本质上是人类工程学,需要关注人类的理解力与协作能力。例如共同的建模语言减少交互成本,基于模型减少设计与实现的分离,易读的代码比高性能的代码更重要,做一件事只有唯一的一种方式等。

注解2:生成系统的演绎远比我们想象的要深刻与复杂得多。例如生命的成长可以被看作是在外界反馈下不断调整的生成过程。

注解3:领域描述是更紧致但却未必是更本质的表达。人类的DNA如果表达为ATGC序列完全可以拷贝到U盘中带走,只要对DNA做少量增删,就可以实现老 鼠到人类的变换(人类和老鼠都有大约30000条基因,其中约有80%的基因是“完全一样的”,大约共享有99%的类似基因),但是很难认为人类所有智慧 的本质都体现在DNA中,DNA看起来更像是某种序列化保存形式而已。

注解4:模型转换这一提法似乎是在强调模型之间的同构对应,转换似乎总是可以双向进行的,仅仅是实现难度限制了反向转换而已。但是大量有用的模型变换却是单向的,变换过程要求不断补充新的信息。

注解5:模型驱动在很多人看来就是数据库模型或者对象模型驱动系统界面运行,但实际上模型可以意指任意抽象知识。虽然在目前业内广泛流行的对象范式下,所 有知识都可以表达为对象之间的关联,但是对象关系(名词之间的关联关系)对运算结构的揭示是远远不够充分的。很多时候所谓的领域模型仅仅是表明概念之间具 有相关性,但是如果不补充一大段文字说明,我们对于系统如何运作仍然一知半解。数学分析其实是将领域内在的意义抽空,仅余下通用的形式化符号。

posted @ 2011-02-07 02:56 canonical 阅读(1497) | 评论 (0)编辑 收藏

    html主要通过内置的<script>,<link>, <img>等标签引入外部的资源文件,一般的Web框架并没有对这些资源文件进行抽象,因此在实现组件封装时存在一些难以克服的困难。例如一个使用传统JSP Tag机制实现的Web组件中可能用到js1.js, js2.js和css1.css等文件,当在界面上存在多个同样的组件的时候,可能会生成多个重复的<script>和<link>标签调用,这将对页面性能造成严重的负面影响。资源管理应该是一个Web框架的内置组成部分之一。在Witrix平台中,我们主要借助于tpl模板引擎来输出html文本, 因此可以通过自定义标签机制重新实现资源相关的html标签, 由此来提供如下增强处理功能:

1. 识别contextPath
   tpl模板中的所有资源相关标签都会自动拼接Web应用的contextPath, 例如当contextPath=myApp时
   <script src="/a.js"></script> 将最终输出 <script src="/myApp/a.js" ...>

2. 识别重复装载
   <script src="a.js" tpl:once="true"></script>
   tpl:once属性将保证在页面中script标签实际只会出现一次.

3. 识别组件内相对路径
  开发Web组件时,我们希望所有资源文件都应该相对组件目录进行定位,但是直接输出的<script>等标签都是相对于最终的调用链接进行相对路径定位的. 例如在page1.jsp中调用了组件A, 在组件A的实现中, 输出了<script src="my_control.js"></script>
 我们的意图一般是相对于组件A的实现文件进行定位, 而不是相对于page1.jsp进行定位. tpl模板引擎的相对路径解析规则为永远相对于当前文件进行定位. 例如
  <c:include src="sub.tpl" />
在sub.tpl中的所有相对路径都相对于sub.tpl文件进行定位.

4. 编译期文件有效性检查
   在编译期, tpl引擎会检查所有引入的资源文件的有效性. 如果发现资源文件丢失, 将直接抛出异常. 这样就不用等到上线后才发现文件命名已修改等问题.

5. 缓存控制
  浏览器缺省会缓存css, js等文件, 因此系统上线后如果修改资源文件可能会造成与客户端缓存不一致的情况. 一个简单的处理方式是每次生成资源链接的时候都拼接文件的修改日期或者版本号, 这样既可利用客户端缓存, 又可以保证总是使用最新版本. 例如
  <script src="a.js"></script>将会输出 <script src="/myApp/myModule/a.js?344566" ...>

6. 字符集选择
  为了简化国际化处理, 一般提倡的最佳实践方式是坚持使用UTF-8编码. 但是很多情况下可能使用系统内置的GBK编码会更加方便一些, 另外集成一些既有代码时也存在着不同字符集的问题. 在Witrix平台中, 所有输出的资源标签都会标明对应的字符集, 如果没有明确设置就取系统参数中的缺省字符集.
 例如 <script src="a.js"></script> 将会输出 <script ... charset="GBK"></script>

7. 缺省theme支持
  为了支持多种页面风格, 往往不是简单的替换css文件即可实现的, 它可能意味着整个组件的实现代码的更换. Witrix平台中通过一系列缺省判断来简化这一过程. 例如如下代码表明如果设置了ui_theme系统参数, 并且对应的特殊实现存在, 则使用特殊实现, 否则系统缺省实现.
  <c:include src="${cp:ui_theme()}/ctl_my_ctl.tpl" >
    <c:include src="default/ctl_my_ctl.tpl" />
  </c:include>

posted @ 2010-01-17 17:51 canonical 阅读(1317) | 评论 (0)编辑 收藏

     AOP(Aspect Oriented Programming)早已不是什么新鲜的概念,但有趣的是,除了事务(transaction), 日志(Log)等寥寥几个样板应用之外,我们似乎找不到它的用武之地。http://canonical.javaeye.com/blog/34941 很多人的疑惑是我直接改代码就行了,干吗要用AOP呢?AOP的定义和实现那么复杂,能够提供什么特异的价值呢?
    Witrix平台依赖于AOP概念来完成领域模型抽象与模型变换,但是在具体的实现方式上,却与常见的AOP软件包有着很大差异。http://canonical.javaeye.com/blog/542622 AOP的具体技术内容包括定位和组装两个部分。简化切点定位方式和重新规划组装空间,是Witrix中有效使用AOP技术的前提。
    在Witrix平台中,对于AOP技术的一种具体应用是支持产品的二次开发。在产品的实施过程中,经常需要根据客户的特定需求,修改某些函数的实现。我们 可以选择在主版本代码中不断追加相互纠缠的if-else语句,试图去包容所有已知和未知的应用场景。我们也可以选择主版本代码和定制代码独立开发的方 式,主版本代码实现逻辑框架,定制代码通过AOP机制与主版本代码融合,根据具体场景要求对主版本功能进行修正。AOP的这种应用与所谓的横切概念是有所 区别的。典型的,一个横切的切点会涉及到很多类的很多方法,而函数定制则往往要求准确定位到某个业务对象的某个特定的业务方法上。传统AOP技术的切点定 义方式并不适合这种精确的单点定位。在Witrix平台中,我们通过直接的名称映射来定义切点。例如,修正spring中注册的MyObject对象的 myFunc方法,可以在app.aop.xml文件中增加如下标签

<myObject.myFunc>
      在原函数执行之前执行
      
<aop:Proceed/> <!-- 执行原函数内容 -->
      在原函数执行之后执行
</myObject.myFunc>


[spring对象名.方法名]这种映射方法比基于正则字符串匹配的方式要简单明确的多。spring容器本身已经实现了对象的全局管理功能,spring对象名称必然是唯一的,公开发布的,相互之间不冲突的,没有必要再通过匹配运算重新发现出它的唯一性。
   对于一些确实存在的横切需求,我们可以通过Annotation机制来实现切点坐标标定,将复杂的切点匹配问题重新划归为[对象名.方法名]。

@AopClass({"myObject","otherObject"})
  class SomeClass{
     @AopMethod({"myFunc","otherFunc"})
     void someFunc(){}
  }

 

针对以上对象,在app.aop.xml文件中可以定义

<I-myObject.I-myFunc>
   .
</I-myObject.I-myFunc>

posted @ 2009-12-13 11:34 canonical 阅读(1497) | 评论 (0)编辑 收藏

   结构的稳定性,直观的理解起来,就是结构在存在外部扰动的情况下长时间保持某种形式不变性的能力。稳定意味着小的扰动造成的后果也是“小”的。在数学中,Taylor级数为我们描绘了变化传播的基本图景。

 
F(x0 + dx) = F(x0) + F'(x0)*dx + 0.5*F''(x0)*dx^2 + 

扰动dx可能在系统F中引发非常复杂的作用过程,在系统各处产生一个个局部变化结果。表面上看起来,似乎这些变化结果存在着无穷多种可能的分组方式,例如 (F'(x0)-2)*dx + 2*dx^2, 但是基于微分分析,我们却很容易了解到Taylor级数的每一级都对应着独立的物理解释,它们构成自然的分组标准。某一量级下的所有变化汇总归并到一起,并对应一个明确的整体描述。在抽象的数理空间中,我们具有一种无所不达的变化搜集能力。变化项可以从基础结构中分离出来,经过汇总后可以对其进行独立的研究。变化本身并不会直接导致基础结构的崩溃。
   在软件建模领域,模型的稳定性面临的却是另一番场景。一个软件模型一旦被实现之后,种种局部需求变更就都会形成对原有基础结构的冲击。一些局部的需求变化可能造成大片原有实现失效,我们将被迫为类似的需求重新编写类似的代码。此时,软件开发并不像是一种纯粹的信息创造,而是宛若某种物质产品的生产(参见从编写代码到制造代码 http://canonical.javaeye.com/blog/333167 )。显然,我们需要一种能力,将局部变化从基础结构中剥离出来,经过汇总归并之后再进行综合分析和处理。这正是AOP(Aspect Oriented Programming)技术的价值所在。

   
M1 = (G0+dG0)<M0+dM0> ==> M1 = G0<M0> + dM
  AOP本质上是软件结构空间的自由修正机制。只有结合AOP技术之后,软件模型才能够重新恢复抽象的本质,在时间之河中逃离随机变化的侵蚀,保持实现层面的稳定性。在这一背景下,建模的目的将不是为了能够跟踪最终需求的变动,而是要在某个独立的层面上能够自圆其说,能够具有某种独立存在的完满性,成为思维上可以把握的某个稳定的基点。模型的真实性将因为自身结构的完备性而得到证明,与外部世界的契合程度不再是价值判断的唯一标准。http://canonical.javaeye.com/blog/482620

posted @ 2009-12-06 12:23 canonical 阅读(1199) | 评论 (0)编辑 收藏

   说到软件建模,一个常见的论调是模型应该符合实际需求,反映问题的本质。但是何谓本质,却是没有先验定义的。在成功的建立一个模型之前,无论在内涵上还是在外延上我们都很难说清楚一个问题的本质是什么。如果将模型看作是对领域结构的一种显式描述和表达,我们可以首先考察一下一个“合适”的结构应该具备哪些特征。
   按照结构主义哲学的观点,结构具有三个要素:整体性,具有转换规律或法则(转换性),自身调整性(自律性)。整体性意味着结构不能被简单的切分,其构成要素通过内在的关系运算实现大范围的关联与转换,整体之所以成为整体正是以转换/运算的第一性为保证的。这种转换可以是共时的(同时存在的各元素),也可以是历时的(历史的转换构造过程),这意味着结构总要求一个内在的构造过程,在独立于外部环境的情况下结构具有某种自给自足的特性,不依赖于外部条件即可独立的存在并保持内在的活动。自律性意味着结构内在的转换总是维持着某种封闭性和守恒性,确保新的成分在无限地构成而结构边界却保持稳定。注意到这里对结构的评判并不是来自外在规范和约束,而是基于结构内在的规律性,所强调的不是结构对外部条件的适应性,而是自身概念体系的完备性。实际上,一个无法直接对应于当前实际环境的结构仍然可能具有重要的价值,并在解决问题的过程中扮演不可或缺的角色。在合理性这个视角下,我们所关注的不仅仅是当前的现实世界,而是所有可能的世界。一个“合理”的结构的价值必能在它所适应的世界中凸现出来。
   在信息系统中,我们可能经常会问这个模型是否是对业务的准确描述,是否可以适应需求的变更,是否允许未来的各种扩展等等。但是如果换一个思维方向,我们会发现这些问题都是针对最终确立的模型而发问的,而在模型构建的过程中,那些可被利用的已存在的或者可以存在的模型又是哪些呢。每一个信息模型都对应着某种自动推理机,可以接收信息并做一定的推导综合工作。一个可行的问题是,如何才能更有效的利用已有的信息进行推导,如何消除冗余并减少各种转换成本。我们经常可以观察到,某一信息组织方式更充分的发掘了信息之间的内在关联(一个表象是它对信息的使用不是简单的局域化的,而是在多处呈现为互相纠缠的方式,难以被分解),这种内在关联足够丰富,以至于我们不依赖于外部因素就可以独立的理解。这种纠缠在一起的信息块自然会成为我们建模的对象。
   如果模型的“覆盖能力”不再是我们关注的重点,那么建模的思维图式将会发生如下的转化


最终的模型可以由一系列微模型交织构成。模型的递进构造过程并不同于组件(Component)的实物组装接口,也不是CAD图纸堆叠式的架构概念所能容纳的。在Witrix平台中,模型分解和构造表达为如下形式  http://canonical.javaeye.com/blog/333167
     Biz[n] = Biz[n+1] aop-extends CodeGenerator<DSLx, DSLy>。
   在软件发展的早期,所有的程序都是特殊构造的,其必然的假设是【在此情况下】,重用不在考虑范围之内,开发可以说是一个盲目试错的过程。随着我们逐步积累了一些经验,开始自觉的应用理论分析手段,【在所有情况下】都成立的一些普适的原理被揭示出来,它们成为我们在广阔的未知世界中跋涉时的向导。当我们的足迹渐渐接近领域的边界,对领域的全貌有一个总体的认知之后,一种对自身成熟性的自信很自然的将我们导向更加领域特定的分析。很多时候,我们会发现一个特化假设可以大大提高信息的利用率,推导出众多未被显式设定的知识。我们需要那些【在某些情况下】有效的规则来构成一个完备的模型库。这就如同有大量备选的数学定理,面对不同的物理现象,我们会从一系列的数学工具中选择一个进行使用一样。
  


posted @ 2009-10-07 17:10 canonical 阅读(2372) | 评论 (0)编辑 收藏

    软件开发技术的技术本质在于对代码结构的有效控制. 我们需要能够有效的分解/重组代码片断, 凸显设计意图. 面向对象是目前最常见的代码组织技术. 典型的, 它可以处理如下模式
  A1 --> B2,  A2 --> B2,  A3 --> B3 ...
我们观察到A1和A2之间, B2和B2之间具有某种概念关联性, 同时存在某种抽象结构 [A] --> [B].
对于这种情况, 我们可以定义对象 [A], [B], 它们分别是 A1和A2的聚合, B1和B2的聚合等. 举例来说, 对于如下表格描述, <ui:Col>所提供的信息在映射为html实现的时候将在多处被应用.
<ui:Table data="${data}">
  <ui:Col name="fieldA" label="labelA" width="20" />
  <ui:Col name="fieldB" label="labelB" width="10" /> 
</ui:Table>
这里<ui:Col>提供的信息对应三个部分的内容: 1. 列标题 2. 列样式(宽度等)  3. 列数据

面向对象的常见做法是抽象出 UiCol对象, 它作为UiTable对象的属性存在, 在生成表头, 表列样式和表格数据内容时将被使用. 但是我们注意到面向对象要求多个方法通过this指针形成状态耦合

,在某种意义上它意味着所有的成员方法在任一时刻都是同时存在着的。它们所代表着的存在的代价必须被接受(存储空间等)。即使并不同时被使用,我们仍然需要同时持有所有成员函数指针及

共享的this指针。实际上, 我们并不一定需要A1和A2同时在场. 在这种情况下, 编译期技术可以提供另一种不同的行为聚合方式.


<table>
  <thead>
    <sys:CompileTagBody cp:part="thead" />
  </thead>
  <cols>
    <sys:CompileTagBody cp:part="cols" />
  </cols>
  <tbody>
    <sys:CompileTagBody cp:part="tbody" />
  </tbody>
</table>

只要<ui:Col>标签的实现中针对编译期的cp:part变量进行分别处理, 即可实现信息的部分析取.


posted @ 2009-07-11 21:37 canonical 阅读(1216) | 评论 (0)编辑 收藏

  html最早的设计目标只是作为某种多媒体文档展现技术,其设计者显然无法预料到今天Web应用的蓬勃发展,一些设计缺陷也就难以避免。特别是html规范中缺乏对于复杂交互式组件模型的支持,直接导致企业应用的前台开发困难重重。AJAX技术可以看作是对这种困境的一种改良性响应,它试图通过javascript语言在应用层创建并维护一系列复杂的交互机制。很多完善的ajax框架走得相当遥远,最终基本将html作为一种底层“汇编”语言来使用。例如,一个很整齐美观的类Excel表格可能是由一个个div拼接而成,与html原生的table元素已经没有任何关系。
 
   Witrix平台中对于前台html模型也作了一定的增强,但基本的设计思想是尽量利用原生控件,并尽量保持原生控件内在的数据关系,而不是重新构建一个完整的底层支撑环境。采用这种设计的原因大致有如下几点:
1. 前台技术目前竞争非常激烈,我们优先选择的方式是集成第三方组件,尽量保持原生环境有利于降低集成成本。
2. 通过javascript构造的控件可能存在性能瓶颈和其他浏览器内在的限制。例如一般Ajax框架提供的Grid控件都无法支撑大量单元格的显示。
3. Witrix平台的tpl模板技术可以非常方便的生成html文本,并提供强大的控件抽象能力,因此在前台动态创建并组织界面元素在Witrix平台中是一种不经济的做法。
4. Witrix平台提供的分解机制非常细致,存储于不同地方的不同来源的代码会在不同的时刻织入到最终的页面中,基于原生环境有利于降低平台快速演进过程中的设计风险。

   Witrix平台中对于html模型的增强主要关注于以最少的代码实现界面控件与业务逻辑的自然结合。基本结构包括:
1. 通过ControlManager对象在前台建立一种container结构,统一管理控件的注册和获取。js.makeControl(elmOrId)返回特殊注册的控件对象或者根据原生html元素生成一个包装对象。
2. 通过js.getWxValue(elm)和js.setWxValue(elm,value)这两个函数统一对控件的值的存取过程。
3. 通过js.regListener(elm,listenerFunc)统一管理控件之间的相关触发,实现控件之间的相互监听。当js.setWxValue(elm,value)被调用时,注册在ControlManager中的listenerFunc将被调用。
4. stdPage.setFieldValue(fieldName,value)和stdPage.getFieldValue(fieldName,value)统一针对业务字段的值的存取过程,这里fieldName对应于实体上的业务字段名。
5. 通过ajax.addForm(frmId)等函数统一前台提交参数的提取过程,通过stdPage.buildAjax()等函数统一后台服务的调用方式。
6. 通过stdPage对象统一封装业务场景中的"常识"。
基于以上一些基础机制,Witrix平台即可提供一些复杂的业务组件封装。例如<input name="productCode" onkeypress="stdPage.keyPressToLoadRefByCode({objectName:'SomeProduct',queryField:'productCode'})" .../>通过简单的调用一个js函数即可实现如下功能:
a. 在文本框中输入回车的时候自动提交到后台查找对应产品代码的产品,并更新前台多个相关字段的值
b. 如果没有查找到相应产品,则弹出对话框根据界面上已有的部分字段信息提示客户添加新的产品信息。
c. 如果找到多个对应产品,则弹出列表允许客户选择其一。
d. 具体的处理过程可以通过函数参数进行精细的控制。
在meta文件中,结合上下文环境中的元数据信息,我们在缺省情况下可以直接使用 <ds:LoadRefByCodeInputor objectName="SomeProduct" />标签,不需要任何其他附加参数。

   Witrix平台中一般利用原生控件来保存数据值,而不是将数据保存在分离的js对象中。例如对于一个选择控件,经常要求选择得到的是实体的id,而显示在界面上的是某个其他字段的值。Witrix平台中一般的实现结构是
   <input type="hidden" name="${fieldName}" value="${entity[dsMeta.idField]}" id="${id}" textId="text_${id}" />
   <input type="text" value="${entity[dsMeta.nameField]}" id="text_${id}" />
通过textId等扩展属性即可明确定义控件多个部分之间的关联关系,同时保证控件的实现完全与html规范相兼容。
   Witrix平台中目前使用的"标准化"的扩展属性有textId(对应文本显示控件的id), showName(某些无文字显示的选择控件需要保留显示字段值), op(字段作为查询条件提交时的比较算符),validator(字段值对应的检验函数),setWxValue/getWxValue(重定义控件值的存取行为),serializer(特殊处理前台控件的提交参数)等。扩展属性不仅可以引入说明信息,还可以引入丰富的控件行为。
  


posted @ 2009-05-30 00:44 canonical 阅读(2679) | 评论 (2)编辑 收藏

    分层是最常见的软件架构方式之一。分层之后可以区分出横纵两个维度,纵向往往表现出一种隔离性。出于有意无意的各种原因,层次之间传递信息很容易出现模糊甚至丢失的现象。B/S多层体系架构下的程序因为浏览器和服务器之间的状态空间相互独立,相对于共享全局状态空间的C/S程序,更容易出现信息传递不畅的问题。实际上,我们经常可以观察到B/S程序中存在着大量的"接力"代码,即在交界处,总是存在着大量用于读取变量,拼接变量,转换变量等与主体业务无关但却又不可或缺的代码。在多层架构程序中,信道构建应该是一个需要给予足够重视的问题。

    在系统规划中,多层结构应该内置与具体语义无关的通用信道,它跨越多个层次,允许信息透明的通过,并以未预期的方式在不同的层面激发各种相关的行为。在Witrix平台中,平台代码与特定应用中的业务代码处于高度交织的状态,一个特定业务功能的实现往往需要多处业务代码相互协同,平台必须成为某种透明的背景。例如,假设我们编制了一个通用的列表选择控件,它封装的逻辑是从一个实体列表中进行选择
      <app:SelectOne objectName="MyEntity" />
如果现在要求选择时只列出某个类型的实体,则调用形式为
      <app:SelectOne objectName="MyEntity" extArgs="$bizId=select&amp;$type=1" />
在调用入口处补充必要的信息之后会推动系统在遥远的状态空间中应用一个特定的过滤条件。这里$bizId负责指示平台应用特定的元数据配置,而其他的参数则由元数据中的逻辑负责处理。平台与特定业务代码各取所需,相互配合,将尽可能多的逻辑剥离为通用机制。


posted @ 2009-03-22 21:10 canonical 阅读(591) | 评论 (0)编辑 收藏

javaeye提供的电子书制作功能非常实用。
http://www.blogjava.net/Files/canonical/canonical-blog-20090228.rar

posted @ 2009-02-28 17:21 canonical 阅读(435) | 评论 (0)编辑 收藏

   现代数学是建立在等价类这一概念的基础之上的。同构是对等价关系的一种刻划。简单的可以把它理解为两个系统之间的一种“保持”运算规则的一一对应关系。在 数学中一个符号所代表的是所有能够互相同构的对象。例如整数3可以看作是与某个元素个数为3的集合可以建立一一对应关系的所有的集合所构成的整体。所以在 数学中,如果我们解决某个特定的问题,它同时也就意味着我们解决了一系列相互等价的问题。
    同构关系对于认知可以起到本质上的简化作用。如果通过一个推理链条,确认了A == B == C == D,则可以直接从概念上推导出 A == D, 这一关系有可能被直观理解,而不需要理会中间的推理步骤。(注意到以上元素两两建立同构关系的时候可能要采用不同的对应手段,因此上面的等式并不是平凡 的。)另一方面,我们可以确定一个模型元素M,  将系统简化为 A == M, B == M, C == M, D == M。只要理解了元素M就理解了等价的其他元素。

    Witrix平台中PDM定义作为基础的结构模型,它同时映射成为数据库表,以及hbm, java, meta等多个代码文件,此外还对应于约定的WebObject名称和BizFlow文件名称,相应的报表文件目录等。我们只要理解了pdm模型,即可通 过推理自然的掌握各个层面上对应的结构。这意味着只要知道实体名称,就知道如何通过Web访问这个对象,知道数据在数据库中对应的数据库表,而不需要知道 后台是如何读取前台提交的参数以及如何执行保存数据指令的。不仅仅是在模型驱动领域,在系统设计的各个方面,我们都应该尽量充分的利用当前的信息通过推理 得到系统其他部分的结构,而不是通过手工关联或者判断在程序中动态维持这种对应关系。例如在flow-cp机制中,biz的id, action的id等都根据step配置的id推导得到,这样在工作列表跳转的时候就可以根据规则推导出跳转页面对应的链接,而不需要手工编写页面重定向 代码。




 
    同态(homomorphism)关系相对于同构关系,只强调单向映射的可行性,它是一个舍弃属性的过程。同态作为最基础的认知手段之一,它不仅仅是用一 个符号来置换一组元素,而是同时保留了某种全局的运算关系,因此同态映像可以构成某种独立的完整的研究对象。通过同态映射,我们可以在不同的抽象层面上研 究原系统的一个简化版本。例如meta中的layout是一种典型的领域特定语言(DSL)。
    userName userTitle
    emailAddress

每一个字段表示了一个可能任意复杂的inputor或者viewer, 字段之间的前后关系描述了最终显示页面上显示内容的相对关系。当viewer根据需求发生改变的时候,并不影响到layout层面上的关系,因此 layout可以保持不变。如果我们在系统中把问题分解为多个抽象层面上,多个观察视角上的同态模型,则可能实现更高的软件复用程度。
    在Witrix平台的设计中,很多细粒度的标签都定义为tpl文本段,这样平台只要理解某一层面上的交互关系,实际应用中可能出现的细节在标签内部进行局 部处理,不会突破原始设计的形式边界,不会影响到原先设定的主体系统结构。例如BizFlow中的tpls段,action的source段等。
    上世纪50年代以前,生物学家做梦也想象不到具有无限复杂性的生物遗传过程,竟然可以被抽象为ATGC四个抽象符号的串联。生命竟然不理会各种已知的或是 未知的物理化学作用,被抽象的三联码所驱动。一种抽象的本质似乎成了生命世界的本原。在软件的世界中,可以被识别的抽象元素绝不只是语言本身所提供的那些 机制。

posted @ 2009-02-28 16:57 canonical 阅读(1625) | 评论 (0)编辑 收藏

      有一个心理学实验,要求被试者将青草,公鸡,牛三个东西分 成两组,结果多数中国儿童将青草和牛分成一组,而多数美国儿童将公鸡和牛分成一组。中国人的思想中青草和牛之间存在现实的关系,牛吃草,而西方人的典型逻 辑是公鸡和牛都属于动物这一范畴。通过分类将物体类型化,这是西方人从小就接受的训练。据说美国婴儿学习名词的速度要快于动词,而中国的婴儿则相反,这并不是偶然的。

       中国人的传统哲学认为世界是普遍联系的,事物之间存在着祸福相依的辩证转化关系。而古希腊人强调个体意识,以两分法看待世界,他们将世界看成是孤立的物体组成(原子论)构成,然后选择一个孤立物体(脱离背景),开始研究它的各项属性,接着将属性泛化,构成分类的基础。西方语言中大量抽象概念都是从作为属性的形容词直接转化而来,例如 white --> whiteness 。而中文中很少有精确的类型定义,而多半是富有表现力的,隐喻性的词语,例如我们不谈论抽象的白,而只说雪白,没有抽象的 size ,而只说具体的大小。

       亚里士多德认为铁球在空气中下落是因为它具有“重性”,而木块在水中漂浮是因为木块具有“轻性”。这种将一切原因归结为事物内在属性的传统在一定程度上妨碍了西方人认识到背景的存在和作用,但使得他们可以把问题简化。

       古希腊人对于类型的热衷源于他们对于永恒的迷恋。静态的亘古不变的世界才是他们的思想栖息的场所。具体的物体是易逝的,多变的,只有抽象的类型才是永恒的存在,也只有抽象概念之间的关系才是永真的联系。而具体实例之间的关联在某种程度上被认为是不重要的,甚至是不可靠的。



       将具有某一属性的所有物体定义为一个集合,这一做法在上世纪初被发现会引起逻辑悖论,动摇了整个数学的基础,它绝不像表面上看起来那么单纯。但确定无疑的是,通过类型来把握不变的事实是一种非常重要且有效的认识策略。面向对象语言强调名词概 念,从引入类定义以及类之间的继承关系开始,这符合西方一贯的作风。而 Ruby 这种强调实例间关系的动态语言首先由日本人发明,可能也不是偶然的。虽然现在大家都在玩高科技了,可实际贩卖给你的多半仍然是包治百病的祖传秘方。文化可能造成认知上的一种偏执,在技术领域这一现象并没有被清楚的意识到。

 

posted @ 2009-02-21 19:43 canonical 阅读(1731) | 评论 (2)编辑 收藏

    软件开发作为一种工程技术,它所研究的一个重点就是如何才能有效降低软件产品的研发成本。在这一方向上,组件技术取得了空前的成功。它所提供的基本图景是 像搭积木一样从无到有的组装出最终的产品。在某种意义上,这是对现代建筑工业的模仿和致敬。新材料,预制件,框架结构,这些建筑学的进展在软件领域被一一 复制,建筑工地上的民工自然也成为了程序员们学习的楷模。毕竟,在组件的世界中码代码,基本上也是一种“搬砖”的行为。

    值得庆幸的是,软件开发作为一种智力活动,它天生具有一种“去民工化”的倾向。信息产品有着与物质世界产品完全不同的抽象本质。在物理空间中,建造100 栋房屋,必须付出100倍的努力,老老实实的干上100遍。而在概念空间中建造100栋房屋,我们却可以说其他房屋与第一栋一模一样,加点平移旋转变换即 可。一块砖填了地基就不能用来盖屋顶,而一段写好的代码却可以在任何可用的场合无损耗的被使用。一栋建好的房屋发现水管漏水要大动干戈,而在完成的软件中 补个局部bug却是小菜一碟。在抽象的世界中有效的进行生产,所依赖的不应该是大干,苦干的堆砌,而应该是发现某种可用于推导的原理,基于这些原理,输入 信息可以立刻转换为最终的结果,而不需要一个逐步构造的过程。即我们有可能超越组装性生产,实现某种类似于数学的原理性生产。http://canonical.javaeye.com/blog/325051

    代码复用是目前软件业中鼓吹降低生产成本的主要口号之一。但是在组件技术的指向下,一般所复用的只是用于构建的砖块,而并不是某种构造原理。即使在所有信 息已经确定的情况下,我们仍然不可能从需求立刻得到可执行的产品。很多代码即使我们在想象中已经历历在目,却仍然需要一行行把它们誊写下来。当我们发现系 统中已经没有任何组件值得抽象的时候,仍然留下来众多的工作需要机械化的执行。代码复用的理想国距离我们仍然非常的遥远。

    子例程(subroutine)是最早的代码重用机制。这就像是将昨天已经完成的工作录制下来,在需要的时候重新播放。函数(function)相对于子 例程更加强大。很多代码看起来并不一样,但是如果把其中的差异部分看作是变量,那么它们的结构就可以统一了。再加上一些判断和循环,很多面目迥异的东西其 实是存在着大量共享信息的。面向对象技术是一次飞跃性的发展。众多相关信息被打包到一个名称(类型)中,复用的粒度和复杂度都大大提升。派生类从基类继 承,可以通过重载实现对原有代码的细致调整。不过,这种方式仍然无法满足日益增长的复用需求。很多时候,一个名称并不足以标定我们最终需要的代码结构,在 实际使用的时候还需要补充更多的信息。类型参数化,即泛型技术,从事后的角度看其实是一种明显的解决方案。根据参数动态的生成基类自然可以吸纳更多的变 化。经历了所谓Modern C++的发展之后,我们现在已经非常明确,泛型并非仅仅能够实现类型共变,而是可以从类型参数中引入更丰富的结构信息,它的本质是一种代码生成的过程。http://canonical.javaeye.com/blog/57244 认清了这一点,它的扩展就非常明显了

   BaseClass<ArgClass> --> CodeGenerator<DSL>

DSL(或者某种模型对象)相对于普通的类型(Class),信息密度要大很多,它可以提供更丰富也更完整的输入信息。而CodeGenerator也不必拘泥于基础语言本身提供的各种编译机制,而是可以灵活应用各种文本生成技术。http://canonical.javaeye.com/blog/309395 CodeGenerator在这里所提供的正是根据输入模型推导出完整实现代码的构造原理。

    现在很多人热衷于开发自己的简易代码生成工具,这些工具也许可以在简单的情形下减轻一些体力工作,但是生成的代码一般不能直接满足需求,仍然需要手工执行 大量的删改工作。当代码生成之后,它成为一种固化的物质产品,不再能够随着代码生成工具的改进而同步改进,在长期的系统演化过程中,这些工具并不一定能够 减少累积的工作量。

   修正过程  ==> CodeGenerator<DSL>

为了改进以上代码生产过程,一些人试图在CodeGenerator中引入越来越多的可配置性,将各种变化的可能内置在构造原理中。显然这会造成CodeGenerator的不正常的肿胀。当更多的偶然性被包含在原理中的时候,必然会破坏原理的简单性和普适性。

   输入信息 + 一段用于推导的原理 + 修正补充 = 真实模型

必须存在[修正补充]这一项才能维持以上方程的持久平衡。

    为了摆脱人工修正过程,将模型调整纳入到概念世界中,我们需要超越继承机制的,更加强大的,新的技术手段。其实在当前的技术背景下,这一技术已经是呼之欲出了。这就是AOP, Aspect Oriented Programming。http://canonical.javaeye.com/blog/34941

      Biz ==[AOP extends]==> CodeGenerator<DSL>

继承仅仅能够实现同名方法之间的简单覆盖,而AOP所代表的技术原理却是在代码结构空间中进行任意复杂的删改操作,它潜在的能力等价于人工调整。

    为了实现上述生产模式,需要对编程语言,组件模型,框架设计等方面进行一系列改造。目前通用的AOP实现和元编程技术其实并不足以支持以上模式。http://canonical.javaeye.com/blog/275015
    这一生产模式将会如何演化,也是一个有趣的问题。按照级列理论,我们立刻可以得到如下发展方向:

    Context0 + DSL1 + EXT0 = DSL0  
    Context1 
+ DSL2 + EXT1 = DSL1 
   

 http://canonical.javaeye.com/blog/33824

Witrix平台中BizFlow可以看作是对DaoWebAction的修正模型,但是它本身具有完整的意义,可以直观的被理解。在BizFlow的基础上可以逐步建立SeqFlow,StateFlow等模型。http://canonical.javaeye.com/blog/126467

      现在有些人试图深挖函数式语言,利用模式匹配之类的概念,做符号空间的全局优化。但是我们需要认识到通用的机制是很少的,能够在通用语言背景下被明确提出 的问题更是很少的。只有在特定领域中,在掌握更多局部信息的情况下,我们才能提出丰富的问题,并作出一定背景下的解答。DSL的世界中待做的和可做的工作 很多。http://canonical.javaeye.com/blog/147065

      对于程序员而言,未来将变得越来越丰富而复杂,它将持续拷问我们的洞察力。我们不是一行行的编写代码,把需求一条条的翻译到某种实现上,而是不断发明局部的生产原理,依靠自己制定的规则在抽象的空间中不断的创造新的表象。

posted @ 2009-02-15 18:21 canonical 阅读(2063) | 评论 (2)编辑 收藏

       负数没有直接的几何意义,因此它被认为是对应于不存在的事物。而按照古希腊的逻辑,不存在的事物是不可能存在的,因而也就是无法被理解的,更不可能参与到 推理过程中,因此是无意义的,无法被定义的, 因此它是不存在的。中国人注重的是运算的合理性,而不是数的真理性,大概在公元前400年左右就创造了负数和零的概念。而在西方直到公元7世纪(唐代)的 一本印度人的著作中才出现负数,它被用来表示负债。西方人没有能够创造负数,他们对负数的接受更迟至15世纪左右。这件事实在一定程度上说明了存在某种深 刻的困难阻碍我们理解负数概念。
       在引入负数之前,3x^2 + 8 = 4x 和  3x^2 + 4x + 8 = 0 这两个方程的结构是完全不同的,它们需要不同的求解技术,因此也就不可能利用符号抽象出 a x^2 + b x + c = 0。引入负数才使得我们能够以统一的方式提出问题,并研究通用的求解技术。
      群论(Group Theory)是对结构进行抽象研究的数学分支。群的定义包括四条规则
1.    元素之间的运算满足结合律 (a * b) * c = a * (b * c)
2.    元素之间的运算封闭,即 a * b 仍然属于该群
3.    存在单位元,即对所有a, a * e = e*a = a
4.    每个元素存在对应的逆元,a * a^-1= e
      逆运算是非常重要的结构要求,逆元是对负数的一种抽象推广。如果没有逆元,则只能构成半群(semi-group),它的性质要少很多。


      目前软件设计中所有的原则都指向组装过程,从无到有,层层累进。构件组装的隐喻中所包含的图像是操纵实际可见的积木,是缺少逆元概念的。

      考察一个简单的例子,假设需要的产品是三角形内部挖去一个五边形的剩余部分。有三种生产策略:
1.    对最终需要的产品形态进行三角剖分,使用8个小三角形拼接出来。这种方式比较繁琐,而且最后粘接工序的可靠性和精确性值得怀疑。
2.    拿到一个真实的三角形,然后用刀在内部挖出一个五边形的洞。这种方式需要消耗一定的人工,而且也很难保证五边形的精确性,即使我们曾经精确的生产过其他五角形和三角形。实际上一般情况下我们是逐步锉出一个五边形的,并没有充分利用到五边形的对称性。
3.    在概念空间中做一个抽象计算  (-五边形) + (三角形) = 所需产品
如果我们能够生产一种负的五边形和一种正的三角形,则可以立刻得到最终的产品。



 
在软件开发的实践中,我们目前多数采用的是两种方式:
1.    采用可视化设计工具通过拖拽操作开发出完整的界面和后台
2.    拷贝一些已有的代码,删除掉不需要的部分,增加一些新的实现,也可能对已有实现做一些不兼容的修正。

在第二种方式中
           新结构的构造 = 已有结构 + 软件之外的由人执行的一个剪裁过程
这个剪裁过程表现为一个时间序列。如果我们对原有结构进行了调整,则需要重新关联一个时间序列,而此时间序列并不会自动重播。为了压缩以时间为度量单位的 生产成本,我们必须减少对时间序列的依赖。在时间序列中展开的一个构造过程可以被转化为一个高维设计空间中的一种更加丰富的构造原理,我们最终的观测可以 看作是设计空间向物理空间的一个投影(想象一束光打下来)。这种方式更容易保证程序的正确性。
          时间序列 --[原理转化]--> 空间关系


    这样我们就可以使用第三种生产策略:利用构造原理进行抽象计算。如果我们只盯着产品的最终形态看,只是想着怎么把它像搭积木一样搭建出来,就不可能识别出 系统结构本身所蕴含的对称性。如果我们发现了系统内蕴的结构特征,但是却只能通过构造过程中的行动序列来追随它,同样无法实现有效的工作复用。同时因为这 个行动序列一般处于系统规则约束之外,完全由人的自觉来保障,因此很难保证它的正确性。现实世界的规范要求并不是模型本身所必须满足的,只要我们能够创造 新的结构原理,在概念空间中我们就可以拥有更多的自由。现在业内鼓吹的软件构造原理多半是参照物理世界中生产具体物质产品的生产工序,却没有真正把握信息的抽象本质。掌握规则,制订规则,才是信息空间中的游戏规则。

    物理学中最重要的分析学思想之一是微扰论(Perturbation). 针对一个复杂的物理现象,首先建立一个全局的规范的模型,然后考虑各种微扰条件对原有模型的影响。在小扰动情况下,模型的变化部分往往可以被线性化,被局 域化,因而问题得到简化。微扰分析得到的解依赖于全局模型的解而存在,因而这是一种主从关系的分解方式。但是如果主体模型是我们已经熟知的物理现象,则我 们关注的重点可以全部放在扰动解上,认为所有特定的物理规律都体现在扰动解中。如果微扰分析得到的物理元素足够丰富,则微扰模型本身可以成为独立的研究对 象,在其中我们同样可以发现某种普适的结构规律。
    考察如下的构造过程
       X = a + b + c
       Y = a + b + d = (a + b + c) - c + d = X - c + d
    对于数学而言,上述的推导是等价的,但是对于物理学而言,Y = a + b + d 和  Y = X - c + d是有着本质不同的。第一种方式要求打破原先X的构造,而重新的组装其实是有成本的,特别是在X本身非常复杂的情况下。典型的,如果X是经过测试的功能, 重新组装后原先由测试保障的概念边界被打破。
         我们可以从Y = X + dX抽象出扰动模型  dX = - c + d
主从分解模式自然的导出逆元概念。

      如果没有逆元,我们必然需要分解。但是如果发掘了背景这一概念,在逆元运算下,对背景不是分解让其成为可见的部分,而是采用追加的,增删的方法对背景结构 进行修正,则我们有可能在没有完整背景知识的情况下,独立的理解局部变化的结构。即背景是透明的,知识成为局部的。在Witrix平台中,BizFlow + DaoWebAction + StdPage 才构成完整的程序模型,BizFlow其实是对标准模型的差异描述,但是它可以被单独的理解。如果我们从接触程序开始就接受BizFlow, 就可能完全不需要了解数据库访问和前台界面渲染的知识。我们并不是通过在DaoWebAction中设定各种可预见的调用形式,而是在BizFlow中通 过类似AOP的操作方式直接对标准模型进行修正。这种修正中一个很重要的部分就是删除标准模型中缺省提供的功能。
     WebMVC之前世今生 http://canonical.javaeye.com/blog/163196
     Witrix架构分析 http://canonical.javaeye.com/blog/126467


      变化的部分构成独立于原始模型的新的模型,它的结构关系是完备的,可以独立的理解。在原始模型崩溃的情况下,它仍然可能保持有效性。
      从物理学的角度看,我们所观测到的一切物理现象,都是某种物理作用的结果,也就是物质结构相对于背景状况的一种偏离。我们只可能观测到变化的部分,因此我们对世界的认识其实只是世界的扰动模型而已,世界的本体不属于科学研究的范畴。

posted @ 2009-02-07 22:22 canonical 阅读(1582) | 评论 (1)编辑 收藏

    软件技术的发展是一个结构化不断加深的过程,我们逐渐拥有了越来越丰富的结构识别, 表达和处理手段。在这一方向上, 组件技术最终取得了巨大的商业成功。但是区分同时也就意味着隔阂。面向对象技术最基础的概念在于 O = C(O), 对象的复合(Composition)仍然是对象.  然而在越来越复杂的软件生态环境中,这一图景实现的可能性被大大的压缩. 面对层层封装造成的形态各异的表达方式, 不同来源, 不同目标, 不同运行环境下的信息的交互变得越来越困难。我们逐渐丧失了概念上的简洁性, 也丧失了数学世界中的那种穿透一切的统一的力量. 所谓SOA(Serivce Oriented Architecture)技术试图通过补充更多的环境信息,放弃状态关联,暴露元知识等方式来突破现有的困境。 http://canonical.javaeye.com/blog/33803 这其中一项关键的技术抉择是基于文本格式进行信息表达。

      在过去10年中Web技术取得了空前的成功,它造就了互联网这一世界上最大的分布式集成应用。SOA从某种程度上说是对这一事实在技术层面上的反思。基于 文本传递的web技术所表现出来的开放性,可理解性和可观测性,与封闭的,难以直接解读的,必须拥有大量相关知识才能正确操作的二进制结构相比,这本身就 是革命性的创新。不需要特殊的工具就可以非常轻易的察看到网页程序的输入输出,所有交互过程都处在完全透明的检查下,各种不曾事先规划的文本处理手段都可 以参与到web创建的过程中。随着速度,存储不再是我们考虑的首要问题,文本化作为一种技术趋势逐渐变得明确起来。但是任何一种技术思想的成功或失败都不 可能是因为某种单一的原因所造成的,因此有必要对文本化的技术价值做更加细致的剖析。
  
   1. 文本化是一定程度上的去结构化,但是通过这种方式我们获得了对程序结构的更强的控制力。无论我们所关心的文本片断层层嵌套在大段文本的哪个部分,我们都可 以通过text.indexOf(subStr)来实现定位,通过text.replace(oldStr,newStr)实现变换。而在组件系统中,我 们只有通过某种预定的遍历方式逐步递归才有可能访问到组件内部的组成对象。如果某个组件不按照规范实现或者规范中缺少明确的设定,则这一信息关联链条将会 断裂。在一些组件系统中,甚至没有规定一种统一的遍历方式,则我们根本无法定义出通用的大范围的结构定位/变换手段。即使是在设计精良的组件框架中,受限 制于组件的封装性要求,我们也不可能访问到全部的信息。当我们以组件设计者意料之外的方式使用这些组件的时候,就会遇到重重障碍。即使所需的只是微小的局 部调整,因为无法触及到需要修改的部分,我们也可能被迫放弃整个组件。
  
   2. 文本是普适的信道。各种操作系统,各种程序语言所具有的最基本的能力就是文本处理能力,对于文本格式的约定是最容易达成共识的。虽然对于机器而言,理解基 于地址定位的二进制数字可能更加直接,但是所有的应用程序都是由人来负责编制,调试,部署,维护的,如果人可以不借助特殊的工具就可以明确了解到系统内发 生的过程,则系统的构建和维护成本就会大幅下降。
  
   3. 文本表述形式上的冗余性增强了系统的概念稳定性和局部可理解性。在二进制格式中,经常出现的是根据相对地址定位,这要求我们完整理解整个二进制结构,才能 够逐步定位到局部数据块。同时,二进制格式中也经常使用一些外部的信息,例如某个位置处的数据为整型,占用四个字节等。这样的信息可能只能在文档说明里查 到,而在数据体中没有任何的体现,这限制了独立的理解可能性。与此相反,文本格式经常是自说明式的,例如width:3.5px, 这提高了系统的可理解性,特别是局部可理解性。即使我们对系统只具有很少的知识,一般也能根据数据周围的相关信息进行局部操作。一般很容易就能够定位到特 殊的局部数据区,安全的跳过众多未知的或者不被关心的结构. 一个程序新手也可以在很短时间内靠着连蒙带猜, 实现xml格式的word文件中的书签替换功能,而要搞清楚word的二进制格式,并独立编制出正确的替换功能,显然就不是一两周的工作量可以解决的了. 这其中, 可理解性的差异是存在着数量级上的鸿沟的.
  
   4. xml这种半结构化的文本格式规范的兴起, 以通用的方式为文本描述引入了基本的形式约束, 实现了结构表达的均一性. C语言中的宏(Macro)本质上就是一种文本替换技术,它的威力在于没有预定义的语义, 因此可以超越其他语法成分, 破除现有语法无法跨越的限制. 但是它的危险性在于缺乏与其能力相适应的形式约束, 难以控制. 而在xml格式规范下, 不同语义, 不同抽象层面的节点可以共存在同一个形式体系中, 可以用通用的方式进行定位,校验, 转换等. Witrix平台在动态xml方面发展了一系列技术, 为文本处理引入了更多应用相关的规则, 增强了文本描述的抽象能力和表达能力. 
  
   5. 文本作为描述(description)而不是执行指令(execution). C语言的源代码与机器码基本上是一一对应的, 即源代码本身所表达的就是指令的执行过程. 而在Web应用领域, HTML语言仅仅是声明界面上需要展现什么, 但是如何去实现是由通用的解析引擎负责,它并不是我们关注的重点. 描述需要结合诠释(解释)才能够产生实际的运行效果, 才能对现实的物理世界产生影响.这在某种程度上实际上是延迟了执行过程. 一种描述可以对应多种诠释, 例如同样的元数据在前台可以用来生成界面,在后台可以用于生成数据库, 进行数据有效性校验等. 在特定的应用领域中,执行引擎可以是通用的, 可以独立于描述本身不断演化, 因此一种领域特定的描述,它所承载的内容并不是固化的, 而是可以随着执行引擎的升级不断增强的. 例如, 在Witrix平台中, FlowDSL本身所做出的流程描述是稳定的, 但是随着流程引擎不断改进,不断引入新的功能,所有使用DSL的已实现的应用都同步得到升级. http://canonical.javaeye.com/blog/275015
  
   6. Text = Process(Text) 这个不动点在Unix系统中得到了充分的应用: 多个小程序通过管道(Pipe)组合在一起, 可以完成相当复杂的功能. 一个更加丰富的处理模型是 XML = Process(XML). 文本描述很自然的支持多趟处理, 它使得我们可以充分利用全局知识(后续的处理过程可以使用前次处理过程收集的全局信息), 可以同时支持多个抽象层面(例如DSL的不断动态展开). Witrix平台中的编译期运行技术实际上就对应于如下简单规则: 编译期运行产生文本输出, 对输出文本再次进行编译. 通过这一递归模式, 可以简单的实现动态解析与静态描述之间的平衡. 模板(Template)技术是具有关键性作用的文本生成技术. out.write("<div>");out.write(value);out.write("</div>");这种 API拼接方式显然不如<div>${value}</div>这种模板生成方式直观且易于使用. 在Witrix平台的tpl模板语言中, xml的规范性使得在多趟编译过程中我们一直可以维持某种形式约束.
  
   7. 不是所有的情况下都应该使用文本. 普元EOS中所鼓吹的XML总线之类的技术是我所极力反对的. http://canonical.javaeye.com/blog/33794

posted @ 2009-01-04 00:55 canonical 阅读(1968) | 评论 (1)编辑 收藏

    代码生成(Code Generation)本身是一个非常宏大的概念。从某种意义上说,当我们明确了计算的意义之后,所做的一切都只是一系列代码生成的过程,最终的目标是生成某种可执行的机器码。对web程序员来说,代码生成是最熟悉不过的了,每天我们所做的工作就是JSP=>Servlet=>HTML。不过,现在多数人脑海中的代码生成,指的一般只是根据配置输出一个或多个程序文本的过程,最常见的是根据数据库模型生成增删改查相关代码。这种技术其实很少在小型以上的项目中起到积极的作用.因为一般的生成工具都没有实现追加功能,无法适应模型的增量修改。此外一般生成的代码相比于手工书写的代码要更加冗长,需要被直接理解的代码总量不降反升.为图一时之快,所要付出的是长期的维护成本。

   在应用开发中,有些领域是非常适合于使用代码生成技术的。例如根据领域模型生成ORM(对象-关系映射)描述,或者根据接口描述生成远程调用代理/存根 (Proxy/Stub)等。因为它们实际上只是对同一信息的不同技术形式或者不同技术层面的同义反复而已。这种生成最理想的方式是动态进行,可以随时保持模型的有效性。RoR(RubyOnRails)框架中ActiveRecord技术便是一个成功的范例,它甚至提供了动态生成的DAO函数,减少了一系列的包装调用过程。

   代码生成更加深刻的应用是完成高层模型向低层模型的转化,这一过程往往是非平凡(non-trivial)的。在Witrix平台中通过代码生成来支持领域抽象,可以用非常低的成本跨越结构障碍,将自定义的领域模型嵌入到现有的技术体系中。这其中我们的主要工作是解决了生成代码与手工书写代码之间的有效隔离及动态融合问题,确保代码生成可以反复的以增量的方式进行,同时支持最细粒度处对生成的代码进行定制调整。

   举一个简单的例子,假设现在需要开发一个三步审批的流程,每一步的操作人可以录入意见,可以选择通过或者回退,可以选择下一步操作的具体操作人,系统自动记录操作时间,每个操作人可以查看自己的操作历史等。虽然在现有技术体系中实现这一功能需要不少代码,但是在业务层面上描述这一功能并不需要很多文字,实际需要提供的信息量很小。显然,建立领域模型是比较适合的做法,可以定义一种DSL(Domain Specific Language)来描述这一模型。

   <flow_cp:SeqFlow>
     
<step id="draft" userField="draferId" dateField="draftTime" waitStatus="drafted" />
     
<step id="check" userField="checkerId" dateField="checkTime" opinionField="checkOpinion"
                   waitStatus
="sent" />
     
<step id="approve" userField="approverId" dateField="approveTime"
                opinionField
="approveOpinion" waitStatus="checked" passStatus="approved" />  
   
</flow_cp:SeqFlow>

   以上功能涉及到多个操作场景,实现的时候需要补充大量具体信息,其中很大一部分信息来自于背景知识,例如显示样式,界面布局,前后台通信方式等。以上模型可以进一步抽象为如下标签
   <flow_cp:StepFlow3/>

  在不同应用中复用以上流程逻辑的时候可能需要局部修正,例如
   <flow_cp:StepFlow3>
      
<step id="check" userField="checker" />
   
</flow_cp:StepFlow3>

   更加复杂的情形是DSL本身提供的抽象无法满足全部需求,而需要在局部补充更多模型之外的信息,例如物品接收单审批通过后自动导入库存等。
 
   在Witrix中,代码生成不是直接产生最终的输出,而是在编译期生成基础模型,它与补充描述通过extends算子进行融合运算之后产生最终输出, 这种融合可以实现基础功能的新增,更改或者删除。典型的调用形式为

  
<biz-flow>
       
<extends>
         
<flow_cp:StepFlow3>
           
<step id="check" userField="checker" />
          
</flow_cp:StepFlow3>
       
</extends>
       
       
<action id="pass_approve">
         .
       
</action>
   
</biz-flow>

这里的操作过程可以看作是BizFlow extends SeqFlow<FlowConfig extends StepFlow3Config>,与泛型技术非常类似,只是需要更强的局部结构控制能力。
    按照级列理论http://canonical.javaeye.com/blog/33824 ,我们可以定义一个DSL的级列,整个抽象过程为
     Context0 + DSL1 + EXT0 = DSL0
     Context1 + DSL2 + EXT1 = DSL1
       

    在目前一些通用语言中,也有一些所谓内嵌DSL的方案,可以提供比较简洁的业务描述。但是仅仅建立DSL描述是不充分的,从级列理论的观点看,我们必须提供一种DSL的补充手段,能够在细节处补充DSL模型之外的信息,实现两者的自然融合。同时我们应该可以在不同的抽象层面上独立的进行操作,例如在 DSL1和DSL2的层面上都可以通过类似继承的操作实现局部调整,这同时也包括在不同的抽象层面上都能对模型进行合法性校验。
   

posted @ 2008-11-23 11:57 canonical 阅读(1898) | 评论 (0)编辑 收藏

   软件系统的构建之所以与建筑工程不同,无法达到建筑工程的精确性和可控性,其中一个很重要的原因在于建筑的产物是一个静态的结构,建筑的过程主要是采用各种预制件填充某个规划好的建筑空间,而软件是一种动态运行的产品,它的各个组成部分之间的关系不是可以静态描述的,而是存在着复杂的交互关系,而且软件在运行的过程中还需要根据需求的变化进行动态的调整,这种动态性使得软件开发中很难抽象出固定的预制件,很难像建筑工程那样实现标准件的组装。现在所谓构件技术的构件插拔图景其实是具有误导性的。

    但是从另外一方面说,软件内在的动态性使得它可以具备更强的适应能力,如果所编制的软件把握住了业务机制的核心内容,则在运行过程中只需要进行少量调整就可以应对大量类似情况。100栋类似的建筑需要花费100倍的建造费用,而100个近似的软件需求的满足可能只需要花费2至3倍的开发费用。现代软件企业在研发过程中都在不断的追求自身产品的平台化,其目的正在于以不断提高的适应性来应对不断变化的客户需求。我们所面对的要求不仅仅是精确把握需求,而是要深刻把握需求背后所对应的业务机制。

posted @ 2008-09-01 23:24 canonical 阅读(448) | 评论 (2)编辑 收藏

  AOP(Apsect Oriented Programming)概念的正式出现也有一些时日了,但是它在程序构造过程中似乎仍未找到合适的切入点,一般系统的设计实现很少将AOP作为必要的技术元素。AOP作为一种普适的技术思想,它所代表的是程序结构空间中的定位和组装技术。http://canonical.javaeye.com/blog/34941 AOP使我们可以通过非侵入性的方式动态修改“任意”已经构建好的程序,而不需要事前有大量的设计准备。原则上说,这种技术思想是可以在任何程序语言基础上进行表达的,并不是只有java, C#这样的面向对象语言才允许AOP操作. Witrix平台中所应用的部分技术与AOP有些类似,只是大量的结构调整表现为xml生成和xml变换,在具体的使用方式上也有一些微妙的差异。http://canonical.javaeye.com/blog/126467

  相对于通用程序语言,xml语言其实是AOP技术的一个更加合适的形式载体。
1. xml格式特殊的规范性确保了在最细的逻辑粒度上,程序结构也是可识别的,可操纵的(在这一点上非常类似于函数式语言)。而所有的命令式语言(imperative language)中,函数内部的结构都是很难采用统一方式进行描述和定位的。
   <ns1:loop>
     
<rpt:Row/>
   
</ns1:loop>

2. xml节点的坐标可以采用xpath或者css选择符等通用方式进行描述,而一般程序结构无法达到xml格式这样的均一性,其中的坐标定位方式要复杂得多。
3. xml节点上可以增加任意属性,不同的属性可以属于不同的命名空间(namespace),这些属性可以辅助AOP的定位机制。而一般程序语言中如果没有Annotation机制, 则定位只能依赖于函数名和类名(函数参数只有类型没有名称),而类名和函数名随时可能因为业务变化而调整(不是专为定位而存在), 由此构建的切点描述符是不稳定的。
 
<ui:PageTable pager="${pager}" cache:timeout="1000" />
4. xml节点的增删改查显然要比字节码生成技术要简单和直观得多。
   

   AOP技术难以找到应用的一个重要原因在于很多人机械式的将它定位为一种横切技术,认为它的价值完全在于某个确定的切面可以插入到多个不同的切点,实现系统的横向分解。而在实际应用中,业务层面上很少具有可抽象的固定的共同性,我们所迫切需要的一般是对已有程序结构进行动态扩展的一种能力。横切是AOP的一种特殊的应用,但不是它的全部。相对于继承(inheritance)等依赖于概念诠释的结构扩展机制,AOP所代表正是对程序结构空间进行任意操纵的一种能力。AOP可以为基础结构增加功能,改变原有功能实现,也可以取消原有功能实现,它不需要把所有的扩展逻辑按照树形结构进行组织,不要求在基础结构中为扩展编写特殊的代码。这种自由的结构扩展能力在Witrix平台中被发展为“实现业务代码与平台基础架构之间的动态融合”。

   在Witrix平台的实际应用中,AOP的切点匹配能力并不是十分重要。一般情况下我们主要通过整体结构规划来确保控制点意义明确且相对集中,因此不需要额外通过切点匹配进行业务功能的再组织,不需要再次从杂乱的程序逻辑中重新发现特殊的控制点。例如在Witrix平台的Jsplet框架中所有后台事件响应都通过objectName和objectEvent参数触发,在触发后台事件响应函数之前都会调用bizflow文件中的beforeAction段。
   在bizflow文件中,aop操作是明确指定到具体函数的,使用模糊匹配在一般情况下只会使问题变得不必要的复杂化。例如扩展actQuery函数
   <action id="aop-Query-default">
    
<source>
       通过自定义标签抽象出多个Action之间的共用代码
      
<app:DoWorkA/>
    
</source>
  
</action>

   
   在Witrix平台中结构组装主要是通过自定义标签库和extends算子来实现,它们都依赖于xml格式的规范性。
1. 通过在custom目录下实现同名的自定义标签,即可覆盖Witrix平台所提供的缺省标签实现,这里所依赖的并不是复杂的匹配过程,而是自然直观的映射过程。http://canonical.javaeye.com/blog/196826
2. 所有的xml配置文件支持extends操作,它允许定制两个具有业务含义的xml节点之间的结构融合规则。例如<biz-flow extends="docflow">。

   实际使用中, AOP技术的一个应用难点在于状态空间的管理问题。一般interceptor中所能访问的变量局限为this指针所携带的成员变量,以及函数调用时传入的调用参数。interceptor很难在状态空间中创建新的变量,也很难读取在其他地方所产生的状态变量。例如对于如下扩展 A(arg1); B(arg2); C(arg3); =〉 Ax(arg1); B(arg2); Cx(arg3); 因为原有的调用序列中没有传递额外的参数,因此A和C的扩展函数之间很难实现共享内部变量x。在TPL模板语言中,tpl本身是无状态的,状态变量通过外部的$thisContext对象统一管理。通过这种行为与状态的分离,结合灵活的变量作用域控制机制,可以以比较简单的方式实现扩展函数之间的信息共享。

posted @ 2008-07-07 00:12 canonical 阅读(1666) | 评论 (0)编辑 收藏

    说到分解,很多人心中的意象大概只有正交分解。正交分解无疑是最重要的一种分析方法,它也是所谓“分而治之”思想最常见的实现策略。但是正交分解一般潜在的假定是分解后的子部分是大致均衡的,它们是相对具有独立价值的,可以彼此脱离独立发展。这是分解后实现系统解耦的重要原因。http://canonical.javaeye.com/blog/33885 但是物理学中另一种重要的分析学思想是微扰论(Perturbation). 针对一个复杂的物理现象,首先建立一个全局的规范的模型,然后考虑各种微扰条件对原有模型的影响。在小扰动情况下,模型的变化部分往往可以被线性化,被局域化,因而问题得到简化。微扰分析得到的解依赖于全局模型的解而存在,因而这是一种主从关系的分解方式。但是如果主体模型是我们已经熟知的物理现象,则我们关注的重点可以全部放在扰动解上,认为所有特定的物理规律都体现在扰动解中。如果微扰分析得到的物理元素足够丰富,则微扰模型本身可以成为独立的研究对象,在其中我们同样可以发现某种普适的结构规律。
    Witrix平台中系统化的应用主从分解模式,通过类似AOP的技术实现了业务模型与平台技术的自然结合。http://canonical.javaeye.com/blog/126467 最近我们的一个产品的新版本即将在全国范围内部署,如何有效的控制众多相近的二次开发版本,同时确保主版本的快速升级,是在架构层面必须解决的问题。http://canonical.javaeye.com/blog/73265 在Witrix平台中,各部署版本并不是直接修改主版本源代码得到,而是将差异化代码放在单独的目录中进行管理,由系统运行平台负责将差异化定制代码与主版本代码进行动态融合,实现部署版本的客户化。在这一过程中,系统模型本身支持逆元结构至关重要,否则某些多余的元素无法通过差异性描述去除,则将出现局部模型失效的情况。
    Witrix平台定义了特殊的_custom目录,它的内部目录结构与defaultroot目录相同,系统平台优先使用该目录下文件所提供的功能实现。同时定义了系统参数global.app_id和global.default_app_id,它们分别用来区分当前程序版本以及程序主版本代码。例如当global.app_id=beijing,global.default_app_id=main的时候,系统中装载ui.xml这个标签库时经历如下过程,
1.    装载平台内置的标签库,文件路径为 /_tpl/ui.xml.
2.    根据global.default_app_id设置,装载/_custom/main/_tpl/ui.xml, 其中定义的标签实现将覆盖平台缺省提供的标签实现。对于那些不需要特殊定制的标签,继续使用平台提供的缺省实现。
3.    根据global.app_id设置,装载/_custom/beijing/_tpl/ui.xml, 其中定义的标签实现将覆盖产品主版本的标签实现。

基础平台中对于代码动态融合定义了精细的融合策略,将通过编译技术检查扩展标签的接口与缺省实现的接口相兼容,由此确保代码扩展后不会破坏主版本中的已有调用代码。
    在基础平台的实现中,很多实现代码都是类似
          <df:WhenAllowFinishWf>
            
<df:FinishWfButton />
          
</df:WhenAllowFinishWf>

这样的类似废话的标签调用。但是通过这些标签的标记,我们确立了系统的逻辑结构,标定了系统中可以被安全替换的逻辑片断。

posted @ 2008-05-26 00:41 canonical 阅读(1716) | 评论 (0)编辑 收藏

     在与一些年岁较大的C程序员接触的过程中,可以比较明显的感受到C的思维方式与面向对象思想的不同。C的世界很清澈,先做A, 再做B, 我们所期待发生的计算过程与源代码的结构是直接一一对照的。这意味着程序将要执行的计算过程在编写代码的时刻就已经确定下来。面向对象首先需要确定的是类,对象等中间元素,而并不是最终的计算过程。对象可以之间可以产生很复杂的结构关系,透过这种中间逻辑结构我们来理解最终要发生的计算过程。在事件驱动的应用场景下,面向对象是一种更加有效的描述,
  o.someFunc()                  o.onEventA();
    sub1.someFunc();      ==>     sub1.onEventA();
    sub2.someFunc();              sub2.onEventB();
如果把对象看作是函数+状态的集合,则对象组装的关系实际上是函数集合之间的一种组装关系。当具体的事件发生的时候,将触发对象上确定的响应函数,此时在各个层面上所实际发生的计算才能被确定下来。



posted @ 2008-03-16 15:04 canonical 阅读(439) | 评论 (0)编辑 收藏

   所谓WebMVC即Model2模型是目前Web开发领域的主流模型,Struts/Struts2框架是其典型实现。在概念层面上,这种程序组织模型是怎样建立起来的?与其他Web开发模型(如面向对象模型)具有怎样的联系? 它未来可能的发展方向在哪里? 结合Witrix开发平台的具体实践,基于级列设计理论我们可以看到一条概念发展的脉络。http://canonical.javaeye.com/blog/33824

   1. 外部视角:原始的servlet规范提供了一个简单的面向IO的程序响应模型。一次前台访问由一个特定的servlet负责响应,它从request中读取输入流,在全局session中保持临时状态,向response中写入输出流。在此基础上,JSP提供的模板概念翻转了程序和输出文本之间的相对地位,简化了文本输出过程。至此,这种整体的程序模型基本上只是规范化了外部系统访问Web服务器的响应模型,并没有对后台程序的具体实现制定明确的约束条件。因此在最粗野的后台实现中,读取参数,业务处理,生成页面等处理步骤是纠缠在一起的,很难理解,也很难重用。每一个后台页面都是一个不可分析的整体。
<%
   String paramA 
= request.getParameter("paramA");
   ResultSet rsA 
= 
%>
   result 
= <%=rsA.getString(0%>
   String paramB 
= request.getParamter("paramB");
   ResultSet rsB 
= 
<%
   rsB.close();
   rsA.close();
   conn.close();
%>


2. 自发分离:在复杂的程序实践中,我们会自发的对业务处理代码和界面代码进行一定程度的分离。因为我们可以直观的感受到这两种代码的稳定性并不匹配。例如不同业务处理过程产生的结果都可以用一个html表格来展现,而同一个业务处理过程产生的结果页面可能经常发生变化。一般我们倾向于将业务代码写在页面上方,而界面代码写在页面下方,并使用一些原始的分解机制,例如include指令。这种分离是随意的,缺乏形式边界的。例如我们无法表达被包含的页面需要哪些参数,也难以避免全局变量名冲突。需要注意的是,分层的一般意义在于各个层面可以独立发展,它的隐含假定是各层面之间的交互是规范化的,只使用确定的数据结构,按照确定的方式进行交互。例如业务层和界面层通过标准的List/Map等数据结构交互,而不是使用具有无限多种样式的特殊的数据结构。(在弱类型语言环境中,实体对象的结构和Map是等价的).
<%
   List header 
= 
   List dataList 
= 
%>
<%@ include file="/show_table.jsp" %>


   3. 规范分离:JSP所提供的useBean和tag机制,即所谓的Model1模型,是对程序结构分离的一种规范化。业务代码封装在java类中,一般业务函数与web环境无关,即不使用request和response对象, 允许单元测试。tag机制可以看作是对include指令的增强,是一种代码重用机制。tld描述明确了调用tag时的约束关系。调用tag时需要就地指定调用参数,而include页面所依赖的参数可能是在此前任意地方指定的,是与功能实现分离的。此外tag所使用的参数名是局部对象上的属性名,从而避免了对全局变量的依赖。很遗憾的是,jsp tag所封装的仍然是原始的IO模型,对程序结构缺乏精细的定义,在概念层面上只是对文本片段的再加工,难以支撑复杂的控件结构。早期jsp tag无法利用jsp模板本身来构造,无法构成一个层层递进的概念抽象机制,更是让这种孱弱的重用模型雪上加霜。在其位却无能谋其政,这直接造成了整个j2ee前台界面抽象层的概念缺失,以致很多人认为一种前台模板重用机制是无用的。在Witrix平台中所定义的tpl模板语言,充分利用了xml的结构特点,结合编译期变换技术,成为Witrix平台中进行结构抽象的基本手段。实际上,xml能够有效表达的语义比一般人所想象的要多得多。
 <jsp:useBean id="myBiz" class="" />
  
<% List dataList = myBiz.process(paramA) %>
  
<ui:Table data="<%= dataList %>" />

  
  4. 框架分离:在Model1模型中,页面中存在着大量的粘结性代码,它们负责解析前台参数,进行类型转换和数据校验,定位特定的业务处理类,设置返回结果,控制页面跳转等。一种自然的想法是定义一个全局的程序框架,它根据集中的配置文件完成所有的粘结性操作。这也就是所谓面向action的WebMVC模型。这一模型实现了服务器端业务层和界面层在实现上的分离,但是对于外部访问者而言,它所暴露的仍然是原始的自动机模型:整个网站是一个庞大的自动机,每次访问都触发一个action,在action中可能更改自动机的状态(作为全局状态容器的session对象或者数据库)。struts作为面向action框架的先驱,它也很自然的成为了先烈。struts中所引入的FormBean, 链接管理等概念已经在实践中被证明是无益的。一些新兴的框架开始回归到通用的Map结构,直接指定跳转页面,或者利用CoC(Convention Over Configuration)缺省映射.
public class RegisterAction extends Action {
    
public ActionForward perform (ActionMapping mapping,
                                  ActionForm form,
                                  HttpServletRequest req,
                                  HttpServletResponse res)
{
    RegisterForm rf 
= (RegisterForm) form;
    
    
return mapping.findForward("success");
}

  
5. 横向延展:分层之后必然导向各个层面的独立发展,我们的视野自然也会扩大到单个页面之外,看到一个层面上更多元素之间的相互作用.在面向对象语言大行其道的今天,继承(inheritance)无疑是多数人首先想到的程序结构组织手段.后台action可以很自然的利用java语言自身的继承机制,配置文件中也可以定义类似的extends或者parent属性.但是对于前台页面一般却很少有适用的抽象手段,于是便有人致力于前台页面的对象语言化:首先将前台页面采用某种对象语言表达,然后再利用对象语言内置的结构抽象机制.放弃界面的可描述性,将其转化为某种活动对象,在我看来是一种错误的方向.而JSF(JavaServerFace)规范却似乎想在这个方向上越走越远.JSF早期设计中存在的一个严重问题是延续了面向对象语言中的状态与行为绑定的组织方式.这造成每次访问后台页面都要重建整个Component Tree, 无法实现页面结构的有效缓存.而Witrix平台中的tpl模板语言编译出的结构是无状态的,可以在多个用户之间重用.

  6. 相关聚合:对象化首先意味着相关性的局域化,它并不等价于对象语言化. 当面对一个大的集合的时候,最自然的管理手段便是分组聚合:紧密相关的元素被分配到同一分组,相关性被局域化到组内.例如,针对某个业务对象的增删改查操作可以看作属于同一分组. struts中的一个最佳实践是使用DispatchAction, 它根据一个额外的参数将调用请求映射到Action对象的子函数上.例如/book.do?dispatchMethod=add. 从外部看来,这种访问方式已经超越了原始的servlet响应模型,看起来颇有一些面向对象的样子,但也仅仅局限于样子而已.DispatchAction在struts框架中无疑只是一种权宜之计,它与form, navigation等都是不协调的,而且多个子函数之间并不共享任何状态变量(即不发生内部的相互作用),并不是真正对象化的组织方式.按照结构主义的观点,整体大于部分之和.当一组函数聚集在一起的时候,它们所催生的一个概念便是整体的表征:this指针.Witrix平台中的Jsplet框架是一个面向对象的Web框架,其中同属于一个对象的多个Action响应函数之间可以共享局部的状态变量(thisObj),而不仅仅是通过全局的session对象来发生无差别的全局关联.http://canonical.javaeye.com/blog/33873 需要注意的是,thisObj不仅仅聚集了后台的业务操作,它同时定义了前后台之间的一个标准状态共享机制,实现了前后台之间的聚合.而前台的add.jsp, view.jsp等页面也因此通过thisObj产生了状态关联,构成页面分组.为了更加明确的支持前台页面分组的概念,Witrix平台提供了其他一些辅助关联手段.例如标准页面中的按钮操作都集中在std.js中的stdPage对象上,因此只需要一条语句stdPage.mixin(DocflowOps);即可为docflow定制多个页面上的众多相关按钮操作.此外Witrix平台中定义了标准的url构建手段,它确保在多个页面间跳转的时候,所有以$字符为前缀的参数将被自动携带.从概念上说这是一种类似于cookie,但却更加灵活,更加面向应用的状态保持机制.

  class DaoWebAction extends WebContext{
     IEntityDao entityDao;
     String metaName;

     
public Object actQuery(){
       
       thisObj.put(
"pager",pager);
       
return success();
     }

     
public Object actExport(){
       Pager pager 
= (Pager)thisObj.get("pager");
       
       
return success();
     }
    }


  7. 描述分离:当明确定义了Action所聚集而成的对象结构之后,我们再次回到问题的原点:如何简化程序基元(对象)的构建?继承始终是一种可行的手段,但是它要求信息的组织结构是递进式的,而很多时候我们实际希望的组织方式只是简单的加和。通过明确定义的meta(元数据),从对象中分离出部分描述信息,在实践中被证明是一种有效的手段。同样的后台事件响应对象(ActionObject),同样的前台界面显示代码(PageGroup),配合不同的Meta,可以产生完全不同的行为结果, 表达不同的业务需求。http://canonical.javaeye.com/blog/114066 从概念上说,这可以看作是一种模板化过程或者是一种复杂的策略模式 ProductWebObject = DaoWebObject<ProductMeta>。当然限于技术实现的原因,在一般框架实现中,meta并不是通过泛型技术引入到Web对象中的。目前常见的开发实践中,经常可以看见类似BaseAction<T>, BaseManager<T>的基类,它们多半仅仅是为了自动实现类型检查。如果结合Annotation技术,则可以超越类型填充,部分达到Meta组合的效果。使用meta的另外一个副作用在于,meta提供了各个层面之间新的信息传递手段,它可以维系多个层面之间的共变(covariant)。例如在使用meta的情况下,后台代码调用requestVars(dsMeta.getUpdatableFields())得到提交参数,前台页面调用forEach dsMeta.getViewableFields()来生成界面. 则新增一个字段的时候,只需要在meta中修改一处,前后台即可实现同步更新,自动维持前后台概念的一致性。有趣的是,前后台在分离之后它们之间的关联变得更加丰富。

8. 切面分离: Meta一般用于引入外部的描述信息,很少直接改变对象的行为结构。AOP(Aspect Oriented Programming)概念的出现为程序结构的组织提供了新的技术手段。AOP可以看作是程序结构空间中定位技术和组装技术的结合,它比继承机制和模板机制更加灵活,也更加强大。http://canonical.javaeye.com/blog/34941 Witrix平台中通过类似AOP的BizFlow技术实现对DaoWebAction和前台界面的行为扩展,它可以在不扩展DaoWebAction类的情况下,增加/修正/减少web事件响应函数,增加/修正/减少前台界面展现元素。当前台发送的$bizId参数不同的时候,应用到WebObject上的行为切片也不同,从而可以自然的支持同一业务对象具有多个不同应用场景的情况(例如审核和拟制)。在BizFlow中定义了明确的实体化过程,前台提交的集合操作将被分解为针对单个实体的操作。例如前台提交objectEvent=Remove&id=1&id=2,将会调用两次<action id="Remove-default">操作。注意到AOP定位技术首先要求的就是良好的坐标定义, 实体化明确定义了实体操作边界,为实体相关切点的构造奠定了基础。http://canonical.javaeye.com/blog/33784

9. 背景消除:在Witrix平台中, (DaoWebAction + StdPageGroup + Meta + BizFlow)构成完整的程序模型,因此一般情况下并不需要继承DaoWebAction类,也不需要增加新的前台页面文件,而只需要在BizFlow文件中对修正部分进行描述即可。在某种程度上DaoWebAction+StdPageGroup所提供的CRUD(CreateReadUpdateDelete)模型成为了默认的背景知识。如果背景信息极少泄漏,则我们可以在较高抽象层次上进行工作,而不再理会原始的构造机制。例如在深度集成hibernate的情况下,很少会有必须使用SQL语句的需求。BizFlow是对实体相关的所有业务操作和所有页面展现的集中描述,在考虑到背景知识的情况下,它定义了一个完整的自给自足的程序模型。当我们的建模视角转移到BizFlow模型上时,可以发展出新的程序构造手段。例如BizFlow之间可以定义类似继承机制的extends算子,可以定义实体状态驱动的有限自动机,可以定义不同实体之间的钩稽关系(实体A发生变化的时候自动更新实体B上的相关属性),也可以定义对Workflow的自然嵌入机制。从表面上看,BizFlow似乎回归到了前后台大杂烩的最初场景(甚至更加严重,它同时描述了多个相关页面和多个相关操作),但是在分分合合的模型建立过程中,大量信息被分解到背景模型中,同时发展了各种高级结构抽象机制, 确保了我们注意力的关注点始终是有限的变化部分。而紧致的描述提高了信息密度,简化了程序构造过程。http://canonical.javaeye.com/blog/126467
  <bizflow extends="docflow"> <!-- 引入docflow模型,包括一系列界面修正和后台操作 -->
<biz id="my">
     
<tpls>
       
<tpl id="initTpl">
          
<script src="my_ops.js" ></script>
          
<script>
            stdPage.mixin(MyOps); // 引入多个页面上相关按钮对应的操作
          
</script>
       
</tpl>
     
</tpls>
    
</biz>
  
</bizflow>



posted @ 2008-02-18 22:02 canonical 阅读(1790) | 评论 (0)编辑 收藏

  自从离开学校就基本上不再使用C++了,最近却又因为项目上的原因重新走入这一迷失的世界, 感觉很是缺乏一些顺手的工具。首先就是做配置管理有点麻烦, 因为缺乏反射机制, 无法直接映射, 所以一般需要手工书写配置设置功能.
  我们希望配置类在配置阶段能够支持动态属性名,
  GConfig cfg;
  cfg.set(
"bgColor.b",3.0);
  cfg.set(
"lightEnabled",false);

  t_float b 
= cfg.get("bgColor.b");
  bool l 
= cfg.get("lightEnabled");

    但是内部使用时支持直接的属性访问,便于编译器检查, 也提高运算速度。
        t_float b = cfg.bgColor.b;
        bool l 
= cfg.lightEnabled;


所幸C++的类型系统能够偷偷的去干很多见不得人的勾当,因此便有了下面这个简易机制。

#define S_P(x) do{if(strcmp(name,#x) == 0) { x = value; return; } } while(0)
#define G_P(x) 
do{if(strcmp(name,#x) == 0) { value = x; return; } } while(0)

class _GConfig{
public:
  bool lightEnabled;

  t_float minX;
  t_float maxX;
  t_float minY;
  t_float maxY;

  _GConfig(){
    
// initialize all primitive members
    memset(this,0,sizeof(_GConfig));
  }
};

class GConfig: public _GConfig{
public:
  GColor bgColor;

  GConfig(){
  }

  _variant_t get(
const char* name){
    _variant_t value;
    get(name,value);
    
return value;
  }

  
void get(const char* name,_variant_t& value){
    G_P(lightEnabled);

    G_P(minX);
    G_P(maxX);
    G_P(minY);
    G_P(maxY);
   
    G_P(bgColor.r);
    G_P(bgColor.g);
    G_P(bgColor.b);
    G_P(bgColor.a);
  }

  
void set(const char* name, _variant_t value){
    S_P(lightEnabled);

    S_P(minX);
    S_P(maxX);
    S_P(minY);
    S_P(maxY);
   
    S_P(bgColor.r);
    S_P(bgColor.g);
    S_P(bgColor.b);
    S_P(bgColor.a);
  }
};

_variant_t是VC++在<comdef.h>中提供的对变体数据类型的封装。使用S_P和G_P这样的宏可以由编译器检查变量名的正确性。

posted @ 2008-01-12 20:58 canonical 阅读(1777) | 评论 (0)编辑 收藏

    关系数据库模型在理论上主要解决的是消除数据冗余的问题。关系模型的数学基础是所谓的集合论,而集合的基本含义正是一组具有某种原子性的互不相同的元素。面向对象技术是对相关性进行局域化的一种手段(相关的数据和操作聚集到同一对象名义下),在这一局域化过程中,相同的元素被识别出来,成为独立的对象。从某种意义上说,关系模型与对象模型是殊途同归的过程,是从不同侧面对同一事物的反映。关系模型中,我们关注的重点是元素组成的集合,允许的连接关系定义在集合之上。而在对象模型中,我们关注的首先是横向关联的实体,实体之间具有稳定的联系。在概念层面上,从对象模型映射到一种关系存储模型只是一个分组问题。为了断开实体之间的直接联系,关系模型创造了一个id字段,而对象模型并不是需要显式id的。在关系模型中,关联并不是通过某种存在的结构来表达的(一个实体持有另一个实体的指针,拥有直接联系),而是将直接关联问题弱化为某种计算过程,我们必须检查id的值(不是某种直接的存在性),通过某种运算过程才能重新发现数据之间的关联。
   
    通过id(伴随一个匹配计算过程)来进行间接关联对于保证模型的一致性是非常关键的。在ORM中恢复了对象的强关联其实会造成很多潜在的复杂性。例如为了维护对象层面结构的一致性,在更新父子关系的时候,我们需要同时调用 child.setParent(parent); parent.getChildren().remove(child); 当关联结构更加复杂的时候,这里所需要的维护工作是随之增加的。不过,在ORM中,对象的形态是暂时性的。在ORM的一次session的操作过程中,对于对象状态的修改可以是不一致的。例如我们可以只调用child.setParent(parent); 而不需要同时  parent.getChilren().remove(child); 只要我们在此次session操作中,不需要同时用到parent.getChildren(). 这种关联的暂时性对于很多ORM应用来说是必不可少的。
   
    对象模型中可以直接表达的结构关系比关系模型要丰富一些,例如继承关系,many-to-many, one-to-list等。但是所有这些都不是真正本质性的差异。抛弃概念诠释,基类与众多派生类之间的关系基本上可以等价于一组one-to-one关系。而当关联对象本身的重要性凸现出来的时候,当我们无法把它约化为对象上的一些附属特性的时候(例如数组的下标),我们必然要建立相应的关联对象,而这正对应于关系模型中的中间关联表。中间关联表上增加额外的字段是一个自然的扩展过程,而对象模型上做这样的扩充往往表现为形态上的重大的不兼容的变化,例如从getManyToManyEntity() -> getToManyRelation(), 这实际上意味着这里的对象形式是偶然的,简化的。
   
    在原始的关系数据库模型中,所有的表之间的地位是平等的,所有字段之间的地位是平等的(主键和外键在参与数据关联时和其他字段的处理方式一致)。这种概念上的均一性和普遍性往往被认为是理论的优美之处。但是现实世界是复杂的,发展的方向就是逐步识别出不同之处,并找出自然的表达形式将这些不同表达出来。均匀的关系模型是对称性最高的,最简化的模型。在面对物理约束时,它隐含的假设是集合之间很少发生相互作用,单表(表单到数据表之间的映射)和主从表是最广泛的情况。试着想象一下关系模型,在思维中一般我们只能看到两个数据表,当考虑到多个表的时候,因为这些表之间没有明确的可区分性,因此它们的意象是模糊的。只有明确意识到主键,外键,主表,从表,字典表,事实表,纬度表这些不同的概念的时候,当对称性出现破缺的时候,我们思维中的模型才能够丰富化起来。
   
    关系模型理论应用到数据库具体应用中时,并不需要死守关系范式教条,它们只是描述了某种极端化的对唯一性的追求。面对具体应用的时候,理论本身也在不断丰富化。我并不把现实应用中必然需要增加冗余字段看作是关系理论失效的结果。从关系完全分解,到关系完全不分解之间,我们可以建立大量的模型。建立冗余字段的时候,我们存在着大量可能的选择,到底哪一种选择是最优的,理论方面仍然可以给我们以具体的指导。理论在各种不同纯化程度的关系模型中都可以给我们以直观的建议。数据仓库理论中建立的snowflake模式和star模式,强调了针对主题域的允许部分冗余的关系分解。这里实际上是强调了表之间的不同性。不再是所有的表都处于同一地位。Fact Table和Dimension Table之间的区别被识别出来,并被明确处理。在我看来,这是原始关系模型的一种自然发展,它也是关系模型理论的一部分。理论不应该是单一的,而是提供一个模型级列,在不同的复杂性层次上,我们可以根据理论的指导选择具体的实现模型。
   
    关于ORM http://canonical.javaeye.com/blog/111500
    关系模型中的所谓关系是在使用时刻才定义的,所有建立关系的方式在某种程度上都是等价的,也是外在的。而在ORM中主键与外键之间的关联被独立出来,成为模型内置的部分。这在很多时候简化了数据查询的结构构造过程。
    在ORM中主键因为缓存的存在而显出与其他字段的区别。ORM的使用使得数据存储的分解策略得到扩充。并不是所有的表的更新频度都是一致的,而且表中的数据量大小也不同。字典表一般较小,而且很少更新,可以安全的复制。在整个数据存储框架中,ORM作为独立的技术元素参与数据存储过程,通过主键提供缓存服务,产生了新的数据分布模型,提供了新的性能优化契机。


posted @ 2008-01-06 19:04 canonical 阅读(3149) | 评论 (3)编辑 收藏

    我平时Kill Time的主要方式是阅读各类学术书籍。但是学习本身只是了解前人的发现,间或锻炼一下自己的思维能力,对自己的工作和生活并没有什么直接的助益。学习本身无论如何深入,多半也只是采用前人的话语复现前人思考的历程。在我们通过独立的思考获得直观的体验之前,在我们将所学到的知识应用到书本之外的场景之前,我们所学习的知识只是考试的工具,只是可供欣赏的对象,却不能成为我们思考中活跃的因素,无法参与我们思维的进程。别人只能给你思想的引子,并不能给你真正的思想。只有自己找到了所学知识的超出书本的非显然的应用,只有独立建立了不同概念之间未曾阐明的联系,我们才能真正获得对于真理的体悟。无论我们自己的发现是如何的微不足道,无论它是否只是重复发现了劣质的轮子,它唯一的意义只在于它是我们独立思考的结果,是我们自己的创造。即使它是卑微的,是重复的,它对我们的理解的作用仍然是任何外在的教诲都无法比拟的。

    现代社会中创造所需的成本已经被空前降低了。想想百年前的天才们,他们缺少信息来源,只能一页页翻查文献,反复誊写文稿来保存知识,大量的时间被花费在了与思考完全无关的事情上。同时,他们所处的时代存在着更多的不确知性,他们的思考所能够凭依的事实更少,做出错误判断的可能性与今天相比也更大。即使Bill Gates这样的挣钱能手在1981年也能放出"640k ought to be enough for anybody"的厥词,显示出我们在预测未来的时候是何等的乏力。当我们今天站在人类文明的巅峰,拥有前人无法想象的工具,并不断制造着从未经历过的实践的时候,我们理应拥有超越历史上任何天才的,更加宽广的眼界。

    现代社会中的创造看似简单了,但从另一个方面看,却又是大大的复杂化了。前人的成就成为了难以逾越的丰碑,而为了进入科学殿堂,我们需要的准备工作也变得异常的繁复。这里有科学内在的规律,但也有人为制造的障碍,而这其中最主要的是数学障碍。只要想一想,针对软件工程,经济运行,企业管理,每个参与其中的实践者都可以提出一些自己的意见,但是如果涉及到数学,为什么大多数人只有三缄其口了?现代抽象数学取得了辉煌的成就,但是它也可能毁掉了更多学生创造的激情。宏伟精深的大厦让人敬畏,却无法激发我们任何直观的共鸣。甚至Arnold这样的数学大师也坦承读不懂当代数学家们的著述:因为他们从不说“彼嘉洗了手”,而只是写道:存在一个t1<0,使得t1在自然的映射t1->彼嘉(t1)之下的像属于脏手组成的集合,并且还存在一个t2,t1<t2<=0,使得t2在上面提到的映射之下的像属于前一句中定义的集合的补集。当Bourbaki学派致力于在课本上消灭所有图示的时候,理性达到了非理性的彼岸。

    有时我在想为什么现在的程序员似乎对于程序的理解能力降低了。排除教学水平的降低和个人努力的不足之外,是否是因为现在需要学习的内容过多,以至于丧失了自我思考的勇气?在C的时代,每个程序员对于程序的理解都是直接的,原始的,对程序结构的把握都是充满自信的。当新的概念不断涌现的时候,人们总是说,Object不过是..., Component不过是..., AOP不过是..., ORM不过是..., IoC不过是.... 这体现了人们试图把新的概念融入自己原有知识体系的一种努力。虽然仔细考究起来,这里的理解很多时候都是似是而非的,未必掌握了新技术真正创新的思想方向,但是这里的思考总是独立进行的,总是对我们的理解和工作有所助益的。而新一代的程序员生活在Object, Pattern等概念已经无需饶舌来证明自己的时代,他们是否在思想中独自评估过所有概念的意义,是否建立了概念和实现之间直观的联系,是否在统一的思维世界中为所有的概念找到了合适的坐标?这一切,我不得而知。
    
推荐:Arnold 论数学教育 http://www.ieee.org.cn/dispbbs.asp?boardID=64&ID=25892

posted @ 2007-12-24 01:10 canonical 阅读(2012) | 评论 (6)编辑 收藏

    我在各种场合一直都在强调结构问题是独立的,在程序语言之外存在着独立的,可研究的,富有成效的结构问题。http://canonical.javaeye.com/blog/147424 在这个方向上更进一步,我们注意到所有的代码并不是天然出现的,而是由人所编制的,因此代码世界内部并不构成封闭的,自足的某个世界。代码中的结构问题并不是由代码本身完全解决的,即在代码之外仍然存在着技术上可研究的结构问题。
    我们在编制代码的同时也在编制着大量的说明文档。这些文档描述了代码片断之间的相互关系,描述了代码未来的扩展方向,描述了代码之间的可能的交互方式,同时也描述了针对现有代码实现的很多具体约束。例如我们在文档中约定某个量要在10和20之间,但在代码中却不一定显式进行了判断。针对代码结构的很多具体约束条件和相关性描述可能只在文档中体现,只在程序员的头脑中存在,而并不一定忠实的在代码结构中得到表达。
    我在设计领域基本持有一种物理实在论,即某种技术相关的约束应该在技术世界中通过技术手段得到表达。只是这里的技术手段却不一定指在应用中加上特定的代码实现,虽然我们在代码实现中更直接的表达设计要求无疑是需要提倡的。为了在程序中有效的维护结构相关性,我们并不一定需要抽象出所有可能重用的代码,并不一定需要确保某一概念在程序中只有精确的唯一的表达。程序中难以直接精确表达的弱关联,仍然可以通过开发/设计工具等技术手段得到有效的维护。我们需要保证的是代码世界中知识的自恰性,而自恰性并不等于唯一性。http://canonical.javaeye.com/blog/33788
    在Witrix中我们采用一种物理模型驱动的开发方式,http://canonical.javaeye.com/blog/29412 由pdm模型出发,自动生成hibernate的hbm文件,java实体类,元数据meta文件,前台Action注册文件等。生成的配置文件通过syncWithModel标记与模型保持着稳定的关联。所有配置文件都支持手工修改,开发工具识别syncWithModel标记,当pdm模型发生变化的时候,工具自动将变化信息同步到各个配置文件中。注意到这里并不是采用一个统一的元数据模型的方式,各个配置文件所表达的信息在一定程度上是重复的,也可能是不一致的。例如后台数据库允许保存100个字节,但是前台meta中我们可能配置只允许录入20个字节。根据不同应用场景的需要,我们可以在各个层面对每个配置文件进行独立的调节. 当然,在一般情况下并不存在这种需要。整个开发过程中,信息表达的自恰性并不是在应用程序代码中得到定义的,而是因为开发工具的存在而在技术上得到保证的。放松模型之间的唯一匹配要求,我们可以得到更加丰富,更加灵活的软件结构。实际上我认为RoR(RubyOnRails)采用直接映射的ActiveRecord的方式虽然有效保证了系统变动中知识的一致性,但是如果不允许在各个层面上都能够自然的定义某种偏离,它在复杂应用中的价值就要打上大大的折扣。

posted @ 2007-12-15 19:46 canonical 阅读(1395) | 评论 (0)编辑 收藏

   设计考虑的是最终需要什么,最终我们要提供的调用接口是什么,我们所直接需要的某个有价值的,直接存在的,直接可以接触的结构是什么,而不是它所依据的原理是什么,不是它的具体构造过程或者构造方法是什么。比如说我们在程序中完全不需要内置Map这一结构,因为它可以通过列表等结构组合构建出来,但是很显然,Map这一概念的直接存在对我们来说是方便的,是经济的,是有效的。我们在思考的时候并不需要考虑它是采用List实现还是采用Set实现,或者任何它本身的构造结构。这一概念在我们的思想中成为某种原子化的东西。

    那么,我们到底需要构建哪些概念,才能够最方便的基于这些概念应对万千应用系统的开发呢。 这是我们需要在结构空间作出探索的。 这里的思维方向不是把系统推向某种纯粹化,某种极致的简单化,而是让它更加物理化,揭示出更多的层次,更加关切在物理约束情况下如何实现灵活性的最大化。一种概念在物理上如果被证明能够在很多场景下成为不变的基元,则它便是有价值的,是可以进行物理诠释的。

   很多人习惯于接受语言存在的现实,接受设计后的结果,但是作为程序语言设计者,他们又是如何工作的?他们是否真的是从纯粹的数学关系推演得到所有的语法特征,还是他们预先已经在心中设定了应该出现的语法特征,然后去寻找它的数学表达,只是借此清除思维中潜在存在的矛盾性呢?

    语言中存在的所有特征是经过全局考虑的,是清除了所有概念的矛盾冲突的。但是在现实中,我们偶然构造的结构却可能是局限于当下的信息构造的,因此它们相会的时候,可能会出现不协调,可能会出现结构障碍。例如同样是关闭操作,有些人命名为close, 另一些人命名为destroy. 可能一个具有额外参数,另外一个没有。这里可能需要一种adaptor接口的封装,也可能使用ruby那种method-missing的动态判断。对于更加错综复杂的结构问题,其解决方案就不是那么显然的了,但这并不意味着我们无办法可想。究竟设计何种结构边界才能最小化结构融合时所要付出的代价呢?结构被识别并表征出来以后,是否允许它在一定范围内有所变形?在变形中我们需要保持的拓扑不变量是什么?结构动态调整的时候,我们是否需要定义调整的物理代价,是否能够定义某种动力学?

   我所阐述的只是在计算机理论中从数学视角向物理视角的转换,它不是必然给你提供某种超越当下的能力,而是提供一种不同的眼光看待所有的一切。视角变换后,我们发现了一些新的命题,而在原先的视角下在我们的话语体系中原本是无法表达这些命题的。串行程序假设了只有1颗CPU, 而函数式语言假设了可以有无限多个CPU, 你不觉得1至无穷之间缺点什么吗。我们可以创造一些东西把1至无穷之间的空白补齐,概念空间是连续的。
 

posted @ 2007-12-10 23:57 canonical 阅读(1284) | 评论 (1)编辑 收藏

    没有人否认抽象的意义,但是抽象是否就是抽象到无穷大,这是个可以明确定义的问题,也是数学领域正在解决的问题。在我们的思考中没有明确定义何处是边界, 没有明确的限制,这便是导向无穷的一种思维方式,它和现实中是否真的允许消耗无限多的资源,创建无限多的对象无关。当我们认为自己明白了终极的意义,明白 了一种推向无穷的抽象,这并不是理解了世界的全部,我们仍然要明白如何解决一些更加小范围,但是却又普遍发生的事情。
例如现在我的系统中只需要10个相互依赖的线程,如果我们定死了10这个数字,显然我们可以发展一种这个领域特有的高效的一些算法结构。而抽象到通用语言 中的时候,显然我们只能假设线程数是任意大,或者是充分大的,而无法充分利用10这一领域信息,因此在这个意义上我说通用语言不是有效的。
说到10这个确定的数字,不过是一种极端化的比喻。我常说概念是连续的,并不是非此即彼的,因此并不是从一种普遍情况到一种最特殊的情况之间不再有其他情 况了。在这中间环节,存在着非常深刻的复杂的物理事实。但是这些事实却又是某种有限性向我们揭示出来的。(请不要把这里的有限性理解为算术中的10以内)

现在来一个理论推演吧:
1. 任何系统都在一定约束下运行,即它们需要符合某些约束条件
2. 通用语言描述了某些结构,但是这些结构是充分通用的,能够应用到尽可能广泛的领域的
3. 线程数=10这个约束过份特殊,显然通用语言是不会考虑这个约束。实际上目前在通用语言设计中,无限资源假定基本都是默认的。
4. 我们承认某些现实的约束通用语言是不会考虑的
5. 在最特殊的,明显不会考虑的约束以及非常通用,一般通用语言必然考虑的约束之间,是否存在着更多的,非平凡的结构呢
6. 假如10年以内我们所有的硬件都只能支持10个内核,在我的产品研发中假定10个线程有问题吗。难道我在开发的时候就不再需要抽象了吗。我在其他方面仍然是需要建立抽象的。
7. n个抽象约束+1个具体约束,以及 n个无限约束+1个有限约束 仍然是有效的抽象形式。原谅我在这里又使用了有效一词,它真的很难理解吗。
8. 不是在我们的思维中存在着具体的或者有限的物理量,就意味着这种思维是不抽象的.

函数式语言或者任何一种其他我们已知的与Turing机等价的语言,它们在某种数学的含义上说是"没有差别的"。但是在我们的实际使用过程中,显然我们是 能够感受到它们之间的结构差异的。否则那些不断发明新的函数式语言的人的大脑都进水了吗?在具体使用中,总有人偏好这个语言,有人偏好那个语言, 总是某种情况下应用某个语言会方便一些,另一些麻烦一些。难道在所有函数式语言中开发类似ErLang解决的那些程序结构都是一样方便的吗?

有些人的论调是无论啥都逃不出函数式语言的思想。但是假如现在限定你必须使用java语言来开发商业应用,难道你罢工吗?如果你不使用函数式语言,你所做 的工作就不是程序工作了?你所解决的难道不是程序结构问题了吗?现在就是有一个结构问题要解决, 它是和语言无关的. 语言提供了就可以直接用, 语言没有提供我们可以写代码构造. 难道除了语言直接体现的结构之外, 程序本身就无法构造任何具有通用价值的结构了吗?

我在说到函数式语言的时候,基本的态度只是说不要太沉迷于一种特殊的抽象方式,应该多看看别的视角。在不同的情况下存在着做事情的不同的最优方式。思维中不要只允许永远,而容不下现在。

非此即彼,天下唯我,是我们思维中经常陷入的一个误区。现在计算机领域所谓的理论主要是基于数学视角的,没有考虑物理世界因为我们观察的有限性,因为资源 的有限性所造成的种种约束,此外数学中目前也没有考虑到物理世界真实的各种复杂性的存在。在我们想到一种计算机理论的时候,图像过于简单化,这种简单化被 认为是优美的全部。其实我们应该思维更加开放一些。

posted @ 2007-12-09 22:25 canonical 阅读(1169) | 评论 (5)编辑 收藏

    数学上的有效性与物理中的有效性是不同的,例如对于密码学问题,如果通过穷举法破解密码成功时,经过这些密码加密的数据已经过了有效期限,此时我们在数学上定义穷举法不是一种有效的破解方法。但是物理层面上我们说只要一种方法比另一种方法能够更快的解决问题,我们就说第一种方法比第二种方法有效,而无论密码被破解的时候该密码是否已经过了有效期限。

    我所表述的论题并不是说特定的领域结构无法在某个特定的通用语言中有效实现。我想很多人对我的话语都有些误解。
如果我们认为一种通用语言是比较稳定的,则它一般选择只内置一些通用的不带有领域特定含义的概念. 而缺乏领域知识,或者说因为通用语言故意的摒弃领域依赖, 它在处理领域相关的问题的时候并不是有效的.这种有效性不是数学含义上的,而是可以进行物理度量的.
现在ErLang对通信领域具有良好的支持,你可以说它对于通信领域的结构是有效的。但是显然在ErLang中编写界面就不如面向对象语言得心应手。在ErLang中实现界面结构的时候,它对于界面结构的表述就不是那么符合我们直观的,对我们的实现过程来说就不是那么经济的。因此在界面结构的实现上,目前我们可以说ErLang相对于面向对象语言而言就是不那么有效的。也许你会说ErLang做XX发展之后怎见得就更差。但是如果允许引入未来这一具有无限可能性的因子,我们基本上无法针对现实的情况作出判断。例如我们目前并无法证明广义相对论相对于牛顿力学是更加精确的,如果允许在太阳星系中增加越来越多的隐蔽的摄动星体的话。按照库恩的科学革命论,每一个科学时代都具有着自己的科学范式,它总是具有着充分的自我辩护能力。范式的更新意味着格式塔的崩溃。回顾历史,哥白尼刚提出日心说的时候,并不是在计算精度,计算简洁性上真的远胜托勒密的地心说,只是日心说的哲学隐喻撼动了人心。

    我说实际上现在的通用语言也是无法有效承载Domain Specific Structure的,这并不是意指在通用语言中无法针对特定应用作出特定扩展来支持特定的结构,而是说Domain Specific Structure是任意多的,作为通用语言它不应该把越来越多的结构内置在语言中(这不是很多人对ruby的希冀吗),这么做对它来说首先是不经济的。同时某些特殊的结构在一定的场景下是有用的,但是把它抽象出来扩展到通用领域的时候,会出现有效性的丧失。例如现在我的系统中只需要10个相互依赖的线程,如果我们定死了10这个数字,显然我们可以发展一种这个领域特有的高效的一些算法结构。而抽象到通用语言中的时候,显然我们只能假设线程数是任意大,或者是充分大的,而无法充分利用10这一领域信息,因此在这个意义上我说通用语言不是有效的。

    传统上数学使用的一种逼近范式是:当n趋于无穷大的时候,偏差趋于无穷小。现在物理学对数学的一种常见要求却是:当n限定在有限数量范围的时候(例如10以内),我们如何才能尽量减少偏差。这要求对小样本数学进行深入的研究,它所具有的物理内涵也是不同的。

    在物理的视角下,我们所关心的不是世界在终极的意义上能否分解为函数的复合,不是要导向一种宗教式的顶礼膜拜,而是强调要尊重自己所直接感受到的,充分利用我们因为在这个世界上存在而获得的直观意象,发掘自己的直觉,这样我们才能在无限复杂的世界上借助有限的信息做出选择。

posted @ 2007-12-09 17:19 canonical 阅读(1342) | 评论 (1)编辑 收藏

   因为在认识上物质和意识的两分,传统上哲学便分为唯物和唯心两大流派。唯物主义因为非哲学的缘故在国内占据主导地位。但在哲学上却从未平息过思想的纷争。胡塞尔的现象学试图重新回归形而上学,摒弃物质和意识的二元论,将它们统一到现象这个一元概念之中。我个人的思想基本是唯物主义的(毕竟少年时受过太多官方的教育),但是对我们目前所掌握的所有知识却持有温和的怀疑。
  
   当一个非哲学家说到“客观规律是客观存在的”的时候,这是以他者的态度,从上帝的第三只眼看世界。但是我们此时此刻却存在于此一世界,因而不可避免的要卷入这滚滚红尘当中。为什么我们在应用分析和综合方法之后,能够通过螺旋式上升的路径逼近终极的真理?这就如同说一个迭代总会收敛,而且必然收敛在正确的结果之上。我们所认识到的一切为什么不能是为了应用方便而不自觉采用的机会主义的产物?所有可能的哲学都是我们在此生有限的存在中所无法证实或证伪的。当我开始质疑为什么我们不能是离(我们在有限时空范围内认为的)本质愈近,离真实愈远的时候,这不过是用一种无意义置换了另一种无意义,但是一个人的观点却因为他的情感的注入而对他显出不同。我们如何才能有勇气,有力量,有方法在有限的时空中创造出变化?
   
    人类千年的智慧传承到我们这些普通的接受者手中的时候,它们都不可避免的陈腐了,庸俗了。当我们在说到这些真理的时候,我们的喉舌已经脱离和思想的联系。这些永真的话语在唇齿间跳动的时候甚至不能在我们的思想中激起微小的涟漪,它们不过是鹦鹉学舌。

    道是无情却有情。有人说我们对规律的揭示是分析,实验和直觉的产物。可是直觉是什么?这里的逻辑类似于“如果A则B,否则其他一切情况则X"。所谓的直觉是一切非直觉的补集,实际上涵盖了我们所有丰富的情感历程和生存体验。理性却需要非理性来拯救。我们的行为总是受到情感的驱使。例如我在blog上写下一篇关于函数式语言的文章,一些技术爱好者纷纷发表议论,这是否是理智推演的结果?来的时候一腔热情,去的时候是否真的带走了什么事实?这样的事情每时每刻都在我们的世界中发生。我现在并不喜欢"XX之道"的提法,其实所论与道无关,只是利用了传统上对于道的敬畏,绑架了我们真挚的情感,却没有带来任何真实的体验。多情使得我们成为被忽悠的对象。
   
    活着,用海德格尔的话说,一个字“烦”,用萨特的话说,两个字“恶心”,用普罗大众的话说,三个字“不想死”。但如何去活?中国人将个人的命运化入了家国的绵延,按李泽厚的话说,实现了心理学和伦理学的统一,在生的途中实现对情感的构建和塑造,这并不是单纯的个人生命体验,也不是宗教中神秘性的情感迷狂或心灵净化。


posted @ 2007-12-09 15:33 canonical 阅读(1402) | 评论 (4)编辑 收藏

    我在前面的文章中列举了大量物理学相关的例子来试图说明采用物理视角的必要性,但是可能因为物理事实大家不熟悉,结果直接被无视了. 在本文中我想有必要举一个软件领域的例子。只是在实际思考的过程中,我主要还是基于物理概念进行推理.
   
    首先我所谓“现在的通用语言”,它并不意指“现在至未来所有通用语言之合集”,而是指“目前正在被使用的某一种通用语言”,这种差别便体现了我所强调的不同的价值观和不同的视角。不是一种覆盖一切的全称判断,而是在特定物理约束下的物理实体。
   
    现在无论我们设计什么大型系统,一般总是要优先考虑微内核设计。但是很显然,如果我们的编程控制能力极强(强大到不现实的地步),我们可以把所有的代码实现为一个大的整体。一个整体的好处是勿用质疑的,否则Linux Torvalds就不会有信心和Tanenbaum PK。但即使是Linux, 随着系统越来越庞大,在内核中也补充了很多模块管理策略。我并不把这种情况看作是一种现在技术能力不到位所造成的结果,而是把它看作是在现实的物理约束下所促成的一种必然的选择。
   
    按照类似的逻辑,我认为在通用语言层面不应该导入越来越多的特征,实际上也不可能把所有可能的结构方式都内置在语言中(这种不可能不是数学意义上的不可能)。这会破坏一种语言的纯洁性,使得它极难维护和发展。为了扩大通用语言的有效应用范围,一种显然的方式是在语言中定义一些支持结构再次抽象的机制,通过可插拔的方式实现与domain相关的知识的融合。ruby这样的语言提供了大量的元编程机制, Witrix平台中tpl模板语言也发展了一系列编译期结构构造技术, 但是显然它们都不能说是结构抽象技术的终极形态. 目前我对所有通用语言所提供的结构抽象和结构组装能力都是不满意的,因此在Witrix中发展了一些领域特定的结构融合手段.例如根据"继承"关系的结构诠释(继承可以看作是两个一维集合之间的覆盖关系), 我们扩展了extends的结构操作方式, 定义了广义的extends算子. 这些特定的结构关系目前在领域特定的BizFlow语言中体现, 它们在通用语言中是难以想象的, 而把它们放置在通用的语言中也是不合适的(这种复杂的结构融合操作如果不能结合领域知识进行直观的理解, 必将导向一种思维的混乱). 这就是我所谓"现在的通用语言无法有效承载Domain Specific Structure"的含义. 这种说法其实类似于"集合论是无法包容所有数学结构的". 我们在集合论中只研究最普遍的关系,而特定的结构在特定的学科中研究.
   
    关于ErLang的例子, 我的原意是用来说明结构问题是独立的,它是和具体语言无关的.即基于消息传递发生数据关联的超轻量级进程模型这一结构不是和ErLang语言绑定的. 为此我特意加了一段说明:"这里不是要证明某种语言中无法描述这些结构,而是说结构是客观存在的,它并不是要在基础语言层面得到充分解决的". 即使在语言层面我们并不解决这个结构问题, 它仍然客观存在着,我们仍然可以用其他的技术手段去定义,去解决. 解决了这个结构问题就必然会带给我们价值,而无论我们使用何种实现语言.

    "什么原因,什么样的约束条件,导致了现在的通用语言是无法有效承载消息传递发生数据关联的超轻量级进程模型". 这一命题并不是我原文中论点的合理推论.我并不是要说某一种特定的领域结构无法在一种特定的通用语言中得到支持.而是说如果我们认为一种通用语言是比较稳定的,则它一般选择只内置一些通用的不带有领域特定含义的概念. 而缺乏领域知识,或者说因为通用语言故意的摒弃领域依赖, 它在处理领域相关的问题的时候并不是有效的.这种有效性不是数学含义上的,而是可以进行物理度量的. 现在也有很多人认为ErLang并不是真正的通用语言,它是针对通信领域进行了特定结构调整的, 是内置了领域特定结构的. 而目前在ErLang上建立GUI的努力也并不算是成功.
   
    在前文中我举了一个例子试图说明:"在限定的物理约束下,我们的选择范围会大大缩小". "比如说我现在有无穷多种方式从北京跑到上海,但是如果限定只允许用1升汽油,那么我们的选择就近乎于0". 这里并不是要说明加上物理约束之后,我们便没有任何选择了.而是说物理约束对无穷多的可能方式起了限定选择的作用, 它最终造成我们在具体的物理场景下可能只有非常有限的选择. 例如现在允许用100升汽油, 有多少种运输方式可以满足我们的要求? 如果允许1000升呢? 但是如果不考虑所有物理约束, 我们是否能够证明说: 飞机和拖拉机的运输能力是完全一致的, 因为它们都能从北京开到上海.

    我的观点是结构问题是独立存在的,它具有自身的价值, 研究它也需要建立特定的价值观. 一个结构可以体现为语言上的某种语法特征, 也可以通过框架等实现, 或者表现为某种设计模式,某种编程技巧. 我们在思考结构问题的时候并不是从特定语言的机制出发的, 当语言不直接支持的时候我们可以发展特定的实现技术支持它. 在未来的日子里某个结构可能被证明具有普适的价值,它会被吸收到某个通用语言中成为所有程序的支撑结构, 但是更多的结构永远都不会进入通用语言, 而是居留在某个特定的领域. 通用语言的发展并不是完全基于抽象的数学分析而进行的, 它可以从更加丰富的物理世界中吸取营养. 当一种结构进入通用语言的时候, 它所带来的绝对不只是一组数量关系,而是同时带来一系列经过实践检验的物理诠释.

    我所谓的领域并不是指业务领域, 而是结构领域, 一个可以定义特定结构的物理场景. 一个特定的结构仍然可以支撑着任意多的具体应用. 例如CRUD操作可以作为数据管理模型. BizFlow作为界面和单实体的交互模型.

    函数式语言为我们提供了一种具体的技术工具, 但是在现实的开发中, 为了有效的处理结构问题, 显然我们需要多种视角的组合, 而不是把所有可想见的图景都纯化为函数. 我们对世界的体验是多样化的. 这就是我所谓"世界比函数的集合要复杂"的含义.

posted @ 2007-12-09 00:16 canonical 阅读(1190) | 评论 (0)编辑 收藏

有一位网友,今年大二,询问我怎样才能成为技术高手. 我并不确定如何定义所谓的技术高手, 但是既然问到我, 便列举一下可能相关的事实.

首先请明确以下事实:
A. 挣钱能力和技术能力相关,但不成正比。
B. 他人的信任与信赖和技术能力无关。
C. 泡妞能力和技术能力可能负相关,请谨慎面对。

   没有人知道未来是什么样子. 也没有人能够保证自己永远都是技术大潮中的弄潮儿. 如果你只是担心知识的老化, 担心能否长期的保有一份工作. 那么只需要不失去别人的信任,越来越熟练的做好本职工作就可以了。任何工作中当时用到的知识永远都只是很少的部分,只要别人给你机会重新学习,你的人生经验就会是你最宝贵的财富。

   大二的小孩不要每天尽整些没用的东西。认真学好自己的专业课。做软件开发并不需要什么高深的知识,但是接受知识传承,得到全面教育的时机却是一去不回的。

   不要怕学得多,更不要自以为学得多。我在学校的时候,横扫图书馆的哥们并不少。

    读了书并不意味着懂得了道理。用自己的语言能否讲述学过的内容?能否用个简图勾勒Unix内核的结构?学过一门语言到底它有什么不同,能否勾勒发展的脉络?你学过的东西并不是你的东西. 你既无法消费它,也无法贩卖它. 在书本以外你是否真的意识到这些知识的存在性? 最重要的问题是, 你创造的东西在哪里?

    读书开始的时候半知半解是正常的。这就如同张无忌背七伤拳经,总有一天你会懂得,只是需要不时的去回味。数学典籍中经常有这样的说法,本书内容是封闭的,只需要某某领域知识,但是掌握它需要数学成熟性。成熟是需要时间的。

    读书不是以数量取胜。一个领域中类似的书只要细读一本,完整的读下来,读的时候多方参照,做简短的笔记。读一些原始的文献,读大师的原著。尽量选用英文的经典教材。懂一点学科的历史,知道什么是文化。了解一些学术界的八卦,吹牛的时候多一些谈资。
     
    学习任何一个领域都需要深入, 需要掌握大量的相关细节, 因为只有这样才能够不再被细节问题所干扰, 而集中精力于真正的思想核心.  

    拳不离手,曲不离口。连Knuth老兄都在勤奋的敲程序,所以请不要找借口, 先编上十几二十万行代码再说话。编写而不是抄写。

   天下没有免费的午餐。不付出相应的成本,无法得到相应的回报。学习没有捷径,只有方法。只是方法正确并不能保证你走到终点,毅力,机缘都是不可或缺的。你是否能够1天10小时以上持续地考虑同一个问题,是否能够保持同样的注意力坚持到每本书的最后一页, 是否一年365天对新鲜事物总是保有一份天真的好奇。

   在工作中除了抽象的思想和具体的技术知识之外,还有大量小的trick. 例如调用equals的时候把常量放在前方if(MY_CONST.equals(myVar)).
 一般不可能通过书本学习掌握所有这些技巧,只能在编程的实践中观察总结,更多的时候是要你主动思考,重新去发现这些方法。一个人的工作效率很大程度上是受其工作习惯所制约的,你是否在随时随地的改进自己的工作?

    怎样才能做技术高手?这个问题我并不知道答案。公司里所需要的也不是技术高手,而是能够解决问题的人。不过如何培养合格的程序员,在公司内部也有两种看法。adun说要给他们指明职业发展的方向,关心他们遇到的困惑。这是P大的浪漫主义情怀。X罗说要给他们可以完成但是不易完成的任务,等待大浪淘沙后的结果。这是T大的现实主义精神。

    开源是不可阻挡的历史洪流,我们只能改变自己的思维方式,调整自己的行为目标来适应它。

    面对未来的挑战,Alan Kay有一句名言:The best way to predict the future is to invent it。如果你不知道该怎么创造,那就先从捏造开始练习吧----事物之间总是可以建立关联的。

posted @ 2007-12-08 03:14 canonical 阅读(4121) | 评论 (20)编辑 收藏

    现在讲软件工程的, 所谈论的多半是项目工程, 即如何在有限的时间内配置使用有限的资源在单个项目中达到既定的目标. 传统上, 在这一领域基于预测和计划的瀑布方法曾经占据主流, 但是随着项目的日益复杂化, 各种基于演化(evolution)思想的工程方法在实证中逐渐发展起来. 在时空跨度更大的软件工程领域, 例如延展到软件的不同版本以及多个相似项目的工程中, 演化更是逐渐取得了无可置疑的主导地位. 但是, 从另一个方面说, 目前所有这些软件工程方法所推崇的演化实际上都是非常有限的, 它们通过迭代(iteration)所能够描述的演化过程都只是片断性的, 例如一个项目中的演化, 一个软件产品的演化, 最大的莫过于一整条软件产品线的演化. 所有这些演化过程都面临着一个天然的屏障: 商业公司.在公司内部, 知识或代码可以由开发人员携带到下一个项目, 或者从一个小组传播到另外一个小组, 在新的基础上继续演化的进程. 但是核心的知识或者代码一般只能通过商业交易传达到其他公司, 这是一条非常受限制的途径. 而一个单个公司所开发的软件包, 即使是平台级的产品, 如果只是内部使用, 受限于该公司所从事的业务领域, 其所面临的使用检验也是非常有限的. 而且出于经济上的原因, 单个公司往往无力支撑多个实现同样功能的解决方案, 因而它倾向于消灭软件中的多样性, 这有可能会进一步限制演化的进程.  
    开源(OpenSource)软件为软件演化创造了新的可能性.商业友好的开源软件可以被不同的公司自由的运用于不同的业务, 因而可以参与到不同的局部演化过程中. 在应用的过程中, 开源软件面临着巨大的重构压力(这往往是超越了应用最广泛的封闭源码软件包的), 有可能保持更快的演化速度. 而通过对开源软件的回馈, 对开源软件的改进可以传播到时空范围跨度巨大的软件开发过程中. 而且基于源码的开放性, 开发人员的知识交流也随之开放起来. 类比于Darwin进化论, 我们可以说开源驱动了整个软件业界的共同进化(co-evolution).
    多年前, Eric Raymond在著名的文章"大教堂和市集"中 http://263.aka.org.cn/Docs/c&b.html, 提出了开源的工程价值, 但其所关注的重点仍然只是单个软件如何在开源的模式下演化, 从今天的观点看来, 这篇战斗檄文已经显得有些局促了. 开源所造就的巨大演化空间远远超越了软件工程所能够提供的. 开源软件现在已经在商业开发领域站稳了脚跟,也渐渐超越了单个公司能够控制的范围. 可以说开源软件的发展是无可逆转的, 我们已经不会也不应该再回复到原先的封闭世界中.

posted @ 2007-12-08 02:58 canonical 阅读(1499) | 评论 (0)编辑 收藏

   每当我在文字中对函数式语言有些不敬之意时,便好像动了某些人的奶酪,以至我的言辞总在被曲解后遭到排斥。我想这就是因为视角差异过大所造成的. 但是谦虚谨慎是传统的美德, 不能容纳他人的观点只会妨碍自己在某些方向的探索。
   首先请不要轻易怀疑我的知识水平。当然如果总无法聚集起足够的注意力来理解别人话语中的细节,我也无话可说。
   容纳他人的观点就意味着不要总在自己的话语体系中试图找到反例. 一个人总是受限于他的知识范围,因此他也经常在自己的知识范围内篡改曲解别人的意见。我从未说过 "一个具体的问题是现有的通用语言无法描述的". 我说的是"现实开发中所需要处理的结构问题并不是在语言层面得到充分解决的", "现在的通用语言也是无法有效承载Domain Specific Structure的". 请注意我对定语和动词的选择。其实我已经举了大量的例子来进行说明,但可能因为大多数人不是物理背景,对相关的内容不熟悉,所以直接无视了。这也很对,符合物理学的精神。

   可能大多数人都知道函数式语言和命令式语言都是和图灵机等价的,因此它具有某种终极能力,怀疑它无异于怀疑我们世界存在的基础。但是请注意,这种等价性是数学性的。它潜在的要求是无限的能量和时间消耗。如果在限定的物理约束下,我们会发现我们的选择范围会大大缩小。所以我说"函数式语言和命令式语言的计算能力相同,但是在具体的情形下它们的描述能力是不同的". 比如说我现在有无穷多种方式从北京跑到上海,但是如果限定只允许用1升汽油,那么我们的选择就近乎于0。飞机和汽车的运输能力是相同的吗。物理学的一个基本精神在于一种物理性的约束是始终存在的。而事实上,我们在实际工作中也总是在各种有限的物理条件下工作。

   也许有些人认为这种区分是无关紧要的,我们只关心某种终极的东西。但是物理学中有着太多的例证,说明在有限约束下,整个系统呈现出完全不同的性质。在通信领域我们都知道Shannon定理,它的物理诠释是在有噪声的信道上可以有效的进行准确的信息传递。但是这一诠释只能在有限的数学精度(远大于我们实际需求的精度)上成立, 在绝对准确的数学意义上,这是不可能的事情。

   你觉得现在的通用语言做起领域相关的东西来很方便吗,这就是我所谓无法有效承载的含义。在这里我也没有否认"未来的牛语言可以轻松搞定目前难题"的可能性。

   因为所有的软件设计最终都要落实到某种代码实现上,所以怎么会有什么神秘的软件结构是现有的语言无法描述的呢。但是ErLang中那种高并发,支持错误恢复的程序结构是在其他语言中能够轻松实现的吗。很多人不是在潜意识中认为ErLang的成功是函数式语言排他性的成功吗,不是认为命令式语言无论如何实现不了ErLang的程序结构的吗。很显然,在命令式语言中是无法直接实现ErLang中的程序结构的,否则它就变成了函数式语言,但是所有发生在ErLang世界中的事实都一样可以发生在命令式语言的世界中。ErLang语言的编译器可以是使用命令式语言实现的,在终极的意义上,语言之间能有什么区别呢?

   我说"实际上现在的通用语言也是无法有效承载Domain Specific Structure的", 这还有另一层含义。通用语言设计总是要考虑到内置结构的某种通用性,设计时能够凭依的信息较少,因此不可能直接制造某种复杂的领域相关的结构。而目前已知的通用语言中提供的结构抽象的手段也不够强大(实际上我认为任何语言都不会强大到内置所有结构,也无法提供所有的结构抽象手段), 相当于是把领域结构问题推给程序员解决。这就如同C语言把内存管理推给程序员解决一样。现在ruby比较流行不就是因为它能够动态处理很多结构问题吗,但是它现在所作的一切就是足够的了吗。难道二十年之后再来看这个语言,不能够发现它存在着巨大的改进空间吗。我们目前在Witrix中通过tpl模板语言,bizflow extends等机制,结合整体框架设计实现了一些与ruby不同的结构构造方法。这些手段都极大的增强了我们面对领域问题时的信心,也确保了我们的领域知识是技术层面上可积累的。但是即使这样,我对程序发展的现状就是满意的吗?难道不存在更加丰富的结构知识等待我们去发现吗?一般人总是习惯接受已经存在的现实,在有限的职业生涯中把它们当作不变的真理,却没有耐心的去思考如何去改变。

  我认为很多结构问题不是需要在语言层面得到解决的,而是应该在独立的结构层(平台,框架)进行解决。这意味着没有必要在语言层面直接内置某种特定的结构,内置某种特定的结构抽象手段。这基本类似于说不要把集合论扩大到包含所有的数学关系,请在别的学科分支中进行研究。需要注意的是,我所谓的领域知识不是特定的业务知识,而是从业务知识中可以分析得到的某种更加通用的普适的结构知识,甚至是可以使用数学进行精确描述的。

  现代软件发展的时间还很短,与数学和物理学这样深刻的学科相比,它无疑是相对幼稚的,是待成长的,是更加的不完美的。在程序构建的基本问题上并没有抽象出什么可以实际操作的精确规律。这是所谓Pattern在软件业流行的部分原因:我们希望用这种半形式化的方式捕获某种思考的结果。但是软件真的除了基于抽象数学的全局的全称性的证明之外,不能够在局部进行某种更加复杂,更加严谨的分析吗。

   我们说结构问题是独立的,这也意味着它和具体的实现语言具有某种意义上的分离性。通过一种语言书写的结构可以在另一种语言中得到表达。我们可以建立语言中立的技术结构。一种所谓的结构在概念上具有某种确定的形态,我们可以脱离具体的语言来理解它。例如我说"面向对象的继承关系从结构观点上看是两个一维集合之间的覆盖关系". 在java中我们可以直接使用语言提供的继承机制,而在C语言中我们就需要建立某种结构体,手动维持所有的指针关联。而在Witrix平台中,我们从继承的结构诠释出发,定义了更加复杂的extends算子,这就需要利用java语言编制特定的parser来实现了。但是显然,在思考的时候我们所有的思维指向是结构本身,而不是任何通用语言的语法。

   在物理学中,通过摄动分析我们可以清楚地意识到:同样一个物理现象对应的数学模型可以是众多的,但是在特定的参数区我们会选择某种特定的数学表述,并确定其中的待定参数。

   delta函数是物理学家狄拉克引入的,在Schwatz引入分布概念建立广义函数论之前,物理学家们已经使用这一函数工作了很多年。后来Abraham Robinsen利用数理逻辑方法,建立了非标准分析,通过模型论的方法精确定义了无穷小的概念,从更加直接的角度论证了delta的合理性。但是在物理学家看来,这些数学又有什么区别呢?物理学只是按照物理的诠释进行工作,具体的数学只是它可选的工具而已。

   物理的真理并不是蕴含在数学中的,它需要我们独立的探索,从与数学不同的观点进行思考,检验,最终我们才能做出真正的发现。广义相对论可以采用Riemman几何进行描述,但是它的物理诠释却是Einstein提出的. 没有人说Riemann或者Hilbert发现了广义相对论。另外一方面,因为Einstein的工作触发了对于微分几何的更加深入的研究,靠着物理直觉的导引,我们将这一数学分支推进到了难以想象的深度。"数学是无法涵盖物理学的". 这不是说最终物理学无法采用数学语言进行描述,而是说在这一发展过程中,所有思想的推动来源于物理学的经验,来源于我们在这个物质世界上所进行的反复验证。不是在一个封闭的小屋中,整天摆弄各种数学符号,我们就能够发明所有的物理公式所对应的数学。实际上,现在学术界普遍承认,没有物理学的推进,很多数学的进展是不可能发生的。

   物理系每天都在演算着Feynman路径积分, 但是所有人都知道这是没有什么严格的数学依据的.目前并无法定义路径积分的收敛性,但是所有人对此避而不谈. 只要形式演算合法,物理预测符合实验, 合理性的证明只是数学家们的事情. 在量子场论中所采用的重整化(Renormalization)方法不过是回避无穷大问题的一种形式手段.我们仍然无法在数学层面对所有的演算都给予合理化解释. 在更多的物理分支中工作,你就会发现物理学家的胆子不是一般的大。也许在未来我们能够发现这些物理过程背后数学机制的精确定义, 但也许最终我们也无法找到合适的定义方式. 但这对物理学家来说, 并不是很大的打击.因为指引我们的是物理直觉,是独立于数学的物质世界的意象。

   我所想讨论的不是某种终极意义上的可能性,不是绝对概念之间的冲突,而是在物理现实的约束下,我们如何才能有效工作的问题。我已经反复表述了自己的观点: "结构是可抽象的,是具有独立意义的。这就是Witrix所提出的面向结构的设计视角。不是强调对象的所谓业务含义,不是强调某种通用语言(例如ruby)的灵活的语法结构。在这之间存在着厚重的具有物理意义的可以进行结构分析的技术层". 也许有人觉得我说的这是废话, 但是当系统化的执行一种思想的时候,就会揭示出未预料到的可能性. 整个Witrix平台简单的说起来就是"面向结构的级列分析", 但是如何找到合适的技术形式来体现这一思想,却绝对不是一件平凡的事情. "在Witrix中我们实现的代码重用程度和程序整体结构控制能力是超越了目前所有已知的公开技术的。这不是什么哲学,而是我们在残酷的商业竞争中得以生存的资本".http://canonical.javaeye.com/blog/126467

   在我看来,计算机领域充斥着纯数学的深沉遐想和从工程实践而来的轻佻常识,还没有注意到物理学所能带来的不同的同样深刻的视角。我常说,好好学习物理是必要的,因为这个世界远比你想象的要复杂的多。


posted @ 2007-12-08 02:51 canonical 阅读(1416) | 评论 (7)编辑 收藏

    我的观点并不是什么具体的程序结构问题不能用函数式语言处理.我所要表述的是这和函数式语言中能否加入结构解决任意复杂问题无关。为什么所有的问题不能在集合论中解决,为什么要有独立的数学学科。物理学所有的定律都使用数学表述,是否意味着物理学的真理蕴含在数学之中。
    我说际上现在的通用语言也是无法有效承载Domain Specific Structure的其实与以下说法是类似的
数学是无法涵盖物理学的,现在的已知的数学工具是无法有效承载尚未得到充分探索的领域的物理的
    我说我所关心的不是语言层面的问题这类似于说不要把所有物理问题都推到数学层面去解决
    我们应该研究独立的结构,应该建立单独的价值观和方法论。不要谈及一个技术进展的时候就说某某语言好,不是一说到DSL的优点就要去抱ruby的大腿。此外,我的观点也不是去做业务分析,不是去如何更好的实现业务到基础技术结构的映射。
    不是强调对象的所谓业务含义,不是强调某种通用语言(例如ruby)的灵活的语法结构。在这之间存在着厚重的具有物理意义的可以进行结构分析的技术层。
    我想说这个结构层面现在并未得到充分的关注,我们对于结构的问题并不是非常清楚,对程序结构的稳定性更是少有经验。我们在Witrix中做了大量的工作,试图做到如下的图景:
   永远只写代码片断,而所有的代码片断组合在一起又构成一个可理解的整体
   对背景不是分解让其成为可见的部分,而是采用追加的,增删的方法对背景结构进行修正,则我们有可能在没有完整背景知识的情况下,独立的理解局部变化的结构。即背景是透明的,知识成为局部的。
http://canonical.javaeye.com/blog/126467
    在Witrix中我们实现的代码重用程度和程序整体结构控制能力是超越了目前所有已知的公开技术的。这不是什么哲学,而是我们在残酷的商业竞争中得以生存的资本。

号外:
  不要把具体的技术和一种技术思想混为一谈。一种实现总是包容了太多的思想。思想错了,实现对了。实现死了,思想活着。

posted @ 2007-12-06 22:12 canonical 阅读(1244) | 评论 (1)编辑 收藏

关于哲学的问题是很有意思的,因为它是引起最多思想冲突的地方。原本[关于认识的悖论]这篇blog是我在重温维特根斯坦之后反思自己的哲学观念所写下的一些文字。我在具体的推理中是明晰的,但是在哲学思想方面一直是混乱的,所以估计对旁人而言它确实没有什么可读性。不过如果有人感兴趣愿意讨论一下, 我想说明一下我在哲学方面的几个观点:

1. 哲学于我而言是严密的逻辑论证,还是浪漫的情感诉求?
2. 我不认为认识的终极指向所谓的规律, 因为即使我们掌握了所有的规律(包括规律的规律?),仍然解决不了规律为何存在的问题。
3. 当我们自以为对XX的认识越来越清晰,越来越深入的时候,是否已经偏离了XX? 偏执的认识可以激发强烈的情感,聚集思维的资源,引导我们穿越未知。但是新的联系怎样从出人意料的地方生长出来?
4. 维特根斯坦晚年创建了分析哲学的日常语言学派。有人说他变成了星宿派,有人说他发现了新大陆。但是无论如何,自然语言作为人类所创造的思维工具,对人们思维的潜在影响是难以估量的。在人造语言中,借助罗素的类型论我们似乎可以回避问题,但是并无法终极解决逻辑系统的循环依赖。波利亚在数学家中曾经做过一个有趣的调查,询问著名的数学家在思考的时候是否会依赖自然语言,回答各异,但很少有人说严格按照公式进行推理。以我个人而言,思考时更多的是某种似曾经历的意象,而很少是严格的逻辑表述。创造的依据是否是事件之间难以言传的微妙联系,抑或是原始的创造力只是源于错误的巧合?
5. 西方哲学从笛卡尔开始,从本体论转向了认识论。但是是否现在我们已经认清了自己的所在?矛盾的产生是事物内在的真实存在的特点,还是因为认识层面的割裂而人为造成?有多少冲突是因为没有发现协调方式而造成?分析和综合之后我们能够达到什么?螺旋式上升的比喻让我感到有些眩晕。
6. 东方的思维方式不是分析法的,也很难说是归纳法的。这种所谓的整体论难道只是披着神秘外衣的巫术?现在学术界流行的是以西方的思维工具来重新诠释一切观念,我的思想中到底受到各方观点何种影响?难道故老相传的东西都只成了被研究的客体,成了手术台上待解剖的木乃伊?
7.从我们的词汇表中抹去那些词语之后我们将无言以对?例如规律两字。

以上所论的一切都是无价值的,因为其实我只是想问自己一个问题:明天我的灵感在哪里,如何回答。

posted @ 2007-12-06 21:02 canonical 阅读(986) | 评论 (3)编辑 收藏

1. 函数式语言可以合理的看作是泛函分析思想的一种体现,从历史发展的时间上看大概也是具有这种可能性的。在Backus的论文中对函数式语言的一种乐观的情绪甚至扩大到functional algebra can be used to tranform programs and to solve equations whose "unknowns" are programms in much the same way one transform equations in high school algebra。这种信心只能来源于泛函分析和方程论,来自于数学分析学的发展。将函数的函数作为分析对象就构成泛函分析。泛函分析的最核心的思想当然不等价于它所研究的那些无穷维空间,不等价于种种正交基的构造。它的思想核心在于函数的函数具有独立的分析价值,具有脱离数量空间的具体的结构。这也是函数式语言试图推销的一种理念。


2. 最近这些年来一种称为"范畴"(Category)的东西在计算机理论研究中频频出现。范畴论是从同调代数发展而来的一种较新的代数语言,但它显然也不是可以解决任何问题的灵丹妙药。多一种表达方式当然在某种程度上可以增进我们的理解。但是范畴本身只是研究一种基础结构,它本身并没有承载所有的物理事实,基于它不可能对所有的规律一网打尽。不是明白了范畴,就懂了程序。范畴论是一种基础性的语言,有些人致力于基于范畴论来构建数学的其他分支,取代集合论的地位。将计算的本质重新归结于范畴论是无意义的,它不过是对事实的另一种陈述方式。说“函数式语言是基于范畴”是无意义的,因为这和说“所有现代数学都基于集合论”一样。无法发现新的相互作用关系,所有的概念都只是同义反复。不是一拿起数学,就找到了组织。

3. 我对函数式语言并没有什么反对意见。它是非常重要也非常优美的一种技术思想。但是现在一些函数式语言的狂热支持者似乎对函数世界充满了乌托邦式的幻想,一种大一统的世界观让人迷醉,但是它却解决不了现实的问题。所以我说无法认同函数式编程的世界观。作为一种具体的技术工具,问题不在于函数式语言是否体现了计算的本质,而在于它是否向我们提供了称手的兵器。现在我要计算两个小球相互碰撞的问题,我可以操起广义相对论,量子力学啥的开始大干一场,也可以用个牛顿力学小试牛刀,甚至可以只用反射关系摆个等式。但是在绝大多数情况下我们都会说这里面的物理是弹性反射而不是相对论。在理论分析中我们经常使用平面波假设,但只要实际关心的对象不在波包的边缘,没有人会认为平面波不是真实的物理机制。理论物理不是理想物理。在具体的参数设定下,我们只会使用特定的物理学。
   对世界的认识并不是非此即彼的。并不是说函数式语言好它就永远都好,要把所有对立面都灭掉。也不是说函数式不好,命令式就必然的好,就必然的能够解决问题。函数式语言的世界观过分单纯而排他,这是我反对的,我同样无法认同面向对象的本体论论调。就像CISC和RISC架构之争一样,最终我们在现实的物理约束下,运行的最好的芯片是两者思想的结合。这是我们面对物理世界的基本态度。
  
4. 函数式语言中时间是个有趣的概念。命令式语言中因为赋值语句的存在,使得我们可以观测到状态的变化,因此必然要定义时间。而函数式语言是无状态的,可以是无时间概念(对Lazy Caculation的依赖是否体现了深层次上对时间概念的需求?)。有些人认为函数可以看作是相空间中的迁移函数,是与相对论协调的,因而反映了时间的本质等等。相对论主要是解决了物理规律的协变性的问题,在此过程中它使人们认识到了时空之间奇异的对称性。但是广义相对论的表述中时间也是可逆的。真正定义了时间之箭的是热力学第二定律。根据Landauer's principle: 擦除(erase)1比特信息,耗散到环境中的能量至少是k*T*ln2, 或者说熵增至少是k*ln2. 这意味着只要我们对眼前的黑板不停的写了擦,擦了写,就必然无法回到过去。物理世界是复杂的。

5. 如果将状态看作是可以附着在某个对象上的标记,显然状态的存在性便于我们识认概念的唯一性。对象还是那个对象,只是状态标记发生了变化。而如果系统中没有状态,则必然产生了一个新的概念。这在很多情况下是不必要的负担。状态的存在使得系统在局部结构上允许出现非常复杂的变化,函数式编程的拥趸们对此多有诟病。但是从另一个方面上说,状态使得我们可以基于局部信息处理很多问题而不需要把它扩大化为一个全局匹配问题。

6. 函数构成函数似乎是很完备统一的世界。 但是在物理世界中发生的一切却复杂的多。虽然世界可以还原为原子,但是原子构成分子,分子构成宏观物质时,系统的基本性状发生了本质性的变化,并不再是统一的形式。每一个层面上都会产生独立的结构规律。

7. 函数式语言和命令式语言的计算能力相同(可以相差一个任意长度的多项式时间),但是在具体的情形下它们的描述能力是不同的。我所关心的不是语言层面的问题,因为语言本身的能力并不足以解决现实开发中的所有问题。即现实开发中所需要处理的结构问题并不是在语言层面得到充分解决的,这是我们需要做工作的地方。
   关于现实中的结构问题,我无意去定义什么万能的描述能力。你可以用微分几何,积分几何,广义变分等等手段去证明圆盘是某种意义下的周长最短的东西,但是这一切对你发明轮子并无本质上的助益。不过可以说说现实中的结构。这里不是要证明某种语言中无法描述这些结构,而是说结构是客观存在的,它并不是要在基础语言层面得到充分解决的。实际上现在的通用语言也是无法有效承载Domain Specific Structure的。
A. ErLang大概是目前世界上应用最为深入的函数式语言了。它确实发挥了函数式语言无显式状态变量的优势。但是它对程序构建本质上的帮助更多的来源于无共享的超轻量级进程模型,相当于定制了一般操作系统所提供的基本服务。微软的一个实验性操作系统项目Singularity, 其中也定义了只通过消息传递发生数据关联的超轻量级进程模型,它使用C#的一个扩展语言,额外增加的功能是消息管道上定义的规格状态机,对消息交互的时空模式进行额外的规约。这里对我们真正有价值的是隔离的单元结构。
B. AOP是程序结构空间中的定位和组装技术。在Witrix中我们规范化了切点处的状态空间,并对AOP进行了偏置处理.这种结构调整大大提高了AOP的可用性,使得它成为Witrix中的核心技术手段之一。
C. 面向对象的继承关系从结构观点上看是两个一维集合之间的覆盖关系。在Witrix中扩展了extends所对应的结构操作,创造了新的结构融合手段。

posted @ 2007-12-05 22:09 canonical 阅读(4286) | 评论 (9)编辑 收藏

    我习惯于概念层的推演,而且所阐述的东西多数是我们创造过程中的副产品,与业内常见的观念实际上是有着很大差异的。有些人感觉我的文章读不明白是因为没有采用类似的视角,或者还没有独立思考过很多问题。如果只是从业内已经熟知的概念出发试图理解我所写的内容,显然是不可能的事情。所以我常说know something already known.

如果在编制一个新的应用,存在大量代码可能是
myFunc(){
  
for each x in set
    doSomethingValuable(x);
  
return packedResult;
}

myOtherFunc(packedResult){
  
for each y in pakedResult
    doSomethingOther(y)
}

其实我们真正关心的是循环内部的某个过程,但是我们经常可以观察到它们被某些通用的或者特定的循环(集合遍历)操作所包围着。Witrix的设计方式是强调业务关注点,而把所有的汇总操作尽量抽象完成。比如现在界面上显示一些字段。从抽象的操作上说
  for each field in dsMeta.viewableFields
    show field.viewer

这一过程在平台代码中实现,它是一个通用的集合操作过程。不同的具体应用只是关心具体字段的展现形式,虽然我们必然需要字段集合,但是它不是我们注意力的重心。
  如果考虑到字段在界面上展示有一个布局问题,我们所要修改的是集合内部的结构方式:
  某种结构循环方式(dsMeta.字段组成的布局集合)
    show field.viewer

抽离出集合,实际上是在最大限度上分离结构问题和内容问题。     
   结构是可抽象的,是具有独立意义的。这就是Witrix所提出的面向结构的设计视角。不是强调对象的所谓业务含义,不是强调某种通用语言(例如ruby)的灵活的语法结构。在这之间存在着厚重的具有物理意义的可以进行结构分析的技术层。http://canonical.javaeye.com/blog/60758  http://canonical.javaeye.com/blog/126467

posted @ 2007-12-03 23:54 canonical 阅读(1147) | 评论 (2)编辑 收藏

    地址(Address)是现代计算机体系架构中的核心概念,它在程序设计语言上的体现就是C语言中的指针(Pointer)。在C语言中,所有的高级技巧都和指针这个概念相关。指针只是一个存放了一个地址的变量,但是C语言中提供了一个方便的间接访问方式,p->x, 它使得拥有指针在概念上就等价于拥有了指针所指的全部内容。在这种诱导下,我们渐渐模糊了地址和地址所存储的内容之间的区别。这也是指针的指针这样的概念总是让初学者迷惑不解的重要原因。
    指针是对地址的符号化。它所带来的第一大好处是使得我们摆脱了对绝对地址空间的依赖。如同Newton第一定律所阐述的:物理规律与所发生的惯性坐标系无关。同样,数字空间中发生的的事件与所处的绝对地址也是无关的。在符号化的方向上更进一步,如果我们专注于指针的关联语义,而放弃指针的指针这样的混杂概念,就会得到具有独立价值的引用(Reference)概念.
    从表面上看起来,数字空间只是一个无限延展的一维地址空间,每一地址处只能存放一个有限大小的离散数值,似乎它的几何学是贫瘠的。但是因为在软件设计中,一般是不考虑寻址时间的。这意味着在拥有指针的情况下,我们可以“立刻”访问到数字空间的任意遥远的地方。这种超时空的信息传递过程使得我们可以利用“引用”概念轻松的构建一个多维的表示空间。在面向对象的技术背景下,x.y.z这样的形式表示暗示着x,y,z是同时存在的。当z发生变化的时候,通过y.z和x.y的信息传导,x对象本身也发生了某种变化。
    随着web技术的流行,独立的状态/地址空间的存在性逐渐成为系统不可回避的假设, "同时性"的物理约束越来越难以维持. 相对论规定了物理现象的定域性, 在数字空间我们一直忽视了它.但有趣的是, 网络上的传输时延却迫使我们重新发现了"引用"形式下必然存在着的物理过程. 引用本身只是标记了某种信息关联, 并不一定意味着同时性约束. 并发编程领域的所谓的Future对象是对传统引用概念的一种有趣扩展.
   result = obj.callMethod(args) ==>  future = obj.callMethod(args)
future对象可以被自由传递, 只有当实际访问到它的属性的时候, 才会触发时序约束. 

posted @ 2007-12-02 22:14 canonical 阅读(1161) | 评论 (2)编辑 收藏

    判断和循环是程序中最基本的语句结构。而在vonNeumann体系架构下,循环是对集合进行操作所需的基本步骤。一个有趣的事实是,函数式语言所宣称的 生产力的来源很大程度上在于集合操作的便捷性。在数学中我们通过张量分析,泛函分析等可以清楚地意识到集合之间的相互作用是可抽象的,是可以独立理解的, 即我们可以在不涉及具体基元结构的层面上独立的定义并执行集合运算。如何将这种概念独立性在框架层面展开是一个非常深刻的命题。
    在基元结构上应用基础操作p(d)这一微观场景一般情况下是容易理解并实现的, 但通常程序中所定义的大量边界是基于集合变量的, 因此很多代码都是封包和解包操作, 在层层嵌套的循环结构深处我们才能发现真正具有业务价值的基元结构. 将集合操作提升到系统层,减少或简化在应用层需要显式编制的循环结构是框架设计层面需要考虑的问题.
    一个最基本的方法是尽量定义通用的同构操作, 避免构造中间集合. 例如前后台之间通过json等通用协议交换复杂结构的对象, 避免定义特殊的中间处理过程. 一个与之相配合的重要技术手段是通过类查询语句(描述方式)直接构造特定的集合. 例如prototype.js中提供的$$('div div.myclass').each(op)这样的处理方式显然要比在循环过程中基于特定条件过滤要方便的多. 而在AOP操作中切点的集合定义方式也是其提供的核心价值之一. 与集合操作相适应的一种代码风格是流式设计(stream), 这正是jQuery试图鼓吹的主要价值(虽然我个人认为它有些走极端). 流式设计的核心结构实际上是 x += dx, 它不需要集合是一次性构造的, 便于支持一种逐步部分修正的概念模型.
    为了支持集合的隐式构造, 我们需要以通用的方式定义元素到集合的组装规则. 在Witrix平台的前台js框架中我们定义了由独立的html组件到复合查询条件的拼接规则, 定义了由每个html组件的数据校验函数到整个form的数据校验函数之间的组装规则, 定义了由单个html组件提交参数到整个form提交参数之间的组装规则. 在Witrix平台的标准界面上, 框架本身的编制基于js.query.buildCondition(frmQuery), js.validate.validateForm(frmUpdate), ajax.addForm(frmUpdate)等少量集合操作进行, 在不同的应用场景下, 我们只需要关心每一个字段如何显示, 提交哪些属性, 而由系统负责将它们自动组装并在后台进行分派. 面向不同的应用, 框架代码不需要做出任何修改, 确保了系统结构的可重用性.
   Witrix平台的后台处理模型中定义了实体化过程, DaoWebAction基于CRUD等原子操作定义了批量提交, 数据导入导出等复合的甚至是嵌套的集合操作. 在不同的应用中, 我们通过修改bizflow文件中<action id="ViewDetail-default">, <action id="Update-default">等针对单实体的业务规则即可适应不同的业务场景, 而不需要为特定的应用重复编制集合处理过程.
    面向集合+通用组装规则是Witrix平台设计中采用的基本设计手法之一, 它使得我们在一般应用中只需要考虑单实体,单字段等基元结构上发生的特定业务, 大大简化了系统构造过程. 但是也需要认识到从个体到集合的扩张(p(d) -> P(D) )是非平凡的, 集合比个体的简单加和要更多, 为此架构中需要保留对集合边界的识别能力, 例如需要允许在数据导入完成之后执行特定的业务规则而不是仅仅针对每一数据行执行业务规则.

posted @ 2007-11-25 23:37 canonical 阅读(1163) | 评论 (0)编辑 收藏

   由于各个公司的领域,规模,人员配备等差异很大,形形色色的公司中顶着架构师头衔的诸般人等所从事工作的内容以及所承担的责任也是大相径庭。务虚者有之,务实者也有之, 难以一概而论。甚至关于架构一词的具体含义在不同语境下也是很难达成共识的。然而作为架构师,他应该做什么,能够做什么,却是我在自己的职业生涯中需要加以回答的问题。
    软件公司中的工作大致分为销售,技术,财务,打杂这几类。架构师所从事的工作大致上属于技术这一摊,应该是一种高度专业化的技术工作。在我看来,一般所谓架构师的工作主要是负责设计规范整个软件项目/产品/产品线的整体结构,他所摆弄的是各种相关的技术元素。虽然作为公司的技术利益的代表者,架构师会在某种程度上参与到公司的商业活动中(在某些巨型公司中,架构师甚至可以通过标准规范对整个产业结构施加影响),但是他更多的是接收商业需求将其转化为技术约束,而很少是商业目标的制定者。业务架构方面的设计更理想的是由业务专家进行,这个工作多半只需要技术的常识,而不需要对于技术本身的深刻洞察。在另一方面,虽然架构师对于技术实现所需的技术/人力等资源需求会提出自己的估算和建议,但是他一般并不具备相应的手段和责任来具体管理整个实现过程。因此在我看来架构师的管理职责并不是很大。当然,有些架构师会更加接近商业和管理而远离技术,将他们称之为"资深架构师"可能更加合适。在某些大型系统的建设过程中,总体设计人员可以只负责收集各个子系统的技术要求,汇总后制定整体技术规范,所起的作用类似于协调人员,在这种情况下倒是对技术要求较低而对管理素质要求较高了。
    关于架构的一个有趣的事实是,技术架构本身其实很少存在设计问题。大部分问题只在于业务问题如何分解到既定的技术架构上,一般的技术架构也只是现有技术元素的简单组合而已。所谓的架构设计工作并不是在真正的系统全景下进行,它往往是基于已有经验所作的短暂延伸,是对业内其他类似结构的复制变形。我们所面临的大量问题是选型问题,不是创造性问题,而是选择性问题。架构师最富技巧性的工作不是现在确定什么,做出选择,而是确定现在可以不确定什么,可以将哪些选择延迟。
    在一般人看来,架构师对于系统成败必然起着关键性作用,否则他们凭什么属于“活少钱多”的那伙人呢。但真实情况是,商业上的成败很少是由技术架构直接决定的。因为技术开放和快速传播等原因造成了技术的趋同性,在技术层面上,大多数公司很难依靠技术形成差异化优势。竞争优势主要来源于业务理解和与用户的接触性,来自于历史形成的业务格局。而在中国这样一个营销制导的商业世界中,架构师的工作更难说是在构造某种与众不同的东西。只有少数大公司依靠把握标准才形成技术的话语权,大部分人不过是在技术的大潮中随波逐流罢了。“不求有功,但求无过”应该是架构师基本的工作精神。技术失败最常见的原因除了不够专业以外(在中国,“专业”的标准也许是不同的),就是过于自信,试图去创造些新的结构,或者试图全面应用某种不熟悉的技术。架构建设应该是一个逐步改进的过程,不要激进盲动。
    国内的架构师多数是从高级程序员发展而来,在工作期间多半是学习掌握外部知识,以掌握知识的细节程度和广度为优先。因为总是在别人搭好的平台上活动,即使是参与过众多大型系统的建设,对于系统整体结构一般也没有提炼出自己的认识观点。而有些大学设置了专业,宣传培养架构师,但是实际上缺乏系统的实践训练,学生所学到的多半是高举高打的套路,在实战中的表现往往更差。掌握技术细节和自主的整体性思考对于架构师而言都是不可或缺的。
    虽然创新的技术未必是商业中核心的元素,但是真正的创造性仍然是每一个设计师的希冀。作为一名实践者,我们都在某种程度上期望超越所经历的偶然,达到某种普遍的真理,在外部的物质世界中留下自己的精神烙印。在这种意义下,架构师的工作便不是简单的技术背景或者技术理解可以涵盖的了。我相信,在业务层和基础技术设施之间存在着物理性的厚重的通用技术层,其中存在着大量的结构规律等待我们的探索,这也正是Witrix平台一直努力的方向。


posted @ 2007-11-18 21:50 canonical 阅读(446) | 评论 (1)编辑 收藏

   我们总认为认识的最高境界是认识到事物的本质。但是越明晰的认识意味着越明晰的区分,而区分意味着认识到事物的独特性,割裂了它与普遍事实之间的联系。我们是否会说这一本质和那一本质是本质上不同的?本质最根本的意义在于内在的规律,在于内在的协调而不是和普遍事物的对立。我们对本质的认识是如何成为可能的?现代语言哲学发掘的一个基础事实是我们的语言中涉及到抽象事物的部分存在含混性和自我证明的逻辑循环。在抽象的概念上我们很难达到共识, 而这恰恰是通常我们认为所谓本质所寄居的地方. 语言文字是人类所创造的思维的工具,我们对它们的存在早已习以为常。只有研究词源的时候, 我们才能清楚地意识到人们的思维和世界的现象之间的巨大鸿沟. 通过千百年的文化积淀和不断的自我强化, 我们才形成了共同的民族心理结构, 看到同样的文字的时候才能激发我们类似的情感, 才能引起类似的意向,但是具体的过程仍然是不可言说的.
    辩证法的两端都能够成为我们认识的对象,因此我们会感到矛盾对立的存在. 但是随着我们认识方向的转移, 很多矛盾可能被弱化,被消解. 在本世纪初, 相对论和量子论无论在理智或者情感上都是如此让人难以接受, 但是新一代的学生接受起来已经自然了很多. 现在我们只需要盲目的遵守规则,而不再需要去寻求自我证明的解释. 在现代物理的框架下, 惯性不是物质本身的属性, 它来自物质之外. 万有引力不是物质之间的额外的相互作用,而是时空扭曲后造成的内蕴约束. 但是在局部情况下, 并不妨碍我们建立一个形式系统使得这个属性内在化。在很多时候只有偏执的认识才能引导我们穿越未知.
    中国人的认识论是整体性的,但却不是公理化的.传统上我们说微言大义,总试图从真切的细节处领悟超越的真理. 这是和分析法不同的认识的途径, 但也很难说它是归纳法. 思维中的意象是符号化的, 但是也是具体的,拥有自己的形象,并具有某种潜在的活动性。

posted @ 2007-11-04 22:33 canonical 阅读(447) | 评论 (2)编辑 收藏

   读书在传统意义上是走向精英阶层的一条路径,这种功利目的一直深刻在我们的心中。学校也是按照培养高于平均水平之上的人才而设定的。只是在如今这个人人都是大学生的时代,大学生早已不是什么“天之骄子”。缺乏可以用于创造或者交换的技能和资源,知识阶层的相对贬值也在情理之中。依然持有着自己应该高于普通生活水平的错觉,非要在面子上有所交待,负担高于平均水准的车/房,很多时候只是徒增烦恼而已。
   读书是获取知识的主要途径,即使在影音世界空前繁荣的今天,它仍然是不可替代的。不知是学生的问题,老师的问题,抑或是整个教育机制的问题,现在接触到的很多人既没有从学校学习到必要的知识,也没有掌握基本的学习方法。很多人津津乐道的是某某很聪明,没见他用功也取得好成绩,某某很能来事,没干多久就挣了大钱之类的传闻。不劳而获可以是一种希望,却很难成为发生在自己身上的现实。我见过的聪明人很多,但是真正能做出一些自己的东西的人,都具备某种专注的能力,都要在某个方向上做出一般人难以达到的持久的努力,所付出的成本往往是不为人所知的。
   学习没有捷径,但却是有方法的。有效的阅读需要在一定的时间内完成,在最短的时间内获得整体的阶段性的认识。太厚或者太过艰难的书会耗尽我们的耐心。循序渐进,举一反三,温故知新是平凡的真理。读书一定要做笔记, 否则所获得的印像很快就会因为没有物质凭依而消逝. 笔记不是抄书,而是从自己的视角重新整理并组织,一般最多两三页纸而已。不要纠缠在文字细节上, 而要努力把握其中的一种图景. 物理中非常强调物理图象的重要, 这些图象未必是仿真的, 未必是要把事实世界中的事情复原,它们更多的是符号性的, 所指向的是一种感觉。有些人总是纠缠在什么是OOA, 什么是OOD这样的概念区分上,但是多数时候这些区分都是毫无意义的。我们需要脱离纸面上的图形和文字,想象它们的真实,这绝不是UML那种已经定义了的符号, 而是与世界上更多事物可以发生共鸣的某种形式. 我们所需要的是利剑迎面击来的那一刹那间对它最直接的感受. 思考问题的时候是现在这种感觉和曾经想过或者曾经看过的其他问题的相似, 而不涉及到任何文字上严谨的表述. 很多书上都列出很多条规则, 但是谁能保证这些规则是完备的, 如何才能从唯一的规则实现逐步的分化, 将它们演变出来. 能不能采用自己的语言复述, 能不能找出一个特定的视角重建这些概念的关联. 当把信息抽离到少数符号的时候, 通过空间形象思维我们有可能把握这一点. 空间优于时间, 实际上对于时间的感受我们是通过空间运动来定义的.
   现在的世界与百年之前已经是有着本质的区别,知识成为公开市场上兜售甚至免费公开的东西。在前所未有的开放中,人人都具有了创造的可能,所谓的创意早已成为我们惯常的生活,以至于一阶变化已经无法称其为真正的创造了。但另一方面,真正的思想仍然具有本质的稀缺性。原创的思想往往来自于少数人,其他人往往是在某一方向上进行衍生。现在很多人已经习惯了快餐式的文化消费,却不知更应该去阅读大师的原著而不是经过别人蒸馏后的转述。原创的思想在文字中跃动,它所关注的不仅是眼前的事实,而是整个世界和当前事实之间的关联,试图为它寻求到真正存在的价值。
   读书是一件有趣的事情,但是将一切都归结于某种感官的快乐,无疑是一种过分浅薄的观念。读书之乐趣未必是真的愉悦的感受。
读书不能使你更加富有,也不一定能带给你安宁, 不一定对你的工作生活有什么帮助. 它只是Kill Time的一种方式而已. 只是相对于电视影像的强制倾销, 游戏竞技的自我沉迷, 它比较适中的维持了一定的自主性和外部性的平衡. 我常说, 读书只是使你明白自己生活在怎样的一个时代, 自己不是一个蒙昧的原始人. 不清楚相对论, 不知道量子力学, 不了解基因技术, 只是很遗憾的在二十一世纪走过.
  读书也是读书人在社会上得以自持的一种方式,因为毕竟我们还可以说:拽什么拽,没文化,不稀的理你。

posted @ 2007-10-29 21:59 canonical 阅读(507) | 评论 (3)编辑 收藏

    虽然现代物理的标准表述采纳了严密的数学语言,物理学和数学的精神还是有着深刻区别的。数学的目标在于在既定的范围内把有限规则的推理推进到极致。物理学格物以致知,我们所了解到的永远只是这个世界变化的部分。当一种物理机制位于我们的视野之外时,物理学将选择直接无视它。因此物理学对数学是按需使用的,是有限理性的,总要受到所谓物理直观的制约。再完美的物理模型也不过是在不完全信息下建立的一种完备的数学模型。当我们沿着一条推理的链条越走越远的时候,一种本能的怀疑便会油然而生。

posted @ 2007-10-07 21:38 canonical 阅读(451) | 评论 (0)编辑 收藏

        Witrix开发平台基于级列设计理论发展了一系列新的设计思想和软件分解机制。并提出了一种新的Web体系架构。http://canonical.javaeye.com/blog/33824
 

Witrix架构呈"可"字形态,其中定义了三条主要的分界线:
1. 浏览器和服务器之间通过语义结构明晰的URL形成两分结构。http://canonical.javaeye.com/blog/99122
2. 系统前台至后台存在一条预制的非侵入的信道. 它维持了一种无害的可扩展结构. 具体的说,系统从前台js到后台处理,对于所有$为前缀的参数是自动传递的,没有识别出的层将自动把这些参数传递下去.系统通过这一信道实现退化过程。在 我以前的文章中曾经指出过, 每一种可退化形式都对应存在一种非侵入性的可扩展设计。http://canonical.blogdriver.com/canonical/993807.html
3. Witrix内置了对于CRUD模型的支持, 而BizFlow通过类似AOP的方法对CRUD模型进行了扩展。这使得Witrix的模型驱动部分并不是仅仅针对单表或者单实体的维护, 而是可以实现特定的业务逻辑和CRUD逻辑的混杂.

    这三条分界线分别规范了基础状态空间,对已有知识的重用以及面向未来的可扩展性。在这种大的宏观结构下,Witrix应用了如下技术手段:
1. 对象化。Witrix中的Jsplet框架以对象的名义提供了对后台的状态和行为空间进行分解的基础手段。 http://canonical.javaeye.com/blog/33873。Witrix 依托于Jsplet对象实现相关性的局域化, 而不需要像一般面向action的框架那样直接访问http session这一全局状态空间. 前台发送的objectName参数同时在系统的不同层面标定了WebAction响应函数, Biz配置, DataSourceMeta元数据, Hibernate实体等一系列相关概念, 使得它们构成一个统一的整体.
2. 标准化。与REST架构风格类似,DaoWebAction规范化了后台响应事件。DaoWebAction上支持的标准事件有Query, ViewDetail,Add, Update, Remove等, 它们构成一个完整的CRUD模型。与REST不同的是,DaoWebAction提供了一个空的响应事件BizAction。它相当于是CRUD模型中的 零元操作。在BizFlow模型下,它将被扩展为一个操作子空间,从而实现对于CRUD模型的超越。而在REST模型下所有的扩展操作必须依附于一个已经 具有固定语义的Method上,例如POST. http://canonical.javaeye.com/blog/99122
3. 实体化。在Witrix中充分发掘了ORM技术的能力, 使得单一业务对象上可以聚集到某一范围内的所有相关结构信息. http://canonical.javaeye.com/blog/111500. 同时在DaoWebAction中通过EntityFilter机制实现了单实体化过程. 这意味着前台应用可以一次性提交多个批量操作, 后台框架负责把它们分解为针对单个实体的一次确定性操作, 在后台实现时只需要考虑单实体的情况即可. 一个简单的例子是前台提交objectEvent=Remove&id=1&id=2&id=3 , WebAction层会为每一个id对应的实体调用BizFlow中的Remove-default操作. 实体化是一个非常重要的过程, 它使我们关注的核心成为单实体, 正是因为明确了单实体作为基本的关注点, 我们才可以建立更加复杂的状态机机制, 驱动系统状态变化.
4. 组件化. 前台的tpl模板和后台的WebAction共享一个thisObj指针, 结合自定义标签机制, 资源(js/css等)管理机制等构成可以重用的组件结构.
5. 偏置的AOP. BizFlow通过一种类似于AOP的操作对DaoWebAction提供的CRUD模型进行扩展, 使得模型的能力得到本质性的扩张. 这种AOP操作与通常意义的AOP的区别在于: 缺省行为在默认情况下发生, 除非显式禁止. 通过这种方式, 反转了base和extension之间的主体地位. 此外BizFlow所提供的不仅仅是行为的扩展,它同时提供了对界面的描述. 在前台tpl页面中通过<ds:BizViewOps/>等无参数的标签调用来定义嵌入坐标. http://canonical.javaeye.com/blog/34941


     与传统的J2EE相比较, Witrix表现出很多变化:
1. 不使用全局的session, 而是使用局域化的thisObj
2. 不定义service层,其功能分解到BizFlow和Handler中,它们都不负责日常的DAO操作。独立的MDA部分负责所有的实体CRUD(Create Read Update Delete)操作。
3. 不定义页面跳转规则,在前台使用拉模式直接表明跳转目标。结合前台stdPage对象在前台控制跳转目标。并可以在BizFlow中配置覆盖的规则,这样就可以针对不同的应用场景定义不同的跳转规则。
4. 不是为每个模块, 每个应用场景编制一组新的页面,而是大多数模块共用少数几个标准页面.
5. 不在与网络无关的service层上定义权限和事务管理。Witrix架构下通过URL明确区分了系统内部和外部, 前台访问后台时调用者的全部意图是以规范化的形式表达在url中的. 因此权限和事务管理作用在WebObject上在概念上也可以认为是约束直接作用在URL上, 这与REST风格是统一的. 当然我们也可以规范service方法的命名等, 但是显然要求一种随意性消失是有代价的, 在URL上我们已经付出了代价,为什么要在service上再付出一次. Witrix中Transaction和Auth的配置更加直观, 因为规范化了WebObject上的事件响应函数,一般我们也不需要进行特殊的配置. Witrix这种设计更加适合于网络这一两分结构的,更加充分的利用这一架构边界所提供的隔离性.
6. 不在页面中使用实体的字段名,而是大量通过元数据表达程序意图。http://canonical.javaeye.com/blog/114066

   一般J2EE多层架构下,所谓的架构分解主要是对程序纵向的分解,但是程序结构方面是没有横向分解的。而witrix架构的一个核心就是横向存在着 CRUD模型和Biz的分解。在系统的所有实现过程中,所有CRUD操作都剥离到MDA模型中,而不需要任何代码编制。典型的, witrix后台代码一般写在handler中,命名为handler而不是service是因为handler中负责的内容和j2ee传统上的 service有所不同,一般service总是要负责某个实体的CRUD操作,大量的findxxx代码。一般提倡的最佳实践是实现某个通用的基类,所 有service都继承该基类获得CRUD能力。但是在Witrix架构中,根本没有这一需要。Handler只要完成自己特定的功能,它不追求操作概念 在其本身的完整性。没有CRUD, handler没有意义。但是handler之所以有意义是因为它提供了CRUD之外的操作。当CRUD成为系统一种自动进行的背景操作时,我们不再需要 明确意识到它的存在。
    我们需要认识到我们最终所需要的东西可能不是规整结构的, 它可能要求对于某个规整结构进行剪裁并增补附加元素. 但是这样的规整结构不应只存在于我们的想象之中,相应的剪裁过程应该是可以增量进行, 反复进行的. 在Witrix平台中, 基本的一种图景变化是: Witrix中不再需要从头开始构造结构, 而只要指定当前业务和背景模型之间的差异部分. 在Witrix中所写的业务代码是对核心模型的扩展。这不仅仅是概念上的,而是实际体系架构上精确的体现。CRUD作为独立的模型吸收了系统中大量的变 化。整个模型内核是采用通用方式借助meta实现功能,并不涉及到特定于业务的类。对于那些我们已经掌握的知识, Witrix提供了超越对象继承,AOP和组件重用的结构抽取手段, 使得知识可以稳步积累.
      数学中存在两种基本的分解方式, 一种是加性分解 (a,b) + (c, d) => (a,b,c,d), 另一种是乘性分解 (a,b) X (c, d) => (ac,bc,ad,bd), 它也对应于张量(Tensor)运算. 在群论(Group Theory)中,直积对于复杂性的化简至关重要,它的重要性要远在加和之上。实际上AOP操作类似于直积分解, 只是它的能力尚未得到充分的探索。 在Witrix中,biz的作用在感觉上很象是陪集(coset)运算:CURD * biz。不同的biz作用到同样的CRUD模型上产生不同的操作集合,而所有biz组成的集合构成独立的整体。   
      Witrix平台中作为内核的MDA部分首先是物理模型驱动, 而不是逻辑模型或者对象模型驱动. 我们通过在物理模型上标注的方法恢复部分对象模型的信息, 但是我们并不试图把整个软件建立为模型. 所建立的仅仅是整个程序模型的内核. http://canonical.javaeye.com/blog/29412 一般业内鼓吹的所谓MDA成功的关键是要提高抽象层次。 但是陪集是更抽象吗。 正规子群更抽象吗。 它们只是系统的指标性表征,使对信息的distill, 是更容易理解的一个侧面而已, 抽象性并不是一个真正的目标。很多时候我们需要的是把系统降维到某个子空间中,形成一种可控性。 但是这个子空间并不一定是更抽象的。
      群作为基本的代数系,一个本质特征是具有逆元。Witrix的MDA中明确定义了逆元结构,即界面上的元素 empty = buttonA + (-buttonA),这一分解公式应用到后台 OpA = Update * (-Update)  * OpA。假设我们已经建立了结构X, 现在需要建立一个与X略有不同的结构Y
       X = a + b + c
       Y = a + d + c = (a + b + c) - b + d = X - b + d
虽然Y的两种构造方式在数学上是等价的, 但在物理上并不等价。第一种方式对原有系统进行分解后再组装,而第二种方式没有打破原有的东西,不需要拆分。拆分总是可能存在问题的,正如你把所有电脑零 件拆装下来再装上很可能会发现多出几个零件。一般情况下第二种方式的构建成本要低. 特别是当一切都纠缠在一起的时候, 一种细粒度的逆元结构对于一种试图重用的结构是非常关键的. 可重用性的障碍不仅仅是来自于无法追加新的功能,很多时候也在于无法屏蔽原先已经提供的功能。目前所有的设计原则都未能适时识别出逆元的重要性。所有的设 计教条其实所指的方向都是加和, 如何分解出更小的组元, 如何把它们加和在一起, 如何从细部开始进行重新构建, 而不是说依赖于现有已经形成的宏观结构, 如何进行细粒度的调整. 所谓的AOP技术思考的关键点也在于如何给系统增加功能, 很少有人想到场景是为系统减少功能并把这种概念大规模正式应用的, 虽然说AOP已经在某种程度上具有了这种能力, 但是真正应用它仍然需要对AOP进行进一步的诠释. 当然,现在的软件业连基本结构的构造问题都没有完全搞清楚, 更别提所谓结构稳定性的问题了.
     从物理上说,Y = X - b + d的分解方式具有特殊的意味。如果没有逆元,我们必然需要分解。但是如果发掘了背景这一概念,在逆元运算下,对背景不是分解让其成为可见的部分,而是采用 追加的,增删的方法对背景结构进行修正,则我们有可能在没有完整背景知识的情况下,独立的理解局部变化的结构。即背景是透明的,知识成为局部的。      
    Witrix试图提供的一种图景是永远只写代码片断,而所有的代码片断组合在一起又构成一个可理解的整体。这个整体可以独立理解,不需要额外的结构元素。 Witrix架构所追求的是在不完全信息下建模,不进行整体建模。整体模型 + 不断变化的局部修正 构成 最终模型。平台技术的目标是让一切应该发生的自动发生,让一切不该发生的无法发生。这一模型的构建并不是trivial的,在概念和实现方面都要作出很多 的努力。
     
题外:
     今天中午参加同学的婚礼, 席间和一个与同方有些渊源的同学谈到ezOne的现状, 大致的评语是: 垃圾, 自己人也不用. 听来也让人有些感叹. 中国原创的技术总是欺骗的代名词,  这一断言不应总是得到证实.

posted @ 2007-09-23 23:53 canonical 阅读(1245) | 评论 (0)编辑 收藏

  建筑学的隐喻在软件业中一直很流行。开发软件和建筑楼房从某种意义上说都是一种构造过程,而建筑学的成熟无疑让很多软件工程师非常羡慕。Design Pattern的始作俑者坦承受到建筑学著作的影响更是让一些人对建筑学的精深顶礼膜拜不已。不过,建筑决不只是表面上的形象/功能设计,也决不是民工就可以干的活计,在现代建筑设计背后是土木工程的蓬勃发展。正是框架结构的出现和材料工艺的进步才使得批量生产的大型现代建筑成为可能。
  虽然建筑学有着它不为人知的复杂性的一面,但是与软件开发相比,它也有着简单贫瘠的一面。现在架构师言必称设计模式,但是估计很少有人读过Christopher Alexander的原著"A Pattern Language"。在Alexander的概念中,所谓的模式"describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice". 关键在于这些模式在建筑学中往往映射为某种独立的原子化的实体(entity), 因此可以把它们作为一种语言的基础组分,构成所谓的Pattern Language. 例如现在要开发一个门廊,你可以从"私家的沿街露台(140)"开始,在"有阳光的地方(161)"做成一个"有围合的户外小空间(163)", 选择"6英尺深的阳台(167)", 保留"小路和标志物(120)", 注意"天花高度变化(190)"和"角柱(212)"的位置,在"各式座椅(251)"的旁边安排一个"高花台(245)". Alexander共定义了253个模式,括号中的便是模式的编号。很明显,物理空间的不可重入性直接规范了建筑设计空间的有限性。
  在软件设计中,类似VB/Delphi的可视化界面设计的操作过程与此类似:理想情况下界面开发基本就是用各种界面元素填满一个Form的过程。但是软件的一个本质复杂性在于它的基本结构单元是函数,而设计空间中同一个功能点对应的实现函数的个数和形式并不存在有限性的约束,函数的组合形式也不是线性延展的。建筑基本上依赖的是静力学,而软件无疑需要用动力学来刻画。即使是界面开发,我们所关注的也决不仅仅是静态摆放问题,而更重要的往往是界面元素动态相关和动态变化的问题。
  在Web开发领域,一直有人鼓吹模仿VB/Delphi的快速开发工具,但是我并不认为这其中的设计哲学是与软件的本质相匹配的。软件中所蕴含的无限的动态变化不应该仅仅通过有限的配置过程来应对,我们需要更加强大的结构抽象和结构构建手段。

posted @ 2007-09-09 20:00 canonical 阅读(875) | 评论 (0)编辑 收藏

    程序中大量的工作其实都是在定义结构以及结构之间的关系. 一般情况下我们应该识别出结构,并把它们封装到函数,对象和组件中去. 但是封装并不永远都是有利的. 将某个结构独立出来, 在某种程度上也就割裂了它和其他元素之间的关系, 这会引发结构融合的障碍, 也会造成思维上的负担. 事实上如果程序整体具有足够的可理解性和概念稳定性, 我们并不需要独立识别出什么子部分. 一个简单的例子是数组循环. 一般情况下我们应该尽量把循环查找等操作封装到函数中, 避免多重循环嵌套时产生过于复杂的代码块. 但是如果数组或者语言本身提供了each, map等函数式操作符,则这种封装需求就大大减弱了.
    随着系统结构的日益复杂化, 在系统中会积累大量的背景知识.此时当我们需要完成一个功能的的时候, 往往不再需要指定所有的信息, 而只需要指定背景知识之外的部分信息即可. 例如在界面上通过一个分页表格来显示实体列表这样一个功能, 在Witrix平台中通过模型驱动的标准页面即可自动完成. 一般的定制需求往往是过滤显示部分数据, 在表格行上增加一些操作按钮, 定制表格的表头等. Witrix平台实现这些需求并不需要封装出一个独立的表格组件, 调用它的属性修改方法等, 而是把定制部分嵌入到BizFlow的配置中, 这里并没有明确的结构界限.
  <biz id="default">
    <filter>
       <eq name="status" value="1" />
    <filter>
     <tpls>
        <tpl id="thead>
         <thead>
          <tr rowspan="2">...</tr>
          <tr>...</tr>
         </thead>
        </tpl>
        <tpl id="rowOps">
          <ui:FlatButton .../>
        </tpl>
     </tpls>
      其他与表格无关的信息
  </biz>
  注意到对于我们理解业务而言, 我们并不需要知道表格具有分页, 排序, 隔行变色等功能. 所有和业务相关的代码聚集到BizFlow文件中, 它们构成一个可以独立理解的整体, 在此过程中也通过背景知识实现了大量结构的消解.

posted @ 2007-09-02 09:45 canonical 阅读(814) | 评论 (3)编辑 收藏

   传统上报表引擎主要完成两项工作:结构描述和结构转换。一般报表设计人员通过可视化设计工具完成对报表结构的描述,然后报表引擎根据这些描述生成不同格式的报表文件,如PDF格式,XLS格式等。这一图景中报表设计工具扮演着关键角色,因为它不仅仅是向用户提供一个直观的界面,更重要的是配置过程本身就是一种分步骤的结构构造过程。理想的情况是用户指定报表中具体有哪些单元格,表格具体有哪些列,而在运行期报表引擎负责向单元格中填充数据。但是对于设计期只能进行动态描述,无法预先确定所有结构元素的报表(例如交叉表的列只能在执行时确定),这种报表模型就会出现问题。一般处理方式都是在报表引擎中内置所有可能的动态报表模型。无论设计工具多么复杂,其内置的原理如果是基于静态结构模型,就无法建立一种抽象机制,这样我们就只能通过重复劳动来应对众多结构类似但是略有不同的报表。
   Witrix平台的报表引擎是对程序友好的,它引入了编译期结构运算,在报表编译时可以通过程序吸收大部分结构差异性。在Witrix平台中,报表制作分为三个阶段:设计期 -> 编译期 -> 运行期。报表引擎负责完成三种工作:结构描述,结构生成和结构转换。具体实现动态结构生成的过程其实非常简单。目前所有的Witrix配置文件都通过基础配置引擎进行解析,它定义了统一的dynamic和extends元机制。
   <report dynamic="true">
定义了dynamic="true"的报表定义文件首先作为tpl模板文件来运行,其运行结果再作为报表格式解析。在这种模型下,报表引擎并没有内置如何把动态结构拼接出来的知识,这些知识存在于编译期,而tpl标签的抽象能力使得我们可以把复杂的报表结构生成过程抽象成简单的标签调用形式。
   <report dynamic="true">
      <body>
        <table>
         <thead>
            <c:forEach var="_h" items="${cols}">
             ....
        </table>
      </body>
   </report>

==>
   <report dynamic="true">
      <body>
         <rpt:GenCrossTable tableMeta="${tableMeta}" loopVar="tableData" />
      </body>
   </report>

   在编译期通过tpl封装可以解决大部分结构生成问题,在运行期报表引擎主要负责的结构问题就简化为数据行展开和单元格合并等确定操作。
   Witrix报表引擎的另一个特点是运行期结构生成过程和结构转换过程同时进行,因此不需要在内存中构造一个完整的报表数据对象,大大减轻了内存压力。Witrix报表引擎输出的文件格式目前有html, XML格式的Word文件和XML格式的Excel文件等。每一种输出格式相当于定义了一种渲染模型,它们都是对报表模型的一种展现方式。从某种程度上说这些模型的结构都是等价的,但是完成模型转换所需要的操作往往不是局域化的。例如在html的table中某一单元格具体对应哪一列是受到其他单元格的rowspan和colspan属性影响的, 在Excel中则需要明确指定列的index属性。为了简化运行期逻辑,内置的报表模型必须提供一些冗余结构,从而兼容多种渲染模型。

posted @ 2007-09-02 09:45 canonical 阅读(1655) | 评论 (1)编辑 收藏

 元数据(meta)是描述数据的数据。它所描述的有一部分是数据本身的特性,如字段的长度,精度等,另外一部分描述的则是我们使用这些数据的可行方式和目的等。使用meta可以在程序中更加清楚的表达出我们的意图。例如现在需要在界面上显示一个列表,我们的意图未必是要在界面上显示指定的字段A, 字段B,字段C对应的列,而是"显示那些应该显示在列表中的字段"。这一看似同义反复的表述,如果采用元数据表达,则成为 <ui:PageTable fields="${dsMeta.listableFields}" />。通过使用元数据,我们可以做到系统中众多的功能可以共用实现,即通过同一个页面应用不同的meta则得到不同的最终展现,而后台一个通用的DaoWebAction通过使用meta可以完成对所有实体的操作。这也可以看作是一种复杂的策略模式的应用。

posted @ 2007-08-19 13:39 canonical 阅读(1192) | 评论 (2)编辑 收藏

  ORM(Object Relational Mapping)技术为什么是有效的?对这个问题一般的答案是ORM解决了面向对象技术和关系数据库之间的阻抗匹配问题。但是任何一种成功的技术,它的支持理由都不会是单一的。在Witrix平台的实践中,ORM的如下几个特性是关键性的:
  1. 主键和对象之间的一一对应关系。在Web应用中,前台浏览器持有的只能是对象的某种表示, 因此一种locator机制是最基础的要求。
  2. Container结构。Container所拥有的信息包括其所有元素以及元素之间的关系。因为Container具有全局知识,所以它可以解决对象图中的循环依赖问题。正是因为session中一级缓存的存在,才能在实体延迟加载的情况下保证对象引用的唯一性,保证a.b.c.a == a。在程序设计中,所有支持对象图的Container结构都是non-trivial的,都必然是有价值的。例如spring容器在配置管理方面最重要的价值就在于可以管理循环依赖的对象创建过程。而在witrix平台的前台所定义的ControlManager管理器其核心作用就在于协调前台控件之间的消息响应依赖关系。从spring的配置文件可以清楚的看出,bean的声明过程是顺序进行的,但是bean的创建过程是非顺序结构的。在数学上,我们说系统的拓扑(topology)发生了变化。
   A a = new A();  B b = new B();  a.setB(b); b.setA(a);
  3. 对象作为复杂属性的集合。关系数据库中保存的都是原子性数据,每一列都是不可再分解的原始数据类型,数学上称之为标量(scalar). 而ORM引擎返回的直接就是嵌套的复杂数据类型,因此不再需要一个额外的造型过程.虽然总是返回全部数据列不是很优化,但是这种强制性的较粗的处理粒度使得前台程序可以有更多的选择自由.
  4. 对两两关系的内蕴表达及充分利用.关系数据库中所保存的是系统分解后的表示,即关系被分解了而不是被表达了,外键对数据关系只起到一种约束作用,它对于查询语句的构建并没有直接的影响.所有数据之间的关系都必须在查询的时候明确指定出来,即调用者必须拥有数据关系的知识,而不是数据本身拥有这些知识.在如下的sql语句中
   select * from a, b
    where a.fldA = b.fldB
    and a.fldC = 1 and b.fldD = 2
a.fldA = b.fldB 可以称为关联条件,而a.fldC=1可以称作是坐标条件.SQL的复杂性很大程度上来源于我们频繁的需要在各处指定完全一样的关联条件而无法把它们抽象成可复用的组分.在ORM所提供的对象空间中,对象之间的两两关联只要指定一次,就可以在增删改查等各种操作过程中起到作用,特别是在对象查询语句中,可以通过两两关联自动推导出多实体之间的关联关系,虽然推导出的结果未必是最优化的.
  select from a where a.fldC = 1 and a.b.fldD = 2
   在Witrix平台中,我们做了大量的工作以确保对象上的复合属性(例如a.b.fldD)和本征属性(例如a.fldC)在使用上是完全等价的.这些工作的结果并不仅仅是减少了一些应用层的代码量,它使得系统结构发生了深刻的变化.复合属性把单表模型推进到了业务主题模型,使得单一业务对象可以聚集某一范围内的相关结构信息,这才使得witrix的模型分解技术成为可能。因此在我们看来,HQL是hibernate价值的集中体现.
   5. POJO提供了纯粹的first class的持久化结构空间.采用POJO结构可以充分利用现有语言及开发工具中的一系列基础设施,大大降低了持久化结构的构造成本.而透明的get/set操作使得我们可以完全基于相对知识对持久化结构进行变换,在完全不依赖外部环境信息(例如数据库连接和ResultSet界面)的情况下解决系统的主要业务结构问题.这一切成为可能在技术上都源于AOP(Aspect Oriented Programming)所引入的重新诠释的能力,它使得我们可以将对象上的某种相对操作重新诠释为对数据库的相应操作.
   http://canonical.javaeye.com/blog/37064
     6. Entity具有活动能力.Entity并不是完全被动的数据容器,而是可以定义复杂动作的对象.在Witrix平台中,后台程序大致具有如下结构
        Entity  ---- 结构问题
      Handler ---- 业务动作定义
      BizFlow ---- 动力学问题
 Handler类似于J2EE中传统的Service层,只是一般实现的方法要少的多.而BizFlow是某种结合了界面表示的流程引擎.基于实体结构使得系统在细粒度处具有某种活动能力,便于我们构造一些局部结构来解决问题,因而也就缓解了大量操作在Service层的堆积过程,有利于维护系统整体结构的对称性.
    -----------    ==>   -------------|--
    -----------                               |--
    
    通过对于ORM技术的理论分析,Witrix平台采取了一些和一般J2EE架构不同的设计.实际上目前J2EE架构下的常见的DAO模式在使用ORM技术的情况下往往不是合适的选择,因为DAO的一般设计是封装某个实体相关的操作,它直接破坏了ORM的container结构。原先我们只需要EntityManager.get(entityClass, id)这一通用方法既可得到各种数据实体对象,而现在需要对每种实体调用不同的Dao函数,显然这是对系统结构的重大破坏。在Witrix平台的设计中没有独立的DAO层,它通过通用的EntityDao统一完成所有对象的存取过程,而不是每个XXXManager继承公共的Dao类。即整个系统架构中尽量维护数据存取过程的统一性而不是实现它的分散化。
  在Witrix平台的Workflow引擎,Wiki引擎等模块的设计中,IWorkflowStore和IWikiStore等类的设计类似于DAO模式,是对存取方式的一种封装。在比较复杂的模块中,对于存取逻辑做出一定的封装是需要的。但是注意到Store类的设计和实体框架的设计相比,其结构可分解性要相差很多,它基本上只提供对外的服务接口。如果我们能够对于文件系统等存储设施作出充分多的工作,我们一样可以对于非关系数据库的某些存取形式完成Container结构,只是这个工作量过大,而我们一般并不需要对非通用的存取结构掌握如此充分的知识。
     实体结构隐含的扩展了系统的同时性视图,a.b.c.a == a 所隐含表达的事实是a,b,c是同时存在的.http://canonical.javaeye.com/blog/33797 在某些时候,例如当我们需要将系统结构顺序化,序列化的时候,这种同时性会成为一种障碍.因此Witrix平台中数据同步所使用的ETL(Extract Transform Load)引擎是基于表结构(分解后信息)的,而不是基于实体结构(关联信息)的.实际上,关系模型在某种意义上是系统分解后的必然结果,因此随着我们对系统的理解的粒度要求越来越精细,很可能最终需要我们明确意识到关系对象本身的存在性,最终实体模型会非常近似于关系模型.只不过在实体模型级列中我们选择的余地更大,关系模型可以看作是它的某种极限.理想的情况是在不同的时刻我们可以使用不同的关系抽象,只是受限于目前的实现技术,在系统构建时刻基础的关系结构往往就被固化下来.

posted @ 2007-08-13 00:25 canonical 阅读(1035) | 评论 (2)编辑 收藏

   JSF(Java Server Faces)技术从发布时间上看已经是一种比较古旧的技术了,但是目前仍未能成为主流的开发实践。从我知道这种技术开始, 我对它的判断就与我最早对于EJB的判断一样, 它们都在某种程度上捕获了真正的需求,但是因为它们自身诡异的技术路线.我很怀疑是否这些标准制定者故布疑阵, 便如Microsoft的OLE技术一样, 故意抛出一个错误的方向, 将大批组件开发商带入死局.
   JSF技术是一种双重的存在:它首先是一种标准,然后也提供了一种缺省的实现。但是从这两方面,我都无法看到JSF的未来。
   从设计上说,强类型的视图模型对象层与Witrix的架构设计原则严重冲突。Witrix的基本架构是浏览器和后台服务器通过具有显明语义的url实现两分,这也是所谓REST风格的一种内在要求。隐蔽了链接的技术破坏了基本的web超链模型. 为了获得那么一点点结构控制能力, 做出这样的抽象是不合适的.JSF的配置模型继承了structs的传统,仍然是那样的冗长繁杂。我们是否真的需要这些配置文件,还是希望像ROR那样在代码中直接完成一切?
   不能在标准的浏览器中预览. 可以说创造了一个JSF IDE的市场, 但是这无疑是一个无聊的市场. 现在有一些备选的方案, 如Facelets, 使得jsf可以采用属性语法, 但是只要想想仅仅为了这么一点小小的修正所需要付出的开发量就足以让人崩溃。
   JSF提供了组件级别的事件响应机制,因此似乎是AJAX应用的理想场所.但从Witrix平台的开发实践来看,JSF对于AJAX的使用是受限制的,有着很大局限性的.组件事件响应并不一定要采取JSF那种体系结构.
   从实现角度上说,基于jsp tag可以说是JSF的致命弱点之一. jsp tag从设计之始就一直是未经过实践考量,其设计无法支撑复杂的控件架构. 特别是早期JSF与标准的JSP tag不能互通实际上是明显的设计缺陷, 而且性能问题是内置在该设计中的. 现在虽经多个版本的不断补救, 但是为了兼容性, JSP Tag负担过重, 它始终是基于文本处理模型,实际上不可能有什么本质性的进步. JSP tag模型过分孱弱必然造成JSF设计中大量处理过程堆叠到界面对象层,更加剧了JSF的模型复杂度和性能瓶颈。 实际上根据Witrix平台中tpl模板技术的设计经验,大量界面构建过程是可以在模板层以直观的方式完成的,而不需要借助视图模型对象。
   所有问题的一个集中体现就是增加一个新的JSF组件绝对不是一件平凡的事情.如果有一天这个问题可以得到解决,那时的JSF从思想和实现上都必然和现在的JSF有着本质性的区别.

posted @ 2007-07-29 23:43 canonical 阅读(1309) | 评论 (7)编辑 收藏

  REST(Representational State Transfer) 是Roy Thoms Fielding在其博士论文中所提出的一种架构风格(Architectual Style)。http://roy.gbiv.com/pubs/dissertation/top.htm  http://www.redsaga.com/opendoc/REST_cn.pdf 可以说它也体现了整个互联网架构的基本设计原则。随着AJAX等技术的发展,互联网的资源语义逐渐得以恢复,最近对于REST的讨论也热烈起来。http://www.ibm.com/developerworks/cn/web/wa-ajaxarch/
  在Fielding的论文中对REST有如下陈述:
The name “Representational State Transfer” is intended to evoke an image of how a well-designed
Web application behaves: a network of web pages (a virtual state-machine), where the
user progresses through the application by selecting links (state transitions), resulting in
the next page (representing the next state of the application) being transferred to the user
and rendered for their use.
  点击超链接之后,服务器端返回资源的某种表示(Resource Representation), 客户端的状态(Representational State)随之发生迁移(transition),在服务器端返回的表示中存在着到其他状态的迁移链接,使得我们始终自动具有进一步迁移的能力(这和ajax中所谓返回纯粹数据其实是不同的). 这种图景其实也正是Tim Berners-Lee最早所设想的web的图景。因为REST是对互联网架构设计的一种抽象,因此所谓的Restful其实就是尽量符合现有Web标准。从概念上说,REST所关注的主要还是分布式资源信息交换,而不是对复杂的业务逻辑进行抽象。遵循REST将会使整个互联网受益,确保路由器,缓存,代理,浏览器,服务器等各个组件可以协同工作,它的意义并不是针对单个应用程序的。不过与witrix体系架构相对比,我们还是可以发现REST架构风格对于一般web程序的参考价值。
   1. 通过唯一的,确定的url来标定可访问的资源。url定义了后台资源的访问界面,明确区分了浏览器和服务器两个分离的状态空间。这种两分的架构约束是witrix平台最重要的设计原则。在witrix平台中,Jsplet, BizFlow, PageFlow,AuthMap等技术完全体现在url的精化设计上,系统中所涉及到的所有关键概念都对应于url中的明确组分。而JSF等技术通过对象封装模糊了资源的访问界面,迫使我们只能通过一个错综复杂的对象包络来理解系统的交互过程。
   2. REST强调整个交互图景中所有需要的相关信息都是in band的,而且语义上不同的部分都对应于特定的语法上不同的部分。例如为了保证url的不透明性和稳定性,一些信息通过http协议的header来传递。这里强调的语义自明性实际上是脱离具体知识体系的形式上的可区分性。在现有的服务器端程序中, 因为直接接触到的是已经解析好的parameter集合, 因此Witrix平台中关注的是在参数集合中识别出特定的形式结构, 这通过EngineURL对象实现.
   3. HTTP中明确区分了GET/POST/PUT/DELETE四种标准操作, 并对它们的语义进行了精确的限定. 而所谓的RPC(Remote Procedure Call)与此是不同的. RPC上承载的方法数可以是无限多的,但是这种无限多的操作反而从某种层面上说是简单的,是对称的,没有可区分性。而在REST中,在确定资源这一限制下,我们可以区分出GET/POST/DELETE/PUT这四种有限但是不同的语义。 是否这四种语义构成完备的操作空间?从某种意义上说, 这四种操作覆盖了资源的整个生命周期, 它自然满足了某种完备性. 但是抽象意义上的完备性并不意味着它在物理层面上也是完备的. 我们都知道二维空间是完备的,但是二维描述并不是三维空间的合适表达. 在Witrix体系下,WebAction所提供的完备的操作集合包括Query, ViewDetail, Update, Add和BizAction.
BizAction是个特定的扩展,所有不便于直接归类到CRUD操作的操作都可以归类到这一Action的名义下. Witrix架构中把CRUD看作是一个更大的Action空间的一个子空间, 对此子空间的结构进行了细致的划分, 所关注的重点是对部分信息的更明确的表达, 而不是对程序整体的建模.
   区分GET/POST/PUT/DELETE四种操作, 最重要的意义在于定义了GET和其他操作的区别. http://www.w3.org/DesignIssues/Axioms.html
 a. in HTTP, GET must not have side effects
 b. in HTTP, anything which does not have side-effects should use GET
   在GET这种语义下, 才可能建立独立的cache组件, 而正是因为cache的存在, 才使得web成为infomation space而不是computing program. 而在Witrix平台中, 很多通用机制的建立(例如精确到数据行的访问权限)都需要对于CRUD做出精细的区分, 而不仅仅是识别出GET操作.
  4. REST中虽然定义了具有概念稳定性的url, 但是因为操作集合有限,而且强调服务器端的无状态性, 因此一般认为这种结构并不是面向对象的. 不过,在我看来,面向对象首先意味着一组相关性的聚集, 实际上从语义层面上看, REST与witrix平台的jsplet框架非常接近, 最大的差别在于服务器端明确定义的thisObj---this指针. 服务器端的无状态性对于网站应用而言是必要的, 但是对于复杂的企业应用而言并不是合适的约束.
  5. 对整个互联网应用而言,URI应该是不透明的,它的结构对互联网中间应用而言应该是不暴露的. 资源的结构是与整个互联网架构无关的. 最近业内鼓吹REST的时候往往也推崇所谓beautify的url, 例如 http://www.my.com/blog/3/comment/1. 这种结构不过是维护url稳定性的一种副产品, 在我看来, 它对于REST而言并不是必需的. 不过根据这些年关系数据库应用积累的经验,识别集合和集合中的个体是一种非常通用的场景,因此我们可能规范化这种结构,甚至搜索引擎等外部组件也可能理解这种语义,从而实现更加复杂的语义网络。在现有的所谓支持REST的web框架中, 往往支持这种规范化的url直接映射到后台的响应函数上. https://cetia4.dev.java.net/files/documents/5545/38989/cetia4_tutorial.pdf. 例如在cetia4框架中, GET /blog/3将会被映射到后台函数 String render(RenderContext context, int id)函数上. 在witrix平台中, 缺省并不采用beautify的url, 但是因为对于语法成分具有明确的定义, objectEvent=ViewDetail&id=3这样的url将映射到后台biz-flow中的action段.
 <action id="ViewDetail-default">
  <source>
    在这里直接拿到entity对象,而不是id值
  </source>
 </action>
在action中我们直接接触到的不是id值,而是id值相对应的实体对象本身. 对于objectEvent=Remove, 我们可能一次删除多条记录, 则在后台bizflow中action=Remove-default段将会被调用多次,每次处理一个实体. 这些自动处理机制都离不开对于标准化url组分的明确理解.
   在网站应用中, 我们一般也通过url rewrite机制来支持简化的层级url. 但是这种根据位置来确定语义成分的url格式其实也存在着很大的局限性, 在cetia4的映射中很多时候都会出现含混的情况. 而且因为资源是抽象的, 页面中的相对路径计算会出现一定的困难. 在witrix平台中, 通过tpl模板语言的标签增强机制, 我们重新定义了页面中的相对路径计算规则, 从而大大简化了资源管理问题. 在tpl中相对路径计算永远是基于当前模板文件的, 例如对于通过<c:include src="subdir/included.tpl" />引入的子页面included.tpl, 在其中的<script src="included.js" />等使用相对路径的语句会在编译期被转换为使用绝对路径, 生成<script src="/contextPath/root/subdir/included.js" >等语句.
  6. 一旦url格式被规范化, 我们就可以基于它在应用程序中发展很多全局结构. 例如在cetia4中, 可以建立全局的navigation模型, 并提供了一个breadcrumb 导航栏. 这是一种全局的链接管理机制,在一定程度上实现了导航信息压缩.  但是因为其没有对象结构支撑, 与Witrix平台的PageFlow技术相比, cetia4的方式不过是非常原始的一级策略, 它对于对象生命周期的管理也是过于简陋的.http://canonical.javaeye.com/blog/32552
  7. REST中强调generic interface 而不是强调custom interface. 实际上目前业内的对象化方案很多时候都沉迷于提供特定结构的构造方法, 很多面向对象框架本身就在构造各式各样的特定结构。一种通用的结构只存在于概念中,是复杂结构背后的事情。但是在很多情况下, generic interface无论在实现成本还是概念成本上都是更加低廉的. 在witrix平台中, jsplet框架所实现的对象化就是一种generic方式的,弱类型化的, 而不是强类型的对象结构的。
  8. 关于REST的另一个有趣的问题是如果大量使用HTTP内置特性,是否我们的应用将严格绑定到http协议上,不再是所谓的protocol independence。不过我们真的需要同一语义模型的不同实现吗.

posted @ 2007-07-08 22:06 canonical 阅读(1856) | 评论 (3)编辑 收藏

    今天adun给我讲了一个他所谓可退化的设计,在我看来问题还是多多。从直观的角度上说,在java中声明一个具有多个参数的函数,调用的时候对于不需要用到的参数都传入null, 这不是理想的可退化场景。所谓的退化不仅仅是概念层面的,不仅仅是关于语义的,很大程度上它也是形式上的,是关于语法结构的。
    理想的退化场景是尽量维持形式/结构稳定性的情况下实现诠释范围的缩减,在任何层面上都不需要知道超出当前需要的信息。而如果我们被要求必须传入自己实际上不需要使用的参数,则必然存在着一定程度上的信息泄漏。一个朴素的看法应该是,当我们需要它是一个参数的时候它就是一个参数,当我们需要它是三个参数的时候它就是三个参数。对于系统形式结构的有效规划是实现可退化性的前提条件。


posted @ 2007-06-27 22:54 canonical 阅读(905) | 评论 (3)编辑 收藏

    描述所关注的是“what”,而运行所关注的是“how”。在现代软件开发中,描述信息作占的比重日益加大。甚至一种极端的倾向是把所有业务逻辑都写在各种格式的配置文件中. 配置文件目前多采用xml格式,它的优点是自说明的:属性名直接标示了其基本含义,但是这也在一定程度上加重了命名的负担, 造成了配置文件的臃肿。因为在普通的程序语言中,可以用来传递信息的结构更加丰富,例如参数的相对位置,参数类型, 匿名函数, 指针引用等。而一般配置文件中没有定义合适的继承,封装等抽象机制,很难如同普通程序语言那样进行有效的结构压缩。
    在很多灵活的弱类型语言中,借助各式语法糖(syntax sugar)可以实现描述性的运行结构, 或者可以看作是构造性的描述, 它在部分程度上消解了描述的诠释问题, 不需要额外的解释器即可实现描述结构的解析. 这有些类似于编译理论中的语法制导翻译, 在动态结构组装方面具有明显的优势. http://www.blogjava.net/canonical/articles/19697.html. 但是独立的描述信息仍然是有着重要作用的, 关键是作为元数据存在的描述信息可以以多种方式被使用, 并可以被部分使用. 此外一些特殊设计的描述文件可以很自然的汇集系统各个方面的信息到同一层面加以展示,而一个通用语言无论语法如何灵活, 抽象能力如何强大, 毕竟受限于先天的结构, 要做到这一点还是不现实的.
    在witrix平台中配置文件的设计一般是综合考虑静态描述和动态调整的需要, 在设计上分成静态描述段和动态运行的init段, 系统将确保init段中的tpl代码会在适当的时候被调用.


posted @ 2007-05-27 18:48 canonical 阅读(1234) | 评论 (1)编辑 收藏

   在商业产品开发中,如何有效的控制同一产品的多个衍生版本是一个非常重要的问题。客户的需求是多样化,差异化的。这些差异有些很小,可以通过参数配置,资源装载,skin切换等方式加以吸收,而有些则要求对界面布局和程序逻辑等作出较大调整。Witrix开发平台在系统基础架构方面为程序的客户化提供了有力的支持。
   1. 多版本控制的关键首先在于系统良好的模块划分。因此Witrix平台的beans,auth-map(权限归约规则)等配置文件格式都支持import/include等基础的分解策略,字符串资源和错误码映射等支持多重定义文件,而对于sql.xml(外部sql语句定义), meta.xml, biz.xml, hbm.xml等配置文件采用分模块动态装载机制。
   2. 在Witrix系统中定义了一个特殊的custom目录,规定了一般性的覆盖规则:custom目录作为系统根目录的影子目录,如果custom目录下存在同名文件,则优先装载custom目录下的文件。例如,如果custom目录下存在/_config/my/my.biz.xml文件,同时在根目录下也存在/_config/my/my.biz.xml文件, 则实际装载的是custom目录下的实现。这里的一个关键在于只有meta.xml(元数据),biz.xml(BizFlow描述文件),.lib.xml(tpl模板库)等具有一定完整性的文件才支持custom机制,而并不是所有资源都采用custom机制。如果每一个tpl文件,css文件,js文件等都优先从custom目录下装载,则很快就会出现循环引用,相对路径计算将会变得非常混乱,更重要的是我们将无法定义资源删除语义。
   3. 元数据文件,BizFlow描述文件,PageFlow描述文件等都支持复杂的extends机制,使得我们在扩展时只需要对于系统差异部分进行描述,而不是大段拷贝代码。
   4. tpl模板库和sql-map机制等采用的是追加覆盖策略。例如custom目录下的ui.xml标签库文件并不是直接覆盖系统根目录下的ui.xml文件,而是按照标签名进行细粒度的覆盖。系统编译时会自动检查覆盖标签的所有参数要求和原标签相兼容(例如允许增加参数而不允许减少参数),确保所有引用到原标签的tpl代码仍然有效。实际上整个witrix平台多版本扩展机制的一个设计目标就是确保平台主系统向各个分支产品的单向信息流动。在具体的表现上就是我们随时可以拷贝平台主系统覆盖到分支产品的相应目录,所有扩展实现与主系统实现保持分离状态。当然为了保持设计的弹性,系统中也定义了开关参数用来有选择的跳过一致性检查。
 

posted @ 2007-04-22 23:15 canonical 阅读(1503) | 评论 (4)编辑 收藏

    命名(Naming)无疑是人们认识世界最基本的手段之一。从软件技术的发展中我们也可以观察到命名技术的不断深化。
    1. 助记的名:汇编之中我们有了move/push/pop这样的指令,所面对的不再是010101这样的同质的数字世界。变量也逐渐可以拥有自己的名字,甚至多个名字。在C语言中指针的概念被进一步抽象化,使得我们可以为任意内存地址起一个可读的名字。我们甚至渐渐忘怀了pStruct是指针的名字,而直接把它等同于指针所指的内容本身。
    2. 局部的名:函数(例程)概念的出现把局部名称引入系统,从此精细结构的发展才成为可能。
    3. 多义的名:面向对象的出现可以看作是命名技术的一种重大进展,它可以把一组相关的数据和函数放在一起起个名字。继承概念为名引入了多义性。通过虚拟函数表所实现的lazy-binding部分松动了对象的名和实之间的指称关系。现在一些所谓dynamic dispatch技术,依然是顽强的希望在同一名下,纳入更多实的变化。
    4. 特指的名:面向对象技术创造一个特殊的名---this指针。它是一种约定了的固化了的局部名称。使用this指针使得我们区分了领域(domain)的内外。在domain外对象可以有各种称谓,而domain内我们直接通过this直接触及到对象的实体。在javascript这样的动态语言中,函数和this指针是动态绑定的。在某种意义上,这意味着一个操作所依赖的domain知识可以是动态变化的。
    5. 相对的名:面向对象技术所创造的知识相对化概念的一个直接体现是命名的相对化。一个函数它的具体含义不再是绝对的,而是相对于this指针的。因此我们不再采用user_load, book_load这样的全称的函数名, 而只定义load这样的具有依赖性的函数。在面向对象的理想操作图景下,首先应该是通过一个整体的参数直接区分出多个大的情景,然后在每个特定的情景下分别调用相对函数进行操作。在模板(template)技术或者动态语言中,这种相对性可以得到更加充分的发挥。因为在泛型或者弱类型语言中,我们需要的只是对象提供特定名称的函数或属性而已。
    6. 持久的名:在早期的语言中,名只在编译时刻存在。在编译出的二进制代码中,名所提供的丰富的描述空间不复存在,我们所有的只是同质性的二机制地址而已。而在现代语言中,反射已经成为了不可或缺的技术,它使得我们在运行时刻仍然可以拥有复杂的描述结构。
    7. 分离的名:在一般的程序中,我们早已习惯了变量名直接指代实际可操作的对象本身,名的问题显得太平庸以至于大家似乎忽略了它的存在。但是在web体系架构下, 因为存在着浏览器和服务器这样两分的状态空间, 名成为两个系统交互的直接手段,名的重要性也重新凸显出来。只有在一个封闭的container中,才能通过名字解耦. 因此web架构设计的一个核心问题是构建出各种各样的全局的container. 浏览器前端技术的一个本质性困难即在于多个浏览器窗口之间没有共享的全局对象空间,因而很难在前台独立建立container结构。
   在witrix平台的jsplet框架中,在前台我们通过如下url来访问后台
  /view.jsp?objectName=MyObj&$bizId=test&objectEvent=ViewDetail&id=1
MyObj这一参数标定了BeanContainer中的一个Java对象, $bizId参数指定应用某个Aspect到此对象上,objectEvent参数映射到WebAction上的一个java方法,而EntityManager最后负责把id映射到具体的实体对象。当我们在后台需要编制代码的时候,entity对象已在手边。
    8. 名的结构:当名越来越多的时候,我们需要对它们进行有序的组织。名字空间(namespace)所采用的树形结构可以说是最直接的一种组织方式。这一结构不仅仅可以用于描述,同时可以用于控制。

posted @ 2007-04-01 21:30 canonical 阅读(1451) | 评论 (3)编辑 收藏

    软件这个领域中传统上占优势的是自vonNeumann以降的数学视角,计算问题是其思想内核,而函数式语言无疑是其比较贴切的表现。但是仅有数学,我们对于世界的认识是不充分的。有这样一个笑话。烧一壶水的完整步骤如下:1.向空壶中注满水 2.放到火炉上 3.烧到冒泡。现在有半壶水,求解烧水的步骤。数学家的回答是直接把半壶水倒掉,然后宣称问题已经解决,因为它已经被归结为第一个问题。实际上数学的视角直接限制了某些命题进入研究者的领域。现在业界占主流的面向对象技术,并不像是理论界的自然创造,它的思想来源更像是来自于开发窗口系统的工程实践。http://gagne.homedns.org/~tgagne/contrib/EarlyHistoryST.html.
    面向对象技术的核心是描述问题,它试图实现业务概念和业务关系在程序中的直接表达,所谓更贴近人的思维方式。但是从面向对象的实际操作过程我们即可得知,这里所谓的贴近只是贴近人们的常识而已。常识是有意义的,但也是浅薄的。当程序功能变得愈加复杂,程序的结构不再那么直观的时候,我们从面向对象理论得到的支持并不如最初它宣称的那样的巨大。早期面向对象技术所提出的枚举系统中所有名词和动词的设计方法现在看起来无疑是非常的幼稚。如何确定一个系统中到底需要定义多少个对象,以及如何确定它们之间错综复杂的交互关系,这些并不是通过我们的业务常识能够确定的。
    在具体的,特定的常识与抽象的数学之间,存在着所谓的物理学。常识总是和一定的“有意义”的场景绑定着,可以直观的理解。而数学则蒸馏出一些纯粹的符号,试图与所有预置的意义划清界线。物理学是折衷主义的,或者说是非常狡诈的。它选择了只在需要的时候诠释。在物理学的推导中,大量的中间过程都是数学性的,难以寻找到明确的物理含义的,但是我们无视它,只对某个最终结论加以解释。事实就是公式千千万,但是我们却只对其中的某些说:嗯,这里面有物理。与此对应,在软件开发中,将直观的业务需求映射到通用的程序语言实现时并不是那么直接的,在这之间可以存在着超过一般人想象的与特定业务无关的厚重的技术层。在这个层面中可以定义出非常复杂的交互模式,并在不同的特定场景下将其诠释为不同的业务应用。这也正是平台技术赖以生存的基础。
    丰富的物理学的根基在于丰富的物质结构,它的核心是动力学。实际上单量子体系并不如想象中那样难以研究,这个领域充斥着粗鲁的线性近似,而它们的预测精度却都难以想象的高。真正的复杂性来自于简单元素所构成的复合结构,以及这些结构之间的相互作用。随着ROR这样的动态语言框架的流行,很多人把它们的成功归结为语言特性的增强,这在我看来并不是富有成果的方向。诚然,更多的语法糖(syntax sugar),更多的动态性可以降低我们构建某些复杂结构的代价,但是这种降低最多是减少一两倍代码录入量,而绝不可能是数量级上的影响。对于程序开发可以起到决定性作用的是那些复杂的大范围结构,而不是通用语言本身所提供的那些抽象的简单的局部结构本身。当然,一般人大概很少有能力做出超过一定难度的创造,一般都只是依赖现有的语言,现有的框架以直白的方式实现功能,因此很难想象可以在语法特征更少的java中轻易实现超过ROR的开发便捷性。
    通用语言的发展必然是有极限的, 也必然是贫瘠的, 因为它无法利用有限场景下的信息. 而DSL(Domain Specific Language)将注意力集中在某个特定的领域(domain)中,便可以名正言顺的引入非常复杂的语法结构. 这些结构旨在本领域中具有意义,而不用担心超出应用范围后遭到"冗余设计"的诟病. 我无法想像在一种通用程序语言中会规定Witrix平台中BizFlow这样的结构设计,但是作为一种domain相关的语言结构,它的表述却无疑是非常高效的。我相信,随着我们对于程序结构的认识的不断深化, 在DSL所构建的复杂结构空间中可以发展出一些真正有趣的技术.
    对于DSL存在着两个常见的误解.一是DSL是存在于特定领域的,因此它是一种受限制的语言,应该在计算能力上弱于图灵机.但其实DSL的核心在于高效的表达,在于直接存在的高阶结构,与它的形式计算能力并无关系.正如物理中Lagrange表述和Hamilton表述在数学上是等价的一样,我们的选择只在于某种情况下某种描述方式会更加方便。这里所玩的游戏更像是概念空间中的拓扑变换。
    关于DSL的另一个误解是DSL的表述形式应该接近自然语言.但事实上数学符号和化学公式都是高效的DSL,它们的表达形式甚至内在逻辑都和我们的自然语言相去甚远.我们是否已经完全解决了程序问题,而只是要把这种能力向无知的客户转授?目前在程序编码的过程中我们仍然面临着大量未决的问题,程序员应该是DSL的直接受益者.此外,西人对于语言的认知是偏狭的,因为他们眼中的language只有拉丁语系和日耳曼语系,而不知道这个世界上还存在着不符合西文语法的汉语。按照朱光潜的诗论,西人长时间认为诗是不宜于写景状物的,因为语言是串行的,因而只适于按照步骤叙事,却不了解汉语的自由组合形式和丰富的单字表现力可以轻易捕获微妙的瞬间.

posted @ 2007-03-18 23:05 canonical 阅读(1642) | 评论 (0)编辑 收藏

   最近D语言发布了1.0版,这是一个由编译器开发者所设计的编译语言,语法类似C++, 但是针对C++的弊病作了大量修正,并增加了很多现代特征,其中还是有一些新意在其中的。http://www.digitalmars.com/d/overview.html 我对其比较感兴趣的部分是D语言明确提出的编译期运行的概念。虽然C++让大众了解了meta programming技术,很多人因此认为meta programming的威力在于类型演算,但是在我看来meta programming的真正作用在于在编译期可以“动态”产生或调整代码结构。它依赖于类型是因为我们只能利用类型来承载一些额外信息, 这无疑也是对于类型的一种滥用。在D语言中template所接受的不仅仅是类型, 或者类型的类型,它可以是任何符号.
  template MixInAttr(T, char[] name){ 
     mixin(" T _" ~ name ~";");
     mixin(" T "~name~"(){ return _"~name~"; }");
     mixin(" void "~name~"(T v){ _"~name~" = v;}");
  } 
 
  template MixInOtherAttr(T, T v){
    T _otherAttr = v;
   
    int otherAttr(){ return _otherAttr; }
  }
 
  const int myValue = 1;
 
  int addFunc(int x){
    return x + 1;
  }
  
  class MyTest{ 
     mixin MixInAttr!(int, "myAttr"); 
     mixin MixInOtherAttr!(int,4);
    
     static if(addFunc(myValue) 〉 0){
        int testFunc(){ return 5;}
     }
  }
 
  void main(){ 
     auto t = new MyTest;
     t.myAttr = 3;
     int v = t.myAttr;   
     v = t.otherAttr;
     t.testFunc();
  }  
  不过现在编译期运行无疑是一个正在探索的方向, 在D语言中的使用方式并不是非常理想的, 在形式和功能实现上都存在着重大改进的可能. 
 
  在witrix平台的tpl模板语言中,我们也引入了编译期运行的概念,只是tpl的语言特征非常简单再加上xml格式特殊的规范性,编译期运行在tpl中的实现是非常直接和明确的.在tpl中定义了<cp:run〉标签,它的内容在编译期运行, 输出结果(xml字符串)将被继续编译. 在<cp:run〉中可以执行任何有效的tpl代码, 它们在编译期状态空间中运行. 例如
 〈cp:run〉
   〈c:forEach var="_x" items="${tagBody.children()}"〉
       bla bla bla...
   〈/c:forEach〉
 〈/cp:run〉
在EL表达式中我们通过cp前缀实现普通调用和编译期调用的混杂.
  〈cp:const class="mypkg.MyConstantClass"/〉
  〈c:eval expr="${myFunc(cp:const.MY_CONST, commonVar, cp:funcInCompileTime())}" /〉
  〈cp:compile test="${exprInCompileTime}"〉
     any tpl ...
  〈/cp:compile〉

 其实当我们把改进的目光开始放到编译期的结构方面的时候, 可以做的工作是非常多的. 例如从结构上看, 继承策略相当是类结构的一种一阶替换策略.
  类B(methodB) extends 类A(methodA, methodB)  ==〉 类B(methodA[inA], methodB[inB])
如果我们放弃概念层面上的纠缠,而如同template那样只关注语法结构, 则可以发展出更加复杂的替换操作.
  NODE_B(                           NODE_A(                                  NODE_B(
    SUB_NODE_A1       〉〉    SUB_NODE_A1             ==〉    SUB_NODE_A1
      SUB_NODE_B11                  SUB_NODE_A11                           SUB_NODE_B11
                                                     SUB_NODE_A12                          SUB_NODE_A12
  )                                          )                                                       )
C++中及D语言中的template技术在某种意义上相当于是在代码结构中预留了空洞, 可以实现跨越类结构的替换填充. 只是D语言挖洞的能力无疑比C++强大的多. 
  如果我们考察的再细致一些, 就会发现两个语法节点的融合也是存在多种策略的.
  B 〉〉 A ==〉 (remove_A, replace_A, insert_B_before_A, insert_B_after_A, insert_B_into_A, insert_A_into_B)
而通过insert_A_into_B/insert_B_into_A策略既可实现所谓的AOP intercept操作. http://canonical.javaeye.com/blog/34941 在witrix平台的BizFlow技术中, 我们通过高级的结构融合策略实现为任意实体引入流程支持. 最简单的情况下我们所需要做的工作只是增加一个语法元素
 〈bizflow extends="myflow"〉
引入流程意味着对界面的一系列修正, 例如增加,删除某些菜单, 同时要增加很多流程相关函数, 对某些函数实施AOP操作, 在各处引入权限控制等等, 这些都可以通过extends操作引入.

  在传统上, 编译器结构是固化的, 它所编译的代码结构是固化的, 而它所编译出的代码也是固化的, 但是现代语言的各种发展正致力于在各个层面引入动态性. 编译期运行抑或是编译期结构操纵技术的引入是在一个层面上打开了潘多拉魔盒. 它让我们看到了前所未有的技术可能性, 但它无疑也是危险的,难以控制的. 我们目前的做法是尽量克制,只在局部使用, 但我相信它在很多语言中都是可以在理论上严格定义,并精确实现的, 我们最终也能够找到合适的形式来约束它的破坏性. 在这个过程中我们需要引入更多的物理化的视角。

posted @ 2007-03-04 18:14 canonical 阅读(1641) | 评论 (0)编辑 收藏

       代码生成现在已经逐渐成为软件开发中的一种标准技术,在众多的软件领域都大大减轻了我们重复劳动的工作量。程序中总是存在着这样那样的隐蔽的关联,它们无法在通用的程序语言框架下得到明确的表达,代码生成是我们突破既定的语言和框架限制的一种重要手段。但是代码生成也存在着严重的弊病,一方面一般的程序语言在设计时没有考虑到和代码生成工具的相互配合,因此生成代码是一次性的,代码生成工具无法以增量的方式修正已经生成的代码。另一方面,程序的结构是复杂的,代码生成工具一般基于某种简化的通用的程序模型(例如CRUD)来产生代码,它无法承载完整的程序结构,因此代码生成后手工调整量仍然很大,有的时候甚至为了微小的界面调整,将生成的代码修改的面目全非,无法发挥代码生成的优势。
       在witrix平台中主要使用meta generation而不是code generation. meta实际上是对一种定制模型(model)的描述,它在某种意义上可以看作是完整程序的简化版本,但它本身并不意味着最终的程序结构。在witrix平台各处meta的使用都是可选的, 特别是在多变的前台页面,我们可以选择根据meta描述自动生成界面,也可以选择通过<df:Field name="字段名"/>来引用单个字段的meta数据. 在witrix平台中, meta可以看作是系统运行的内核, 它通过syncWithModel等属性与设计工具发生耦合. 当设计模型修改之后, 这种修改能够以增量的形式通过可控制(修改)的信道传播到系统各处.

posted @ 2007-01-21 18:11 canonical 阅读(2514) | 评论 (5)编辑 收藏

    目前的AOP(Aspect Oriented Programming)技术虽然以动态代码织入为核心,但是这种织入仍然是一次性的。一般在系统构造的时候(例如ClassLoader装载Class的时候)实现类和成员函数的增强。此后在运行时刻代码结构是固定的而不再发生变化。但是在真正的业务处理过程中,我们在不同的应用场景下可能要求织入不同的Aspect。例如基本的权限Aspect, 如果在不同的应用场景有不同的权限设定,则我们显然希望进入一个确定的操作场景的时候就指定一整套的权限策略,而不是在每个函数调用时刻写上一大堆的if/else(这种分离的条件判断正是AOP试图从结构上消除的)。
    为了实现AOP的二级动态化,我们首先需要约定一些公共标记(坐标),便于在标记处插入Aspect Container, 其次我们需要在系统中建立一个隐蔽的信道,可以通过该信道传递一个标志符(Aspect的id),用于在各处选择特定的Aspect. 建立这种动态特性之后,我们就可以据此发展出Aspect组的概念,并实现Aspect组之间的继承关系等高阶结构,从而最大限度的限制程序结构的分散化。
    Witrix平台的BizFlow设计在概念上可以看作是AOP的一种二级动态化织入设计,它通过$bizId这一特定参数来选择织入的Biz。一个BizFlow对象是一组Biz(Aspect)的集合, BizFlow可以通过extends机制实现集合之间的合并等(BizFlow实现的合并策略其实是非常复杂的)。一个简单的应用就是流程支持,例如一个普通的实体对象对应的bizflow只需要加上如下代码即可获得流程相关的代码,前台菜单等。
  <bizflow extends="testflow.biz.xml">

posted @ 2007-01-14 17:04 canonical 阅读(1085) | 评论 (2)编辑 收藏

  几年前Michael Atiyah受邀在浙江大学做过一个讲演,题目是Mathematics in the 20th Century,
http://www.cnw3.org/smth/Mathematics/historiesandmathmaticians/goodessays/00000035.htm, 在其中他回顾了二十世纪的主要的数学发展。被他列在第一条的进展就是From Local To Global. 在传统上,数学的主要研究对象是一些得到显性表达的局部公式,而拓扑学对于整体性的“不变性质”的研究最终将我们对于数学和物理学的理解推进到一个新的高度。我想在其他领域中,这种认识上的深化也将是一个必然的过程。随着AOP这种大范围结构操纵技术的兴起,软件技术是否也发展到了可以对程序的整体结构做一些反思的时候?
  面向对象有什么用?它是在各个层面都可以使用的一种描述工具。从一些早期的文献我们可以看出一些端倪,一种整体性的均一的概念是我们迫切需要的。对象可以构成对象,Everything is Object. 只是因为我们对这些太熟悉以致于在今天看来显得有些陈腐。很多人现在津津乐道于CoC(Convention over Configuration)作为一种局部程序设计技巧所带来的可以少些一些代码的经济性,却没有看到CoC更大的作用在于在大范围内保持了程序结构的一致性,使得某些轻灵的设计可以在框架层面得以展现。目前的框架技术更多的是在各个层面各自为战,如何将同样的信息从局部传播到整体是一个耐人寻味的问题。
   传统上的程序世界缺乏一些具体的技术手段使得我们可以方便的触及到程序的整体结构部分,这些整体性的关联更多的是存在于文档中,存在于我们的思想中,存在于程序表达世界之外。而AOP技术从本质上说也只是方便在各个层面实现某种局域化的抽象。 当某些东西被拘束在某个具体的孤立的点中的时候,我们似乎就可以松一口气了。但是在程序中仍然存在着大量"弱"的关联,它们很难被清晰的局域化。模型(Model)和Meta必然在程序构建的过程中扮演愈加重要的角色。AOP只是一种技术手段,它必须和更加宽广的框架技术和模型构建技术结合才能起到最大的作用。   

posted @ 2007-01-03 16:10 canonical 阅读(833) | 评论 (1)编辑 收藏

  http://partech.blogdriver.com/partech/1217744.html
  partech基于AspectJ对于AOP的深入应用作了一些有益的探索。ORM的价值之一正在于通过ORM引擎对于对象上的局部操作作出持久化诠释(参见 面向对象之形式系统 )。在partech的方案中,对象删除操作(destroy)的引入显得有些勉强:因为我们需要标记一个删除的时刻,所以调用了一次空的destroy()方法。但是如果在事件驱动的应用场景中,调用时刻唾手可得,这样的问题便很少出现了。
  我个人所关心的方向主要是框架层面上对于AOP概念的应用。在Witrix平台的BizFlow方案中,借助于框架技术的支撑,我们甚至连new和set 调用都不需要,例如在biz文件中只需要声明相应的事件响应函数,框架负责生成界面从用户处收集信息,负责创建对象,负责执行保存和删除操作:no new ,no set, no save, no load, no remove.
java 代码
  <action id="Add-default">
    <source>
       do anything on entity to be added
    </source>
  </action>
  <action id="Remove-default">
  </action>
  <action id="ViewDetail-default">
    <source>
      entity is accessible here
    </source>
  </action>

BizFlow的实作中是实现为DaoWebAction的一个interceptor。

posted @ 2006-12-05 00:40 canonical 阅读(1363) | 评论 (0)编辑 收藏

    面向对象技术中最重要的概念是什么?在面向对象理论发展的初期,几乎所有的正统声音都在鼓吹继承(inheritance)概念,言必称虚拟函数和多态性。但是依赖继承这种推导关系来构建庞大系统的弊病在实践中逐渐暴露出来,随着组件(Component)技术的发展,所谓的封装概念逐渐被推崇为面向对象思想的精华。在此过程中,接口(interface)概念作为系统细粒度正交分解的手段也逐渐发展起来。在软件系统结构的日益复杂化的今天,封装概念开始成为了质疑的对象。是否与一个概念相关的所有实现都要统一封装到一个具体的对象中?伴随着Java,C#等语言进入主流程序界的AOP(Aspect Oriented Programming)给出了不同的答案。在AOP的环境中,Object不再是牢不可破的黑箱模型,而是成为了外部嵌入的方面(Aspect)的容器。在应用AOP这种大范围结构操纵技术的对象构成体系中,封装不再是问题的核心,我们所关注的是面向对象技术中更为本原的概念:this指针。
    在纯粹的技术层面上,面向对象所指的首先是一组相关性的聚集:它指代了一组相关的数据和函数。为了配合这种相关性的表达,在调用形式上发生了重大变化。从全局函数的调用方式转变到了基于对象指针的调用方式:
    func(this) ==> this.func();
    这里关键性的区别在于从全局性的,绝对的表达方式转变为局域化的,相对的表达方式。this指针限定了一个知识域(domain),调用对象函数是在限定知识域的情况下提供一些相对信息,即调用的时候只需要相对知识。例如现在界面上有两个按钮,其中一个跳转到编辑页面,另外一个跳转到列表页面。为了表达出这两个按钮的不同,我们只需要提供非常少的信息。
    〈input value="编辑" onclick="stdPage.gotoEditPage('${pkValue}')" /〉
    〈input value="列表" onclick="stdPage.gotoListPage()" /〉
所有的公共知识集中在stdPage这个对象指针中。所谓组件技术,关键点也正在于这里。基于一个给定的组件对象,我们只需要知道如何调用它的函数,就可以使系统呈现不同的表现形态。我们所关心的并不是如何构造这个知识域(对象本身),而是如何使用相对知识构造出我们最终所需的系统。封装性使我们摆脱了对系统全局知识的依赖。
   从形式主义的角度上说,任何一种调用方式都只是一种表达,它的具体含义需要一个诠释的步骤。基于对象指针的调用形式直接导向了诠释的多样化:我们只需要替换this指针,就可以改变整个一组调用的具体含义。传统上,对象指针是封闭的,指代的是具体的实现,所有的信息都必须来自于对象指针本身,这造成诠释的局限性。但是在AOP的支持下,诠释可以不仅仅是源于其内的,而且可以是发自其外的。例如基于POJO的ORM框架中,我们只需要纯粹的基于对象自身的知识对其进行操作,ORM引擎通过enhance POJO对象来重新将其诠释为对数据库的持久化操作。

posted @ 2006-12-04 00:15 canonical 阅读(1378) | 评论 (2)编辑 收藏

  随着IoC(Inversion of Control)容器的流行,AOP(Apsect Oriented Programming)似乎逐渐成为了主流技术的一部分,但是除了Transaction, Lazy Load, Cache, Log等少量样板应用之外,AOP的技术价值究竟何在? 它能否在广泛的领域发挥作用? 为什么考虑到传统领域之外的应用时,我们的想象力是如此的贫乏?回答这些问题需要对AOP的技术实质作详细的审视.
  传统上, 程序的结构是静态的. 定义了一个类, 它的成员变量和成员函数就是确定的了,定义了一个函数, 它的具体实现也是确定的. 传统程序设计主要定义了一些固化的规则来规范这些确定性组分的组合关系,如类继承体系所表达的推理关系. 而AOP是一种动态代码织入技术, 抽象的说, 一维拓扑的基本元素是线段与边, 而AOP通过mixin, interceptor等机制可以自由的实现这些元素之间的自由组合而不拘泥于预制的规则. AOP就像是一把锋利的砍刀, 我们用它从最终所期望的程序结构中随意的砍下一部分来, 起个名字,就叫Aspect吧. 实际上AOP技术本身并没有限定程序中哪些部分可以作为Aspect, 这种技术本身并不保证你可以抽象得出真正有价值的Aspect, 它只是一种纯粹的程序结构操纵技术而已.
  AOP技术有两个主要组成部分: 定位技术和定位后的组装技术. 定位技术是AOP所宣称的无侵入性的关键所在. 如果我们使用interface等机制来实现功能,则要在程序各处写下调用语句:
    interfaceA.methodA();
    ...
    interfaceA.methodB();
这可以看作是一种占位技术. 定位技术则一般不需要预先在程序中写下什么调用语句, 根据外部的某些定位规则,我们可以在基础的程序结构中搜索到适当的位置. 在理论上说,这种定位方式非常灵活, 即可以是非常精准的定位到某个点,也可以是非常宽泛的定位到一组切入点. 但是, 这里的一个隐含假设是程序基础结构本身已经具备了良好的,具有某种均一性的坐标系, 只有这样我们才能够拥有定位所需的基本信息. 想象一下,如果整个程序只有一个函数, 所有功能的实现通过传入不同的参数值来实现, 则这样的程序结构中是没有什么可定位性而言的. 早期AOP定位所能够依赖的坐标只有类,方法名称, 方法参数类型等, 而这些信息本身又具有自己的业务含义,随着业务的发展,它们本身的名称也可能需要不断的变化,这直接造成AOP所依赖的坐标系的不稳定性.今天还有效的位置描述, 明天也许就突然包括了某些不应该包含进来的程序位置或者排除了某些应在其中的位置. 在JDK5.0中补充的annotation机制为程序补充了新的坐标维度, 基于它无疑可以建立更加灵活而且专用的坐标系统, 它对于AOP的价值必然会逐渐被发掘出来. 在javascript这样的动态语言中,虽然它们内置的动态性直接支持程序结构的动态组装, 但是在定位支持方面却要比java这样的语言弱上很多, 在其上建立AOP应用未见得比java更具优势. 从另外一个角度上说, 定位方式也并不总比占位方式优越. 我们需要牢牢记住"一次描述"的优势在于可以"多次应用". 有的时候我们用很多唇舌去描述一个物品看起来像什么什么样, 有多么大, 多么重, 还不如直接拿给人看, 说:嘿, 就是这个(this).同样在程序中, 占位方式可能更加直接简单,甚至因为代码明确写在那里,概念也更加明确,更加完整. 此外, 在一些特定的程序结构中, 需要定位的位置大大减少, 我们也不需要复杂的定位机制. 例如在witrix的jsplet框架中, 因为它特殊的规范一致性造成程序的处理点只有一个, 应用AOP是一个非常直接的过程.
  AOP的第二个组成部分是组装技术. 程序结构的组装在java中早已不是什么技术难点, 很多人干起这活来都是轻车熟路. 但是组装不仅仅意味着程序结构的融合, 它同时意味着程序运行时状态空间的融合. 在AOP的基础模型中, 在切入点可以得到的变量有this指针,调用函数对象和传入参数列表, 但是AOP本身并没有进一步规范化这些变量的具体形式, 因此在一般情况下, 这些变量对于interceptor来说是只读的, interceptor之间也无法通过这些变量交换信息并协同运行. 这实际上意味着在切点处inteceptor的状态空间是极端受限的. 而当一个切面横跨很多切点的时候, 在interceptor中一般只能对切点处状态空间的共性部分进行操作, 这进一步限制了interceptor的能力.实际上目前AOP的主要应用都是无状态的,或者是基于全局状态空间的(例如transaction interceptor只访问线程上下文), 这反映出对于AOP的primitive方式的应用的一种局限性. 在witrix平台的BizFlow设计中,通过对状态空间明确建模, 实现了基于AOP概念的一种新的应用方式.
  AOP需要对程序坐标空间和状态空间的良好规划, 才能保证无缝的织入, 保证信息交互通道的通畅.应用AOP所指的不仅仅是配置使用现有的AOP实现, 基于这种程序结构操控技术我们所需要做的抽象工作还很多.
 

posted @ 2006-11-19 19:59 canonical 阅读(2728) | 评论 (6)编辑 收藏


  实现是一种具体的东西,对于同一种实现可以有不同层面的各异的诠释,而每一种诠释都可能对应于不同的概念体系。对于一种具体的技术实现,总有人争论说这样这样一下,不是也可以做到什么什么效果吗。但这只是实现上在某种程度上可以达到目标,并不意味着该实现基于的概念体系自身直接体现了我们所需要的结构。这就如同我们可以用汇编实现一切,但并不意味着汇编语言本身的结构包含了所有高级程序结构一样。同样,说到一种失败的技术的时候,也需要区分清楚问题是出在实现细节上还是对应的概念体系上。

posted @ 2006-11-12 14:04 canonical 阅读(360) | 评论 (0)编辑 收藏

  按照Tim Berners-Lee的原始设想,互联网的核心概念是超链接(hyperlink), 每一个可访问的资源都具有自己的URI(Universal Resource Identifier), 我们通过唯一的url可以访问到这些资源。从概念上说,每一个页面可以由一个两元组[title, url]来进行描述,title是url显示在界面上时的可读表述。在这一描述下,我们可以建立基本的页面浏览模型,包括浏览历史模型:我们可以把浏览过的页面保存在浏览历史列表中,通过选择历史列表中的条目实现页面跳转。但是随着网页的动态性不断增加,页面的资源语义逐渐丧失,url所对应的不再是一种静态的资源表述概念,而逐渐演变成为一种动态的访问函数. 例如
  /view.jsp?objectName=MyObj&objectEvent=ViewDetail&id=1
  一个url模式所对应的网页个数在理论上可能是无限多的. 从单一数据值到函数是系统复杂性本质上的一种飞跃. 为了不致在这种无限的世界中迷失方向,我们显然需要对于浏览过程进行更加有效的组织,建立更加有约束性的导航模型. 一种常见的导航模式是在页面的上方显示一条线性的导航路径, 与浏览历史模型不同的是, 页面转换时不是
  list > view item 1 ==>  list > view item 1 > view item 2
而是
   list > view item1 ==> list > view item2
  为了支持导航路径, 最简单的方式是将页面模型扩展为三元组[id, title, urlExpr], 其中urlExpr是动态表达式, 而id是固定的值. 例如 id=view, urlExpr=/view.jsp?objectName=MyObj&objectEvent=ViewDetail&id=${id}
  导航路径的变化规则为navHistory.removeAfter(newPage.id).add(newPage)
  为了进一步约化导航路径, 可以将页面模型扩展为
  [id, title, urlExpr, group, before, beforeGroup],
  其中group定义了页面的分组, 同组的页面在导航路径中相互替代, 而before和beforeGroup定义了页面在导航路径中的相对顺序. 例如对于
  [id='list' beforeGroup="detail"] [id='view' group='detail'] [id='update' group='detail']
在页面转换时, 导航路径变化不是
  list > view item1 ==> list > view item1 > update item1
而是
  list > view item1 ==> list > update item1
  在以上的页面模型中, 每一个id对应的是一个不同的页面模板(页面布局), 但是有时我们也需要在同一个页面布局中浏览无限分级的栏目, 此时可以将页面模型扩展为
  [id, title, urlExpr, group, before, beforeGroup, path]
 
  将以上的导航模型应用于一般的web应用时还需要克服一个小小的困难: 动态url的副作用. 例如/update.do?id=1&value=2这种具有动作语义的url访问直接破坏了页面的浏览模型,例如现在我们不再能够按F5键刷新页面,不能通过window.location=window.location调用来刷新页面,在页面回退时我们也将遇到种种困难。为了防止重复提交,一种常见的设计模式是分离POST和GET方法,即通过Form POST向上提交数据,处理完毕后通过redirect指令通知浏览器再次发起GET方法得到新的页面.具体做法如下  
    /redirect_after_action.do?id=1&value=2
  在执行完action之后, 服务器调用response.sendRedirect(get_url), 通知前台浏览器使用新的url重新取回页面. 这样最终我们可以确保所有页面都是可以通过url直接访问的(没有隐含的post参数),而且是可以重复访问的(无副作用因而可以反复刷新)!
  另一种方式是使用ajax方式提交更新请求,当ajax访问成功后, 在回调函数中进行页面跳转.例如:
  new js.Ajax().setObjectName('Test')
      .setObjectEvent('Update').addForm(myForm)
      .callRemote(function(returnValue){
        window.location = '/view.jsp?id=1';
      })  
这里我们也可以根据returnValue的不同选择跳转到不同页面.
  在Witrix平台中, 基于Jsplet框架我们定义了PageFlow对象, 它将可配置的导航模型引入到任意页面的组织过程中. 在跳转到一个新的页面的时候, 访问url如下:
  /pageflow.jsp?objectName=MyNav&objectEvent=NavToPage&_pageId=view&id=3
在重新访问历史页面的时候,只需要
 /pageflow.jsp?objectName=MyNav&objectEvent=NavToHistoryPage&_pageId=view
  基于jsplet框架的对象化特性,MyNav对象在session中保持了flow的所有状态变量,不需要任何框架上特殊的支持。我们可以在任意页面跳出pageflow, 并在任何时刻选择跳回pageflow, 这些动作不会影响到flow对象的状态。而通过objectScope的设置我们可以精细的控制flow对象的生命周期。同时基于对象化设置,访问页面时我们只需要资源的相对名称(relative identifier), 例如对于各种不同的flow, 页面id都可以叫做view, 通过_pageId=view来访问。
  Apache软件基金会旗下有一个beehive项目, http://beehive.apache.org/docs/1.0m1/pageflow/pageflow_overview.html, 其中也定义了所谓pageflow的概念, 这是始创于BEA的一项技术. 但是与Witrix Page Flow不同的是, Beehive Page Flow的核心概念仍然是从struts引申而出的action的概念,所谓的flow是action的连接,而不是通过资源id之间的连接。虽然beeive宣称支持各种对象injection, 并且管理了flow对象的生命周期,但是它并没有规范出this指针这一面向对象最重要的概念之一。Witrix Page Flow关注的重点是已存在的页面之间的有效组织, 它所描述的是某种具有静态资源语义的页面模板之间的关联, 这种关联是较松散的,而不是通过action强关联的. 实际上基于Ajax技术和jsplet框架的消息路由机制, 在每一个页面上都可以进行多种业务操作,甚至更换页面,而不发生pageId的变动, 并不是所有的操作过程都需要在page flow描述中对外暴露。另外一个比较细节化的不同之处是Beehive使用Java Annotation, 而Witrix PageFlow使用xml描述文件, 并实现了pageflow的继承和部分覆盖等高级构造方法. 使用xml描述文件的必要性在于需要复用WebAction, 支持复杂的变换操作, 并集成tpl模板引擎等. Annotation的方式看似简单,但这种简单性同时将系统中的所有组分绑定到了一起, 它也不允许复杂的变换操作的存在. 实际上Beehive的目标并不是真正支持页面编制(包括页面上的页面操作)和页面组织的分离.

posted @ 2006-11-05 20:35 canonical 阅读(2287) | 评论 (7)编辑 收藏

  IoC(Inversion of Control)是一个很宽泛的概念,对于我们常说的IoC容器(如spring)所做的工作,一个更加准确一些的说法是依赖注入(Dependency Injection), 即容器将一个对象所依赖的其他对象push到该对象中,而不是该对象从外界环境pull相关的依赖对象. 不过, 细究起来这种依赖注入仍然只是DI的一种特殊形式, 可以将它称之为data dependency injection, 因为IoC容器所许诺的是: "啊哈, 当我们需要对象A的时候,它就在这儿". 虽然IoC容器管理的不仅仅是数据,而是具有行为的对象,但是如果要让这些行为具体发生, 我们仍然需要额外的调用步骤.
  对于一个自动触发behaviour的系统, 我们一般将之称之为引擎(Engine). 例如工作流引擎在处理完本步骤的业务逻辑之后会自动触发流程流转操作. 一个引擎对于我们的承诺是: "当我们需要某个behaviour的时候, OK, 它会在适当的时候发生的". 对于一个软件开发框架或者更宏大的软件开发平台而言, 如果我们以业务逻辑为主体来审视整个程序运行过程, 则它们的核心价值也在于在适当的时候将适当的操作Inject到业务逻辑中. 对于目前所谓的软件开发平台而言, 除了工作流的内容之外, 一个主要部分就是CRUD(Create/Read/Update/Delete) Ready. 一个开发平台的优劣往往直接体现在它在多大程度上能够将CRUD操作剥离出主体程序逻辑, 这不仅仅涉及到数据库存取操作, 同时还包含界面交互, 数据逻辑关联等.
  除了引擎,框架,平台等应用层面的实现之外, AOP(Aspect Oriented Programming)在语言层面为behaviour注入也提供了一种特定的实施手段. 在AOP的概念中, 往往作为切点的函数被认为是基础的部分, 而interceptor是在基础蓝图上的一种扩展. 这也体现在基础函数定义了当时执行环境中可以访问的状态变量(参数/属性),而interceptor则依附于pointcut处所能得到的状态变量, 它本身一般并不维护独立的状态变量(不产生也不消灭状态变量). 从数学上看, base function和interceptor之间构成一种对偶(dual)关系, 当我们的关注重点转移到interceptor上来之后, 它本身也应该具有完整的业务语义, 这需要对AOP的执行过程做小小的偏置处理.
  在witrix平台的BizFlow设计中,BizFlow是通过类似AOP的方法作为CRUD操作的Filter实现的, 但是从bizflow本身的配置文件来看,它可以构成一个完整的业务描述, 而CRUD成为某种自动注入的behaviour.例如对于新增操作, BizFlow中的配置可以如下
  <action id="Add-default">
  </action>
虽然没有写任何代码, 新增操作(包括从request中读取操作并做有效性校验等操作)是自动进行的. 我们也可以在新增前和新增后分别执行一些操作
  <action id="Add-default">
    <source>
      some operation before add
      <biz:Proceed/>
      some operation after add
    </source>
  </action>
与AOP中的常见做法不同, 这里并没有明确定义新增前和新增后这样的切点, 而只是定义了Add-default这样一个action. 在BizAction的source段可以执行任何tpl代码, 而tpl代码的执行上下文可以看作一个Map, tpl代码执行过程中可以获取变量, 也可以设置任意变量, 因而bizFlow拥有对于状态空间的完全的控制权.

posted @ 2006-10-22 18:44 canonical 阅读(1253) | 评论 (0)编辑 收藏

  对于目前MDA(Model Driven Architecture)的理论和实现,我一直持一种消极态度。以前和hotman_x的讨论中,我也明确表述过对于MDA的看法。
  MDA:以有限搏无限 http://canonical.blogdriver.com/canonical/787637.html
  图形 vs. 文本 http://canonical.blogdriver.com/canonical/1090209.html
  所谓的MDA一般总是从高层抽象模型出发,希望通过预定的建模过程推导出底层的全部实现细节。但是implementation is also interpretation. 实现过程本身也是对高层模型的一种诠释过程, 是一个逐步明晰并逐渐消除概念之间矛盾冲突的过程。从高层模型到底层实现并不是一个同构(isomorphism)的过程,甚至一般情况下也不是同态(homomorphism)的。在概念模型到具体代码实现的过程中,总是存在着需要不断补充的细节。这些细节如何才能成为高层模型的一种自然的衍生部分是一个非常复杂的问题。如果考虑得太细(需要指定过多难以从整体上进行控制的参数),似乎就会丧失高层模型的抽象性和概括性,而如果不深入到细节,则难以平衡高层模型之间的互相冲突的属性。随着细节的不断增加,试图维持高层模型在各个层面的统一性无疑将变得异常困难。实际上在每一个抽象层面概念都可能出现重组和混合的情况,试图统一建模在目前的技术水平下是不太现实的。
  MDA所需要解决的一个核心问题是维护模型的持续有效性, 即当根据模型构造出实际系统之后, 对模型的修改仍可以自动反映到已实现的系统中, 而不是每次重新生成一个新的系统. 或者说MDA应当如何支持实现层面的重构. 为了解决这个问题, 一般的实现策略是建立完整的程序模型, 提供一个强大的集成开发工具, 可以在一个特意构造出的IDE环境中对模型进行调试, 修正, 尽量避免程序员直接接触实现代码, 确保一切细节尽在单一开发工具(单一信息驱动源)的掌握之中. 但是很显然, 这样一个大一统的开发工具在各个层面(如数据库设计, 表单设计等)都只能是专业工具的一个简化版. too simple, sometimes naive. 当我们需要对程序有较深入的控制力的时候, 这些工具往往就很难起什么作用了, 甚至会成为某种障碍.
  在witrix平台的设计中, 也有部分"MDA"的内容. 只是我们的设计思想是Physical Model Driven(物理模型驱动), 而不是Logical Model Driven(逻辑模型驱动). 具体做法是
1. 采用power designer建立数据库物理模型(PDM 而不是 CDM), 然后根据一些命名约定和附加注释(例如pdm中的package映射为java实体类的package, pdm的domain指定字段是否附件字段等)来标注出物理元素的逻辑含义.
2. 解析pdm文件, 生成hibernate映射文件(.hbm.xml), meta文件(.meta.xml), spring注册文件(.action.xml)等
3. 通过jboss的hibernate-tools工具生成java实体类.
   
  根据自动生成的配置文件, 可以直接完成对于数据库的增删改查操作, 包括维护一对多,多对多等关联关系. 此后我们可以根据程序具体需求, 对生成的文件进行修改. 通过一些程序设计技巧, 我们可以实现手工修改的代码与工具自动生成的代码之间始终有明确的边界, 从而可以做到pdm与代码的自动同步, 不需要手工进行任何调整.
  我们选择从PDM出发的一个基本理由在于, 从高层模型向下, 路径是不确定的,而从物理模型向上,路径是确定的. 在pdm中我们需要做的不是补充细节(增加新息)而是标注出已经存在的逻辑概念。选择PDM建模也集中体现了我所一直倡导的Partial Model的概念. PDM模型并不包含界面的具体展现方式, 也不包含更加复杂的业务处理过程, 它只是完整程序模型的一部分. 我们不试图建立一种完全的模型,追求概念上的一种自我完备性, 而只是关注当某些被共享的模型元素发生变化的时候, 这些变化如何以保真的方式传播到系统各个角落. 实际上一旦获得物理实现,高层模型在某种意义上就变得不再那么重要了. 为了支持模型的局部修改, 我们只需要从物理模型中提取部分信息,而不需要恢复出一个完整的业务模型。我们不需要把一个高层概念在各个层面的表达都考虑清楚,我们只需要知道某一特定的物理模型应该对应的部分高层模型即可。
  目前国内一些软件开发平台也包含所谓MDA的部分, 例如浪潮Loushang MDA (www.loushang.com)号称不需要写一行代码,定制出所需应用系统. 可从其技术白皮书来看, 它所谓的Model虽然是使用UML来建立, 但是大家似乎故意忘记了对象是成员变量+行为构成,不包含行为模型的对象模型不过是数据模型的一种翻版而已. 从Loushang MDA的元模型对象的UML图可以看出, MofTab, MofReference等固定了几种界面显示模式, 似乎其MDA只是针对既定场景应用的一种预制代码框架. 从我们的实践来说, 数据模型驱动的应用并不需要限制在基础数据对象维护这一非常特定的领域,而可以在通用应用领域发挥作用。

posted @ 2006-09-10 22:19 canonical 阅读(1570) | 评论 (4)编辑 收藏

  程序中的各种类(Class),包(package)等首先体现的是架构设计中的一种概念分布. 一个良好的设计相当于是建立一个结构合理的概念框架, 随着系统的不断发展, 作为概念载体的类(Class)不断吸收相关的实现, 从而使其外延不断丰富起来, 而其内涵也愈加变得明晰. 系统中概念的分化, 最显著的不是业务模块的划分, 而是技术层面与业务层面的分离. 因为技术手段与业务在很大程度上是相互独立的, 因为 [无论]实现什么样的业务, 我们[都]将用到某种技术手段.  而当我们可以回答一个"无论..都" 的问题的时候, 它意味着某个概念可以容纳众多变化, 而它自然有资格成为某种独立的部分. 
  作为技术层面概念聚集的例子, 我们可以看一下spring framework中的JdbcTemplate类, 这个类在spring的概念体系中对应于"Jdbc调用帮助类"这一概念, 它的目的是帮助我们尽量通过一次函数调用得到我们所要的结果, 但是我已经不止一次的看到很多人使用如下调用
   List results = jdbcTemplate.query(...);
   List ret = new ArrayList();
   for(int i=0;i<results.size();i++){
     ret.add(((Map)results.get(i)).get("someField"));
   }
这段代码的目的是为了得到某一列的值, 而JdbcTemplate类没有直接提供这一函数. 为了不等待spring的升级, 显然我们需要建立一个JdbcTemplate的扩展类, 它直接提供一个queryScalarList函数, 而不是让这种纯粹技术性的循环语句散见在程序代码的各个角落.
  告别裸奔编程是我对同事的基本要求之一. 即使是考虑最细致的软件组件, 它也难以保证能够预想到所有的变化形式, 而在系统中集成一些第三方组件的时候, 一般总要加入一些特定的假设, 此时也需要一个技术隔离层. 例如在页面开发中, 我们强制使用witrix平台定义的js.Ajax对象, 而不是prototype.js中原始提供的Ajax.Updater等对象. 在应用一段时间之后, js.Ajax对象上聚集了一系列与ajax相关的调用指令.

posted @ 2006-08-06 16:30 canonical 阅读(1027) | 评论 (1)编辑 收藏

  hibernate的应用中一般总是将entity映射为强类型的java类,这为程序操纵带来很多便利,同时可以将大量动态过程隐蔽在对象包络之下。映射为java类的一个主要问题在于无法在程序运行时刻对于程序进行修改,而数据结构的局部修改几乎是无法避免的。hibernate3本身支持动态数据模型,它允许我们把entity映射为Map数据类型, 当数据结构发生变化的时候, 只需要修改hbm文件即可改变映射模型,而不需要修改java实体类代码. 
    在hbm定义文件中,如果我们不指定name属性,而是指定entity-name属性,则我们可以将entity映射为Map, 而不是一个java实体类.
  <class
    entity-name="test.DynamicEntity"
    table="DYNAMIC_ENTITY"
  >...</class>
  此外, 也可以选择将部分字段动态映射到Map
  <class ...>
    <dynamic-component name="dynamicAttributes">
      <property name="foo" column="FOO"/>
      <property name="bar" column="BAR"/>
    </dynamic-component>
  </class>
在HQL语句中可以直接使用o.dynamicAttributes.foo来访问foo属性,所有操作与普通属性相同。
  为了实现hiberante映射模型的动态更新,我们首先需要实现sessionFactory的动态更新。目前hibernate的实现只允许从hbm文件重建sessionFactory, 即新建一个sessionFactory替换原有的sessionFactory, 在使用spring的情况下,这需要对org.springframework.orm.hibernate3.LocalSessionFactoryBean进行小小的hack。
  为了将动态属性和普通属性同样对待,要求在操作实体对象属性的时候需要能够自动处理nested property, 即需要如下类似的方法:entityDao.getProperty(entity,"dynamicAttributes.foo"), entityDao.setProperty(entity,"dynamicAttributes.foo", attrValue).
  为了使得应用程序自动处理新增属性,要求程序是meta驱动的:当实体对象增加了一个属性时,只需要修改meta文件,即可完成对于该属性的增删改查汇总等普通应用需求。

posted @ 2006-07-23 21:13 canonical 阅读(5060) | 评论 (8)编辑 收藏

  最近ruby语言的流行似乎再次引发了DSL(Domain Specific Language)讨论的热潮。从语法表现形式上看,通过对于ruby语言的深度hack, 充分挖掘ruby语言的某些语法特征,可以使得正常的ruby语句看起来比其他计算机语言更接近于人类的自然语言,某些人因此认定ruby语言是DSL的天然载体。但是在我看来,具体语言的语法表达形式对于DSL的核心价值而言并不是最关键的。
   首先,DSL的核心在于高效的表达语义,而并不在于是否接近自然语言。接近于自然语言并不意味着更加domain, 因为自然语言也是一种通用语言,它未必能够比采用其他语法形式的语言更加有效的对domain事物进行描述。典型的有数学符号和化学分子式。
   第二,作为DSL, 紧凑的表达形式是一方面,另一方面是这种表达形式的稳定性,即如何防止人们写出不符合DSL规范的语句。ruby语言的片断直接作为DSL无疑是一种naive的解决方案,我们可以轻易写出大量不同形式的ruby语句,而它们在语义上是等价的(这意味着通过单元测试也无法发现它们的不同),即人们不按照设计的DSL语法书写,这造成DSL的解体。
   作为一种DSL构造语言,其核心能力在于如何将second class的domain中的概念(非语言本身内置的概念)封装到first class的表达形式中。ruby作为一种动态语言,可以更加轻易对于自身meta data进行内省,典型的如ruby中的ActiveRecord设计. 但是在我看来,这种概念提升能力在ruby的语法结构中也是有限的,原因恰在于ruby的语法太多样化了。实际上,我更加看好xml结构的均一性。

posted @ 2006-07-16 22:41 canonical 阅读(2053) | 评论 (2)编辑 收藏

  CRUD(Create Read Update Delete)是一般应用程序中最基础的操作,但是用户的需求却很难直接映射到CRUD操作上。例如常见的需求如下:
 1. 不同的业务处理处于不同状态的业务对象:
     业务A处理状态为X的业务对象,而业务B处理状态为Y的业务对象
 2. 业务对象处于不同状态的时候允许的操作不同:
    状态处于X的业务对象允许操作U, 而状态处于Y的业务对象允许操作V
 3. 不同的业务操作可能修改业务对象的不同属性:
     操作U修改业务对象的属性P, 操作V修改业务对象的属性Q
 4. 具有不同权限的人能够从事的业务不同:
      角色R处理业务A, 角色S处理业务B
 5. 具有不同权限的人即使从事同一业务,所能够操作的业务对象集合也不同:
     角色R处理部门M的业务对象,角色S处理部门N的业务对象.
 6. 具有不同权限的人即使可以操作同一业务对象,所能够采取的业务操作也不同:
      角色R只能进行操作U, 角色S只能进行操作V
 7. 在业务对象上执行操作之后可能造成状态变迁:
      处于状态X的业务对象上执行操作U后状态变为Y

以上这些需求往往是系统中最易变的部分, 而它们在概念上恰恰表现为对CRUD的一种限制性描述. 因此通过如下扩展我们可以定义BizFlow的概念: BizFlow = CRUD + Filter. 根据这种观念, witrix平台中BizFlow被实现为DaoWebAction的一种无缝扩展.
   在jsplet框架中我们通过如下url模式来访问后台的CRUD操作:
   /list.jsp?objectName=MyObj&objectEvent=Query
为了实现BizFlow只需通过spring为DaoWebAction配置一个xml配置文件, 此后仍然可以通过
    /list.jsp?objectName=MyObj&objectEvent=Query
来访问后台的CRUD操作,只是后台会自动应用配置文件中的 bizId="default", bizActionId="Query-default"等配置项.
如果我们采用如下url来访问
    /list.jsp?objectName=MyObj&objectEvent=Query&$bizId=test&$bizActionId=test   
则后台将应用配置项 bizId=manage, bizActionId=Query-test, 而
    /list.jsp?objectName=MyObj&objectEvent=BizAction&$bizId=test&$bizActionId=test   
则对应于配置项 bizId=manage, bizActionId=BizAction-test.
   应用BizFlow配置项之后,所有前台代码都可以不做出任何改变, 因为它们只是对于给定数据的展现.
  
   BizFlow可以看作是CRUD加上简单的流程控制和权限控制所构成, 但是它与完整的工作流模型还是有着显著区别的. 工作流中所关注的重点首先是流程实例而不是业务对象实例, 在一个流程中是否存在唯一的业务对象,以及业务对象的状态是否随着流程流转发生变化完全是一件独立的事情,它们并不属于抽象的工作流模型本身. 理论上说,一个业务对象可以同时参与多个流程. 在工作流建模中主要通过流程步骤的先后顺序的约束来描述业务进程, 处于同一状态的业务对象可能处在不同的流程步骤中. 而BizFlow可以看作是状态驱动的, 当前业务步骤直接由业务对象的状态决定. 在BizFlow中因为视角是业务对象的状态,因此我们直接面对的是大量处于同一状态的不同的业务处理过程, 而workflow中往往建模的时候强调单流程实例视角,而一般缺乏对于流程实例相关性的描述. 现在国内很多人认为工作流就是状态机其实是对workflow概念的一种误读.
 

posted @ 2006-07-15 22:25 canonical 阅读(1644) | 评论 (5)编辑 收藏

  在国内做项目,客户需求控制总是个令人头痛的问题。很多时候问题在于用户并不知道确定自己需要什么,他只是就着目前系统的状况提出一些他可以设想到的情形, 但最终他可能并不真的使用这些功能,或者他的想法会发生其他的变化。所以如何引导客户走到我们所谓的"正确"方向上来是一个非常重要而复杂的问题。
  昨天一个同事就此事做了个有趣的比喻。客户的需求是吃饱饭,你给他一个馒头,他和你争论馒头是大点好还是小点好。但是如果你给客户一个包子,情况就完全不同了,客户关注的重点肯定是包子是什么馅,猪肉三鲜还是韭菜鸡蛋。。。很多人还有忌口的。从此没完没了。

posted @ 2006-06-10 20:24 canonical 阅读(475) | 评论 (0)编辑 收藏

  http://code.google.com/webtoolkit/
  最近google发布了Google Web Toolkit(GWT)开发包,这是一种使用java语言开发AJAX应用的开发框架。从技术上看,GWT并没有什么新鲜之处,类似的概念在多年之前就已经有各种尝试了,这些尝试从未真正吸引到足够的注意。GWT的优势也许在于提供了一套模拟工具,另外可能在屏蔽browser的兼容性和bug方面做得更好一些,但是真正的技术思想并没有什么突破. Ruby On Rails同样是试图将ruby语言直接映射到前台程序, 但是它通过一个通用的prototype.js库最小化了ruby语言和js语言之间的区别,在概念上要比GWT的java2js的compiler概念要更加新颖一些. (http://mir.aculo.us/stuff/COR_20060413_RailsAjax.pdf)
  对于web开发而言,我总认为要发挥web的特色,而不是把它约束到其他领域的开发模式上。js+dom+html文本所能做到的结构控制程度要远远超越组件技术,我也从未发现学习java要比学习html要更加容易。也许对于某些对于web一无所知的java开发人员来说,GWT有些意义,也许GWT会特别适合于某些特定的领域,但是作为一种通用的开发框架,我并不看好它。

posted @ 2006-05-19 21:27 canonical 阅读(2167) | 评论 (3)编辑 收藏

  传统的Mode2模式的服务器端框架在处理AJAX应用的时候存在一定的不适应性,这主要的原因在于Model2基于推模式,它隐含的假设是基于action的处理结果生成整个页面,而AJAX应用中所强调的是页面局部的变化,只更新发生变化的部分,而不是重新生成整个页面(change instead of create), 这两者之间存在着内在的不协调。有些人推崇后台服务程序只返回xml数据的方法,将显示层完全推到前台。虽然在前台通过js脚本操纵DOM节点可以实现非常细粒度上的控制,但是我们并不总是需要最细粒度上的控制权的。例如现在我们在前台实现一个grid控件, grid控件本身只需要控制到单元格层次即可,而不需要对于单元格里存放什么内容有预先的假设. grid.getCell(i,j).innerHTML = cellHtml是非常自然的一种解决方式。完全通过dom来构造界面面临着众多问题,除了浏览器bug这种挥之不去的噩梦之外,在实现过程中我们往往会引入对界面元素的大量限制条件,而无法做到集成各种来源的控件。
  在服务器端生成页面片断的方式也称为AJAH,表面上看起来它比AJAX要简易一些,是很多服务器端框架引入AJAX概念的乡间小径。但有趣的是在基于拉模式(pull mode)的服务器端MVC框架中,AJAH是在架构上比AJAX更加灵活的一种方式。在witrix平台的jsplet框架中,web访问的基本形式如下:
   /view.jsp?objectName=XXObject&objectEvent=XXEvent&otherArgs&tplPart=XXPart
其中objectName对应于后台的服务对象,objectEvent参数映射到服务对象的方法,view.jsp是对于后台对象进行渲染的模板页面,而tplPart参数可以指定只使用模板的某一部分进行渲染。如果我们选择json.jsp或者burlap.jsp作为渲染模板,则可以退化到返回数据而不是内容的方式。在js中进行简单的封装后我们可以通过如下方式进行远程调用:
  new js.Ajax().setObjectName("XXObject").setObjectEvent("XXEvent").addForm("XXFormId").callRemote(callbackFunc);
   它对应的url请求为
   /json.jsp?objectName=XXObject&objectEvent=XXEvent&...
对于同样的后台业务处理,我们可以自由的选择渲染模板,则可以很自然的得到更多的处理方式,例如返回javascript代码来实现对于前台的回调。

posted @ 2006-05-09 22:56 canonical 阅读(1603) | 评论 (2)编辑 收藏

http://xp.c2.com/OnceAndOnlyOnce.html
http://c2.com/cgi/wiki?DontRepeatYourself

     OAOO(Once And Only Once)是我们在软件开发中需要关注的基本原则之一. 唯一性当然是一个值得追求的目标. 从正交分解的角度上说,系统可以由少数的正交基通过组合构造出来。尤其在分析阶段,我们需要牢牢把握住系统内核的几个变化维度。但是这并不意味着我们最终能够做到每种可以想见的软件元素都是唯一的,也不意味着保持唯一性永远都是最好的。
     唯一性在软件中最直接的体现就是代码的重用(reuse), 除了实现起来节省了工作量之外,代码重用的另一个作用在于维护了系统中概念的唯一性,或者更广泛的说,它维护了系统中知识的唯一性。例如,如果我们经常用到圆周率Pi,我们可以选择在各处都直接写3.1415926, 也可以选择定义一个系统常数PI, 在使用的时候引用这个常数,保持关于PI值的知识的唯一性。其实只要各处的PI值是相同的,甚至只要是在误差范围内相互匹配的(例如有些地方用3.14, 有些地方用3.1415926),程序就可以正确无误的运行,这样就达到了我们开发程序的目的,并不需要什么常数定义。只是为了保证这种知识的一致性,定义一个常数无疑是最简单直接的一种方法。从理论上说,我们实际需要的只是知识在软件中能够得到一致的表达,或者更加抽象一些,我们所需要的只是知识的自洽性,而唯一性无疑是维持自洽性的一种廉价方法。特别是在一个不断演化的系统中,保持形式上的唯一性可能是实现自洽性的唯一可行的方法。
     但是, 我们需要认识到知识的一致性与代码的唯一性并不是等同的,例如同样是释放资源的函数, 在不同的应用情形下我们可能将其命名为close, 也可能是destroy, 或者是dispose, 如果我们使用一个接口IDisposable.dispose(), 则引入了一种形式上的唯一性要求. 在使用reflection的情况下, 我们可以放松要求, 不要求对象实现特定的接口, 只要提供指定的函数名(例如dispose)即可. 我们也可以更加宽容, 通过外部描述性数据指定函数的用途, 只要求概念上的一致性, 例如spring中通过destroy-method属性指定对象资源释放函数. 没有语言级别的形式唯一性, 我们就无法依赖于编译器来维护其隐含的知识的一致性, 此时我们所能使用的通用方法就只有测试(test)了. 实际上, 很多知识上的自洽性要求都无法在程序中直接得到表达, 而只能通过一个构造的测试网络来进行验证.

     正如排他锁(exclusive lock)是实现transaction的一种强形式一样, 唯一性也是自洽性的一种强形式。在保持了唯一性的情况下,当然不可能出现冲突的情况,也就自然的维持了系统的自洽性。但是,很多时候概念的多样性也是我们不得不考虑的内容。在C语言中, memmove函数的功能包括memcpy的功能,到底要不要取消memcpy以避免无谓的错误可不是一件容易决定的事情. 在数学上,同一个定理可能存在着多种非平凡(non-trivial)的等价表述形式, 从表面上看,它们可以是完全不相关的,但是原理上是等价的. 而不同的表述往往适用于不同的应用情形. 同样的,在软件系统中,It is ok to have more than one representation of a piece of knowledge provided an effective mechanism for ensuring consistency between them is engaged. 在软件设计中, 引入中间层是在控制内在统一性的同时获得丰富的外在表现的一种重要方式. 在CORBA中idl编译器将idl文件翻译成不同程序语言的版本, 我们在程序中使用的是特定程序语言的版本而不是直接的idl接口文件, 这些版本之间的自洽性是通过idl编译器来保证的. idl编译器所做的只是一对一的翻译工作, 它本身并没有提供额外的知识, 而它所生成的各个程序语言版本所表达的知识也是相同的. 可以想见, 一种更加复杂的,甚至是具备一定推理能力的引擎(engine)可以基于元知识进行更加复杂的变换工作, 并可以融合其他外部的知识, 最终输出一系列自洽的表现结构.例如, 我们可以根据一个描述文件生成所有CRUD(Create Read Update Delete)操作的程序代码和界面代码. 这些生成的文件中可能存在着重复的代码,可能重复的表达了某个知识, 例如界面布局等, 但是它们之间通过引擎隐蔽的存在着稳固的联系

posted @ 2006-04-23 16:39 canonical 阅读(1682) | 评论 (0)编辑 收藏

    软件设计中总是存在着general与special的竞争, 一方面我们希望提出更加general的概念和方法, 在更大的范围上捕获更多的关联,  另外一方面我们又希望在局部使用特殊定制的接口和实现, 提高局部信息利用的效率, 很多时候两者之间是存在一定的冲突的. 从实际操作的过程来看, general这个方向很难控制, 当我们试图提供更多的时候, 最终真正实现的多半只是更多的限制而不是更多的灵活性. 对于不是非常熟悉的领域, 我们很难避免各种意想不到的信息泄露, 最终它们会使得general的设计名存实亡. special的方向相对容易控制一些, 只要保证所有用到的参量都是目前必须的就可以了.
    现代数学技术与古典方法的一个鲜明区别在于, 传统方法总是假设信息是完备的, 因而它试图首先建立一个更加通用的模型, 解决一个更为一般性(往往更加复杂)的问题, 然后再以这个通用问题为基础来解决我们的特定问题. 例如为了估计某个随机波动造成的损失, 传统方法将从估计随机分布的密度函数开始, 但是密度估计是统计学中的一个"终极问题"(一旦密度函数已知, 我们就可以求解各阶矩,从而解决各种统计问题), 它需要大量观测数据(信息)才有可能满足渐进估计所需要的数学条件. 而现代方法更加强调问题的特殊性, 强调信息的不完备性, 因而倾向于直接对于给定的问题建模, 因而模型中包含更少的参数, 这样我们才有可能得到更加稳定的解.
    在软件设计中我们遇到的最大的问题也是信息不完备的问题, 我们同样需要注意避免把解决一个更为一般的问题作为解决当前问题的一个中间步骤.

posted @ 2006-04-23 15:58 canonical 阅读(1057) | 评论 (0)编辑 收藏

    软件设计虽然是需要智力付出的一种过程,但是它并不意味着必然产生出一些创造性的东西. 一般的设计工作只是将业务架构映射到一个通用的软件技术架构上. 这就如同大多数时候我们只是应用某个算法来解决具体问题, 而不是发明一个新的算法一样. 最近所见的一些失败的设计, 其关键问题往往不是简单的过度设计的问题, 而完全是一种错误的设计. 当我们试图在软件中创造一种新的关联关系, 建立一种新的交互方式和交互规则的时候, 往往会走到错误的方向上.

posted @ 2006-04-16 22:16 canonical 阅读(1472) | 评论 (5)编辑 收藏

http://www.bstek.com/
    上海锐道的Dorado框架号称是一个基于构件技术的、面向B/S和多层架构体系的web应用开发平台, 其前身称为Extra。从具体功能来看,如果将其看作是一个全功能的web应用开发平台, 那它现在的功能集显得就太单薄了一些, 其主要部分还是提供了一些前台的界面控件, 其他如web框架部分,很像是struts的一个简化版,没有什么特异之处。
    Dorado的技术特点是大量采用ajax技术来实现前台控件. 其前后台交互采用了自定义的基于xml格式的rpc方式, 而数据绑定使用了xml数据岛,例如
    <xml id="__datasetEmployee" >
    <records>
    <record isCurrent="true"  state="none" >
    <new>,~73~73~73~73~73,~68~68,~44~31~32,true,true,295113600000,2034.0,,</new>
    </record>
    </xml>
    record内部使用的是Dorado私有的编码/解码规则, 大概是为了压缩数据量吧.
    从Dorado目前提供的界面来看还是比较丰富的,基本满足了一般信息系统开发的需求, 但是其可扩展性似乎并不是很好. 它虽然号称是组件式开发,但是其前台和后台引擎似乎都没有提供完善的组件模型支持, 只是实现了一些既定的界面组件而已.
    1. 其前台的js函数中存在着大量针对数据类型的switch语句,似乎其所能够支持的控件类型已经内置在前台引擎中, 如果我们要增加一种新的界面组件大概需要在各处修改引擎代码, 缺乏一种抽象机制.
    2. 后台ViewModel模型似乎是想构造出一个Component架构来, 但这个模型目前看起来明显没有Echo2这样的组件模型精致, 似乎缺乏一种一致的组件重组机制.  Dorado的ViewModel是有状态的, 通过RPC机制, 它实际上可以独立于系统web层与前台控件交互.
    3. Dataset是Dorado中最重要的数据供体接口, 从它所提供的方法 getField,deleteRecord, insertRecord, movePrev, moveNext, getPageSize等可以看出, 这明显是绑定到了可分页表格的数据模型上. 实际上整个系统设计似乎都隐含假定了一个Table模型, 例如Validator接口中具有一个函数 ViewField getField(), 这里明确假定了validate只能针对单个字段进行, 而不是属于某个整体组件的.
    4. Dorado中所有组件的界面代码生成都是以程序方式进行的, 没有模板机制. 因而增加新的控件的实现时, 需要在后台通过java代码输出一部分界面, 在前台通过js脚本动态更新界面, 工作量相当大.
    5. Dorado中界面输出应该是通过Outputter接口来进行
      public interface Outputter{
        public String getStartOutput(HttpServletRequest req, Object o)throws Exception;
        public String getEndOutput(HttpServletRequest req, Object o) throws Exception;
      }
      这里一方面与web层形成了绑定,另一方面它必须在内部完整的生成界面代码之后一次性传入response, 这无疑加重了后台的内存压力. 输出分成了StartOutput和EndOutput大概是为了支持布局组件等容器类组件, 相当于是组件内部可以有一个洞, 这与Jsp Tag模型是匹配的, 但是这种方式很难以高效率实现界面上的结构融合.
    7. Dorado似乎没有直接提供组件的再封装机制, 在现有组件中作局部修正往往需要通过代码方式来进行.例如表格中的性别字段需要显示图片而不是文字, 则需要在Column的onRefresh事件中写入下代码,
            if (record.getValue("sex")){
        cell.innerHTML = "<img src='images/man.gif'>";
        }
        else{
        cell.innerHTML = "<img src='images/woman.gif'>";
        }
    这明显已经不是可配置的内容了. 如果我所需要增加的代码是一个复杂的组件, 这里就很难进行处理了.
    6. Dorado的技术绑定在了IE浏览器上, 无法兼容其它浏览器, 这有些偏离目前的标准化方向.

    目前的快速开发平台的一个共同特点是集中在单表的CRUD(Create Read Update Delete)上, 可以快速完成单表或主从表的增删改查. 这本也是正确方向,毕竟目前系统开发中的大量工作都是重复性的简单劳动, 但是一般系统设计中为了支持单表操作而在建模的时候引入了对表格模型过强的依赖,  大大降低了系统的可扩展性. 另外现在一般web组件框架的设计往往是模仿桌面开发领域的组件模式, 希望提供完全黑箱式的组件调用方式, 这其实是放弃了web开发的优势.  实际上借助于xml格式的规范性和简单性, 我们完全可以提供更加强大的结构组件, 并把组件封装的能力开放给普通程序员.

posted @ 2006-04-02 14:57 canonical 阅读(2174) | 评论 (2)编辑 收藏

    http://www.ezone.com.cn
    同方这个公司在我的印象中更像是个系统集成商,在软件方面没有什么独特之处。不知道是否是待遇问题,我也未听说同方旗下招揽过什么软件高手。同方最近两年 在软件平台方面花了一些气力,据说有70多个人,干了一两年,将同方以前的项目进行提炼,开发了ezOne平台。现在同方各个事业部开发新项目的时候好像 要求使用该平台,不过可能是涉及到总部和各个部门分钱的问题,下面的人未必有很大的应用积极性。
    同方的特点是做过大量项目,行业经验多,所以ezOne中提供了很多行业组件。ezOne的一个核心部分是portal服务器,是基于Apache项目组 的Jetspeed修改而来,应该是符合JSR168标准的,所强调的概念也正是应用集成。以前扫过一眼ezOne的代码,感觉质量一般,性能可能会有一 些问题,但编码还算工整。同方握有大量国家资源,应该不至于成为平台产品开发商,开发平台的目的大概还是降低自身开发的难度。其产品倾向于符合标准,中规 中矩,大概很难有什么创新之处。ezOne中很多基础组件都是独立开发的,例如存储层,没有使用hibernate等开源软件。

posted @ 2006-04-02 14:53 canonical 阅读(1440) | 评论 (5)编辑 收藏

   http://www.kenoah.com
   前两天和科诺的研发经理聊了一会,也简单看了一下他们的演示。因为比较匆忙,没有谈到什么实质性的内容,只是有一个粗浅的印象。科诺目前的发展重点还是国 外的外包业务,其网站上相关介绍材料很少,不过据说今年将投入较大的资源在国内公司建设和市场开拓上。
    科诺产品的特点是代码生成。经过可视化配置之后,可以根据模板生成jsp源代码,程序员可以基于这些代码进行修改。据说遵守一定的规则之后,自动生成的代 码与手写代码是相互隔离的,自动生成的代码可以无限多次生成而不影响手写代码质量,但我未看到演示。科诺生成的页面只支持IE, 不支持Firefox等浏览器。大概是因为其从事的主要是国外外包业务,其界面的美观程度一般。虽然可以修改页面模板来改变界面风格,但从我实际看到的模 板代码而言,重新写一套并不太容易。
    科诺产品的功能大概是可以完成单表或者主从表的增删改查,并配合一个或者多个业务流程。其工作流引擎据说是符合WFMC规范的,但从实际操作上看,似乎不 是一个通用的工作流引擎。至少给我演示的时候,工作流步骤的每一步,actor所能采取的操作是固定的,如退回,通过等,似乎是为审批定制的。
    与普元EOS相比,科诺在功能上还是要弱不少。在开发工具上,也显得要简单许多。与普元EOS类似的是,科诺的产品似乎也只是利用工具根据现成的模板制造 固定模式的代码,在设计思想方面并没有什么本质性的突破,与其宣传的组件思想相差甚远。如果要超越自动生成的代码作一些事情,这些平台都无法提供什么有力 的支持。科诺所谓的业务组件似乎就是对table的描述,其设计工具不算太好,至少我没有找到一个汇总视图让我可以看到所有已经配置的字段属性。在设置复 杂字段或者汇总字段方面,科诺的产品也有很多限制,不太灵活。在前台框架方面,科诺写了一个类似于struts的框架,其他就乏善可承了。流程方面的设置 被称为所谓的流程组件,运行时可以通过一个js库在界面上进行展现。不同的业务组件应该可以对应到同一流程组件,这大概就是科诺所谓的组件重用了吧。开发 工程文件以xml格式进行存储,看了一眼,相当复杂,竟然还和SOAP有关系,不知道他们为什么这么设计(符合标准?)。
    在科诺的另外一个印象是,公司里有不少女性程序员在干活。看来至少他们的产品可以由初级程序员掌握并满足美国外包开发的需求。

posted @ 2006-04-02 14:50 canonical 阅读(1855) | 评论 (7)编辑 收藏

   前两天一位普元的朋友衣风http://blog.sina.com.cn/u/1495516693在我的blog上留言,谈到对数据总线的不同看法. 我本人并未使用过普元EOS,所做的判断只是基于我个人肤浅的第一印象, 很有可能是不准确的. 不过, 目前我仍然坚持自己对于普元EOS的看法,呵呵.
    
    1. EOS产生的xml描述文件中的大量条目都是EOS自身的结构要求,而与实际业务无关,即EOS描述文件中的有效信息量密度很低.
    衣风认为条目并不是EOS自身的结构要求,而是业务对象的结构描述. 这里我的看法是业务对象应该尽量通过领域(Domain)语言来描述, 领域信息的外在表象应该尽量是卷缩的,而不是展开的, 应该尽量是抽象的而不是实现细节层面上的.例如:
    <function class="test.MyFunctionProvider">
        <args>
            <arg>
                <name>argA</name>
                <value>3</value>
            </arg>
        </args>
    </function>
    以上信息可以描述一个方法调用, 这里的function, args, arg, name, value等标签的设置都是解析器为了理解该调用的语义而规定的结构元素,这些元素并不属于函数本身. 例如我们可以规定如下的调用格式来简化描述文件而不损失信息,
    <function class="test.MyFunctionProvider">
        <arg name="argA">3</arg>
    </function>
    而在我们的工作流引擎中, 业务操作的调用以封装后的形式出现
    <BusinessA:DoWorkA argA="3"/>
    通过标签封装之后, 我们在调用的时候所需要提供的信息是最小化的, 每一个涉及到的元素都是有着业务含义的, 而将解析器本身的结构要求隐蔽在封装的标签定义中.此后我们如果更换了实现,而业务需求不变, 则我们的调用处是不会受到影响的.
    现在基于xml语法的文件格式很多, 我们的工作流引擎也采用了xml描述. 但是我们的一个基本观点是, xml配置文件解析器不应该直接理解文件中所有的标签, 即它只需要理解自身的结构元素, 而为引入Domain知识保留余地.
   
    2. 普元EOS中的结构似乎很难进行有效的扩展。而所谓的xml总线技术更加剧了这一点
    衣风认为"正是将XML作为数据传递的总线,才使应用在数据层次上具有了较强的扩展能力"。从面向对象的观点看, 程序中普适性的基本基元是数据与行为的集合体, 而程序模块之间的交互也绝不仅仅是通过数据来进行, 而是往往呈现出一种数据与行为的交织状况. 普元的模型应该包含了大量发生紧密关联的局部元素, 它们应该处在同一进程(状态)空间中, 直接访问对象应该是最简单,最经济, 最完备的信息传递方式, 而"xml节点的表达能力远远超越了普通的数据类型,但充其量也不过是对现有数据的规整的树形表示,并不具有动态计算能力(甚至是最简单的lazy evaluation)". 实际上对于所谓的总线, 最简单的模型是一个可以动态管理的变量集合, 那么一个Map就足够了. 在集合中我们可以保存各种对象, 比如xml节点, 但是又不限于xml节点. 从建模的角度上说, 把xml节点定义为一级集合元素我认为是不合适的. 通过对象的成员函数, 我们在对象图中可以包含大量的动态计算信息, 例如
     obj.getSomeCalculatedAttribute().getLazyLoadValue()
    这些动态语义在纯数据含义的xml总线技术中不知道如何表达.
    对象图表达数据关联的能力也强于树形结构的xml节点, 例如 obj.getRefObj().getRefObj() == obj, 不知道这样的关联在普元EOS的数据总线中如何表达.
    在并发访问中如果需要同步, 对于对象, 我们可以使用synchronized机制, 但是对于xml节点不知道如何才能有通用的处理方式. 

posted @ 2006-04-02 14:47 canonical 阅读(1885) | 评论 (2)编辑 收藏

     http://www.primeton.com/

    普 元软件公司是国内专业的中间件提供商,从国家得到了不少投资,做出来的东西也是相当的庞大。最近普元EOS的宣传和发展的势头都很盛。其宣传材料中屡次提 到“软件的涅磐“这一用语,这明显是一种危言耸听之举,当然这在业内也不算什么新鲜的事情。按照EOS的宣传,"以图形化的构件组装方式“画”出来的软件 无论从结构上、形式上还是开发过程上都堪称简捷而美的软件"。这一提法倒是别开生面。图形化与简洁,与美竟然还存在着这样必然的联系,实在是一种创举。
     从普元公开的资料来看,EOS的一个鲜明特征是全面基于xml描述,即所谓的xml数据总线。表面上看起来,xml结构内置于系统内核中似乎很时尚,但实 际上,EOS产生的xml描述文件中的大量条目都是EOS自身的结构要求,而与实际业务无关,即EOS描述文件中的有效信息量密度很低。这是一个危险的信 号。EOS的xml描述本身可以看作是一种完全新的编程语言,但这个语言似乎没有什么抽象能力和组合能力,对于关联的表达能力也很弱(到处都是数字 id)。如果直接手工编写,那是一件要死人的事情。只有通过集成开发环境的可视化界面,EOS才呈现出可理解的一面。
     EOS的概念与Language Workbench是不同的,其中的结构似乎很难进行有效的扩展。而所谓的xml总线技术更加剧了这一点。xml数据总线其实与面向过程编程类似,只是过 程变成了service,数据变成了xml节点而已。对象与简单数据结构在结构表达上的本质差异就在于对象通过成员函数可以封装动态结构。虽然xml节点 的表达能力远远超越了普通的数据类型,但充其量也不过是对现有数据的规整的树形表示,并不具有动态计算能力(甚至是最简单的lazy evaluation)。丧失了动态计算能力,就意味着我们很难在系统中动态引入结构,程序中所操纵的结构都需要事前定义出来,这将极大的限制系统的可扩 展性。另一方面,xml节点受限于自身格式,其描述关联的能力也要弱于java对象结构本身。对象通过引用访问相关对象,其隐含意义是对象处于同一地址 (状态)空间中,可以非常自然的保证对象的唯一性并实现同步访问。在跨越状态空间的边界时,xml表示是有意义的,因为我们需要把所有的结构都暴露出来并 加以描述(外在化)。而在状态空间内部,我们需要更加紧致有效的表述方式。
     在具体的实现中, EOS暴露给程序员的xml操纵API相当的原始,使用起来很繁琐。在前台展示页面中,如果不使用EOS的界面组件,提取数据本身就是一种不小的困难。 EOS的前台展示组件与后台的结合也比较弱,后台改变之后,缺乏有效的手段来检测并保证前后台结构的同步性。所谓的前台构件层似乎只是提供了一些帮助函数 和功能固化的组件,并没有提供什么有效的利于结构抽象和结构重组的机制。
     整个EOS的构架看起来很象是一个monster, 我很难想象它的各个部分如何才能独立的,深入的发展下去。

posted @ 2006-04-02 14:45 canonical 阅读(1615) | 评论 (0)编辑 收藏

  在采用AJAX技术的应用中, 常见的是两种架构设计, 一种是采用RPC(Remote Procedure Call)方案, 后台应用直接将java对象包装为service接口, 在js对外暴露(java对象完全不知道web层), 在js中通过类似函数调用的方式访问后台服务.而另外一种方案是在后台维持一个前台DOM节点的映象,触发前台事件之后, 前台引擎自动截获该事件, 并翻译为对后台事件监听器的调用, 将请求提交到后台, 后台程序处理请求之后更新后台DOM节点, 然后将页面变更部分传回前台页面. 这两种方案一种是倾向于在js中提供自我封闭的程序模型(对远程服务的调用体现为对一个js函数的调用), 一种是倾向于在后台提供封闭的程序模型(对前台页面的更新体现为对一个后台java对象的属性和结构的改变). 这两种方案的共同之处在于它们都试图在一定程度上屏蔽前后台程序的交互细节, 而提供一种统一的程序模型.
  但是我们需要记住软件设计的第一要义在于系统的分解, 而Browser和Server之间客观存在的http信道是天然存在的一种分界线. 任何强迫我们忘记B/S之间的界限的技术都是需要谨慎对待的. 例如控制后台对象的权限问题, 很多RPC方案限制了应用程序对于web接入层的直接接触, 而只能通过AOP(Aspect Oriented Programming)技术来动态织入权限控制代码. 在实际使用中, 这种方式往往因为service接口函数的多样化而造成配置上的繁琐.  而在我们的体系架构中, 系统边界划分在web访问层上(而不是java service层), 借助于web访问协议自身的一致性和透明性, 我们只需要如下实现
   Object invokeWebEvent(){       
     return new ActionMethodInvocation(getWebObject(), getObjectEvent(), getInterceptors()).proceed();
   }
就为所有WebObject加入interceptor堆栈, 完全不需要AOP的动态织入技术.
  在witrix平台的设计中, 因为大量采用pull方案, 我们对于前后台交互方式采取的是完全开放的态度, 前后台的交互接口完全定义在web访问层上(即前台程序直接访问一个url获取数据), 尽量避免将系统架构的限制引入到应用程序设计中来. 确实, Browser和Server之间的原始交互方式是受限制的, 狭窄的, 但是从系统设计的角度上说, 这正是异构系统集成和进行系统核心控制的最好场所, 任何试图独占该连接信道并在层层封装之后提供更加丰富的访问语义的努力在我看来都是可疑的.
 

posted @ 2006-04-02 14:43 canonical 阅读(1261) | 评论 (0)编辑 收藏

1. 对象的一个特性是局域化。每一个对象函数(非静态函数)都是在对象的上下文中调用的,因而隐含假设了一定的环境信息,这些信息以封装的形式被使用。
   一个具体的体现是函数的命名,如果没有对象,我们必须给函数进行全局命名,例如 find_friends, find_users, find_departments, 而在对象的环境下,我们可以抽象出一个采用局域化命名的接口 IQueriable.find(),实现系统的深度分解。
   在对象成员函数的内部,我们通过统一预定的名字(this指针)来访问对象环境中的变量。而在 jsplet web框架中的action和前台jsp中,我们通过thisObj变量来访问后台逻辑对象的上下文,而不需要知道对象的具体名称 (objectName)。

2. 对象的另一个特性是静态化。我们从面向过程走向面向对象时,我们思维中的图像出现了一个重大的变化:在面向过程的程序中,我们想象中存在的是一个个动态的处理过程,它们在时间轴上按照一定的顺序依次执行。而在面向对象的图景中,我们考虑的主要是一个静态化的世界,系统分解为对象,而对象是同时存在于我们的思维中的。这里一个重要的技术手段就是成员函数。例如我们编写一个成员函数getChildren(), 这个函数的语义是返回一个数据变量,概念上它是静态的,但其内部实现其实可能是很复杂的,非常的动态化:可能需要查询数据库,可能需要校验权限等等。
理想的情况下,我们可以利用成员函数封装可以实现对象图的遍历。例如  x.getParent().getChilldren(), 在微软的COM模型或者Sun的JavaBean标准下,我们可以写成
x.parent.children。
特别注意这里的属性语法原则上是与时间无关的,即在同一个时刻我们认为这些属性同时存在着,是一个静态的关系。而以下的调用出现了明确的步骤,因而明确引入了时间因素
ITree parent = node.getParent();
if(parent == null)
   return null;
List children = node.getChildren();
return children;

在witrix平台的tpl模板引擎中使用的EL(Expressin Language)表达式语法会自动处理函数返回值为null的情况,因而维护了对象图的语义,不需要中断调用过程来进行异常处理,是简化界面编程的一个重要方面。

这里顺便提一句:人的思维是倾向于静态化的,但现实世界却是在不断的变动中的。正所谓不变的只有变化。Von Neumann体系结构强迫我们在最细节处引入变化过程,很多时候是过分的负担。我们需要的是识别出各种可以静态描述的部分,集中精力于那些真正需要随需应变的应用。

posted @ 2006-03-25 11:36 canonical 阅读(944) | 评论 (0)编辑 收藏

    关于DSL(Domain Specific Language)的确切含义纷争很多,但是其核心观念还是很明确的。DSL的关键是使用领域特定的概念,即它的概念系统中具有一些非General Purpose的基元。但是这种所谓非General Purpose仅是就诠释层面而言,它所指的并不是DSL对应的形式系统。例如,在物理学中声波(sonic wave)无疑是对原子系统的集团运动模式的一种抽象,基于wave概念的声波方程可以看作是对应于原子系统的一个特定领域的DSL。从声波系统是无法逆推出原子系统的所有行为的,但是理论上量子力学在形式上同样可以采用wave描述,而它却可以构成对原子系统的诠释。
   
    DSL因为基于Domain概念,可以提高理解性是应该的,但是现实中的大多数DSL仍然存在大量技术性的形式要求,它们是很难让非计算机领域的业务人员掌握的。期待业务专家使用DSL在目前多半只是一种不切实际的幻想。但是如果认为DSL就是更甜的代码也并不适当。搜索google的时候我们使用的查询字符串也是一种domain specific language, 很多人都会使用""和-等语法结构。

    DSL应该关注用的层面(what), 而不是how-to,how-to是DSL的实现问题。从what到how to是我们希望通过DSL解析器屏蔽起来的内容,而不是DSL本身需要表达的内容。

posted @ 2006-03-25 11:24 canonical 阅读(859) | 评论 (1)编辑 收藏

    目前的框架设计中,引入元数据(metadata)已经是必然的事情,jdk5.0的annotation机制也为metadata的物理驻留位置提供了新的选择。常见的一些元数据设计方案中往往是元数据直接驱动系统的展现甚至运行过程(例如普元EOS),大有完全取代程序代码的趋势,这无疑是对元数据概念的一种滥用。一般在界面层所使用的元数据其实类似于某种新的界面描述语言,即某种特定目的的DSL(domain specific language). 但这种描述一般是不完备的, 一旦遇到扩展情况, 往往需要很多额外的工作。
    实际上并不是所有信息都要在独立的xml 中描述, 前台模板页面本身就可以是一种元数据, 前台元素之间的关联已经隐含表达了多种关系, 不需要把这些关联再在额外的xml文件中重复.  比如说一个数据集展现在页面上的时候需要支持几种操作,即对应的需要显示几个按钮. 在witrix平台的tpl模板中, 我们的调用方式大概如下
        <ui:EditTable pager="${pager}" dsMeta="${dsMeta}">
        <buttons>
          <ui:RemoveRowButton/>
          <ui:EditRowButton/>
        </buttons>
        </ui:EdiTable>
    操作集合这一信息仅在模板中表达一次.  实际上很多时候, 不同的界面我们需要展示不同的操作集合, 它本身并不一定是数据集内在的性质. 数据集的属性只能是支持全部操作的集合, 它并不需要直接对应到界面上. 对于多个界面我们需要尽量共用一个meta配置.

    在witrix平台的元数据方案中,关键是采用pull mode, 由前台模板系统驱动, 模板决定使用何种资源(包括元数据),而不是由元数据驱动整个系统的展现。当一个元数据条目不适用的时候我们可以忽略它,但是仍然可以使用元数据配置中的其他部分。这与我们的jsplet web框架的设计是一脉相承的。

    元数据的驻留形式本身也是很有意思的问题。假设现在我们需要描述如下信息:本字段采用input框显示,它有一个参数value. 它的meta形式可以如下,
       <inputor type="TextInput">
          <arg name="value" />
       </inputor>
    我们也可以选择如下形式
       <inputor>
        <input type="text" value="${value}" />
       </inputor>
    第二种方式的特殊之处是它选择了与html规范本身兼容的表达形式,即寄生于html格式之中。这种设计的好处在于我们只需要一个通用的模板引擎,而不需要任何特定于该控件的解析器,就可以产生最终所需的文本输出。这种元数据表达方式更重要的地方在于它是导向更高复杂性层次的自然途径。例如我们现在需要一种更加复杂的自定义控件来显示该字段,则
       <inputor>
        <ui:DateInputor value="${value}" />
       </inputor>

    在元数据的设计中,适可而止永远都是我们需要铭记在心的核心原则。对元数据描述的范围要适可而止,不要试图包罗万象。例如,在界面元数据的设计中不要对于数据供体有任何假定。一个前台表格,无论它的数据是数据库中的一组记录, 还是通过pop3协议收取的一组信件,应该都不影响它对于meta的使用。元数据引擎所能够直接理解的粒度也要适可而止。在witirx平台的元数据方案中,viewer和inputor等配置段其实是由tpl模板引擎负责解析的,在DataSourceMeta的解析器并不能识别其中的细节,它也不需要识别其中的细节。

posted @ 2006-03-25 11:19 canonical 阅读(1487) | 评论 (2)编辑 收藏

     adun今天问我xslt到底有什么用。相对于其他技术,它有什么存在的必要性。
    xslt的主要作用是对xml结构的转换,即它是一种描述结构变换规则的语言。不过也可以将它与我们更熟悉的用于生成html(结构)的模板语言作一个对比。
1. 两者都能直接生成xml格式的文本(结构)
2. 两者都有循环和判断等逻辑处理语句。
3. 模板语言可以通过EL表达式等语言取得源数据, xslt通过xpath语法取得源数据.
4. 模板语言通过自定义tag等机制实现分解和组合, xslt通过xsl:call-template等语法实现分解与组合.

     tpl等模板语言直接操纵java中的对象, 体现的是图(Graph)模型, 并通过对象函数封装了部分动态性. 一般访问的时候是通过短程关系逐级访问,例如x.y.funcA().b。利用apache项目组的jxpath包,我们也可以使用类似于xpath的语 法在对象图上进行全局访问。
    xslt使用严格定义的xpath语法访问Tree结构,具有很强的数据操纵能力,例如我们可以通过//mynode等语法轻易的实现节点的聚合,构造出 新的结构。在一般xslt包的实现中,都提供了javascript扩展,可以使用javascript构造出的数据节点。

     在tpl这样的模板语言中java中的数据对象结构与tpl中的模板结构(处理规则的结构)是两分的,而在xslt中输入数据结构与xslt自身处理规则 的机构却是统一的。实际上tpl这样的模板语言主要是基于过程语义,即先做。。。,用到。。。,然后。。。, 而xslt对于输入数据的结构具有明确的全局性假设,主要是一种转换语义,即不论在什么情况下,只要遇到。。。,就做。。。。 在xslt中可以通过xpath语法指定处理规则针对数据(输入结构)的的触发条件, 而在tpl中只能通过<c:decorator>为tpl标签指定转换器,而无法针对数据指定处理规则。

     在数据访问模型这一方面,原则上说xslt与模板语言各有优势。实际在用于html生成的过程中,xpath的全局访问和匹配能力一般难以得到发挥,而 xml格式的限制又在一定程度上阻碍了使用灵活的数据供体,这方面模板语言有一定的优势。但是xslt的约束更强也有本质性的好处,它使用xml数据并输 出xml数据,维护了结构的同质性,便于管道操作。因为假设更明确,xslt对于xml格式的数据的操纵和封装能力也要强于模板语言。例如它可以使用来为 节点追加属性。

     xslt在用于xhtml生成时的一个主要劣势在于它是对变换规则的分解之后的描述,而我们所希望得到的是最终的结果,即这些规则综合应用所生成的结果。 虽然xslt的语法是xml语言,与xhtml在语法格式上保持了一致性。但是在xslt中,xslt的标签破坏了xhtml语义上的结构,使得目前无法 做到所见即所得。我们不得不在头脑中运行xslt规则来想象最终的结果,这无疑是痛苦的。模板语言在这方面要轻松许多。

     xslt的另一个问题是有时xml语法显得过于冗长了。在操纵一些局部结构的时候,xml节点并不一定是合适的表达。例如

<xsl:value-of select="$x"/> 对比 ${x},


<xsl:call-template name="subrule">
    <xsl:with-param name="argA"><xsl:value-of select="substring-after($path,'.')"/></xsl:with-param>
</xsl:call-template>
对比
<my:subrule argA="${path.substringAfter('.')}" />

在调用子模板方面,显然xslt封装的抽象层次也要弱于tpl模板语言。


     xslt作为一种xml格式的结构变换语言,维护了xml世界的概念完整性,其意义无疑是重大的。但并不是所有时候xml都是最适的描述模型。我们最好还 是将它与其他技术结合使用。目前在witrx平台中,我们对于xslt的使用主要是对tpl模板进行布局和过滤处理,即xslt用于对处理规则进行变换, 而将根据数据生成html这个最细致的工作留给过程处理能力和封装能力更强的tpl模板语言。

posted @ 2006-03-05 00:02 canonical 阅读(854) | 评论 (0)编辑 收藏

    最近强调弹性设计的比较多,大概是因为需求的多变太令人挠头了吧。但从道理上说,一个良好的设计必然是刚柔并济的。所谓没有规矩不成方圆,你能想象没有钢 筋骨架结构的高楼吗。在基础核心架构方面特别需要适当的刚性和足够的稳定性,需要用接口明确表达出将用到的假设。内核稳定了,固化了,外围的aspect 才能安全的织入到系统体系架构中来。象变形金刚那样的自由组合变化的结构(在流动结构与固化结构之间转换)目前还只是一种幻想。

    强类型语言在控制大型系统的核心方面还是远胜于弱类型脚本语言的。有些人认为单元测试可以取代编译器的强类型语法检查,这完全是一种臆想。程序的正确性应 该在不同的层面上得到约束,验证,而不是一古脑的推到单元测试那里。在单元测试中对于概念一致性的保障是绝对无法与静态类型检查相比的。类型一致性的检查 在编译器的帮助下以非常低的成本就可以进行,而如果要在单元测试中实现同样的约束,成本要高得多。有一点我们应该明确:除了功能实现代码,其他任何文档, 代码都应该是多余的。为什么需要单元测试,那还不是因为没办法嘛。

posted @ 2006-03-04 23:57 canonical 阅读(808) | 评论 (2)编辑 收藏

   我其实很少谈到OO这个概念,一般情况下我只提结构的表达与结构的控制。软件开发是一个从二进制指令构造出一些高级结构的过程。无论是PO, OO, 还是XO, 只要它能有效的降低这种结构构造过程的复杂性,能够增强我们对程序结构的表达和控制能力,那么它就是有价值的。在我看来,继承(inheritance) 必然是有用的,因为它是一种表达推理结构的方式而无论它的概念诠释是什么。行为函数聚合在对象的名义下是有意义的,因为它使得这些关联得以明确化,静态 化。为什么函数式编程是有效的,它和OO是什么关系。说白了,FP能够方便的表达OO不易表达的结构。xml与OO是否是冲突的?xml能够方便的表达结 构,通过dtd或者xml schema又可以方便的实现对结构的约束(动态的类型系统?)。
    级列设计理论要求我们所有的讨论必须在一个统一的模型(最广义的模型)下进行。OO与非OO的思想其共同之处是什么,它们在什么层面上是统一的?无论是 OO还是PO,都可以归结为结构问题。所以我多谈结构,少谈OO。两个不同的概念,可能意味着它们处于复杂性的不同级列(可以实现平滑的过渡),也可能意 味着它们之间是正交的,互补的

posted @ 2006-03-04 23:54 canonical 阅读(1312) | 评论 (4)编辑 收藏

                                      

http://www.xml.com/pub/a/ws/2003/09/30/soa.html
http://canonical.blogdriver.com/canonical/809426.html

   随着IBM, Microsoft这些世界级大厂商不遗余力的推销,SOA(Service Oriented Architecture)已经成为企业应用中的核心概念之一。我的一个同学在IBM做SOA架构咨询,前两天也和他聊到这个话题。从他们IBM的态度来 看,SOA无非是后EJB时代一个profitable enabled的概念而已。现在软件厂商的日子变得愈加艰难了,很多厂商都希望向服务转型,成为所谓IT service provider. 这是某种商业架构上的service oriented, 与技术上的SOA似乎有一些相互映衬的关系。IBM极力希望把SOA中的service概念提升到business layer, 希望在超越BPM(Business Process Management)的层次上提供一站式的打包服务。但是很显然,此service非彼service, 这种SOA的宣传中颇有一些偷换概念之嫌。把所有可以让用户买单的理由用MDA(Model Driven Architecture)做包装,打包到一个独立概念的package中在目前确实是一种可行的商业策略,只不过相对于用户脆弱的技术理解力而言,这种 SOA实在是不可承受之重。大多数用户所能理解的SOA不过是Integration的代名词,与EAI(Enterprise  Application Integration)没有什么可区分性。目前java技术的普及已经使得各个厂商的区别变得很小了,IBM这些巨型厂商鼓吹它们在异构系统集成方面的 优势当然是势所难免,在此过程中加点料也是可以理解的。


   虽然SOA这一概念中混杂了太多的商业因素,它也并不是一种单纯的fantasy. 从技术角度上说,SOA存在着如下关键性特征:
1. 独立运行(standalone)。所谓的service, 它与组件(component)的根本不同,首先在于service是独立于调用者自行运转的。访问service的接口相对狭窄,我们只需要知道 service如何满足我们的功能需求,而不需要管理它的生命周期,不需要理会那些维持service运行所需要考虑的种种细节。即我们对于 service的了解只需要局限于功能接口即可,不需要理会它的那些管理接口,配置接口等。各种service在功能接口这一层面发生交互,整个图景得到 简化。最常见的一个例子就是数据库服务器,调用程序只要知道如何通过sql语言进行数据访问即可,另有数据库管理员去对付各种配置管理问题。从技术上说, 这可以看作是singleton模式的一种扩展。实际上spring容器中管理的bean在某种程度上就可以看作是独立于bean的调用者的,因而 spring这样的容器可以成为SOA架构的自然接入点。

2. 异步调用(asynchronous)。内禀的异步特性是SOA包容真正的商业智能的关键所在。想象一下我们现在需要评估用户的信用度,它对应于函数 evaluateCustomerCredit(customer). 最简单的情况下,我们只需要根据用户的某些指标直接进行算术运算即可。如果评估规则非常复杂,我们可能放弃硬编码,而引入规则引擎(Rule Engine)这种人工智能的解决方案。在更加复杂的情况下,我们可能无法抽象出以数学方式表达的规则,而必须依赖于业务人员的人工处理(真正的智能)。 此时,系统可能需要弹出一个页面,等待业务人员作出判断,部分情况下还需要经过审批流程才能决定对于用户信用度的最终评定。对于计算机系统而言,这些都是 漫长的过程,是同步调用方式所无法容纳的。同样是一个函数调用,只有异步特性才能够包容真正的商业智能,才能在函数这种最小的程序结构单元中引入最复杂的 处理过程。现在SOA的宣传往往集中于机器之间的互相调用,强调异构系统之间的一种包容性,但事实上异步特性所能承载的内容要远远超越机器世界本身。当 然,同步调用方式也是SOA架构的自然组成部分,就像我们既需要email, 也需要手机一样。

3. 基于消息(message based)。基于消息的调用方式是分布式系统的一种内在要求。消息是一种数据,它并不是远程对象指针。distributed object这种基于proxy-stub的方案其内禀要求是远程状态同步(状态的一致性),而分布式系统是由众多独立的状态空间(进程空间)所构成的, 这种内在的不协调是造成分布式对象方案难以实现可扩展性的关键原因。
  SOA与EBI(event-based integration)的区别主要在于EBI往往是push模式的,消息源向注册的消息consumer推送消息,而SOA往往还是一种pull的调 用。这两种方式各有千秋,在大的scale情况下,pull模式的可扩展性要更好一些。但是两种模式在企业应用中都是必不可少的,可能这些概念很快就会出 现融合。


4. 纯文本协议+元数据(Plaintext Meta)。SOA架构中基于纯文本协议是一个非常关键的技术抉择。当需要跨越众多硬件平台和软件系统的时候,各种二进制的RPC方案总是存在着难以彻底 解决的可理解性的问题。凭借纯文本的结构透明性加上元数据的自描述特性,我们希望实现一种语义透明性,使得各种中间层都能够以通用的方式传递经过的消息, 并理解其中需要理解的部分。与OOP(Object Oriented Programming)中的传统模式不同的是,SOA架构中强调的是结构(structure)与内容(content)的分离,而不是数据与行为的耦 合与封装. 表面上看起来,SOA中的调用方式似乎是对procedure(过程)化调用的回归,但实际上其中有着深刻的不同。在与元数据结合之后,在系统之间传递的 消息(信息)并不是完全被动的,而是具有某种smart特性。当数据这一方面可以包容更多的变化的时候,处理函数这一方面的结构可以得到简化。实际上,在 SOA架构中,我们推崇一种uniform interface, 也只有通过一种通用的接口,信息才能以比较低的代价穿越各种状态空间边界。基于通用接口,我们又重新发现了世界的简单性,而pipe-and- filter这种在unix系统中久经考验的设计模式也得到了新的发挥空间。
    说到纯文本的元语言,xml无疑是这一概念最强势的候选者。作为一种半结构化的文本表述,xml天然就是人机共享的信道。但是,随着应用的深入,当越来越 多的xml设计变得无法让人直观理解的时候,我们也感觉到了一些对于xml滥用的痕迹。W3C的Web Service协议栈提供了非常关键性的思想,并作为标准化的实现方式存在了很长的时间了,但是其实现和调用的繁琐和冗长逐渐引起了开发者的不满。 SOAP虽然号称Simple Object Access Protocol,但是今天已经很难把它和simple这个词拉上什么关系。也许Web Service的未来就如同EJB一样, 渐渐被更加pragmatic的方案所取代。

    在SOA架构中,松散耦合(loose couple)是late binding(discovery), standalone和message based等多种技术策略综合作用之后所达到的一种效果,这为外部灵活的流程配置做好了铺垫。

posted @ 2006-03-04 23:51 canonical 阅读(1063) | 评论 (1)编辑 收藏

    web开发这个领域是很有意思的。首先,web的兴起是在软件业发展到一定阶段才发生的,它必然吸收了软件业最优良的思想,必然有其本质上先进的地方。另 一方面,web的应用毕竟是时日较短的事情,造成很多基础架构方面也是薄弱的,原始的。
    具体来说,前台html的展现模型本身是非常先进的。xhtml+css+js实现了结构(structure), 表现(presentation)和行为(behavior)的分离。xhtml本身是简单的文本文件,通过工具的支持可以做到结构上的"所见即所得" (WYSIWYG)。 在js中操纵html结构具有多种方式:可以通过id直接访问html片断,可以直接操纵dom的层次结构,可以将html作为线性文本处理,可以应用 xml相关的技术对dom结构进行变换,可以动态切换html元素的css风格等。dom结构的访问方式是高度统一的,通过parentNode, childNodes, setAttribute, getAttribute等少数几个 API函数,我们可以通过一种简洁一致的方式操纵所有的节点和相关属性(当然,IE这方面的bug不少)。html相关技术中所显示的结构控制能力远远超 越了传统桌面程序中组件技术所能达到的程度。
    但另一方面,html也是原始的,缺乏现代应用程序所必需的标准控件,典型的如Tree控件和Tab控件等。每个开发商都不得不实现并维护自己的界面库。 通过web界面调用后台业务逻辑的方式更是很粗糙的。基础的servlet只提供了基于IO的有限状态机模型,对于后台功能缺乏有效的组织,而对于前台界 面也缺乏合适的抽象手段,仅仅作为文本输出。MVC框架建筑在servlet模型之上,将后台逻辑功能以一种统一的组织方式向外暴露。而tag技术在前台 界面中的应用,使得我们可以有效的识别并分离出我们所关心的结构。这些技术的发展都是web开发模型逐渐精细化的必然结果。
    为了在服务器端获得足够强的结构控制能力,有些人求助于桌面程序的历史开发经验,希望通过java语言中的结构表达能力来扩展web开发的模型,于是便有 了echo2, tapestry这样的组件化web开发框架。坦率的说,我并不看好这类强类型建模的框架。除了性能上的原因之外,我反对这类框架的一个主要原因是 java语言直接表达的结构一般无法达到用xml文本表达的结构的统一性和灵活性,从而很难应对界面的快速变化。实际上,对web界面进行组件化的分解并 不一定需要一种强类型语言支持的组件模型。通过自定义标签的使用,我们完全可以实现将页面分解为多个子部分的目的,这一点已经由witrix平台中的 tpl模板技术所证实。

    web开发是个既先进又落后的领域。很多人面对这种矛盾的情况,难免思想上会出现混乱。关键是要认清技术的本质而不要被OO是否必需等抽象的讨论所迷惑。

posted @ 2006-02-22 20:39 canonical 阅读(1845) | 评论 (1)编辑 收藏

web程序需要完成  html <--> java 之间的映射,在界面越来越复杂,越来越多变的今天,这项工作也变得越来越困难。按照级列设计理论的观点,我们应该去寻求一些中间的过渡步骤。在 witrix平台中,tpl模板引擎正扮演了这种中间角色。通过tpl模板我们实现了如下映射路径

html <--> tpl <--> java

注 意到这里html与tpl之间,以及tpl与java之间的映射都不是trivial的同构关系,而是都可能存在着复杂的运算过程,从而实现了html与 java映射过程中复杂性的分解与均摊。tpl与java之间的关联主要通过EL(expression language)表达式来完成,而html与tpl的映射则主要通过自定义标签(tag)机制。
注意到tpl所提供的中间层具有独立的重大意 义,它并不是臆造的或者是简单的技术驱动的结果。实际上,在web开发中除了java结构与html结构之外还存在着第三种结构,即用户眼中的界面结构, 本来它与html所描述的结构是简单的一一对应的,但是随着界面技术的发展,html的描述能力逐渐被耗尽,成为了internet时代的"汇编语言"。 现在一个简单的页面片断就可能对应着大量html代码,因而丧失了"所写即所见"的简单性。tpl通过强大的抽象能力在某种程度上恢复了程序员对于界面表 现结构的直观控制能力,并在一定程度上保留了html所见即所得的特性。

在witrix平台中因为存在着tpl这一强大的抽象层,使得我们对于ajax的支持可以采取更加灵活的方式。
ajax(Asynchronous JavaScript + XML)的标准结构是
html <--> js <==> xml <==> java

在 这种结构中通过xml信道的只是数据,而界面的表达逻辑与展现逻辑完全由js来控制。这种结构发展的一个极端是所有的界面展现结构都由 javascript动态构造出来,而完全丧失了html静态描述的特点,丧失了所见即所得的设计。与直接实现html<-->java之间 的映射情况类似,直接实现 html <--> js之间的映射也是困难的,尽管dom模型的支持可能使得js映射的难度要低于java映射。

在witrix平台中ajax的方案为
html <--> js <==> tpl <--> java

即tpl取代了ajax标准方案中xml的位置,使得映射过程的复杂性得以分散化。

结合jsplet框架的拉模式(pull mode),我们定义了如下ajax访问接口
js.ajax.load({request='objectName=/@Test&objectEvent=query',tpl:'/test.tpl:partA',targetId:'testDiv'});

1。 远程服务请求就是一段普通的http post request, 避免了额外的xml编码解码需求。
2。请求到的数据先由tpl文件来进行处理。注意到这里tpl文件的url分成两部分,前一部分是tpl文件的虚拟路径,而 :后面的部分,即partA指出请求的是该tpl文件内的partA部分,而不是整个tpl文件。
3。返回的html结果被填充到targetId所指定的html元素中。

test.tpl文件的内容
<html>
<body>

<tpl:define id="partA">
<img tpl:tag="ui:EditTable" />
</tpl:define>

<div id="testDiv">
<img tpl:tag="ui:ViewTable" />
</div>

</body>
</html>

tpl具有强大的结构构造能力,在这里我们以非常小的代价实现了tpl片断的定义,例如test.tpl中的partA部分。这里通过id访问tpl片断就如同js中通过id来访问html片断一样。
最后提一个很重要的思想:大量零碎的代码片断需要集中存放,否则人的精力会被耗散。一个反例就是struts中的action, 明明只干那么点事,偏偏要占据一个单独的java文件,占据大量单独的配置条目,最终给程序员带来很大的困扰。

posted @ 2006-02-22 20:36 canonical 阅读(584) | 评论 (0)编辑 收藏

Ajax: A New Approach to Web Applications http://www.adaptivepath.com/publications/essays/archives/000385.php
Ajax(Asynchronous JavaScript + XML)并不是一个革命性的崭新概念(也许根本就不存在突发的革命),它的技术基础在多年之前就已经牢固的建立起来了,在概念层次上的探讨也早就不是一个 新鲜的话题,只是大规模的有深度的应用似乎是最近才开始的。
从广义上说,web应用至少涉及到两个结构,
1. 后台以java语言表达的业务逻辑结构
2。前台以html语言表达的界面表现结构。

web开发很大一部分工作就是建立这两个结构之间的关系。即我们需要
html <--> java

我 们首先要意识到这两种结构之间并不一定是同构的,即后台数据的组织方式与前台展现时的结构是不同的。同样的数据可以对应于不同的展现结构。这也是所谓 MVC架构实现模型与视图分离的依据。传统上,基于Model2模式的MVC框架中,这两种结构的映射只能在很粗的粒度上进行(即整个页面的粒度上),因 此妨碍了封装和重用。为了进行细粒度的映射,我们必须要拥有细粒度的结构控制能力。而目前最强的结构控制能力存在于javascript/DHTML模型 之中,在js中html的结构可以是一段线性的文本(innerHTML和outerHTML), 可以是层级组织的节点(parentNode, childNodes), 也可以是按照key组织起来的Map(getElementById)。在不同的情形下,我们可以根据需要选择不同的结构模型。
ajax体系很直接的一个想法就是将所有关于界面表达和控制的结构都推到前台,由控制力最强的js来控制后台数据模型与前台html模型之间的映射。
html <--> js <==> xml <==> java
在ajax 体系中,xml所扮演的角色是js与java之间的翻译通道,它将js中的结构与java中的结构对应起来,这种翻译比html/java之间的映射要简 单的多。其实它甚至可以是一种同构的映射,可以用一种通用的方式来进行,例如结合burlap与buffalo包的功能。结合webservice的一些 思想,js/java之间的映射是可以在函数调用这种细粒度上自动进行的,从而我们最终可以在概念上完成html/java之间的细粒度映射。

posted @ 2006-02-22 20:33 canonical 阅读(1142) | 评论 (0)编辑 收藏

    AOSD(Aspect-Oriented Software Development)可以看作是AOP技术思想在设计领域的一种投射. 采用Aspect的观念之后, 我们在系统分析时应用如下的分解策略
     base + extensionA + extensionB +... 而不仅仅是 partA + partB + ...
  这种分解的基本理由在于base/extension的依赖关系与extension之间的依赖关系并不相同. 在理想情况下, extension之间是完全正交的, 而它们通过base可以构成一个整体, 这是一种典型的star schema. 但是在实际的软件构造过程中, 软件各个元素之间的交互方式要复杂的多:
 1. extension之间可能存在着相互作用, 最简单的一种情况是extension执行时的序关系(order).
 2. 一个结构上的extension可能分散到多个component上, 如何精确而有效的控制定位是一个非常困难的问题.
    就目前的AOP技术而言, 对于extension的控制其实是非常乏力的(但这并不意味着AOP必然放弃对extension的控制), 我们尚需要积累更多的经验. 在实做中, 更加稳健的方法往往是应用aspect的思想而采用传统的实现方式.
    AOSD在理论上存在一些价值, 例如它为use case的extension符号找到了技术对应, 因而使得这个概念变得更加明晰, 而在传统中, 对于use case的extension的解释一直是模糊而混乱的. 目前在真正的开发中, AOSD所描绘的全程建模仍然只是一个遥远的梦想.

posted @ 2006-02-22 20:28 canonical 阅读(896) | 评论 (0)编辑 收藏

  如果一个东西看起来象花生,闻起来象花生,吃起来也象花生,那它究竟是不是花生? 如果两个事物在所有可观测行为上都表现一致,那两者的本质是否统一就成为了一个不可证伪的问题,从而处于科学范畴之外。而从人的机会主义倾向来看,我们理 所当然的会认为这两者是同一概念。我们以观察来认识世界,当然也就是以行为来界定事物。问题在于,我们在理论上要以事物的所有行为来界定它,而我们目前观 测到的又永远只是它的部分行为。在泛函分析分析中,有一种弱(weak)等价的概念,两个函数如果与某个空间中所有函数的作用(内积)的结果都相等,则这 两个函数在此空间中就是弱等价的。swartz正是通过这种方法定义了广义函数,为delta函数在数学上建立了严格的基础,回避了delta函数的本质 性定义困难。在物理学家看来,delta函数当然是个客观存在的实体,而在数学家看来,它只是通过分布间接定义的概念。(当然,部分研究非标准分析的数学 家认为广义函数兜了个大圈子)
    接口(interface)与类(class)相比,接口的概念完全是由其行为定义的,而类还涉及到其封装了的成员变量,这些变量的作用在继承的时候会隐 蔽的表现出来。毫无疑问,接口所代表的概念是比类变得"浅薄"了,它是明确暴露的行为切片而不是象类那样欲遮还羞的实体定义。


posted @ 2006-01-23 23:16 canonical 阅读(790) | 评论 (0)编辑 收藏

IntelliJ老板的一篇文章Language Oriented Programming: The Next Programming Paradigm
英文 http://www.onboard.jetbrains.com/articles/04/10/lop/
中文 http://blog.csdn.net/chelsea/archive/2005/02/17/290486.aspx

Martin Follower的一篇文章 Language Workbenches: The Killer-App for Domain Specific Languages?
http://www.martinfowler.com/articles/languageWorkbench.html

概念解释:
DSL(Domain Specific Language): a limited form of computer language designed for a specific class of problems, 例如SQL语言,xml配置文件。
LOP(Language Oriented Programming): the general style of development which operates about the idea of building software around a set of domain specific languages.
Language Workbench: the new breed of tools to do language oriented programming

LOP不是一个新的提法,不过随着前段时间MDA(Model Driven Architecture)概念的热炒,LOP似乎终于熬到了应用层面。Sergey Dmitriev的文章中批评了主流程序语言的不足,大概有这么几条:
1. 通用语言表达能力(expressive power)很差,Time Delay to Implement Ideas
2. 领域概念的表达分散在实现代码中,整体图像迷失,因而Understanding and Maintaining Existing Code很困难。
3. 类库等不是用领域概念表达的,因而Domain Learning Curve很陡峭。

这 些都是标准的陈词滥调。地球人都知道,从问题描述到软件实现之间存在着巨大的逻辑障碍,这种障碍有一部分是本质性的,即源于我们认识中的创造性因素,而另 一部分是技术性的,即源于我们使用的技术手段的限制。我们所能想到的解决的办法就是尽量提高解决方法的抽象层次, 提升到所谓的领域层面,从而消解技术性障碍,同时辅助创造性发展。当我们的脑子里不再充斥着各种技术性细节的时候,大概就可以集中精力做些创造性工作了。 LOP把这些老帐又翻出来,到底它提供了什么新意?下面我们简要分析一下LOP方案中概念的含义。

1. 领域(Domain)。
    计算机语言与自然语言在使用上是有着深刻不同的。自然语言只是传递着信息,而计算机语言还要负责具体干事情,即计算机语言要同时说明怎么做和怎么用。做与 用这是两个不同的领域,例如我现在编了一个时光机器的软件,上面就一按钮,只要轻轻一按,嗖的一声我就回到了500年前。怎么样,使用简单吧,但实现呢? 我们当然希望在一个领域中使用最适合它的描述方法和控制指令。目前主流语言都是面向机器实现领域的(是实现领域的DSL?),加上Von Neumann串性化体系的限制, 强迫我们用动态过程来实现静态概念,更加剧了它对使用领域描述的不适应性。我们所要做的就是将做与用尽量分离,但同时尽量增大用的灵活性,domain representation不仅仅给最终用户用,还给程序员用。能在使用领域概念范围内解决的问题,我们不要将其拖延到实现领域。在领域间进行转换,总 存在信息转换成本,甚至会造成信息失真。例如,一幅画看起来结构很简单,但是用自然语言描述起来都相当困难,更别说转换为计算机语言了(当然,这个例子很 有可能是误导的,因为涉及到不同的感官,其中的区别是非常深刻的,无法用单一的原因去解释)。所谓领域区分,最重要的还是使用领域与实现领域的区分(不同 的复杂性,不同的表现形式...),而不仅仅是业务领域与计算机领域的划分。在业务领域内部我们也要区分使用与实现。

2. 领域特定语言(DSL)
    语言与库的最大差别在于语素可以自由组合,以极低的代价构造出无数可验证的结构,而库函数的组合和搭配调用是冗长的,受限的,难以进行校验的。DSL强大 的描述能力和推理能力其实是通过放弃其通用建模能力而实现的。最有效的DSL必须弱于图灵机,必须将大量做的过程分离出去,必须引入大量的领域概念(本 体)。
    在引入外部概念的时候,DSL会将其转义为领域概念之后使用,从而消除歧义性并降低理解难度。
    DSL不是在通用环境下工作,而是在明确受限的context下工作,因而概念的表达可以更加简洁,而且领域概念之间还可以通过context构成一种整体性,例如非此即彼。
    witrix平台中的tpl模板引擎在一定程度上可以看作是一种DSL tools, 我也一直推动tpl在这个方向上发展。tpl具有强大的领域抽象能力,例如
       弹出一个选择系统用户的窗口,直接实现为 
        <a href="select_user.jsp?deptId=1">选择用户<a>

    封装为tag之后,使用形式变为
        <app:选择用户 部门编号="1" />
    经过转换之后,这里的调用不再是API含义,不再是说明怎么做,而是描述用什么。tpl中的标签可以使用调用时明确指定的参数,也可以从全局 context中导入隐含的变量,从而依赖于所在领域的假定。例如,我们的很多界面控件需要与后台交互,依赖于后台jsplet框架,因而这些tpl标签 选择自动导入$thisObj和objectName等变量。领域抽象与context依赖其实正是DSL的精华所在。
    (关于DSL,有些人可能会将其等价于规则引擎(rule engine), 这其实是一种误解。规则引擎实现的是条件空间的分解与合并,它是一个独立的技术,与DSL并没有必然的联系)

3. 语言工作台(Workbench)
    Workbench是LOP的使能技术。我们希望自由的构造DSL,必然需要对结构具有强大的控制能力。而文本语言总是线性的,静态的。看看现在对自然语 言的研究吧,显然我们对于怎样在线性文本中塞入更多的结构缺乏本质性的认识。我们人为构造的语言,限于表现形式,其结构都异常的贫乏。计算机的脑袋是简单 的,于是,我们想到增加维度,通过复杂的工具配合,来实现多层次,多视角, 动态的结构控制。在我看来,很多时候这是一种无奈之举,当然也确实是目前唯一可行的办法。
    工具复杂了之后,造成的本质性障碍在于结构上的僵化,其具体表现之一就是所谓的厂家锁定,一种结构融合上的困难。不同厂家产品的结构具有巨大的差异而无法 互相交互。xml其实正是为了解决这种结构交互困难而产生的,所以我更希望看到基于xml的工作。而在xml的形式下,能够实施的结构变换现在也在不断发 展当中,AOP, XSLT等等。

posted @ 2006-01-23 23:13 canonical 阅读(839) | 评论 (0)编辑 收藏

http://spaces.msn.com/members/zbw25/Blog/cns!1pA6-3FOo9yNp_4lmEHxdDqA!248.entry

     物理和数学的新分支的产生多半有着哲理性的开端,而软件中OO技术的兴起想必也是有一定的哲学基础的。但哲学是一种整体的,超越的认识,当我们在实际的应 用中走得越远,就会发现现实的操作距离哲学的理想越远。早期面向对象总是鼓吹对现实世界的直接表达,鼓吹Object,Class的本体论含义。但现在我 们已经可以清楚的感觉到面向对象的哲学隐喻存在着本质上的困难,而软件希望作为真实世界的翻版也必然面临着建模的本质性问题,即任何一个单一的模型与事实 相比总是简化的,贫瘠的。通过无限多自恰的模型构成的概念包络,再加上无法用技术手段表达的哲学升华,我们才达到了所谓不随人的意志转移的客观世界。软件 只能是客观世界的一部分,而不可能是客观世界的镜像。
    OO技术已经得到了深刻的发展与应用,实际上现在可以不再总是需要一件哲学的外衣了。我一直强调继承(inheritance)是一种推理技术,而接口 (interface)是一种正交分解技术, 希望抛开OO的诠释而从数学上为OO技术找到根基。 无论是推理还是正交分解,我们都可以在数学上严格的证明它们的好处, 因此OO必然是一种好的技术。至于它对现实世界的表达能力,那是另外一个独立的问题。我的这种思想深受测度论(measure theory)的影响。测度论中对于概率的定义是纯粹数学化,满足一定条件的数学量就定义为概率。 至于它是否对应于我们日常思维中的概率概念,那是使用者的责任,那是物理学所面临的问题。只有通过这种公理化的定义,测度论才摆脱了概念完备性与自恰性的 问题,才摆脱了哲学上的循环论证。当然,诠释问题在物理学中仍然是一个非常严重的问题, 例如对于量子力学的Copenhagen诠释的争论从未间断过,只是对于数学层面上的操作过程一般还能保持共识。当然,说的深入一些,即使数学上的定义也 未必是逻辑上必然的。为什么实数轴是完备的,为什么1.999999999...的极限是2, 这实际上是一个公理: 选择公理(axiom of Choice), 等价于Zorn引理。

posted @ 2006-01-23 23:11 canonical 阅读(828) | 评论 (0)编辑 收藏

    最近听到不少人总是叨念着"细节决定成败"这句话,颇像是每天清晨必修的那句"all money go my home"一样。决定成败的因素很多,为什么细节能够成为压倒一切的关键。
    拿产品来说吧,产品的细节处很重要,可能客户的取舍在毫厘之间。但这是否是事实之全部。其实客户需要的是满足自己的价值目标,他真的那么在意主要目标之外 的细节(甚至是刻意造作的细节)吗。制造细节的目的只是为了制造与同类产品的不同,这是市场进入完全竞争状况的标志。只是有意思的是,凡是中国人涌入的领 域,很快就能造成完全竞争的局面。就像我们小区里的煎饼摊,今天刚开张,明天周围马上冒出两家一样的,你两元一份,我两元一份加一个鸡蛋,他两元一份加一 个鸡蛋再送一碗豆浆。很快,经过一场血拼,几家都难以维系,最终撤摊了事,于是小区内就再也没煎饼卖了。再过些时日,有人支起一个状元饼的炉子,于是一段 新的血战征程又开始了。最大的利益永远源于创造。当然,创造是需要成本的,现在财大气粗的主多半没有创造的激情,而被创造力冲昏头脑的家伙却多半在社会的 底层挣扎。

    项目的细节处也很重要,也许见了领导少哈一个腰,过节的时候少送了一份礼,就将项目引至黑暗的深渊。为什么世界是如此的不稳定,要受到细节的摆布。在国 内,操作多不规范,因为利益关系,两两联系,因人而异的情况很多,缺乏一种外在的制度性的保障,而个人的好恶却能在现有的体系中不断放大。结果做人优先于 做事。

    对软件程序来说,细节会决定程序的生死,一个不经意的指针异常就能让整个系统崩溃。但程序员也不总是战战兢兢,如履薄冰。一种职业的素养可以消解细节的危 险。当我们养成良好的编程习惯之后,对这样的细节多半就视而不见了。更理想的方法是引入一种封装机制,例如智能指针使得我们再也不用考虑AddRef和 Release的精确配对了。而java引入的则是新的世界,野指针这个细节在新世界中被消灭了,我们也不需要这方面的什么个人素质了。细节处千变万化, 无一定之规。也许我们最需要的不要应对细节的技巧,而是能够屏蔽和规范细节的规则。细节不是我们的目标, 一组统一简明的游戏规则才决定着所有博弈参与者的成败。

posted @ 2006-01-23 23:05 canonical 阅读(624) | 评论 (0)编辑 收藏

    在我看来,软件开发就是一个从二进制指令构造出一些高级结构的过程(from binary chaos to artificial intelligence)。这种构造依赖于我们控制各种结构的能力。结构化编程向我们展现了一个机械化的分解与合成的世界,但这个世界与我们的真实世界 却差异良多。于是,面向对象编程试图直接跳跃到真实的世界,依赖于我们对真实世界中结构的控制能力,直接对真实的结构建模。早期面向对象技术的陈述中充斥 着这种乌托邦式的理想图景。但是这种隐喻是含混的,两个世界的巨大差异造成了必然的转换成本,我们只能压缩这种成本而不可能完全抛弃它,我们必须要经历一 系列的中间过程,经历一个对结构问题进行深刻思考的过程。近几年软件技术在控制抽象结构方面有了很大进展,模板,AOP, xml, SOA等等技术并不是传统意义上的面向对象,而更像是对结构化编程时代的回归。而我们对于面向对象技术的应用也不再仅仅关注于对真实世界的建模,而是将这 种技术作为一种普适的建模方法应用于软件的方方面面。我们在编程的时候不再斤斤计较于一个Class的定义是否反映了事物的本质特征,而仅仅在意它是否有 助于我们对于程序结构的控制。这就象是神经网络和演化计算等所谓人工智能技术,早期的兴起源于一个让人心血澎湃的理想:向生物世界亿万年的智慧学习,但近 几年的发展则越来越明显的表现为一种对数学的回归。
    EJB和JSP Tag都是很好的技术,它们的最终形态都使我们拥有了更强的结构控制能力。但问题是,它们的构造成本过高,而限制了其意义的进一步引申。轻量级容器的兴起 才真正开发了AOP技术的潜力,使得Meta Object Protocol的思想得到真正的发挥。witrix平台中的tpl模板技术将自定义tag的构建成本降到了最低:没有配置文件,使用tpl自身来构造 tag。这些努力使得tpl技术不再是象是一个帮助库,而成为一种独立的语言。应用tpl模板,我们可以随意的将小段html文本封装为有意义的tag (这在jsp tag中是被明确反对的实践), 从而获得一种崭新的抽象能力。实际上,我认为JSF基于jsp tag技术,在基本结构的构造方面成本过高,无论它的IDE怎样发展,最终只能成为一种界面库而不会是真正引领未来方向的技术。只有突破成本阈值,才能发 展出新的天地。

posted @ 2006-01-23 23:03 canonical 阅读(344) | 评论 (0)编辑 收藏

    代码复用包括两个方面:概念复用和实现复用。这两者在C++的虚拟函数设计中是合二为一的,结果概念上的模糊往往造成继承机制的滥用。为了复用我们往往在 基类中塞入过多的职责,并在程序中制造了过多的层次。java的interface是纯粹的概念复用机制,实现方面的复用我们一般通过Impls类或者 Utils类来进行,即将代码片断写为静态函数。一般应该避免在类中写特别多的帮助性成员函数,因为成员函数隐含的通过成员变量相关着,比静态函数要更加 难以控制。
    类是一个整体的概念,整体概念失效了,类也就不存在了。从这一点上来说,它未必是比静态函数更加稳定。概念与实现是两个不同层面的东西。实际上它们一般也 是多对多的关系。同一个概念可能换用多种不同的实现,而同一段功能代码也可能在多个类中使用。
    代码复用的意义不仅仅在于减少工作量。实际上复用是对软件的一种真正的检验,而测试仅仅是一种模拟的检验而已。每一次复用都是对代码的一次拷问。在不断使 用中感受到不同使用环境中的各种压力,才能实现概念的不断精化并确保实现的正确性。

posted @ 2006-01-23 23:01 canonical 阅读(961) | 评论 (0)编辑 收藏

    我们开发程序的目的是为了完成业务功能, 理想的情况下程序中的每一条语句都应该是与业务直接相关的, 例如程序中不应该出现连接数据库, 读取某个字段等纯技术性的操作, 而应该是得到用户A的基本信息等具有业务含义的操作. dao(data access object)层存在的意义在于将与数据持久化相关的函数调用剥离出去, 提供一个具有业务含义的封装层. 原则上说, dao层与utils等帮助类的功能非常类似, 只是更加复杂一些, 需要依赖更多的对象(如DataSource, SessionFactory)等. 如果不需要在程序中屏蔽我们对于特定数据持久层技术的依赖, 例如屏蔽对于Hibernate的依赖, 在dao层我们没有必要采用接口设计. 一些简单的情况下我们甚至可以取消整个dao层, 而直接调用封装好的一些通用dao操作函数, 或者调用通用的EntityDao类等.
    程序开发的过程应该是从业务对象层开始的, 并逐步将纯技术性的函数调用剥离到外部的帮助类中, 同时我们会逐渐发现一些业务操作的特定组合也具有明确的含义, 为了调用的方便, 我们会把它们逐步补充到service层中. 在一般的应用中, 业务逻辑很难稳定到可以抽象出接口的地步, 即一个service接口不会对应于两个不同的实现, 在这种情况下使用接口往往也是没有必要的.
    
    在使用spring的情况下原则上应该避免使用getBean的调用方式, 应该尽量通过注入来获得依赖对象, 但有时我们难免需要直接获取业务对象, 在不使用接口的情况下可以采用如下方式

    class TaskService{
        public static TaskService getInstance(){
            return (TaskService)BeanLoader.getBean(TaskService.class);
        }
    }

    在程序中我们可以直接使用TaskService.getInstance()来得到TaskService对象.通过命名规范的约定, 我们可以从类名推导出spring配置文件中的对象名, 因而不需要使用一个额外的硬编码字符串名.

posted @ 2006-01-23 22:57 canonical 阅读(908) | 评论 (0)编辑 收藏

    jsp模型为web程序提供了page/request/session/application这四个基础性的变量域. 这种变量域的划分很大程度上是纯技术性的, 与我们的业务应用中需要的scope支持相去甚远. 当我们把业务对象的生命周期映射到这些变量域的时候, 经常出现不适应的情况. 例如我们可能被迫选择把与某项业务相关的所有数据放置在session中并在各处硬编码一些资源清理代码. 为了实现与愈来愈复杂的应用开发的契合, 我们需要能够在程序中定义与应用相关的变量域并实现对这些变量域的管理, 即我们需要一种自定义scope的支持而不是使用几个固定的scope.
    JBoss的Seam项目http://www.jboss.com/products/seam 中引入了一种所谓declarative application state management的机制
    http://blog.hibernate.org/cgi-bin/blosxom.cgi/Gavin%20King/components.html, 其中的关键是增加了business process和conversation这两个应用直接相关的scope, 它们都是可以根据需要自由创建的. business process context使用jBPM支持long running的状态保持. 而conversation context是对session使用的一种精细化, 与beehive项目中的page flow所需的scope支持非常类似 http://beehive.apache.org/docs/1.0m1/pageflow/pageflow_overview.html. 但目前seam中的scope支持仍是非常原始的, 不支持嵌套的context, 这意味着对于复杂应用尚无控制和管理能力.

posted @ 2006-01-15 22:35 canonical 阅读(592) | 评论 (0)编辑 收藏

    在无侵入性的前台页面控件设计方案中, 我们需要一种简便的方法迅速定位页面中的某一节点(dom node). 使用xpath是非常诱人的一个技术选择, 但是在实际使用中, 我们却发现xpath并不是那么方便. xpath的能力非常强大, 它支持绝对定位, 例如//input[@id='3'], 也支持相对定位, 例如 ./input[0], 甚至支持根据节点内容定位, 例如//a[contains(., 'partial text')].
    问题是在一个复杂的界面控件中, html节点本身的结构与界面展现结构并不是一致的,例如一个特定效果的边框可能需要多个html元素互相嵌套才能够实现, 因此xpath的相对路径选择能力往往派不上用场(除非是提供http://www.backbase.com/那 样的界面抽象层), 而根据内容定位的方式过于灵活, 难以维护一个稳定的概念层. 相比较而言, css的选择符所提供的节点定位方式要比xpath更加简单直观, 它的适用性也早已在大量的实践中得到了证实. 基于css选择符实现behaviour机制是一种更加可行的方案. 参见 http://prototype.conio.net/

posted @ 2006-01-08 23:21 canonical 阅读(608) | 评论 (0)编辑 收藏

    在软件设计中分层应该是越少越好, 过度分解一般都是有害的. 虽然说复杂的事物分解之后一般可以得到一些较简单的组成成分, 但这并不是必然有用的. 分析学成功的关键在于分解之后的组分能够出现大量重叠的情况, 参见软件中的分析学 http://canonical.blogdriver.com/canonical/555330.html
    当分解到一定程度之后我们未必能够发现可以重用的部分. 而且即使分解后系统中所有的基元都是简单的, 也并不意味着整个系统就是简单的. 生物遗传密码由四种碱基构成, 但是我们理解了ATGC决不意味着我们理解了生命. 在理论上存在一种连接主义, 认为真正的复杂性蕴含在元素之间的关系之中而不在于元素自身的复杂性. 例如神经网络的研究中可调的参数多半是神经元之间的连接权重,而不是神经元本身的模型参数.
    在理想中, 我们希望系统的功能划分能够泾渭分明, 一个类负责一个独立的功能实现或者一个功能侧面(aspect). 但是这只是一种乌托邦式的理想. 在物理的世界中, 我们未必能够为每一个我们思维中独立的概念找到一个稳定的物质载体. 就像是水中的漩涡, 看上去它也有固定的形状, 一定的稳定存在时间, 但是你无法说是哪些水分子参与了漩涡的构成, 实际上波的传播掠过了整个水面. 同样, 在软件中功能的归属和聚合很多时候并不是那么稳定的.

posted @ 2005-12-29 23:58 canonical 阅读(882) | 评论 (0)编辑 收藏

    敏捷(Agile)开发的灵魂是演化(evolution),其具体的过程表现为迭代(iteration),迭代的每一步就是重构 (refactor),而单元测试(unit test)与持续集成(continuous integration)模拟了程序生存的环境(约束),是merciless refactoring的技术保障。从数学上我们知道迭代总有个收敛问题。一些重型方法将变化(无论是正方向还是反方向的)等价于风险,而倾向于消除开发 中的不确定性,其中的迭代是趋于迅速收敛的。敏捷的迭代是开放式的,强调拥抱变化。敏捷编程排斥过度设计,除了过度设计会增加成本之外,另一个原因就是过 度设计会阻碍重构,阻碍变化。敏捷的目标不是僵化的稳定性而是灵活的适应性。当然敏捷迭代本身并不能保证系统持久的适应性,即使是自然界中的迭代和演化, 失败的案例也是比比皆是。大量的生物物种在经历了历史的辉煌之后最终仍然难免被岁月所埋葬。
    在哲学上,一个悖论式说法是有存在于无中,或者说简单才能更复杂。杯子是空的,所以能包容万物。现在什么都没做,将来才能根据需要决定如何去做。所谓鱼与 熊掌不可兼得,一旦做出了选择,可能意味着必须放弃将来进行其他选择的机会。简单的目的不仅仅是为了最快的完成当前的任务,而且要为将来保留变化的可能。 过分强调目的性,我想是违背了演化的本质。高手过招,最忌把招数用老。我们所要做的是尽量推迟决定的时刻,并切实的保证自己随时拥有选择的权利。
    多样性是在演化中生存的关键。但多样性不是后天的。生物学的实验证实,物种的变异并不是环境变化后发生的,而是始终存在着并隐藏着,环境仅仅起了检选和倍 增的作用。适应性的系统总要允许一定的灰色地带,有时do something for nothing.

posted @ 2005-12-28 23:15 canonical 阅读(975) | 评论 (0)编辑 收藏

     Agile批评过度设计(over-engineering)的声音很大,但对于设计不足(under-engineering)同样是持坚决的否定态度 的。修改过度设计的应用比修改设计不足的程序要容易的多。因为简化的途径是明确的,而走向复杂的途径却往往是难以控制的。Refactoring To Patterns试图引入一些经验,但这些可预见的调整多半只在细节处,其影响是局部的。一个复杂性低层次的设计要支持一个复杂性高的应用,所需的代码量 不是线性的堆砌,而是几何级数式的增长,重构的时候需要做出的改变往往也是影响全局的。而事实上,设计不足是比过度设计更加常见的情况。真实的情况也许 是,在真正需要我们做出创造性设计的地方我们因为无知和无能而设计不足,而在那些渴求简单的地方,我们却自诩为先知而加上很多华丽的设计来维护虚幻的可扩 展性。这里的度是很难把握的。高段位的棋手可以比低段位的棋手预见到更多的步数,而一个优秀的软件架构师也需要比普通的程序员更早的预见到系统发展的障 碍。在我们明确可预见的范围内,当然是要把所有的设计做好,而在我们思维的边界处,"行"就变得比"思"重要了。
    大谈"over-engineering"的主多半都有着丰富的过度设计的经验,千万不要把他们回顾时的话语当成是普遍的真理。所谓大巧若拙,精炼的小诗 可比长篇大论难写的多了。有时采用一种简单的处理方式,是因为我们感觉到它不会成为障碍,虽然此时并没有明确的设计过程。你必须有能力进行过度设计,才能 真正理解简单设计的精妙之处

posted @ 2005-12-28 23:11 canonical 阅读(1537) | 评论 (3)编辑 收藏

  循环结构是imperative language的重要组成部分,一般也是程序中比较难以理解的部分。特别是没有软件技术背景的朋友,明显对于循环的理解力较弱。在Von  Neumann体系结构中,赋值语句是必须的,因而引出了存储概念,也引入了时间概念,因为我们可以区分出赋值前和赋值后的时刻。引入时间之后,本质性的 影响是程序串行化,而强迫我们从并行思考转入串行处理。很多时候这是一种不自然的情况,在我们的自然思维中,我们看到的或想到的也许只是一组静态结构,但 在程序中表达的时候却往往不可避免的需要引入一个动态过程。而我们控制动态结构的能力总是不足的。最近对于函数式语言及处理风格的越来越强烈的要求可能也 从侧面反映了大家对这种结构失配的不满。
   但是串行思维毫无疑问也是我们正常思维模式的一部分(当然这种思维模式在多大程度上是因为Von Neumann 体系造成的,也是个很有趣的问题)。例如在页面渲染的时候,我们可能希望预先把所有用到的数据都转载到内存中,赋予不同的变量名,然后在页面模板中我们只 要知道如何把这些数据变量表现出来就可以了。先做完A再做B,这是分层的思想,也是典型的串行思维。而基于数据进行处理,也是Von Nenuman体系的基本思想。但是如果处处要求预先计算并赋值,往往增加了很多额外的步骤(glue code),并且增大了对内存(计算空间)的需求。分层之后,还存在着一个各个层次之间结构匹配的维护问题。
   面向对象在结构表达方面是一种 巨大的进步。经过多年的发展,我们在表达静态结构关系方面已经是驾轻就熟了。通过属性关联,我们可以沿着对象图进行结构遍历。如果使用成员函数,在这种遍 历过程中还可以包容更多的动态特性。而在数据持久化方面,ORM的盛行也在一定程度上证明了对象图的有效性。使用对象图可以大大降低对赋值语句的需求,减 轻了明确建模的压力(每一次赋值都要求着一个明确的变量名,一个概念),也缓解了Von Neuman体系结构的束缚。例如,我们不再需要
 var user = loadUser(userId);
  var userOrgnization = loadOrgnization(user.orgId);
  var userOrgnizationName = userOrgnization.name
而是直接使用  user.orgnization.name

    目前面向对象所表达的大多数结构还是基于数据语义的,而我们对于函数等高阶结构的控制能力仍然较弱。设计模式在这方面提供了一些经验,但还是远远不够的。 在我们经验不多的时候,我们需要依赖于明确的实体数据,而在我们的理解逐步深入之后我们就可以通过Visitor, Iterator等模式支撑起整个结构。高阶结构比低阶结构难以控制,一方面是因为动态性本身比静态性难以理解,另一方面函数对信息的使用和流动是一种主 动约束,如果约束的不正确,会造成结构的失效。而数据的使用是完全开放的,很多决定都可以延迟到使用时刻决定。当然,开放性带来的问题也早就众所周知了: 不受限制的使用将导致无法控制的困境。在基础的数据层封装方面,一般我并不提倡大量使用domain model似的具有丰富语义的数据对象。因为数据是共享的,应该存在一个开放的数据层,在其上可以建立业务对象。混杂在一起会限制系统的演化。

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

   单元测试随着agile的流行已经家喻户晓了,这正反映了软件的一个本质特征:软件是Man-Made的,而人是不可靠的。软件出错的高频率必然导致控制 间隔的缩短。我最早是在编写matlab程序的时候独立的发现了单元测试的作用。因为matlab是弱类型的,横纵矢量也不区分,很容易犯错误,我就为每 一个matlab函数编写了测试脚本,约定了命名规范为xxx_test.m, 在测试的时候通过 can_assert来做出判断而不是输出变量内容进行人工检查。 每次作了修改的时候,运行一下can_test_all.m就自动搜索测试脚本并运行一遍。 后来xp出现了, 我也第一次听说了单元测试这回事,看来英雄所见略同吧 ^_^
   单元测试可以看作是对编译器的补充。编译器只能进行语法检查(形式),而单元测试可以进行语义检查(内容),它其实维护了程序的一个语义框架。如果单元测 试程序编写出来了,即使一行业务实现代码也没编写,我们手中也已经拥有了一笔宝贵的财富,因为程序的语义已经在某种意义下确定下来了。重构一般是在维护单 元测试不变的情况下进行的。即我们发现在维护语义不变量的情况下,系统具有相当的调整余地。
   坚持单元测试的另一个好处是它倾向于良好分离的模块,因为高内聚低耦合的模块才更容易进行测试。为了测试,我们会在不知不觉中在对象职责的分离上投注更多 的心力。在witrix平台中,虽然基于EasyMock提供了mock支持,在实际中却很少使用,因为模块功能一般很独立,不需要复杂的mock对象, 而对于一般的对象,eclipse有代码生成功能,可以非常轻易的生成简单的测试对象。
   虽然JUnit非常流行,我们的单元测试也是基于JUnit进行的,但是我们还是进行了一个简单的封装,将JUnit框架的特定要求与具体测试代码剥离开来。具体的,测试类从
   test.UnitTest继承,而不是从JUnit的TestCase继承。使用Debug.check()来做判断,而不是JUnit的assertEquals等。
   class MyTest extends UnitTest{
      public MyTest(String caseName){ super(caseName); }

      public void testMy(){
          MyObject my = new MyObject();
          Object value = myObject.myFunc();
          Debug.check(value.equals("aa"));
          // 可以同时提供一个出错消息
          Debug.check(value.equals("aa"),"myFunc return invalid : "+value);
      }

      public static void main(String[] args){
        // 不需要IDE或者其他外部的支持就可以直接调用测试代码,将会自动输出运行时间等
        UnitTest.exec(new MyTest("testMy"));
      }
   }

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

   关系数据库提供的是集合存储模型, query(fields, condition) ==> list of records, 可以从条件集合映射到记录集合。
当condition退化为单一的key, 而fields采用默认值的时候,我们就退化到Map语义, 从key对象映射到value对象,而不是从集合映射到集合。
很 多时候我们只需要这种简单Map语义的存储模型,例如用户偏好设置的存储。在这种受限的模型下我们也可以更直接的实现cache支持。如果我们希望在 Map的基础上稍微扩展一些集合操作的特性,可以通过key的结构扩展来实现。即规定key采用类似url格式的字符串,实现key空间的树形结构。在 witrix平台中,这种树形结构的映射关系通过IVarValueSet接口来实现。
 interface IVarValueSet{
     IVariant getVar(String name);

     // 得到前缀为prefix的所有变量构成的子集合,注意这里自然退化的特点
     IVarValueSet getSubSet(String prefix);
  }
变量名的格式规定为 a.b.c 或者/a/b/c. 这种变量结构的组织和划分方式其实与JBoss项目中的TreeCache结构类似。

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

    级列设计理论中我们谈到一般和特殊的关系, 但这是否指的是“相对抽象” 以及 “相对具体”之间的关系, 而“一般”到“特殊”和“特殊”到“一般”这两个方向是否指的是具化过程和抽象泛化过程? 我猜测有这种想法的人大概是受到软件设计中所谓抽象封装思想的影响. 很显然, 我并不是这样认为的. 一般性(普遍性)与抽象性是不同的概念. 在物理学中相对论是比Newton力学更加一般性的理论,但它和Newton力学一样都是关于我们这个世界的真实的理论,都是非常具体的。虽然我们有的时 候会说相对论更加抽象一些,这不过是暗示这个理论所描述的情形与我们的日常经验距离遥远而已,并不意味着它是某种只存在于概念空间的东西。实际上我很少谈 到抽象与泛化过程,这对于物理学而言并不是一个合适的命题.

    有些人认为"service层, data object层,  dao层只是对程序职责的描述并不是实现,在实现中应该根据实际情况进行合并与取舍"。在我看来, 持有这种看法的人已经把自己的思想限定在了某一个复杂性层次上, 认为这些职责是天然的,必然的存在于程序中的. 但实际上, 我们肯定可以想见更加复杂的情形, 仅仅三层并不足以充分表达程序的结构, 而另一方面, 在极端简单的情形下, 例如只有一个数据库,只有CRUD操作, 此时根本就不存在这种职责. 一种所谓的职责从来就不曾存在过,我们自然也不应先把东西搞复杂起来,再合并取舍回去。

posted @ 2005-12-27 22:21 canonical 阅读(997) | 评论 (3)编辑 收藏

    级列理论是分析学中常见的一个思维框架,我只是把它从我最熟悉的物理学中借用到软件设计领域而已, 它本身并不是我所创造的一种概念(创造是艰难的)。在某些领域,这一分析框架可以表现出完美的数学特性,如时频分析领域的小波分析(wavelet), 统计学习理论中的支持向量机(Support Vector Machine), 分子动力学中的BBGKY级列等等。在其他一些领域它的精确性可能要弱很多,但其思想内核是一致的.
    
    级列理论的基本内容如下:
    http://canonical.blogdriver.com/canonical/562888.html

    首先, 级列理论需要定义一个最一般的普遍模型, 但这并不意味着对系统的一种过分的限制. 实际上, 我们所讨论的任何问题都有一个总体的框架限制,  它的作用只在于揭示出我们所研究的问题的基本要素并勾勒出一幅全景式的图像. 最一般的情况与我们的求解能力的距离可以是非常遥远的,例如真正实作的时候在物理中往往我们只是研究一阶或者二阶情况。而在计算机领域也是存在着最一般的 模型的, 那就是Turing Machine. 在一般情况下, 我们是能够建立一个足够普遍的模型以囊括绝大多数变化可能的, 虽然我们极有可能没有能力去求解这个模型.
    对于系统的完备性, 物理学与数学的态度是有着深刻区别的. 在物理学中我们只需要在研究问题的范畴内维持概念的稳定性即可, 这往往意味很多简化与隐含条件,并不是一种终极的完备性. 而在另一方面, 数学的完备性并不能保证它描述现实世界的完备性. 最明显的, 从经典力学到狭义相对论, 再到广义相对论, 在每一个层次上都是存在着非常完美的数学描述的, 其数学空间都是完备的, 但是很显然, 即使是广义相对论, 它也不是对于物理世界的完备描述. 目前物理学界仍然在量子引力的黑暗中摸索. 所有这一切都不影响我们在建筑工程中以足够高的精度应用力学原理.

    级列理论所描述的绝不是一种从一般到特殊的思想的应用, 而是同时包含着从一般到特殊和从特殊到一般两个方向. 在级列理论中, 我们首先研究的一般是最特殊的情况, 即最简单, 对称性最高的情况. 此时模型中特征元素个数很少, 而且界限分明, 很少交互或者发生关联. 我们得到简单模型的解之后, 就可以将其作为初始解去求解更高阶的模型. 这是从特殊到一般的一种进展. 而在另一方面, 我们可能以创造性的方式得到某个更高阶模型的解, 此时我们需要研究高阶模型与较低阶模型之间的关联, 考察当更高阶的模型退化到低阶模型的时候, 它们的解是如何自然的实现退化的.  虽然迭代这个名词在当前软件工程领域如日中天, 但我想很多开发人员却从未花上一刻时间去真正的体味这个概念本身的内涵, 而陷入了人云亦云的窘境. 从数学上说, 迭代过程的基本问题是收敛问题, 而且往往收敛过程中的控制策略要远比迭代初始值的选择更重要. 那么什么样的控制策略才是保持探索性但又倾向于收敛的呢? 连续性是最基本的要求. 参见 反问题的级列求解 http://canonical.blogdriver.com/canonical/974280.html.

    级列理论最关键的部分其实是两个存在性: 一是复杂性级列的客观存在, 二是级列之间连续性的客观存在. 注意到级列理论所描述的只是存在性,在一般情况下, 我们并不能直接得到从低阶解推导出高阶解的构造方法。而复杂性的级列的存在意味着总存在超越我们当前认知范围的信息, 从低复杂性层次上升到高复杂性层次可能是需要非凡的创造力的, 决不是显然的。此时我们唯一的选择只能是从后验的角度去检验这种存在性。模型的可退化性正是连续性的一种自然推论。这在物理学中是一件不言而喻的事情也是 一种强制性的要求:狭义相对论可以在低速情况下退化为Newton力学,而广义相对论可以在引力均匀的情况下退化为狭义相对论。在软件领域, 似乎只是到了近几年, 可退化性才得到了一些重视,至今很多人对它的思想实质仍然没有充分的了解。退化是否是指系统中不同职责部分的合并但是这些职责依然存在? NO, NO, NO. 精简机构的原因难道是为了裁减人手,增大人均工作量吗? 精简的原因首先在于人浮于事, 本身就没有那么多的职责, 却要生造出那么多的处理步骤来. 我们或者合并部门,或者干脆撤销部门,一些专门因为内部协调而生的部分更是要被毫不留情的裁减掉。在常见的软件开发中, 明明是非常简单的数据库访问操作, 偏偏要在service层, data object层, dao层都把同样的接口代码重复一遍, 美其名曰多层体系架构, 可以保持系统的灵活性. 可是谁需要这种灵活性, 它到底是设计的灵活性还是设计的脆弱性? 在简单的情况下, 选择meta driven的方案,从描述文件直接驱动多个层次的运作往往能够极大的提高系统的稳定性和灵活性.

    假设现在有一个网络应用, 我们首先考虑最简单的情况, 例如我们假设只存在一种通信协议:打电话. 我们可以通过电话通知另一方的接线员, 让他把信息记录下来再录入系统当中,以此维持系统的运转. 如果永远都只有一个固定的接线员X通过唯一的一部电话M来完成通信过程, 则系统中的多个概念就会发生简并: 通信将等价于打电话,等价于打电话给X, 等价于使用电话机M打电话给接线员X. 随着系统的复杂性逐渐增大, 系统的对称性出现破缺, 原本被认为同一的概念出现了微妙的不同, 并可能演变成差异巨大的两个分支. 假设现在多了另外一种通信协议:硬盘交换. 我们可以把数据拷贝到一块硬盘上, 然后携带到远处的机房, 在那里把数据导入系统, 以维持系统的运转. 如果我们的系统演化到了这个阶段是否意味着我们原先的软件模型已经彻底崩溃了? 并不是如此的, 在一个宏观的粒度上, 我们的系统所需要的可能只是一种端到端的通信手段, 只是现在通信这一更高层的抽象概念不再等同于更加细节化的具体通信手段(打电话)了. 如果原先系统设计中的各个主要模块之间只存在高层抽象之间的依赖, 则只需要增加一些数据导入导出模块, 我们的系统就可以平滑(连续)的接纳一种新的通信协议. 一种宏观的高层视图如果直接映射到某种实现, 则它所对应的就是一种最简单的系统模型. 系统的不断发展相当于是给这个最简单的模型不断补充细节, 使它不断的复杂化. 这种变化是有脉络可寻的, 很多时候是局部的, 轻微的. 当然, 持续累积下去, 也许有一天我们会突然发现系统已经面目全非了, 甚至原先的高层视图(简单模型)也无法维持了. 但是即使这样, 是否意味着我们原先的设计失败了呢? 答案仍然是否定的. 软件系统如同其他系统一样, 处在不断的演化状态当中, 但是什么叫做系统的演化? 很多人有一种错觉, 以为软件结构完全不变, 完全不需要源代码级别的修改, 只需要通过外部配置导入扩展对象的设计才是好设计, 才是成功的可扩展设计. 这是以一种静态的僵化态度来看待程序的演化, 没有理解演化的实质.  演化(evolution)首先是一种变化(variation). 按照级列设计理论, 当系统沿着复杂性的级列发生演化的时候, 因为不同复杂性层次之间存在着本质性差异, 我们不能奢望现在的简单设计能够一直包容越来越复杂的模型, 更加现实的态度是能否在未来的复杂的模型中为现在的简单设计找到位置. 我们不预言未来, 也无法保证我们现在的行为能够容纳将来的选择. 在适度重构的意义下实现部分重用已经是我们能够做到的最好程度了. 我们需要理解世界本身是在变迁的, 即使现在我们能够预测到未来, 现在就为其作准备也未必是适当的, 因为现在的最优不等价于将来的最优, 同样将来的最优也不等价于现在的最优.     在上文中的网络应用的例子中, 也许系统最终发展成为一个非常庞大的系统, 而我们最初设计的功能成为了该系统的一个子模块, 这不是完美的可扩展性吗?   http://canonical.blogdriver.com/canonical/1002861.html


    级列理论的思想是非常通俗的, 并没有丝毫神秘的地方. 复杂性的级列可以在空间中呈现出一种复杂性递增的形态,也可以沿着时间轴展开,构成发展的形态。在传统设计理论中也在频繁的处理着这些问题, 因而可以看到很多相近的思想, 但是我们也需要注意到一些细节性的不同.  传统的设计中也讲层次,但是多半说的是stratify而不是hierachical,是同一时刻可以看到的一种层次堆垒关系,而不是在不同复杂性层次上 才揭示出的级列关系. 传统设计中也讲高级抽象与低级抽象之间的关系,但没有提供级列理论中完整的视图, 也很少强调每个层次上元素之间的连续性。

    关于级列设计理论在软件设计中的适用性, 我只想提一下witrix平台中的jsplet开发框架,  参见
       从级列理论看MVC架构 http://canonical.blogdriver.com/canonical/579747.html
       jsplet:对Model 2模式的批判 http://canonical.blogdriver.com/canonical/591479.html
    在2002年左右, 我是先得到级列设计理论, 然后才根据其思想设计的jsplet框架. 在整个witrix平台的设计中, 级列设计理论也是重要的指导思想. 不过, 以我的经验来看, 一般人是很难理解他人的思想的, 即使是对别人的想法有些兴趣, 实际思考的东西与作者的原意往往也有着很大的差异.  http://canonical.blogdriver.com/canonical/1014773.html
    
    最后我还是强调一下一件最最基本的事情, 一件每个人都应该时刻牢记的事情: 不要孤立的看待问题, 而是寻求一种概念之间的连续性.  http://canonical.blogdriver.com/canonical/1016684.html

posted @ 2005-12-27 01:27 canonical 阅读(986) | 评论 (1)编辑 收藏

    近代数学和物理学的发端是从微积分的发现开始的,人类第一次系统化的将连续性的思想推向了极限,也开创了崭新的思维方式。记得高中自学微积分的时候我也花 了很多时间去思考连续性的问题,但是后来渐渐习以为常了。研究生的时候重新开始考虑连续性的问题,只是关注的方向是概念体系中的连续性。
    all or nothing是我们的思维中经常出现的一个误区。很多时候我们的讨论局域在一个封闭的既定的体系中,最后的结论也是在原地转圈圈。就像是“人性本善”与 "人性本恶"的争论一样,数千年无所结论。也许我们真正的问题应该是猪有善恶吗,猩猩有善恶吗,何谓小善,何谓大恶?
    有些时候,某些简单的概念被其复杂的外表所遮蔽。例如提起网格(grid), 很多人的第一印象大概是高深,是一个宏大的体系架构。但是我说如果现在只允许你写下1000行代码,你打算为网格写些什么。我们在网格出现之前所作的工作 与网格之间有什么关系,将所有的概念逐个从网格这个体系中剥离出去之后,最后会剩下些什么。
    我们需要对概念有一个连续性的理解,而不是将它们作为一个不可破的黑箱来看待。这是分析学的基本态度。

posted @ 2005-12-27 01:25 canonical 阅读(779) | 评论 (0)编辑 收藏

    敏捷思想的流行使得很多人对可扩展设计产生了一种怀疑的态度。这有几方面的原因,一方面是J2EE平台本身提供的分布式机制等技术因素很容易诱导你定义不 必要的扩展需求,第二是基于目前的技术手段对于程序结构的分解仍然有着很大限制,具体的程序实现中往往会引入某种强制依赖,削弱了潜在的可扩展性,第三则 是设计者本身对于技术和业务的把握不够深入,在考虑设计的可扩展性时经常做出错误的判断。但是一个只满足当前需求的系统一般不是个好系统,也很难在多次迭 代生命周期后继续生存。XP(extreme programming)强调简单化,其实质在于简单的东西可以在未来被重构(refactor),从而适应未知的变化,它本身并不排斥可扩展设计。
    从基本的常识出发,我们都知道现在应该为将来做些事情,准备些资本。可扩展设计的价值观不应是现在解决将来的问题,而是寻求未来发展之后现在的解是否仍然 部分有效,是否仍然可以部分被继承。即我们考虑的不是将未来的解纳入到现在的体系中,而是考虑现在的解在未来的体系中的位置。不是在现在如何支持我们所预 想到的几种未来的扩展方式,而是无论未来如何变化,怎样才能保证现在工作的有效性。这里所关注的重点是现在而不是将来!面对演化我们所能采取的最好的策略 就是尽量有所积累,尽量不放弃我们的过去,而不是把宝押在对未来的准确预测上。一个厚重的设计往往在后期会因为预料的太多反而在遭遇未预料到的变化时不知 所措,结果造成系统整体架构的失效,必须做更多的工作打补丁来使得它勉强工作。象EJB这样distribution ready的技术现在已经公认有过度设计之嫌,因为这些已经ready的特性一般并不会被应用但是我们却不得不为这些无用的特性付出代价。
    
    可扩展设计所依赖的基本原则之一是IoC(Inversion of Control)。IoC是目前轻量级容器(lightweight container)的核心设计思想,但其实它的应用远不止在轻量级容器这一领域。基于IoC设计,大量的知识(依赖)被剥离出业务对象本身,对象对于其 生存环境和应用场景的假设大大减弱,而我们的期望正在于无论未来的应用环境如何变化,只要提供必要的知识,业务对象就能工作。可以说,IoC是可扩展性的 一种基本要求。
   
    可扩展设计所依赖的另一个原则是连续性(continuous), 这可比IoC要复杂和深刻的多了。如果说现代设计的核心观念是演化(evolution), 那么在我们的思想中演化到底有着什么样的图景? 至少需要一个方向加上一条连续的途径,evolution才能发生。在级列设计中,一个简单的系统架构需要能够scale up,而一个复杂层次上的系统架构也需要能够以优雅的方式scale down。这种变化是自然的因为它们是连续的。

posted @ 2005-12-27 01:24 canonical 阅读(980) | 评论 (2)编辑 收藏

    实际观测到的结果是系统内在结构的外在表现,而软件开发是从需求分析开始,经历系统分析,设计并实现的过程,即从用户需求逆推出软件的结构。这种根据外在 表现求解内部结构的模型的过程,在数学上称为反问题(inverse problem)。关于反问题,一个众所周知的难点在于解的不适定性。因为不同的结构可以有类似的外在表现,因而反问题的解是不稳定的。在一个既定的情况 下,我们按照某种粗略的外在度量标准,从反问题的众多近似解中选择了一个。但是当所需的外在表现发生微小变化后,我们第一次选择出来的结构可能无法适应这 一微扰,而我们再次求解出来的结构可能与原先的结构有着巨大的差别。因而原先选择的解在结构上是不稳定的。在数学上,我们称之为奇异解(singular solution)。在数学上,在求解反问题的时候为了避免选择到奇异解,经常采用的技术手段就是类似于级列理论的所谓镇定方法。即我们提出一系列的模 型,对它们进行一维参数化。当参数较大时相当于对原有模型的一种近似,原有模型的细节被淹没在正定泛函的大范围结构中,整体呈现出一种简单的结构,而当参 数越来越小时,原有模型的细节被逐渐识别出来,整体模型逐渐复杂化,最终参数为0时恢复到原始情况。常见的模拟退火算法(simulated annealing)就属于这一策略族。通过模型的连续性,我们建立了一个复杂模型与一个简单模型(因而物理意义明确)之间的一条连续的纽带,沿着这条可 退化的途径,我们才有可能回避奇异解,保证复杂模型的物理有效性。
    在软件设计中,我所提出的级列设计思想正是这样一种渐进演化的设计思想。我们极力维护模型的可退化性,保证复杂的模型不至于锁定在错误的角落中。而基于模型的连续性,我们对于未来的发展进行外推才有了一定的根据。

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

    架构的可退化性(degragation)指的是架构的结构可以从元素比较丰富,层次比较多,比较复杂的情况退化到比较简单的情况, 而架构的无侵入性(non-invasive)指的是架构对于外部接入对象没有特殊的形式要求, 一般通过依赖注入(dependency injection)向接入的外部对象推送信息. 这两个概念之间存在着紧密的关联, 但并不等同. 无侵入性可以看作是架构的一种局部可退化性, 例如一个业务对象在正常工作的时候需要是完整的EJB对象形态, 而在编写的时候退化到普通的java对象(POJO). 架构的可退化性是一个比无侵入性更加广泛的概念:一个架构对外可以是无侵入性的, 但是它的实现本身可能相当复杂, 是不能退化的. 例如在一般的web表现层设计中, 很多人都试图提供一个RPC层, 将Web请求解析后映射为对java对象方法的调用. 通过一系列的描述文件, java对象本身可以完全不知道web层的存在, 因而这种设计在某种程度上可以看作是无侵入性的. 但是假如现在出现了性能问题,或者RPC层本身出现一些bug, 或者我们需要一些RPC层很难有效实现的映射规则, web层设计应该允许我们越过RPC层, 很方便的直接处理request和response, 这意味着在我们的架构设计中需要把边界划在web接口上(需要在这里定义基本的交互规范),而不仅仅是对象接口上.如果一个架构设计强制规定了一个不可越 过的RPC层, 则意味着该架构在这一点上是不可退化的.

    架构的可退化性是级列设计理论的一个自然推论, 它是对架构整体的要求, 需要同时考虑到架构本身实现的复杂性以及与外部接口的复杂性, 而不是仅仅考虑到对外部接入对象的复杂性的要求. 整个架构需要能够沿着复杂性级列scale down, 而不仅仅是scale up!

posted @ 2005-12-22 22:55 canonical 阅读(1192) | 评论 (4)编辑 收藏

    系统架构通俗的说起来就是系统的结构组织方式.原则上说, 架构只有好坏之分,而不存在有无的问题. 软件的体系架构可以直接体现为代码的类结构, 也可以表现为文档性的编码规范和全局约定等. 如果软件架构中能够抽象出一些稳定的元素, 那我们就可能得到一些所谓的框架代码. 一般业务架构是很难重用的, 目前常见的框架代码所描述的多半是与业务无关的技术架构.
    
    良好的系统架构应该体现出应用本身的结构要求. 所谓各个为自己, 架构为大家. 只要各个局部符合规格, 应该由架构负责在合适的时刻按照合适的方式把它们组装在一些. 一个良好的架构中, 应该很少出现结构性的if语句, 不需要应用代码自己通过动态判断来定义某个特殊的触发时刻. 架构是一种规范, 当然也就是一种局限. 架构的可退化性是非常重要的, 否则一旦出现抽象泄露, 需要超出原有架构设计做出编码补充的时候, 往往无法将代码自然的融入原有的框架结构, 则整个框架出现大面积的失效情况. 而有的时候更糟糕的情况是一些关键性的资源处在原有技术架构的私有控制之中, 我们为了克服架构限制不得不采用各种trick来hack原有框架, 造成错误的累加和传播, 而补丁的补丁是最难维护的.

    架构问题并不是一成不变的. 在一些情形下无关紧要的问题在另一种情形下可能会成为灾难性的架构问题. 例如在多层B/S架构下, 如果现在要求为每一个表增加一个对应的历史表, 并对其进行查看和维护操作. 为了最大限度的重用代码, 这要求我们的多层结构中的每一层都能够参数化, 这样我们才能用同样的代码处理不同的数据表. 如果我们的money很足, 小弟够多, 有足够的人月砸上去, 那么我们完全可以把业务表和历史表分开处理, 但如果反之,我们就会遇到一个典型的架构问题.

    架构师未必有自己的框架, 因为设计不等价于创造, 架构师只要知道如何把系统中的各种元素按照可行的方式组装在一起就可以了. 但是一个架构设计是非常依赖于我们所能采用的技术手段的, 当现有各种可用的技术元素都无法满足我们的需求的时候, 某些架构师可能会选择创造一种技术元素. 当然, 创造是艰难的, 它所要求的甚至是不同的技能. Sun的Green项目创造了java语言, 从而开启了一个伟大的时代, 这绝对不会是大多数架构设计师的选择(有趣的是,Green项目本身失败了). EJB现在还有多少人在真正使用, 想想当年多少架构师在吹嘘这些东西. 他们对于技术的把握真的就那么幼稚吗? 架构设计并不是凭空出现的, 当时可选的东西就是如此, 而spring和hibernate这些都不属于架构设计本身的内容.它们是一种创造.
    
    架构师未必是团队的领导者. 确实,他的工作类似于编剧, 负责执行的一般是导演. 事实上,一个建筑设计师是极少直接领导一个工程队的.架构师也未必比高级程序员要高明, 他们负责的是不同的内容. 至于产品的"商标及商标的相关元素"和"技术市场架构"等也不属于架构师的工作范畴, 他不能去抢产品经理的饭碗. 当然,在国内的现实情况下, 很多所谓的架构师所做的最重要的工作可能是公关工作, 向客户秀出所谓的理念, 与实际开发是不搭嘎的.     
    
    理论上说, 架构师可以不是编程的强者, 也可以不决定一些具体数据结构的选择, 但他不能不了解各种技术抉择潜在的影响. 这就如同一个建筑设计师可以不精通工程力学,但是他不能愚蠢到藐视重力, 设计出倒三角式的大厦. 与建筑不同的是, 在软件中我们所面临的不是一种"凝固的艺术", 我们无法以完全静态的方式理解代码,而必须在头脑中把它们运行起来. 架构师应该写下一些实际的代码, 以检验各个接口的可配合性并获得对于代码结构的直接感觉. 实际上, 按照现在软件业的成熟度, 一般我们无法实现建筑中建筑设计师与土木工程师的分工, 很多时候软件架构师都需要直接面对实现的细节. 如果组内缺乏非常强悍的coder, 有编程能力的架构师亲自操刀实现关键性代码的时候也是很多的.

    架构师必须有经验, 但他所依赖的不能只是经验. 只要算一算架构师的年纪, 就会知道以他们在这个世界上的存在时间, 并不足以使得他们经历各种技术细节. 架构设计更多的是依赖我们对于系统结构原理的理解, 而经验可以让我们规避那些原理失效的地方(例如系统级bug). 君子非异能也, 善假于物也. 很多时候,我们更应该从有经验的朋友或者技术支持那里搜集技术细节, 以确保它们能够满足我们在架构上的原理性需求. Know Why而不仅仅是Know How是非常重要的. 一个农民发明家也许可以得到某个巧妙的机械设计, 但是没有系统的掌握工程力学, 他们是无法去开发精密的导弹控制系统的.当然, 软件开发还处在非常原始的阶段, 掌握一些设计原理和设计模式多半也不过是五十步笑百步而已, 经验的地位是无可替代的.

    架构师不是预言家. 在多变的业务环境中, 架构师的目标不应该是预测到所有的变化可能, 并把它们表达到系统架构中. 这个世界上不乏一些耗资数十亿,设计三四年,但最终每个谈到它的人都要说一句shit的产品开发项目. 架构设计所能做到的最好的程度是自然的标注出系统的结构边界,成功的delay各种技术抉择.

    架构师不是超人, 他所考虑的东西也许要远一些, 所需要平衡的利益也许要多一些, 但是单独一个人是无法对整个产品或者项目的成败负责的. 如果ThoughtWorks的Martin Follower来处理国内的某些项目, 我估计他会死得很难看.架构师也是人, 也会犯错误,甚至是很低级的错误, 而每个人都会有一些独特的想法. 经历的多了, 你就会回归到终极的认识, 一切都只是浮云, 只有money才是硬道理.

posted @ 2005-12-18 17:35 canonical 阅读(3731) | 评论 (8)编辑 收藏

    现在MDA建模的宣传多集中于可视化的表现形式, 鼓吹通过平面图标的摆放来传达信息. 图形的方式是否一定比文本表现要优越呢?  图形的表现能力确实是要强于普通文本的.文本对于信息的组织方式基本上是一维的, 而平面图形本质上是二维的(如果考虑颜色因素, 平面图形可以说是2.5维的). 人的视觉对于图形有着天然的并行处理能力, 通过图形我们有可能更有效的获得信息. 但是程序中细节的关联可能是复杂的, 多维的, 二维图形同样难以直接描述这些关联, 而一维的文本对于所有维度的描述是对称的, 可能在描述多维关系时更加容易维持简单性和一致性. 当描述复杂的关联时, 我们不可避免的需要采取多层次封装的方式, 在图形界面上我们可能要通过多次点击才能到达某一细节层面, 这样反而不如纯文本方式更加"并行化": 在纯文本方式下, 我们在一个文件中能够同时看到所有高层次和低层次的信息, 并能够通过查找和翻页沿着一个固定的维度迅速定位到所需要的章节处. 受限于人类视野的大小, 我们所看到的图形不能过大也不能过小, 这样在一定程度上也限制了图形方式所能够传递的信息量. 有的时候图形上标注了过多的关联反而使我们更加迷惑.  所谓可执行的UML要真的运行起来, 需要大量的信息隐蔽在图形表象之下, 在我看来这是一个没有什么实际意义的概念.

posted @ 2005-12-13 23:32 canonical 阅读(421) | 评论 (1)编辑 收藏

    据说"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 阅读(1361) | 评论 (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 阅读(501) | 评论 (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)编辑 收藏

     接口(interface)总对应于某种明确的概念,它并不简简单单的等价于其成员函数的集合。有的接口如java.io.Serializable甚至没有任何成员函数。接口最重要的就是名, 是对概念的甄别。接口发布出去之后才能够被实现。当我们使用某个接口的时候,即使我们只用到其中部分函数,我们也必须负担整个概念。虽说"有名,万物之母", 并不是任何时候我们都需要名的。我们会说,就要那个,蓝色的,这么高,... blabla, 对,就是这个(this)。模板(template)弱化了类型系统,它对系统的约束直接作用在细节行为上,降低了明确建模的需求,不需要概念的分解,合并,比接口更加灵活。但模板并不是任何时候都比接口更好。想象一下,我们拿着一张采购单,上面写着需要某个物品,前面有个尖,后面有个帽,细长形,大概这么长,这么粗,上面有螺纹,螺距这么大,。。。这是...三号螺钉?嗯,最近有一种新产品,塑料材质的,你要不要试试。
   模板与接口在某种程度上是互补的。

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

    naked的人现在已经无法在自然界中生存了, 我们需要依赖外在的衣服,房屋等才能维持基本的生存条件。曾几何时,那些曾经属于我们身体的一部分的功能已经逐渐被解离到外部对象中,只有思想似乎还要依 赖个人自身的能力。很多研究正努力把人的思维过程也外化了(思维导图MindMap就是一种很有趣的简易方式),也许有一天我们会走到离开工具就无法思 考,无法判断的境地

posted @ 2005-11-29 22:22 canonical 阅读(514) | 评论 (1)编辑 收藏

    tpl自定义标签的设计目标之一是尽量减少配置说明项. 在tpl标签库中, 标签定义格式如下
    <标签库名称>
        <自定义标签名 demandArgs="argA, argB"
            importVars="varA, varB"
            otherArgs="optionalArgA, optionalArgB" localScope="trueOrFalse" >

            自定义标签的内容, 可以是任何tpl代码
        </自定义标签名>

    </标签库名称>

    demandArgs中指定调用时必须给定的参数的名称列表, importVars指定从调用环境中导入的变量的名称列表,otherArgs指定可选参数的名称列表. demandArgs, importVars和otherArgs这三者的集合包含了所有该自定义标签能够接受的参数. tpl编译器会检查这些调用规则是否被满足. 在运行的时候, 未指定的可选参数会被初始化为null.

    在调用时明确的指定的变量值会覆盖importVars导入的变量值. 例如
    <c:set var="varA" value="a" />
    <MyLib:自定义标签名 /> // 根据importVars设定, 在此标签内varA的值为a
    <MyLib:自定义标签名 varA="b" /> // args设定会覆盖importVars导入的值,因此在标签内部 varA的值为b
    // 调用标签完成后, varA的值恢复为a

    tpl中的参数声明方式是非常简化的,但是它仍然保留了最关键的信息:变量名称. 而在弱类型的Expresison Language中, 变量类型本来就不重要. 与jsp tag中的标签声明作个对比.
     <tag>
        <name>template</name>
        <tagclass>edu.thu.web.tags.TemplateTag</tagclass>
        <bodycontent>JSP</bodycontent>
        <attribute>
            <name>src</name>
            <required>true</required>
            <rtexprvalue>true</rtexprvalue>
        </attribute>
    </tag>
    jsp tag这种标签声明方式非常冗长, 提供的有效信息密度很低, 而相对于tpl标签的声明方式所能够提供的附加信息也没有很大的意义. 这种设计上的问题也深深的影响到JSF等派生技术.

    localScope参数指定了此自定义标签是否具有局部变量环境, 如果为true(缺省值),  则调用此标签的时候会自动进行变量压栈处理, 在标签内部无法访问参数列表之外的变量, 运行中所产生的临时变量也不会影响到外部环境. tpl中的变量堆栈与webwork的ValueStack机制是有一些差异的. webwork2中的ognl语言在访问OgnlValueStack中的对象的时候缺省采用的是一种递归查找机制, 即在当前环境中找不到对象, 则自动查找上一层环境中的变量. tpl中的标签结构可以多重嵌套, 产生非常复杂的结构, 所以缺省情况下tpl标签采用了类似于函数堆栈的设计, 在子标签中的代码一般情况下是无法访问父标签环境中的变量的(除非指定了localScope参数为true). localScope支持与importVars机制相结合之后, 我们可以实现比OgnlValueStack更加灵活也更加稳健的变量访问策略.  

posted @ 2005-11-27 20:32 canonical 阅读(1107) | 评论 (0)编辑 收藏

   动态权限最简单的一个表现是时限性,subject只在某个时间段内具有某种权限。这只需要在user和role的映射中,或者role自身的属性中增加startTime和expireTime即可。

    更复杂的动态性一般与流程控制相关,此时权限控制应该由工作流系统完成,而不是在数据上增加越来越多的权限标记。在witrix平台中,使用tpl模板技术来定制权限设置。

posted @ 2005-11-26 10:49 canonical 阅读(284) | 评论 (0)编辑 收藏

权限管理中进行数据访问控制,其基本模式如下
    operation target = selector(resource)       
    selector = user selector + auth filter
这里需要对resource的结构,以及选择算子的显式建模。selector必须允许权限系统追加filter,例如
IDataSource包中所使用的Query对象。
       sql语言的表达能力有限, 作为选择算子来使用有时需要resource作一些结构上的调整,增加一些冗余的字段。例如表达一段时间内的利率,我们需要使用from_date和 to_date两个字段来进行描述,其中to_date的值与下一条记录的from_date相同。
     value     from_date         to_date
     0.01    2003-01-01        2003-05-01
     0.012   2003-05-01        2004-01-01  

如果表达一条航线中的多个阶段,我们可能会在每条记录中增加起始站和终点站两个字段。
      更重要的一个常见需求是树形结构在关系数据库中的表达。为了能够直接操纵一个分支下的所有记录,在层次固定的情况下,我们可能会增加多个分类字段,例如数 据仓库中的层次维度。在层次数目不确定的情况下,我们将不得不使用层次码或者类似于url的其他方案,通过layer_code like '01.01.%' 之类的语句实现分支选择。为了限制选择的深度,我们可能还需要layer_level字段。基于层次码和层次数,我们可以建立多种选择算子,例如包含所有 直接子节点,包含自身及所有父节点等等。

posted @ 2005-11-26 10:48 canonical 阅读(644) | 评论 (0)编辑 收藏

    权限控制中,subject可能不会简单的对应于userId, 而是包含一系列的security token或certificate, 例如用户登陆地址,登陆时间等。一般情况下,这些信息在权限系统中的使用都是很直接的,不会造成什么问题。
    subject域中最重要的结构是user和role的分离,可以在不存在user的情况下,为role指定权限。有人进一步定义了userGroup的 概念,可以为userGroup指定role,而user从其所属的group继承role的设置。一般情况下,我不提倡在权限系统中引入 userGroup的概念。这其中最重要的原因就是它会造成多条权限信息传递途径,从而产生一种路径依赖, 并可能出现信息冲突的情况。一般user与group的关联具有明确的业务含义,因而不能随意取消。如果我们希望对user拥有的权限进行细调,除去 user从group继承的某个不应该拥有的权限,解决的方法很有可能是所谓的负权限,即某个权限条目描述的是不能做某某事。负权限会造成各个权限设置之 间的互相影响,造成必须尝试所有权限规则才能作出判断的困境,引出对额外的消歧策略的需求,这些都极大的限制了系统的可扩展性。在允许负权限的环境中,管 理员将无法直接断定某个权限设置的最终影响,他必须在头脑中完成所有的权限运算之后才能理解某用户最终拥有的实际权限,如果发现权限设置冲突,管理员可能 需要多次尝试才能找到合适方案。这种配置时的推理需求可能会增加配置管理的难度,造成微妙的安全漏洞,而且负权限导致的全局关联也降低了权限系统的稳定 性。我更倾向于将group作为权限设置时的一种辅助标记手段,系统中只记录用户最终拥有的角色,即相当于记录用户通过group拥有权限的推导完成的结 果, 如果需要权限细调,我们直接在用户拥有的角色列表上直接进行。当然,如果实现的复杂一些,权限系统对外暴露的接口仍然可以模拟为能够指定 userGroup的权限。
   推理在面向对象语言中最明显的表现是继承,所以有些人将subject域中的推理直接等价于role之间的继承问题,这未必是最好的选择。继承可以形成非 常复杂的推理关系,但是可能过于复杂了(特别是直接使用sql语句无法实现树形推理查询)。按照级列理论,从不相关发展到下一阶段是出现简单的序关系,即 我们可以说subject出现级别上的差异,高级别subject将自动具有低级别的权限。一种选择是定义roleRank,规定高级别role自动具有 低级别role的权限,但考虑到user与role的两分结构,我们也可以同时定义userRank和roleRank,规定高级别user自动具有低级 别的role,而role之间不具有推理关系。在面向对象领域中,我们已经证实了完全采用继承来组织对象关系会导致系统的不稳定,所以我倾向于第二种选 择,即将role看作某种类似于interface的东西,一种权限的切片。为了进一步限制这种推导关系,我们可以定义所谓的安全域的概念. security domain, 规定推导只能在一定的域中才能进行。
   select user.userId, role.roleId
   from user, role
   where user.userRank > role.roleRank
   and user.domain = role.domain

   将权限控制一般需要施加在最细的粒度上,这在复杂的系统中可能过于理想化了。复杂的情况下我们需要进行局部化设计,即进行某些敏感操作之前进行一系列复杂 的权限校验工作。当完成这些工作之后,进入某个security zone, 在其中进行操作就不再需要校验了。
   总的来说,权限系统采用非常复杂的结构效果未必理想。很多时候只是个管理模式的问题,应该尽量通过重新设计权限空间的结构来加以规避。不过在一些非常复杂 的权限控制环境下,也许简单的描述信息确实很难有效的表达权限策略(虽然我从未遇到过),此时尝试一下规则引擎可能比在权限系统中强行塞入越来越多的约束 要好的多。

posted @ 2005-11-26 10:46 canonical 阅读(649) | 评论 (0)编辑 收藏

                                 

      权限控制可以看作一个filter模式的应用,  这也符合AOP思想的应用条件。在一个简化的图象中,我们只需要将一个判别函数 isAllowed(subject, operation, resource)插入到所有安全敏感的函数调用之前就可以了。虽然概念上很完美,具体实现的时候仍然有一些细节上的问题。基本的困难在于很难在最细的粒 度上指定权限控制规则(连续的?动态的?可扩展的?),因而我们只能在一些关键处指定权限规则,或者设置一些整体性的权限策略,然后通过特定的推理来推导 出细粒度的权限规则,这就引出结构的问题。我们需要能够对权限控制策略进行有效的描述(控制策略的结构),并且决定如何与程序结构相结合。 subject, operation和resource为了支持推理,都可能需要分化出复杂的结构,而不再是简单的原子性的概念。而在与程序结构结合这一方面,虽然AOP 使得我们可以扩展任何函数,但这种扩展需要依赖于cutpoint处所能得到的信息,因而权限控制的有效实施也非常依赖于功能函数本身良好的设计。有的时 候因为需要对结构有过于明确的假定,权限控制的实现不得不牺牲一定的通用性。

   下面我们将分别讨论一下operation, subject和resource的结构分解的问题。首先是operation。
    说到推理结构,让人最先想起的就是决策树,树形结构,在面向对象语言中可以对应于继承。金字塔式的树形结构也正是在现实世界中我们应用最多的控制结构。通过层层分解,operation的结构可以组织为一棵树,
   应用程序 ==> 各个子系统 ==> 每个子系统的功能模块 ==> 子功能模块
      ==> 每个模块的功能点(具有明确的业务含义) ==> 每个功能点对应的访问函数(程序实现中的结构)
   一个常见的需求是根据权限配置决定系统菜单树的显示,一般控制用户只能看到自己有权操作的功能模块和功能按钮。这种需求的解决方法是非常直接的。首先,在 后台建立子系统到功能模块,功能模块到功能点以及功能点到实现函数之间的映射表(如果程序组织具有严格规范,这甚至可以通过自动搜集得到)。然后,在权限 配置时建立用户与功能点之间的关联。此时,通过一个视图,我们就可以搜集到用户对哪些功能模块具有访问权限的信息。

   为了控制菜单树的显示,witrix平台中的SiteMap采用如下策略:
   1. 如果用户对某个子功能具有操作权限,则所有父菜单项都缺省可用
   2. 如果用户对某个功能具有操作权限,并且标记为cascade,则所有子菜单项都自动缺省可用
   3. 如果明确指定功能不可用,则该菜单及子菜单都强制不可用
   4. 如果明确指定功能对所有人可用,则不验证权限,所有子菜单自动缺省可用
   4. 强制设定覆盖缺省值
   5. 不可用的菜单缺省不可见
   6. 明确标记为可见的菜单即使不可用也可见
   7. 父菜单可见子菜单才可见
我们通过预计算来综合考虑这些相互影响的控制策略。尽量将推导运算预先完成也是解决性能问题的不二法门。

  在witrix平台中,每一次网络访问的url都符合jsplet框架所要求的对象调用格式,需要指定objectName和objectEvent参 数,这就对应于功能点的访问函数。访问控制点集中在objectManager并且访问格式是标准的。使用spring等AOP方式实现细粒度访问控制, 困难似乎在于不容易引入外部配置信息(例如功能点信息等),而且控制点所对应的对象函数格式也不统一,因而多数需要在细粒度上一一指定。

posted @ 2005-11-26 10:41 canonical 阅读(726) | 评论 (0)编辑 收藏

     在系统中发生的事情,抽象的说都是某个主体(subject)在某个资源(resource)上执行了某个操作(operation)。
          subject --[operation]--> resource
所谓权限管理,就是在这条信息传递路径中加上一些限制性控制。
    主体试图去做的 limited by 系统允许主体去做的 = 主体实际做的。
可以看到,权限控制基本对应于filter模式。subject试图去做的事情应该由业务逻辑决定,因而应该编码在业务系统中。

    先考虑最粗粒度的控制策略,控制点加在subject处,即无论从事何种操作,针对何种资源,我们首先需要确认subject是受控的。只有通过认证的用 户才能使用系统功能,这就是authentication。boolean isAllowed subject)
   稍微复杂一些,控制可以施加在subject和operation的边界处(此时并不知道具体进行何种操作),称为模块访问控制,即只有某些用户才能访问特定模块。isAllowed(subject, operation set)
   第三级控制直接施加在operation上,即操作访问控制。operation知道resource和subject(但它尚没有关于resource 的细节知识),我们能够采取的权限机制是bool isAllowed(subject, operation, resource), 返回true允许操作,返回false则不允许操作。

    最简单的情况下,subject与resource之间的访问控制关系是静态的,可以直接写成一个权限控制矩阵

for operationA:
            resourceA   resourceB
subjectA      1           0
subjectB      0           1


isAllowed(subjectA, resourceA)恒等于true

如果多个operation的权限控制都可以通过这种方式来表示,则多个权限控制矩阵可以叠加在一起

for operationA, operationB:
            resourceA   resourceB
subjectA      10           01
subjectB      01           11

当subject和resource的种类很多时,权限控制矩阵急剧膨胀,它的条目数是N*M。很显然,我们需要进行矩阵分解。这也是最基本的控制手段之一: 在系统中增加一个瓶颈,或者说寻找到隐含的结构。
      subject_resource = subject_role * role_resource
这 样系统权限配置条目的数量为 N*R + R*M, 如果R的数目远小于subject和resource,则实现简化。这称为RBAC(role based access control),它的一个额外好处是权限系统的部分描述可以独立于subject存在,即在系统中没有任何用户的时候,通过角色仍然可以表达部分权限信息。可以说角色是subject在权限系统中的代理(分解)。

     有时候引入一个瓶颈还不过瘾,有人引入组的概念,与role串联,
subject_resource = subject_group_role * role_resource
或着group与role并联,
subject_resource = subject_group * group_resource

     与role稍有不同,一般情况下group的业务含义更加明显,可能对应于组织结构等。将组织机构明确引入权限体系,有的时候比较方便,但对于权限系统自 身的稳定性而言,未见得有什么太大的好处。并联模式有些多余,串联模式又过于复杂,细节调整困难,特别是多条控制路径造成的冲突情况。一般情况下,我不提 倡将group引入权限控制中。

    比操作控制更加深入的控制就是数据控制了,此时需要对于resource的比较全面的知识。虽然表面上,仍然是
boolean isAllowed(subject, operation, resource),但控制函数需要知道resource的细节。例如行级控制(row-level)或者列级控制(column-level)的实现。 因为我们一般情况下不可能将每一个条目都建模为独立的resource,而只能是存在一个整体描述,例如所有密级为绝密的文档。在witrix平台中,数 据控制主要通过数据源的filter来实现,因为查询条件(数据的定位条件)已经被对象化为Query类,所以我们可以在合适的地方自由的追加权限控制条 件。

     以上的讨论中,权限控制都是根据某些静态描述信息来进行的,但现实世界是多变的。最简单的,当subject从事不同业务时,对应于同一组资源,也可能对 应的权限控制并不同(在witrix平台中,对应于IDataSource的模式切换)。更复杂一些, 在不同的时刻, 我们需要根据其他附加信息来作出是否允许操作的判断, 即此时我们权限设置的不仅仅是一些静态的描述信息, 而是一个完整的控制函数, 这就是所谓的工作流权限控制,一种动态权限控制.


posted @ 2005-11-26 10:39 canonical 阅读(974) | 评论 (0)编辑 收藏

    软件开发是从设计开始的, 而设计的产物是一堆描述性的文档. 我们总是希望这些描述能够尽量完备, 例如在一个用例描述中我们总是希望加入尽量多的异常流描述, 尽量把所有的相关情况都同时呈现出来. 当我们对系统进行了大量的分解和分析工作之后, 往往会遇到一种理解上和验证上的困难, 即我们如何才能确保某个use case的运行结果恰好能够满足另外一个use case的输入需求, 整个系统能否精密的配合在一起. 此时我们可以依赖一些整体架构设计的文档描述, 或者补充更多的系统连接上的说明, 但是无论如何, 要在思维中同时把握那么多条执行路径是一件艰难的事情.
    设计文档可以说是对系统行为的一种抽象性的规约, 为了验证这种抽象描述的正确性, 在缺乏理论保证的情况下, 我们唯一的选择就是抽样检验, 即我们需要构造一些测试用例, 特别是那些描述了一个完整业务流程的全局性的测试用例(用户故事). 在测试用例中, 我们并不需要构造出所有完整的执行路径, 只需要对一些关键性的业务路径进行检验就可以了, 局部的异常流处理很多时候都可以通过局部的单元测试来检验.
    测试用例最好以测试代码的方式提供,而不是一组文本描述. 我们应该尽量在开发的早期使得全局测试用例就能够运行起来, 使它成为系统演化的驱动力之一, 并根据系统开发的进展同步的进行调整. 测试驱动开发(Test Driven Development)所指的绝不仅仅是对单个类所进行的单元测试(Unit Test). Test的一个重要作用在于实例化所有必要的抽象约束条件, 通过sample来驱动系统的发展.

posted @ 2005-11-26 10:37 canonical 阅读(537) | 评论 (0)编辑 收藏

    http://ajaxanywhere.sourceforge.net/index.html
    AjaxAnywhere利用JSP标签把Web页面标注出可以动态装载的区域, 可以直接把任何JSP页面转化为AJAX感知组件而不需要进行复杂的Javascript编码.
    <script> ajaxAnywhere.getZonesToLoad = function(url){ return "countriesList"; } </script>
    <select size="10" name="language" onchange="ajaxAnywhere.submitAJAX();">
        <%@ include file="/locales_options_lang.jsp"%>
    </select>

    <aa:zone name="countriesList">

        <select size="10" name="country" >
            <%@ include file="/locales_options_countries.jsp"%>
        </select>

    </aa:zone>
   
   AjaxAnywhere的这种做法与witrix平台中的ajax方案有些类似, 例如
   
    <select onchange="new js.Ajax().setObjectEvent('changeLanguage').setParam(this).setTplPart('countriesList').replaceChildren('countriesList')"> ...</select>

    <div id="countriesList">
   <tpl:define id="countriesList">
       ....
   </tpl:define>
    </div>

    但是在AjaxAnywhere的方案中, 后台jsp页面总是要完整运行的, 它通过servlet filter机制缓存所有的jsp输出, 而aa:zone标签则把自己的bodyContent运行后的结果保存在request的attribute中, 最后servlet filter根据调用参数决定返回那些zone的运行结果. 而在witrix平台中的方案中, 只有指定的tplPart才会被运行, 其他部分完全被忽略. 这种差异的根源在于Jsp Tag技术本身的局限性. Jsp Tag的设计是非常原始的, 基本上就是在字符串层面上进行操作, 在运行的时候缺乏对页面结构强有力的控制. 实际上, 在我看来, 所有基于jsp tag的技术都受制于jsp tag的先天的局限性, 很难有深度的发展, 包括JSF技术.
 

posted @ 2005-11-23 21:53 canonical 阅读(844) | 评论 (0)编辑 收藏

    witrix平台中的tpl模板技术的重点在于标签定义的设计, 在于如何最大限度的发挥xml格式的表达能力。
    tpl自定义标签的基本结构如下:
    <Namespace:TagName tpl:tag="realTagName"
        tpl:noborder="${booleanExprInCompileContext}"
        tpl:ignore="${booleanExprInCompileContext}"
        attrName="stringOrExpression" cp:attributeInCompileContext="atringOrExpression"
        OtherNamespace:OtherAttrName="stringOrExpression"
        >
        bodyContent
    </NameSpace:TagName>
    自定义标签总是处在某一名字空间中, tpl名字空间中的属性由tpl编译器负责进行解析并处理, 而cp名字空间中的属性在编译期能够访问,其他名字空间的属性被完全忽略, 一般只有decorator会识别这些属性(例如cache:timeout).所有无名字空间的属性都相当于是自定义标签的调用参数,在标签运行的时候 可以在标签内部访问到。
    tpl通过对namespace的使用, 避免了系统属性, decorator属性与普通标签属性之间的相互干扰, 这与JSF和Tapestry都是不同的。
    tpl:tag属性指定此标签在编译时对应的真实标签名, 即编译期会识别RealTagName而不是Namespace:TagName。tpl:noborder为true的时候相当于是直接编译 bodyContent, 例如用来在编译期控制是否在界面上使用某种边框。
    tpl:ignore为true的时候,此标签将被忽略而不会被编译。
    bodyContent在编译期成为tagBody变量, 由自定义标签自己决定如何处理, 这种方式比FreeMarker的<#nested>机制要灵活和强大的多. 例如在标签内部我们可以使用<cp:compile src="${tagBody}" /> 这等价于 FreeMarker的<#nested>. 也可以使用
    <cp:compile src="${tagBody.existingChild('header')}" />从bodyContent中取出header子节点. 甚至我们可以对tagBody进行加工之后再进行编译.

posted @ 2005-11-22 23:09 canonical 阅读(1017) | 评论 (0)编辑 收藏

    经济学的核心概念是合同(contract),多个利益主体(具有不同的价值目标)在交互中达成一致协议。这本质上是个多目标优化的问题。这与物理学的精 神是有着本质区别的。物理学传统上认为世界是完美的,存在着唯一的真理。根据Lagrange原理,最低能量原理等,物理世界总是在众多可比的备选结构中 选择那唯一最优的结果。在经济学中不存在唯一的价值取向,并不是所有的事物之间都是能够进行比较并排出座次的。比如,我们大多数人会认为10个老婆饼比一 个老婆饼好,10个葱油饼比一个葱油饼好,10个老婆饼加10个葱油饼比一个老婆饼加一个葱油饼好,但是10个老婆饼和10个葱油饼之间如何比较,却是没 有确定的结果的。数学上,我们说可以建立偏序(partial order)但无法建立全序。因为多个利益主体对同一事物的评价是不可比的(不同的),因而可以产生交换.交换是一个互惠互利的过程. A交出了部分老婆饼换来了葱油饼,因为A觉得自己的老婆饼很多,还是葱油饼更有吸引力一些,而同样B认为自己的葱油饼很多,他宁肯再要一些老婆饼. 通过一番讨价还价的交互过程,我们可以达到所谓的Pareto有效(optimal): 在最终的优化配置中,没有人能够在不使别人受损的情况下使自己得益. 即继续交换下去,A或B中的一个就会觉得不值得了. 注意到经济学的这种双赢性质与军事学和权谋术也是不同的,权谋讲求争锋相对, 损人即利己, 进攻是最好的防守等等.
   
    多目标优化,意味着我们在一族矢量中寻找最优的一个,尽管在每个维度上我们都能很容易的作出比较和判断,但综合起来却需要反复的权衡。在凸分析 (Convex Analysis)中标量化(Scalarization)是寻找Pareto有效点的常用方法. 即定义一个价格矢量, 优化时考虑总体价格. 价格的存在意味着市场的存在, 意味着我们在考虑优化问题的时候,可以只考虑自身与市场的交互,而不用考虑众多其它利益主体的存在(类似于物理中的场方程). 理论上可以证明,在均衡价格处可以实现Pareto最优。


    说了这么多经济学,它和软件有什么关系呢。稍微留意一下就会发现,现在软件中越来越强调合同和涉众(stakeholder)利益了(参考use case)。早期,面向过程编程时,系统的目标比较单一:满足当前系统功能需求。基本上系统中的每一个函数在编写的时候都服从于一个目标,即当前系统的运 行。在编制的时候多半都想着向着最终的目标迈进,系统的各个部分之间是精确(detailed)匹配的。(想想VB的代码吧)。采用面向对象的思想方法之 后,系统中出现了多个利益的实体,它们定义并保护着自身的利益。系统的目标不再是完成功能,而是实现用户价值,稳定运行,便于维护等多重价值目标。在面向 对象以后,我们经常会发现,最终我们实现的功能点会多于面向过程开发的时候。很多对象函数在当前系统中最终并没有调用,但考虑到重用性和完备性等,我们还 是编写了相应的代码。很多商用组件对象的功能集更是大得惊人,我们永远只是用到其中的一小部分。系统的架构便是由对象之间相互协作并相互竞争支撑起来的.   在面向对象设计中, 我们说对象对外暴露的接口是与外部世界达成的contract, 而接口函数则反映了一次交互过程.有时我们采用如下方法, 让调用双方都能平等的获得处理机会.
class A{
   void someFunc(B b){
      b.someFunc();
   }
}

    XP敏捷编程强调快速迭代, 但绝不意味着不进行架构设计. 按照级列设计理论, 复杂性是分级的, 我们要采用满足当前需求的最简单的设计,而不是绝对意义上的最简单的设计. 而且简单与完备性还是两回事,虽然简单,但是功能仍然要是完备的. 这是个架构性问题. 迭代是个逐步精化的过程,而很少是格式塔式的革命. 在XP中我们应该更加强调architecture centric.
    重构是不影响系统外部特征的情况下,对系统内部结构的修改. 但我们现在要从结构A走向结构B,重构的路径到底在哪里。推翻了重新写并不是重构。虽然XP强调当前,不要过多考虑未来,但这只是个重点问题,并不意味着 不考虑未来,我们需要为复杂性的方向性发展保留出一定的通道。实际上重构的过程中,架构本身对应的概念是基本不变的,只是结构在调整,在细化。如果你大脑 痴呆,先天遗传不利,无论怎么重构知识结构大概也难以避免被淘汰的命运. 重构也会陷入Pareto最优点,因为一些对象作为即得利益者,让它放弃自己的功能集并不是那么容易的事情。它会争辩说,我和XX建立了关联,基于我的 YY功能,ZZ已经作了大量开发而且已经发布出去。。。

    目前软件设计中没有"市场"的概念, 在权衡系统功能归属的时候,我们只能两两进行: 这个功能放在对象A里好呢还是放在B里好呢. 是一个相当费力的过程.
   
    最后,再为物理学说句话。表面上看起来,物理学是由最优化原理支配的,但它还要受到所谓对称性的制约。很多时候当我们面临两难选择的时候,对称性会帮助我 们作出选择。对称性(根据Nother定理,守恒律也是对称性的一种)维护了物理学内部的结构张力。

posted @ 2005-11-22 17:59 canonical 阅读(657) | 评论 (2)编辑 收藏

    过程控制的要点是哪些?我从以下几点来考虑这个问题。
 第一点是测量,衍生出定义,标识,记录,评判等。测量需要在具有分歧的变化之处进行(与其它过程的交连之处)。
  第二点是预测,预测不准的原因一般是信息不足或者是非线性。确定性系统中可以产生貌似随机的混沌现象。物理学中混沌控制的基本要点是缩短控制间隔,尽量利 用确定性的信息,而且间隔减小之后,多半可以部分恢复线性。此外,控制所需的信息也并不需要是整个动力学过程的全部信息,只需要是某个动力学切面上的信息 即可。

 第三点是控制点的选择,控制点分为时点(时机)和位点(对象)。有时需要在系统中制造出某个瓶颈来便于施加控制。

 第四点是执行,控制的基本策略分为负反馈和正反馈,这就需要信息的传递(交流)。正反馈策略在非线性系统控制中其实非常重要(饱和)。

 第五点是协调(组织),控制的整体结构具有周期结构(节奏),网状结构(交互),层次结构(抽象)等。

 第六点是变化,有序的动态结构变化称为演化(持续改进),而演化最重要的是方向,或者说是目标,或者说是价值。

 第七点是学习,改进是学习特别是从错误中学习的结果。传统制造业的控制因为流程非常固化,其实并不注重学习。

 越来越复杂的系统最终只有一个词能够描述:生态。

posted @ 2005-11-22 17:58 canonical 阅读(301) | 评论 (0)编辑 收藏

quartz是一个高质量的任务调度软件包。其主要组成部分为:

Scheduler接口: quartz的执行线程,它根据Trigger决定调度时刻,根据JobDetail的说明实例化并运行Job

JobDetail类: 可持久化的任务描述信息。任务虽然分组,但是仅用作管理标示,任务之间并无实质性关联, 例如无法定义job chain。

Trigger类:任务的调度策略。这里的特点是调度策略与任务描述分开,调度策略和任务描述都可以分别在Scheduler注册,然后再关联起来。JobDetail与Trigger的关系是一对多。

JobDataMap: 将任务的运行时可持久化状态信息从JobDetail类中分离出来

Job接口: 任务的执行代码

StatefulJob接口: 无状态任务对应的JobDataMap可以认为是只读的,而有状态的任务在多次执行过程中保留对JobDataMap所作的修改,一个后果是有状态任务无法被并发执行。

JobExecutionException类: 可以通过JobExecutionException调整调度程序的下一步动作
Calendar接口: 用于从trigger的调度计划中排除某些时间段,例如假期等。

以上几个部分的交互关系如下:
class JobImpl implements Job{
    public void execute(JobExecutionContext context) throws JobExecutionException{
        JobDetail detail = context.getJobDetail();
        JobDataMap dataMap = detail.getJobDataMap();
        ...
    }
}

scheduler.addCalendar("myHolidays", holidayCalendar, false);
trigger.setCanlendarName("myHolidays");

JobDetail jobDetail = new JobDetail(jobName, jobGroupName, JobImpl.class);

scheduler.scheduleJob(jobDetail, trigger);

JobDetail可以设置如下属性:
1. Durability: non-durable的任务当不再与任何active trigger关联的时候将会从scheduler中被自动删除。
2. Volatility: volatile的任务在scheduler的两次启动之间不会被持久化
3. RequestsRecovery: 如果在执行过程中程序意外崩溃,标记为"request recovery"的任务在scheduler重起之后将会被再次执行,此时JobExecutionContext.isRecovering()返回true.

Trigger可以设置如下属性:
1. misfireInstruction: 设定当trigger错过了触发时刻的时候需要采取的处理策略

SimpleTrigger按照固定的时间间隔进行触发
startTime, endTime, repeatCount, repeatInterval

CronTrigger按照日历间隔进行触发
seconds minutes hours day-of-month month day-of-week

在quartz内部,QuartzSchedulerThread按照时间顺序选择trigger(没有任务优先级的概念), 然后在JobRunShell中运行Job。

JobRunShell中的调用顺序如下:

TriggerListener.triggerFired
    Called by the Scheduler when a Trigger has fired, and it's associated JobDetail is about to be executed.

TriggerListener.vetoJobExecution
    Called by the Scheduler when a Trigger has fired, and it's associated JobDetail is about to be executed.

JobListener.jobToBeExecuted
    Called by the Scheduler when a JobDetail is about to be executed (an associated Trigger has occured).

Job.execute
    Called by the Scheduler when a Trigger fires that is associated with the Job.
 
JobListener.jobWasExecuted
    Called by the Scheduler after a JobDetail has been executed, and be for the associated Trigger's triggered(xx) method has

been called.

Trigger.executionComplete
    Called after the Scheduler has executed the JobDetail associated with the Trigger in order to get the final instruction

code from the trigger.

TriggerListener.triggerComplete
     Called by the Scheduler when a Trigger has fired, it's associated JobDetail has been executed, and it's triggered(xx)

method has been called.

SchedulerListener.triggerFinalized [if(trigger.getNextFireTime() == null)]
     Called by the Scheduler when a Trigger has reached the condition in which it will never fire again.

posted @ 2005-11-22 17:55 canonical 阅读(1091) | 评论 (0)编辑 收藏

  hibernate等O/R Mapping软件包中使用javabean来作为数据的载体, 但这些bean一般除了get/set等数据访问方法之外没有什么其它的业务方法。 前一段时间有人认为这是所谓贫血的领域模型(Anemia Domain Model),引发了一场讨论。 其实这些bean的作用仅是表达了领域内的数据关系, 本身并不可能作为完整的领域模型层存在。 在数据层,我们所需要的是数据对外暴露,因为我们无法预知这些数据的使用方式, 就象是实验数据发表出来以后你无法预知别人如何分析一样,这时信息流是开放的,向外的:信息在这里,放马过来吧。 而在业务逻辑层,复杂的逻辑控制交织在一起,我们需要精细的控制信息通道,通过函数封装,我们反转了信息流的方向:取到什么数据是由调用者提供的信息决定 的。
     实际上,在ORM软件中使用的bean基本上与一个Map类似,只是它具有java Class所提供的元数据,而访问数据时又必须通过get/set方法,因而在这些方法中能够根据元数据动态的作出响应。在witrix平台的统一数据访 问框架中主要基于Map等通用数据类型,而不是个性化的bean。为了使得Map具有与bean一样的动态响应能力,只需要加入meta的支持即可。
interface IExMap extends Map{
     IMapChecker getChecker();
  Map getModifications();
  ...
}
在get/set之前可以通过IMapChecker来实现动态处理,对Map中数据所作的修改也可以记录下来

posted @ 2005-11-22 17:53 canonical 阅读(301) | 评论 (0)编辑 收藏

    在程序中需要返回一个数据集合的时候, 应该尽量选用标准的Java集合类接口,例如List, Map等. 有时也见到有人选择返回Iterator对象, 一般情况下这不是很好的选择. Iterator对象的功能有限, 而且存在一种即时消费的特点, 我们一般不能把一个Iterator保存起来留待以后使用. 而且JDK提供的集合类也不能从Iterator直接构造出来,例如没有 new ArrayList(myIterator), 这样为数据复制造成一定的困难.
    Iterator在理论上的好处之一是可以支持延迟加载数据, 但是实现起来比较繁琐而且单条加载也是比较低效的. 在witrix平台中如果需要延迟加载数据集合, 我一般选择使用IPageViewer接口
       interface IPageViewer{
           public List getAll();
           public int getTotalCount();
           public List listPage(int startPos, int maxCount);
       }
    IPageViewer通过getAll()可以转换到List, 也可以通过 new Pager(pageViewer,pageSize).itemIterator()得到单条数据的Iterator, 其内部采用分页加载模式. 搜索引擎返回的结果和数据库查询的结果都可以使用这一接口.

posted @ 2005-11-19 11:04 canonical 阅读(329) | 评论 (0)编辑 收藏

 Ivar Jacobson 在其著作"Aspect-Oriented Software Development with Use Cases"中将AOP(Aspect-Oriented Programming)上升为一种贯穿软件开发全生命周期的设计技术,由建筑学的隐喻引出了decoration overlay(existion + extensions)的策略。在建筑学中,首先基础设计图规划了房间的空间划分,电气工程师基于这张基础图决定电气设施排布图,而装修设计师可能依据基 础设计图决定装修方案。所有这些扩展(extension)设计图叠加到基础设计图之上构成的最终设计方案决定了房间每个部分的最终功能。这也有些象是中 国传统彩画的印制技术:每次印刷一种颜色,多重叠加之后构成成品。
 Jacobson指出传统的面向对象设计将会产生两个问题: tangling和scattering。tangling指的是一个组件要参与多个业务, 因而组件具有的功能是所有业务所需功能的超集, 虽然很多功能只在某一项特殊业务中使用,并无重用需求, 但组件开发者在设计和开发时却无法局域化, 必须同时理解所有需求。 scattering指的是某一特定需求可能需要多个组件协同操作来完成, 这要求实现代码分散在多个组件之中,而没有一个整体概念。这两个问题的产生是因为缺乏一种全局的separation of concern。AOSD(Aspect-Oriented Software Development)正是Jacobson指出的解决方案。
 AOP 在基本的技术含义上可以认为是实现了基本动作与修饰语之间的正交分解,Jacobson将其扩大化为基础架构(architecture)与扩展 (extension)之间的正交分解,将architecture centric设计与use case, AOP等概念联系起来。 Jacobson指出程序中在两个不同的层面上存在着独立的正交分解结构,一个是组件结构(具体化为Class),一个是业务逻辑结构(具体化为use case)。The element structure identifies where elements of the system are located in the element namespace. It structures the elements hierarchically in terms of layers, packages, classes, and the features within these classes. The use case structure defines how you overlay functionality onto the element structure. 因为use case需要多个组件的参与,当我们将use case结构叠加到组件结构之上时就会产生tangling和scattering。Jacobson指出每一个use case是一个功能切片(slice), 并为use case中的概念找到了相应的程序对应物,因而可以在后期通过AOP技术将use case slice织入到系统基础架构中,从而构成一种全程建模技术。use case中的generalization可以对应于对象继承, 包含(include)可以对应于对象组合和调用, 而扩展(entension)就对应于AOP。某些use case具有共性,如常见的CURD(Create,Update,Read, Delete)操作,可以被抽象为参数化的use case, 这可以对应于模板(template)概念。
 我在数年前也注意到了tangling现象,一些类的功能不断膨胀而完备性却始终无法保证。在概念 上我们可以将功能集正交分解为不同的接口,但实现时却需要集中实现,因为它们对应于同一个对象实体。当时我主要通过interface的 aggregation来解决这个问题,例如采用IDynamicInterfaceSupport接口,动态注入接口实现.
     interface IDynamicInterfaceSupport{
      Object queryInterface(Class interfaceClass, Object user);
   void addInterface(Class inf, Object impl);
  }
  使用接口来处理这些问题很多时候效果并不好。首先接口要求预先有一个明确的定义,要求明确标注出功能的独特性,这样在合成的时候具有一定的困难。例如在C ++中我们将对象如果分解为大量的接口,则使用的时候需要不断的queryInterface,十分麻烦。只有弱化种类型区别,才能消除这种合成障碍。例 如在脚本语言中,我们可以自由的使用对象的所有函数,而不需要知道它是哪个接口具体提供的。我在C++中主要通过代码生成技术构造一个合成代理对象来解决 这个问题,在java中主要使用interface的extends。接口意味着某个明确的定义,而模板(template)在这方面的要求则弱得多,模 板只要求对象具有需要的函数,有时可以做到更好的可重用性。接口的另一个困难在于无法捕获cross-cutting concern。在没有AOP的代码注入技术之前我们主要是通过继承来构造扩展点,这些都是所谓侵入性(intrusive)的设计,要求不必要的附加层 次,而且具有很强的事前假定。
  在jsplet中WebObject的注册格式可以认为是实现了action slice。WebObject具有的全部功能可以通过多个action集合组装出来,而这多个action slice共享一个对象名和成员变量空间。
 <type name="Demo">
  <!-- 如果未指定object, 则缺省为WebObject类型 -->
  <object class="demo.MyWebObject" />
  <listener>
   <url-listener url="/demo/ActionSliceA.jsp" />
   <url-listener url="/demo/ActionSliceB.jsp" />
  </listener>
 </type>
       在tpl模板中,因为xml格式定义了明确的结构信息,所以我们比较容易的定义扩展点,并注入新的功能,例如<c:decorator>标签。这也类似于AOP的操作。

posted @ 2005-11-19 11:03 canonical 阅读(729) | 评论 (1)编辑 收藏

交叉表(Cross Table)的基本特点是具有横纵两个自由延展的维度,而平面表结构只有一个可延展的维度,因为平面表的列名和列数是确定的。例如,地区的产品销售数量,在平面表中表达为
district_id product_id sell_num
如果表现为交叉表,则为
           productA  productB
districtA   sellNum   sellNum
districtB   sellNum   sellNum
这种结构上的失配需要通过程序逻辑来进行调整。

注意到平面表结构只具有一个可延展的维度,而join可以看作是该维度上的连接操作。因此我们可以将交叉表看作是多个简单平面表结构并置的结果。即分解为
A:        
           productA
districtA   sellNum
districtB   sellNum

B:
           productB
districtA   sellNum
districtB   sellNum

横向维度的扩展在程序中表达。

SqlInfo结构封装了这种简单平面表的分解片断。
class SqlInfo{
    List fieldNames;
    SQL sql;
    String keyField;
}

我们在程序中通过JoinBuilder来实现横向维度的构造
JoinBuilder.begin(sqlInfoA)
           .join(sqlInfB)
           .leftJoin(sqlInfoC)
           .end();
生成的sql语句示例如下
select sqlInfoA.fieldNames, sqlInfoB.fieldNames
from sqlInfoA.sql join sqlInfoB.sql
on sqlInfoA.keyField = sqlInfoB.keyField

posted @ 2005-11-19 11:02 canonical 阅读(301) | 评论 (0)编辑 收藏

数据导出的功能大致可以分解为三个部分: 1. 从数据源读取一条记录 2. 将一条记录导出为指定格式 3. 循环调用1和2
首 先我们需要一种机制来对外暴露数据源(一种Container)中的数据,Iterator模式恰能满足要求。其次,我们需要一种机制来对一系列数据进行 处理,这对应于Visitor模式。第三,在组合Iterator模式和Visitor模式的处理过程中,我们需要表达出平面表数据集的基本特征。
在witrix平台中,平面表数据导出和转换通过TablePageProcessor对象来完成,
class TablePageProcessor{
 IPageViewer viewer;

 public Object process(ITableVisitor visitor){
  Pager pager = new Pager(viewer, pageSize);
  Iterator it = pager.itemIterator();
  visitor.visitBegin(headers);
  while(it.hasNext()){
   Object row = it.next();
   if(!visitor.visitRow(row))
    break;
  }
  return visitor.visitEnd();
 }
}

interface ITableVisitor{
 void visitBegin(List headers);
 boolean visitRow(Object row);
 Object visitEnd();
}

IPageViewer是暴露平面表数据的标准接口,它通过Pager对象的包装之后可以提供各种Iterator.
ITableVisitor体现了平面表数据的基本特征: header + 一系列row, 存在数据边界(起始与终止)
TablePageProcessor固化了IPageViewer和ITableVisitor的组合过程。
ITableVisitor可以有多种实现,例如CSVBuilder, ExcelBuilder等等。

posted @ 2005-11-19 11:01 canonical 阅读(337) | 评论 (0)编辑 收藏

分页的功能由两部分组成:取数据和计算分页。其中取数据的功能由IPageViewer接口实现
interface IPageViewer{
 int getTotalCount();
 List getAll();
 int listPage(int startPos, int maxCount);
}
Pager是用户调用时的接口
class Pager{
 public List getAll(){}
 public List listPage(){}
 public int getPageCount(){}
 public int getPageSize(){}
 public int getCurrentPage(){}
 ...
}
Pager使用IPageViewer作为数据供体,自身仅提供分页计算的功能。在witrix平台中, IPageViewer是表格数据的标准列举方式,因为与List接口相比,IPageViewer容许部分加载。
IPageViewer 可以有多种实现,如ListPageViewer, XmlPageViewer, ExcelPageViewer, DbTablePageViewer等。一般情况下Pager提供的功能已经足够了,特殊情况下可以通过继承来扩展。例如卡片浏览和分页浏览模式之间的互 相切换通过派生类ItemPager来实现。

posted @ 2005-11-19 11:00 canonical 阅读(250) | 评论 (0)编辑 收藏

java中最常用的数据结构类型是Map和List, 它们也是Container的两种基本模式,一个是根据特征值定位,一个是根据地址定位。 它们共同的一个特征是表达了数据之间的直接的,短程的一种相关性。另一种常见的数据结构Tree则表达了数据之间的一种长程的关联:根节点与其所有层次上 的子节点之间都存在着关联。 文件系统,组织机构, XML文档等都可以对应为Tree数据结构。在描述树形结构的时候,我们经常使用XML文件, 但是XML文件在程序中操纵起来并不方便,这其中的一个重要原因是XML是面向文档的,即操纵XML的API返回的和使用的都只能是文本字符串,而不能直 接使用程序中常见的其他数据结构。在witrix平台中操纵Tree结构的标准接口是TreeNode类,它的设计是面向应用的,即节点的属性值为 Object类型而不是String类型。

Tree由三部分组成: 属性,值, 子节点

class TreeNode implements IVariant{
 List getChildren();

 int getChildCount();
 TreeNode child(int index);

 /** 当name对应的节点不存在时将会自动创建该节点 */
 TreeNode child(String name);

    /** 当name对应的节点不存在时返回null */
 TreeNode existingChild(String name);

 Map getAttributes();
 IVariant attribute(String name);
 void setAttribute(String name, Object attrValue);
}

TreeNode.attribute(name)返回的是IVariant接口,例如
boolean defaultValue = true;
boolean b = node.child("subA").attribute("attrB").booleanValue(defaultValue);

TreeNode本身也是IVariant接口的一个实现,例如
int i = ode.intValue();

通过使用IVariant接口,我们实现了强类型的java语言与弱类型的xml文本之间的自然转换,在转换过程中还可以指定缺省值,这些都极大的简化了实际应用时的编码量。

posted @ 2005-11-19 10:59 canonical 阅读(341) | 评论 (0)编辑 收藏

   CRUD(Create, Read,Update, Delete)操作中最难处理的是查询。因为查询总是多样化的,如果每个特定查询调用都编制一个对象方法,则维护量太大且扩展性很差。如果编制一个通用的 查询接口,一般的做法是直接以SQL文本作为参数,但这样就几乎丧失了封装的意义。这里的核心问题是Query本身是复杂的,我们应该将它对象化为一个 类,在程序中控制Query的结构,而一个文本对象与一个复杂的Java结构对象的差异就在于对于文本对象我们很难有什么假定,因而在程序中也很难编制通 用的程序对其进行处理,一般只能对它进行传递。实际上,文本中描述的结构存在于java程序之外!当然,我们可以利用Parser来重新发现这种结构,那 最容易使用的Parser就是xml parser了,所以我们应该将Query的结构建立在xml描述的基础上。
edu.thu.search.Query类直接体现了对主题域的通用查询条件。(对比我对数据仓库模型的描述)
class Query{
    List getFields();
 TreeNode getCondition();
}
查 询条件主要通过TreeNode进行显式建模,使得程序有可能对它进行进一步的处理。例如,在DataSource处理Query之前,权限配置模块可以 将附加约束直接追加到现有查询条件之后,实现对数据权限的行级控制。因为把Fields明确分离出来,我们也可以做到对权限的列级控制。
Query类的使用示例如下:
Query.begin().fields(TEST_FIELDS)
             .condition().eq(ID,"3")
   .end().resultType(IQueriable.TYPE_ROW_MAP)
   .findOne(dataSource).mapValue();
这里的调用接口的设计基本遵循与SQL类相同的风格,只是面向主题域而不是直接针对SQL语言的封装。

posted @ 2005-11-19 10:58 canonical 阅读(896) | 评论 (0)编辑 收藏