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

  程序中的各种类(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 阅读(1019) | 评论 (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 阅读(5049) | 评论 (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 阅读(2037) | 评论 (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 阅读(1634) | 评论 (5)编辑 收藏

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

posted @ 2006-06-10 20:24 canonical 阅读(465) | 评论 (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 阅读(2158) | 评论 (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 阅读(1592) | 评论 (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 阅读(1671) | 评论 (0)编辑 收藏

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

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

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

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

仅列出标题
共18页: First 上一页 4 5 6 7 8 9 10 11 12 下一页 Last