2007年8月2日星期四

Atlassian Confluence 2.3 的LDAP缓存配置问题

在 Atlassian Confluence 2.3中,如果用户和组来自于LDAP,当一个用户事先未加入用于授权的组,此时用户访问WIKI页面时会提示“Not Permitted”,但管理员将该用户加入授权的组,访问时仍然会提示未许可。这是因为Confluence使用了缓存机制。以管理员进入Confluence后,点击右上角的"Administration"打开管理页面,选择左边菜单 Administration->Cache Statistics。打开这个页面可以看到系统的缓存信息:

点击Flush可以手动清除缓存。


Confluence的缓存使用的是Orachle的Coherence技术,配置在confluence-coherence-cache-config.xml配置文件中定义的(在目录..\confluence\WEB-INF\classes下)。详细的配置定义可以参看
Coherence的配置元素参考,这里简要说明需要调整的配置。


缓存配置的问题


配置文件的根节点包含两个部分:
定义要配置的项,下表中的项是配置文件默认的LDAP部分的定义:


<cache-mapping> 
<cache-name>com.atlassian.user.impl.ldap.LDAPUserManagerReadOnly.users_ro</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING>
<cache-mapping>
<cache-name>com.atlassian.user.impl.ldap.LDAPUserManagerReadOnly.repositories</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING>
<cache-mapping>
<cache-name>com.atlassian.user.impl.ldap.LDAPGroupManagerReadOnly.groups</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING>
<cache-mapping>
<cache-name>com.atlassian.user.impl.ldap.LDAPGroupManagerReadOnly.groups_hasMembership</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING>
<cache-mapping>
<cache-name>com.atlassian.user.impl.ldap.LDAPGroupManagerReadOnly.groups_getGroupsForUser</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING>
<cache-mapping>
<cache-name>com.atlassian.user.impl.ldap.LDAPGroupManagerReadOnly.repositories</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING>


schema-name定义了配置表的名称为user,上面的LDAP的配置都使用user配置表,前面提到加入组无效的问题,就是因为"com.atlassian.user.impl.ldap.LDAPGroupManagerReadOnly.groups_getGroupsForUser"缓存的原因。

接下来我们看配置的缓存值,节定义各个schema的缓存值,与LDAP相关的是如下部分:


<local-scheme> 
<scheme-name>default</SCHEME-NAME>
<class-name>com.atlassian.confluence.cache.tangosol.HitTrackingLocalCache</CLASS-NAME>
<high-units>1000</HIGH-UNITS>
<expiry-delay>3600</EXPIRY-DELAY>
</LOCAL-SCHEME>
<local-scheme>
<scheme-name>user</SCHEME-NAME>
<scheme-ref>default</SCHEME-REF>
<high-units>5000</HIGH-UNITS>
<expiry-delay>300s</EXPIRY-DELAY> //定义缓存过期的时间,可以根据需要进行调整
</LOCAL-SCHEME>

user的schema使用default。从这个表看,user的expiry-delay项为300秒,也就是过5分钟后,该用户就可以具有权限访问WIKI页面了,但实际上并非如此,用户依然未授权。后检查发现,这个配置项根本不起作用!而是使用了default的设置,3600秒也就是60分钟才会过期。问题出在cache-mapping的定义上,在Cache Statistics的页面可以看到缓存定义项的名字比配置文件中的定义名多了一级:ldapRepository。这是atlassian-user.xml配置的ldap库的名字,原来Confluence内部的缓存配置项名加上了库的名字,但在默认的配置项定义中却没有,因此上面的配置根本不起作用。Confluence的文档也未提到这一点,让我折腾了很长时间!!


解决办法

解决办法很简单,就是在cache-mapping的cache-name值的com.atlassian.user.impl.ldap.LDAPGroupManagerReadOnly后面加上库的名字(这里是ldapRepository)即可,新的配置如下:


<cache-mapping> 
<cache-name>com.atlassian.user.impl.ldap.LDAPUserManagerReadOnly.ldapRepository.users</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING


修改后重启Tomcat即可。

2007年7月12日星期四

论“巴比塔”软件的建不成(杂乱无章版)

2005-02-23 12:07:50

某公司要开发一个软件,“能够管理一件事情从商谈到执行的全过程和过程相关的活动”,这是产品的Vision。产品的提出者认为现在人们管理一件事情,需要用到各种独立的软件,比如Instant Messenger进行交流,合同管理前期商谈结果,Microsoft Project进行项目计划和跟踪,财务系统来管理相应的资金活动,生产系统管理生产资料和制造计划,而这些活动都是相关的,目前似乎缺少一种可以把所有这些相关的活动集中管理的软件。于是,产品的提出者希望通过一个叫"Case"的概念来管理与这个Case相关的活动:


  • Case的请求者(Requester)和执行者(Performer)之间的商谈过程。同时管理包括费用、工期、交付项等所有可商谈的内容。

  • Case的Performer方的实施活动

  • Case的Requester和Performer之间关于实施活动的管理

起初听到这个想法的人会有两种反应:部分开发人员认为这将是一个非常庞大的系统,无法实现,但产品经理说可以由简到繁逐步完成;而另一部分人则认为“把相关的事情放在一起管理,似乎是一个不错的主意”。

各位可以先考虑一下:是否存在这样的软件?这样的软件是否合理?
最初的概念大家还能接受:

  • Case的商谈过程。Requester和Performer利用Message交流,修改Case文档,达成一致后签字确定。

  • 填写进度报告,体现实施的进度。

  • 验收Case完成的内容,关闭Case。

注意我前面重点标示的“相关性”,它是这个产品在引入功能时使用的一个原则,产品组在强调一个功能应该存在时,喜欢说“这个功能与Case相关,不放在这里,放在什么地方呢?”。

但随着功能的逐步细化,需求的深入分解,系统要完成的事越来越多。首先是Requester和Performer扩展为一个团队。产品组认为无论从甲方还是乙方在谈判时,都有许多人参与到相关的工作,比如其中一个人被临时邀请绘制Case里需要参考的一张工程图。既然是相关的,就应该让Case来管理这些参与者。

然后需要增加一个文档功能,用来管理Case相关的文档,包括Requester和Performer各自团队的文档以及共享的文档,产品组甚至为此设计了一个复杂的权限控制策略:有的文档属于两个团队共享,有的属于其中一个团队,而有的有只能由某一个团队的某个角色才能看见。

需求开始变得越来越复杂。越来越多的功能被考虑进来:Case的文档需要文档管理系统,Case可以用来制作Business Plan,执行阶段的项目管理,产品组甚至认为这个软件理论上可以替代现有的所有业务软件,“如果有足够的财力和资源的话”。同时一项简单的功能其业务规则也越来越复杂。产品组一直用一个看似颇有些道理的思想来构筑整个产品:


    “相关的事情需要在Case里管理”


于是,Requester/Performer从个体变成独立的团队以体现协商双方实际可能是以小组工作的,所使用的相关文档要放在里面,所有相关的活动要在Case里管理。这使我想起了著名的“六度理论”,一个数学领域的猜想,名为Six Degrees of Separation。20世纪60年代,耶鲁大学的社会心理学家米格兰姆(Stanley Milgram)设计了一个连锁信件实验。他将一套连锁信件随机发送给居住在内布拉斯加州奥马哈的160个人,信中放了一个波士顿股票经纪人的名字,信中要求每个收信人将这套信寄给自己认为是比较接近那个股票经纪人的朋友。朋友收信后照此办理。最终,大部分信在经过五、六个步骤后都抵达了该股票经纪人。六度分割(也叫“六度空间”),“小世界”[1]的概念由此而来。



通过这个连锁实验,体现了一个似乎很普遍的客观规律:社会化的现代人类社会成员之间,都可能通过“六度空间”而联系起来,绝对没有联系的A与B是不存在的。这是一个更典型、深刻而且普遍的自然现象。我们看似不相关的事物常常可能具有相关性。同样在企业活动里,我们仍然可以假定不用六度就可以建立一项活动和另一项活动的相关性,比如人员招聘和财务、生产之间的关系。如果用“相关性”来考虑,一个Case几乎要关系到所有的活动:立项审批、财务、生产系统、资源管理、...,最终这个软件就像一个巨大的“巴比塔”[2],它试图包含所有的业务功能,而结果是变成一个没有边界永远无法完成的怪物。

因此,仅仅使用“相关性”是无法作为一个软件功能的选择基础的。应该怎样来考虑一个产品的功能才合理呢?除了需要有明确的用户需要,为客户提供价值,从分析角度考虑,它应当服务某一个特定“领域”(Domain)。


什么是领域?在回答这个问题之前,我们先来看另一个问题,我们怎样解释“人”?在自然物种学科里“人”是“高级灵长类动物”。在社会系统中,它代表社会中的个体。在自然基因图谱的它不过是一组与其它生物具有一些相同部分又具有不同部分的基因组成。我们得到的解释都是在某一学科或者某种特定的上下文——一个特定的维度下进行的,听上去难以理解,但实际情况是我们无法完全准确的描述一个物体。同样,一个软件也无法解决所有的问题,真实世界具有无穷尽的复杂度,它需要面对特定的问题域。这个域中的问题并非毫无关系,它是根据某个特征维度聚合起来的,这个聚合的纬度就是我们前面提到的“领域”。即便对于Case这样一个看似简单的概念,如果我们不能明确它的领域,就无法对它进行明确的解释,也难以明确解决问题的范围。

领域通常具有以下特征:


  • 具有公共的领域概念。例如字处理器的“Glyph”,“Style”,“View”等概念。

  • 具有共同特征的使用此领域概念的用户。例如Java IDE工具面向Java开发人员,财务系统面向会计、出纳人员。

如果一个系统试图包含一个以上“领域”,概念就会相互混杂,系统将变得难以理解,并带来复杂度的倍增。仍然考虑前面的例子,产品组为前面的文档功能设计了其权限控制规则:

  • Requester的文档只能Requester团队的成员才能查看,有的文档需要Requester Team的特定角色(比如Leader)才可以查看。

  • Performer规则同上。


产品组花了很长时间来解释权限控制规则。而这只是Case的一个很小的功能,却要求如此复杂的安全需求。它需要这种复杂度吗?对于商谈活动来说,不需要单独的文档,所有文档都是为商谈共享的,因此既然是双方都能查看,就不需要控制查看权限。对于团队工作活动,其文档的控制通常基于个人再团队中的角色。两个活动单独来看其文档控制策略要简单得多,当两个活动放在同一模型里,却带来了规则的相互约束,于是就有了上面那个复杂的安全规则列表。产生这种现象的原因是因为这两种业务面向的使用者和业务特征是不一致的。如果关注商谈活动这个领域,其面向的是协约双方,关注协作过程和结果。而如果关注其中的一方怎样以团队的方式工作,则面向的一个团队内部,核心的概念可能是小组结构、任务分解。多个领域概念放在同一个模型里,将使业务规则产生“阶乘”式增长,从而导致业务复杂度的倍增!

因此,系统首先必须要有一个清晰的领域概念,它让我们明确解决的问题与,而这是判断入一项功能应否加入系统的权衡“依据”。考虑下面的例子,有人打算给一个Java IDE工具软件增加如下两项功能:


  • 支持Rename方式的重构。

  • 基于Database的版本控制系统(VCS),使代码可以保存在数据库中。


首先来看Rename功能。Rename是"Code"的重构,而"Code"是Java IDE的领域核心概念。VCS则不一样,VCS可以管理所有文档,它有一个与Java IDE完全不一样的领域概念,如“版本”、“分支”、“标签”等,而Java IDE关注的是Java "Code"的编写、调试、编译、打包。但我们也知道,目前几乎所有的开发工具都支持版本控制系统——它不是直接实现,而是提供对一个独立VCS系统的接口。这两个IDE相关的功能其实正好体现了“六度空间”中不同的相关性。在使用“六度理论”来分析社会关系时,把直接相关("我"的朋友)的称之为“强关联”,而把间接相关(“我”的朋友的朋友)称之为“弱关联”。我们可以把与系统领域相关的功能称之为“强关联”,而把与相关领域有关的功能称之谓“弱关联”。按照这种方式,Rename相对于Java IDE是“强关联”的功能,而VCS是拥有自己领域的“弱关联”功能。对于“强关联”功能应该在系统内实现,而不同领域则应当考虑建立独立的领域系统,使用松散耦合的方式来建立关联。对于一个大的复合系统,如果无法定义为一个领域,就应该分解成多个领域,保持独立领域的概念完整,并使用松散耦合的方式来建立领域之间的关联,使系统保持在一个可接受的复杂度中。

在面向对象设计范畴里,“高内聚”是衡量一个优良设计的基本标准。而从用户和系统价值来看,“解决用户的域相关问题的能力是软件的心””[3],“领域”分析是帮助建立用户问题域为中心的高内聚系统的基础。

要选择一个清晰的案例来阐明上面的观点,并不是一件容易的事。如果有人认为Case案例的场景也可能存在一个我并不清楚的领域模型,我是同意这种说法的,因为领域本身并没有固定的概念,但为了讲解的必要,需要假定Case案例存在这样一种令人迷惑混乱的“百慕大”三角区[4]。而这种误区和带来的后果在我们的软件开发生涯中却并非罕见。

另外一个需要注意的问题是这里的“领域”并不是通常意义的领域模型,它不是指一个具体的模型。我更强调它是根据某个维度产生的,自然内聚的关键概念。


------
[1].小世界。Small World,由Six Degrees of Separation引申出来的概念。从2001年秋天开始,美国哥伦比亚大学的社会学教授瓦茨(Duncan Watts)组建了一个研究小组,利用Email这一现代通信工具,开始进行“小世界假设”(Small World Project)的实验。在1年多时间里,总共有166个国家和地区的6万多名志愿者参与实验,实验结果证明,一封邮件平均被转发6次,即可回到接收者那里。See Small World Project
[2].巴比塔。Babel,也译作“巴别塔”。巴比塔是《圣经》故事中提到的一座通天塔,他由挪亚的后代所建。《旧约•创世纪》第11章曾有这样一段描述:古时候,天下人都说一种语言。人们在向东迁移的进修,走到一个叫示拿的地方,发现一片平原,就住下来。他们计划修一座高塔,塔顶要高耸入云,直达天庭,以显示人们的力量和团结。节选自《失落的文明:巴比伦》,华东师范大学出版社。
[3].引自Eric Evans的《Domain-Driven Design: Tackling Complexity in the Heart of Software》一书,"Part I:Putting the Domain Model to Work"部分。
[4].“百慕大”三角区。美国东南沿海的西太平洋上,北起百慕大,延伸到佛罗里达州南部的迈阿密,然后通过巴哈马群岛,穿过波多黎各,到西经40度附近的圣胡安,再折回百慕大,形成一个三角地区。自1945年12月由五架美国军用飞机组成的飞行中队在这里失踪后,发生了数起飞机、轮船的神秘失踪事件,这个区域因此被称为百慕大三角区或“魔鬼三角”。

More Readings
------
Rational Unified Process关于领域模型开发的相关内容
关于六度分割理论,有很多关于Six Degree of Separation和Small World的链接。

The Small-World Phenomenon: An Algorithmic Perspective
《Domain-Driven Design: Tackling Complexity in the Heart of Software》, Eric Evans, Addison Wesley Published. 中文版《领域驱动设计》由清华大学出版社引进。
 

2007年6月3日星期日

Iris:Web Page Decoration Framework

(实验性项目,已不再维护)

Iris是为了处理Web页面装饰和布局管理的框架,它的设计思想基于GoF 的decoraotr设计模式。Iris分离页面正文和布局装饰的部分,使得Web页面更容易被测试和维护。

如果要了解Iris的更多信息请看Iris IntroduceIris Developer Guide

Features

  • 支持多级装饰器
  • 支持可扩展的decorator filter结构
  • ASP.Net技术
  • C#实现

下载

Enum还是Enum Class(枚举类)[.Net]

常量/枚举类型的表示

系统中常常有一些属性的属性值是固定的一组值,它们的值域是封闭的(有限数量),比如国家代码(每个国家具有唯一的代码,而在一定时期国家的数量是确定的)、性别类型(男、女)。在现代程序语言中,一种典型的表示方式是枚举类型(Enum)。Enum表示封闭值域的类型,常常由程序语言作为一种数据类型直接支持,例如C,C#等。C#支持的enum在C的基础上提供了类型安全的能力,下面是用C#定义的性别枚举类型:


public enum Sex {
Male,
Female,
}

Java不支持enum数据类型,Java认为C提供的enum并不是类型安全的,通常使用称之为Typesafe Enum Class的设计模式来获得类似的效果(参见[Joshua01] P80,Item21 :Replace enum constructs with classes)。Enum Class不允许外部构造实例成员(构造函数为private),提供静态类型成员实例来表示封闭值域。使用Enum Class方式来表示Sex类型可定义如下(C#):


public class Sex{
// 私有构造保证值域的封闭性
private Sex() { }

pubic static readonly Sex Male = new Sex():
pubic static readonly Sex Female = new Sex():

}

同enum一样,可以使用Sex.Male或Sex.Female的方式来访问常量属性,与静态常量字段不一样(如静态字符串、整数),enum和Enum Class可以提供强类型的compile time检查以及提供更好的数据封装性和代码可读性。例如使用常量类型设置和比较属性值:

// 设置属性值
Sex sex = Sex.Male;
// 比较
if (sex == Sex.Male) {
// ... ...
}

如果Sex是使用Enum定义的,则上面比较的实际上是Enum字段的值;如果Sex是使用Enum Class定义的,则比较的是静态实例成员的引用地址,当然也可以使用Equals方法来比较。

虽然Enum Class是来自于Java的设计模式,但在C#中并非没有意义,因为Enum Class提供了比Enum类型更强大的能力。

Enum与Enum Class的比较

Enum与Enum Class均提供了封装常量的能力,都能够实现编译时的强类型检查,使用封闭值域防止非法值。不过,因为实现机制的不同,这两种方式也具有不同的特点。

Enum在C#中是一种值类型(Value Type),其基类型必须是整数类型(如Int16),因此Enum也具有值类型所具有的优点——比引用类型(Reference Type)更高的效率,定义简单。C#的Enum还支持位(Bit)操作,对于用于标志的常量,允许常量的OR、AND、XOR、NOT等位操作,C#提供了内建的语言支持,通过FlagsAttribute即可使Enum具备位操作能力。例如.Net Framework的System.AttributeTargets属性,允许Attribute子类在设置AttributeUsage时可以组合AttributeTargets属性(参见.Net Framework SDK):

// 使用Flags枚举
// 下面的定义表示CustomerAttribute可以作用在Class或者Method上
[AttributeUsage(AttributeTargets.Class AttributeTargets.Method )]
public sealed class CustomAttribute : Attribute {
... ...
}

Enum的缺点不能实现自定义的行为,无法提供常量更多的属性,不过在大多数情况下,这已经足够了。Enum Class没有这种限制,虽然Enum Class本身并不设计为可以继承,但可以修改基类(System.Object)的行为以提供更加丰富的能力(如修改ToString方法,根据使用者的本地语言输出本地化的国家名称),也可以提供更多的属性 。例如我们提供一个候选的国家列表,除了能显示国家名称外,可以提供国家代码、语言代码信息。关于Enum Class更多的应用场景后面的节有详细的描述。

Enum Class的问题

上面的Enum Class实现方式也有它的缺点,在上面的设计中Enum Class通过进程内静态成员引用地址相同的机理来比较 枚举量是否相等,但是当将一个序列化后的Enum Class实例反序列化后,CLR会创建一个新的实例,从而造成反序列化值不等于序列化前值的现象:

IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

MemoryStream stream = new MemoryStream();
// 序列化Sex.Male的值
formatter.Serialize(stream, Sex.Male);
stream.Seek(0,SeekOrigin.Begin);
// 反序列化
Sex sex = (Sex)formatter.Deserialize(stream);
Console.WriteLine(sex == Sex.Male);

上面的代码将输出false。因此通过引用的方式是有局限性的,在Java中这是一个比较棘手的问题,需要修改反序列化的行为(参看[Joshua01]P171)来保证进程中只存在一个枚举量的唯一实例。C#与Java的反序列化实现机制不一样,无法通过修改反序列化的行为来返回同一个常量实例 ,但C#提供了操作符重载的能力。我们可以通过重载操作符“==”来解决这个问题,同时为了保持CLS兼容以及与Equals的行为一致,还需要改写Equals方法:


[Serializable]
public class Sex{
// 性别类型名
private string sexName;
// 私有构造保证值域的封闭性
private Sex(string sexName) {
this.sexName = sexName;
}
public static readonly Sex Male = new Sex( "Male");
public static readonly Sex Female = new Sex( "Female");

// 提供重载的 "=="操作符,使用sexName来判断是否是相同的Sex类型
public static bool operator ==(Sex op1, Sex op2) {
if (Object.Equals(op1, null)) return Object.Equals(op2, null);
return op1.Equals(op2);
}

public static bool operator !=(Sex op1,Sex op2) {
return !(op1 == op2);
}

public override bool Equals(object obj) {
Sex sex = obj as Sex;
if (obj == null) return false;
return sexName == sex.sexName;
}

public override int GetHashCode() {
return sexName.GetHashCode ();
}
}

通过操作符重载,不再使用引用地址来比较常量,而是通过值比较(如上面的sexName),因此要求每个常量实例必须具有唯一的标识值。 在不支持操作符重载的语言中,不能使用"=="来比较两个常量值是否相等,而应该使用Equals方法来代替。

Enum Class的设计

Enum Class一般符合下列规则:

  • 私有构造函数,保证外部无法创建类实例(同时也使得类无法继承)。

  • 静态只读实例字段表示常量。

  • 重载操作符"==",保证序列化后的值也能比较相等。当需要在进程间传递(如分布式应用)或需要序列化时,必须实现"=="操作符的重载。

  • 改写Equals方法,保持"=="行为和Equals一致。(改写Equals一般也同时改写GetHashCode方法 )

除此之外,还通常改写ToString方法以提供显示友好的名字,因为Java和.Net都在绑定或显示对象时使用ToString方法(Java中为toString方法)输出作为缺省的对象显示字符串,比如将Sex数组绑定到ListBox或者使用Console.Write输出时。下面的代码改写ToString方法以提供友好显示的输出:

public class Sex{
... ...
public override string ToString() {
return sexName;
}
}

当然我们也可以利用ToString提供本地化支持,返回本地语言的字符串。

Enum Class另外一种常见的职责是提供不同值系统之间的类型转换,如当从数据库中读取值时,利用Parse方法将数据库中值转换为对象系统的常量实例,而在存储时提供方法转换为数据库的值类型:


public class Sex{
... ...
// 根据一个符合指定格式的字符串返回类型实例。
public static Sex Parse(string sexName){
switch (sexName) {
case "Male" : return Male;
... ...
}
}

// 返回数据存储的值。
public string ToDBValue(){
return sexName;
}
}

使用Enum还是Enum Class?

根据Enum和Enum Class的特点,我们可以根据对常量类型的要求决定使用Enum还是Enum Class。

以下场景适合使用Enum:

  • 常量类型用于内部表示,不用于显示名字。

  • 常量值不需要提供附加的属性。例如只需要知道国家代码,而不需要获得国家的其它属性

  • 枚举值允许组合(即支持位操作)。


Enum Class可以适用于更多的场景:

  • 常用于可提供友好信息的类型。如本地化支持的类型名显示,或者显示与枚举名不一致的名字,例如Country.CHN可显示为"China"。

  • 提供更多的常量属性。

  • 提供更加丰富的行为。如Parse方法。

  • 对常量进行分组。如Country.Asia包含亚洲国家。

使用Struct来表示枚举

如果值域不封闭,但希望提供一些常量,也可以使用struct,如System.Drawing.Color结构中的系统默认颜色设置。采用struct来设计enum值同Enum Class方式没有本质的差异,只是struct默认提供无参数构造函数,因此无法实现封闭值域。


-------------
[Joshua01] Effective Java Programming Language Guide , Joshua Bloch, Pearson Education,2001.Java 高效编程指南(中文版),机械工业出版社,2002

2007年6月1日星期五

零.壹.无穷规则(Zero-One-Infinity Rule)

这是来自于黑客的行话(jargon),对于软件设计来说还是很有借鉴意义的一条经验原则,作为一种推演法的思考方式,相信对于其他领域应该也是有所裨益的。

黑客行话档案(Jargon Files)这样介绍的:


"不允许foo实例,允许一个foo实例或者允许任意的foo实例。"(译者注:软件开发者通常使用foo作为类设计的示例),一个软件设计的大拇指规则(Rule of Thumb),告诫不要随意的限制一个给定实体的实例数(例如:窗口系统的窗体数,操作系统文件名的字符数等)。更明确一些,一个实体要么完全不允许,要么允许恰好一个实例(一个“特例”),或者允许任意用户需要的——如地址空间或者内存使用。

隐藏在规则背后的逻辑是指在大多数情况下可以很清楚的知道需要一个而不是无。然而,如果更进一步,允许N(N>1),那为什么不是N+1呢?如果允许N+1,那为什么不是N+2?一旦超过1,并没有理由不允许任意的一个N;因而,就应当是无穷。

很多黑客还记得艾萨克.阿西莫夫(注1)的科幻小说"The Gods Themselves"中的一个人物说2是不会发生的——如果你相信有超过一个的宇宙,那么你也可以相信有无限个宇宙。


原文参见:Online Jargon Files

真的不存在"2"或特定的"N"吗?自行车有两个轮子,但也有三轮的自行车,四轮也未尝不可,似乎合理。但性别呢?方向(东南西北/左右/上下)?似乎在我们的认知条件下或从某种角度,也存在一些事物有固定的实体数量。

在数学和哲学领域我们可以很容易的表达无穷,它只是一个符号或一种象征,但在真实世界里通常是无法表达的。地球上的各种物质资源都是有限的,宇宙恐怕也是,即便一颗遥远恒星的重量超过我们的想象。有名的计算机"千年虫"问题,因为当时的设计者关于日期位数不周全的限制而带来了一场二十世纪末的恐慌,那么现在是否就彻底解决了这种限制了呢?现在的计算机时钟几百年、几千年后也不会再出现这个问题,但是千万年、亿年后呢?目前采用的年数表达方式也会出现"千万年虫问题"或者"亿年虫问题",因为计算机的资源是有限的,并不能真正表达一个无限的日期。就这个问题来说,这种考虑是没有必要的,没有人会担心这个问题——如果一百年或者几百年不会出现问题,那么就解决了,遥远的未来谁想去知道呢?大多数时候,“无限”并非真正需要。

在设计中考虑Zero-One-Infinity规则,有助于系统获得更好的扩展性。例如一个软件要适应两个领域或被五个用户使用,一个模块被两个或更多的地方调用,那么就应当考虑适应更多的领域,满足更多用户的通用特征,为模块提供扩展的机制以便更多的地方可以调用。

因此,我们在设计时考虑Zero-One-Infinity规则以消除对将来扩展不利的限制,而实现时则需要考虑在有限资源和价值下的"Infinity",采用有限的方式来表达无限,例如根据请求分配内存(对用户感觉是无限大)、无上限集合,一个足够大的数组,以及可扩展的结构。

很多经验开发方法,例如极限编程(eXtreme Programming),还强调平衡考虑扩展性的时机和代价,从简单设计开始,考虑当前的需要(1或者N的情况),当出现冗余设计(N+1)的时候,才决定是否设计为可扩展的结构(Infinity)。重构(Refactoring)的一种有效做法就是发现冗余代码(N>1),将它们通过某种形式提取出来,以便更多(Infinity)的地方使用。这也是人力、时间、机会成本等有限资源下对“无限”的实效价值观点的表达方法。

其实这条规则在现实生活中也有可参考之处。史蒂芬.茨威格(Stefan Zweigs)在《昨日的世界-一个欧洲人的回忆》中曾提到,他到十月革命后的俄国去旅行时,发现俄国人喜欢批条子、签文件而不是像奥地利人那样通过制定法律来处理事务。在我看来,批条子干的就是一个又一个“N”的事,而制度则可以应付“Infinity”类似的事务,当然现实和人远比这条规则复杂得多。

-------
[1].艾萨克.阿西莫夫(Isaac Asimov,192O-1992) ,美籍犹太人,为本世纪最顶尖的科幻小说家之一。“基地”、“机器人” 等系列是艾西莫夫最脍炙人口的代表作。2004的科幻电影"I, Robot"即改编自阿西莫夫的机器人系列。

2007年5月31日星期四

Windows 2003 Server的TOMCAT的内存池配置问题

最近在Windows 2003 Server上安装Atlassian的Confluence时,发现Conflucence经常引起Tomcat 100%占用计算机资源,经查资料发现是TOMCAT(ver5.5)的Java虚拟机内存配置问题,其缺省配置为128m,对于一些大量页面访问、会占用大量资源的应用,容易导致宕机。

解决方案:增加Tomcat的JVM的Maximum memory pool的值,可根据具体机器的配置增加,在我的1G的机器上将其改为512M后,系统运行正常。

Step:

  1. 双击托盘上的Apache Tomcat图标(如果未出现,请先选择程序菜单Apache Tomcat -> Moniter Tomcat), 打开Tomcat Monitor的属性页面。

  2. 选择Java属性页。

  3. 设置Initial memory pool和Maximum memory pool的值。

  4. 确定。重新启动即可。

2007年5月30日星期三

Occam's Razor(奥卡姆剃刀)——以及软件设计中关于"简单"的相关性话题

奥卡姆剃刀(Occam's Razor, Ockham's Razor)是由14世纪逻辑学家奥卡姆的威廉提出的一个原理。奥卡姆(Ockham)在英格兰的萨里郡,那是他出生的地方(后人习惯称之为 "William of Occam")。这个原理称为"如无必要,勿增实体":

Entities should not be multiplied unnecessarily

此外,还有更多的关于"奥卡姆剃刀"原理的阐释:

解释任何事情都不应当增加不必要的实体数。One should not increase, beyond what is necessary, the number of entities required to explain anything.

如果某一原因既真又足以解释自然事物的特性,则我们不应当接受比这更多的原因。 We are to admit no more causes of natural things than such as are both true and sufficient to explain their appearances.(Isaac Newton)

当你有两个处于竞争地位的理论能得出同样的结论,那么简单的那个更好。When you have two competing theories which make exactly the same predictions, the one that is simpler is the better.

威廉使用这个原理证明了许多结论,包括“通过思辨不能得出上帝存在的结论”。这使他不受罗马教皇的欢迎[1]。在科学领域,“奥卡姆剃刀”作为一种“parsimony of explanations”的原则,具有很大的影响,爱因斯坦、霍金都明确地表示使用了这条法则来进行阐释和分析。对于商业以及生活中,作为一种简单的实用主义原则同样备受推崇,这方面的介绍可以参见附录部分的资料[2]。

在软件开发中,使用"Occam's Razor"原理是判断过度设计的一条好的法则,我们常常为了不必要的灵活性而使系统变得复杂。另一种我常见到的情况是在进行Unit Test Case设计时,很多人并非依据代码覆盖和逻辑覆盖的原则,而是简单的数据量增加或者写多处同一种情况的测试代码,测试者一厢情愿的认为这会帮助发现更多的Bug,但这只会让人难以理解测试设计的思路。


两个关于"简单"的相关性话题:

Simple is beauty

我看一个代码设计的好不好,最初的判断通常来自于直觉,它的基础就是"Simple is beauty"。之所有说是自觉,是因为"简单"并没有一个客观的标准,依靠提供者的信息和阅读者的感觉。如果从设计中我能够捕获设计者的思想,获得设计的关键抽象,在头脑中可以形成很清楚的结构图,它们的每一部分职责清楚,个体的概念明确,之间的联系也容易理解,这会给看的人一种愉悦的感觉。当然,好的设计有很多的因素,但这是最基础的"特质"[3]。而如果情况相反,则通常都是不好的设计,意味着还没有搞清楚事物本身的特征和之间联系,或者缺乏抽象和描述能力。自然界和社会的客观规律总是有章可循,但却呈现出复杂的表象,需要设计者具有很强的抽象能力来提取事物的"本质",简化这种复杂度。人的记忆力是有限的,很难理解过多概念的系统,但人却具有很强的逻辑思维能力,善于理解抽象特征,所以,如果一个设计提供的是表象的复杂度,通常很难理解,也可能意味着没有建立好的抽象。

简单并非简陋

上面提到的情况其实指的是一个系统的设计超过了它不该具备的复杂性,也就是说我们完全可能满足同样的要求下,通过分析抽象获得更简单的设计。但正如Fred Brooks所说,有的事物是本质复杂的[4]。如果一个系统打算完成10项功能,那只完成了其中5项功能的系统相比较完成了全部功能系统要简单。

本质复杂度有时候很难通过设计达到某一种简化,而常常需要通过减少一些功能来达到简单的目的。一项功能的存在总是有其价值的,如果没有原则的简化,也许系统的设计是简单了,但因为缺少了必要的功能可能使系统无法使用或极其难以使用,简而言之,"简单并非简陋"。

要简化系统的本质复杂性,需要考虑系统的使用价值和其它制约的平衡,比如时间、财务等资源性因素,以及系统设计复杂性。系统可以完成的功能就像巫师的口袋,是看不见底的,有时候为了努力保持系统设计的简单,删减一些不必要的功能是明智的。

关于这方面,我的另一个经验原则是:

当一个功能的价值没有明确时,尽量选择简单的方案。

正如人们说的,“软件开发是权衡的艺术”,要敢于舍弃,更要善于舍弃。

------
[1].引自 《What is Occam's Razor? 》 英文版, "三思科学"上的中文版:奥卡姆剃刀(Phil Gibbs 著,杉原广 补充,柯南 译)
[2].《奥卡姆剃刀:影响全球精英命运的思维法则》,作者:罗耶 编译,中国民航出版社
[3].参见C.Alexzander的"建筑的永恒之道",Alexzander认为好的建筑都具有某种无名"特质"。
[4].参见Fred Brooks的 "No Silver Bullet:Essence and Accidents of Software Engineering"

See Also
Occam's Razor On Principia Cybernetica Web

关于一个工作流项目的简略评估报告

原文:2003/2/13
修订:2004/3/17

这并不是一次正式的评估,而是一种访谈性的非正式评估(当时称之为交流)。事情的来由是这样的,公司有一个项目组承接了国家的863项目开发一个工作流产品,部门的领导对这个东西有较高的期望,但并没有提供与这种期望相应的支持。项目的结果并不太理想,因此部门领导对这个项目组非常不满,另一方面开发小组经常加班,成员因为得不到认可,士气低落。我当时并不在这个项目组中,但我觉得这个项目的不成功(从领导的期望来说)是有很多不属于项目组的原因,作为公司的一名资深的技术人员,我希望能够做一次简单的评估,以帮助领导能够清楚地了解项目的现状、形成的原因,好的和不好的地方,能够公平的看待项目组的努力(希望如此)。但最后这份报告并没有提交,开发室的主管有一些担心,而我也尊重他的意见,只在小组成员中进行了交流。

本文的主题涉及到公司进行863项目的一些内容,希望不会成为人们抨击公司和863项目本身的工具,我的目的是提供一些关于失败的教训或值得借鉴的经验。

原文序:2月11号下午,工作流项目组成员、部门主管XXX和我进行了一次比较长的关于工作流项目情况的总结性交流,以下是我通过交流对项目的一些认识和看法,从项目的最终结果的状态、项目的目标、项目开发过程中的好的和需要改进的地方等多个方面进行了分析。

项目背景

项目来源于公司申报的国家863项目。工作流项目于2002年4月启动,12月份基本结束,但还未通过国家验收。

项目成果状态

项目并未进行同类商业化产品的比较,主要目的是通过国家的验收,因此产品仅停留于技术原型状态。项目组选取了一些典型的应用案例,并基于原来在群件系统上的工作经验和WfMC(工作流管理联盟)的标准制定了需求,开发了可运行的基于J2EE平台的模型,其中包括了工作流引擎、建模工具以及一些示例,具备了应用开发包(SDK)的基本内容。

产品化能力

鉴于时间和本身的原因,我们并没有真正进行产品的技术性测试评估,而是根据项目的一些信息来了解它的成果。相对于市面上可使用的主流服务器(如BEA/IBM)厂商产品和第三方产品以及开放源码产品来说,在实用性、成熟度、同其它技术和环境的整合度(如安全)、开发支持产品(文档、指南等)等方面还有较大的差距,毕竟这些产品的开发时间较长,并具有良好的开发基础。总的说来,离产品化还有很大的差距,有些原因不可忽略:

  • 项目组具有群件平台(办公)协作的工作流方面较深厚的背景,但对业务流程协作相关的工作流仍然缺乏对应用场景的了解,公司的项目也没有类似需求提炼出来,项目组缺乏使用场景的引导,难以产生有效、完善的产品需求。
  • 原来的项目目标是通过国家的863验收,而863验收向来比较形式化,公司也没有明确的在产品的实用化价值上的诱导。
  • 有效的开发时间和人力太少,不可能产生商业化或可实用化的产品。通用化产品必须具有相当的成熟度后才可使用,否则大量的问题会是使用者失去信心,并让项目组陷于铺天盖地的维护工作中。这个问题下面会有描述。

项目的主要问题

经过交流发现项目存在以下现象:

高层目标模糊

公司在处理863项目没有自己的立场,为863项目而作863项目,没有考虑自己的发展方向,带来的结果是公司的高层领导并不关心以863计划为基础的项目,只有项目部才关心,因为他们需要对验收负责。到公司和部门来说,这些项目大多与公司的业务方向和基础背景不同,这带来了两个不利的方面,一是项目的目标不具有实用性,二是缺乏开发的基础。

工作流项目原来的目标据项目组说上级领导的意见是:

  • 完成863项目
  • 培养J2EE人员

因此项目并不来自于自身的需求,而这样模糊的高层目标难以指导项目组完成具有真正可用性和市场化的产品。当然这些并不是在《前景》文档中反映出来的,然而是事实上的指导目标。

缺乏需求和应用场景获取渠道.

公司在过去企业应用的开发上虽然有一定的业务协作工作流的应用场景和办公协作应用的开发经验和理论基础,开发时对需求的处理也因无法找到好的需求获取渠道显得效果不是特别好,而且用例我觉得在描述这种需求上还很有局限性,无法体现应用价值和应用场景,应当增加其他的需求描述方式,当然需求渠道和应用场景是最主要的。

项目组制定了计划,但计划经常被打断,并造成计划失效

被外界干扰来源于几个方面:

  • 人员调整。项目从初期到后期,人员一半以上被调换,无法保持一个一致的设计思想,特别是9月前后,后来的人需要重复以前的工作,这种人员的变化,是项目有效工作时间减少的主要原因。9月是项目组的编码实现阶段,虽然计划仍然在执行,但事实上受到了人员变化的影响,理解/废除/改进原来设计,增加了工作量,在按时完成的要求下,项目组只有缩减需求。
  • 被抽调从事项目外的工作。在9月以前,项目组成员经常被抽调去进行其他工作,例如编写标书,项目成员认为在4月-9月之间基本上没有进行太多的工作,这段时间内的有效工作是需求和高层的设计。
  • 需求的问题也导致了在计划被打断时调整的随意性。在整个开发过程中项目人员变动较大,受外界影响也很大,出现冲突时项目选择了削减需求的策略。

计划存在阶段,但成员对阶段的结束点和完成标志感觉不清楚

因为工作流项目不是一个面向直接用户的产品,而是一个面向应用开发的基础服务产品,因此没有很明确的像MIS项目的交互式功能类型的需求,项目组在需求完成后无法获得对用户价值或“可视化”结果的主观感觉。

在处理这种类型项目上,以后的项目应该注意这点。我建议采用“领航者”项目来解决这个问题,即项目需求确定一个应用项目,可以是外部的,也可以是自己根据应用场景建立起来的,关于这个问题,以后的项目可以参考《软件架构:组织原则与模式》一书。这主要是让应用来驱动项目,并让项目组感到每一个完成点带来的价值。

项目的有效开发时间短

前面已经提到9月以前项目的工作基本上提留在需求和部分的前期设计上,9月是项目的主要开发期,10月份项目成员有部分被抽调出去,10-11月集中在引擎的完善和增加外围部件。12后项目为了准备结项验收基本上结束了开发。基本上看来,项目整个的有效开发工作量约为5月*5人=25人.月,这对于像J2EE工作流应用这类产品来说太少了。

值得保持的地方

  • 每周的周例会,短小,但起到了了解计划、解决问题的作用。实际上周例会起到了项目周计划的作用。
  • 单元测试成为有效的检测手段。项目组采用JUnit的测试技术,对错误的查找、提高开发质量起到了很大的重要,另外在没有其它检测手段的情况下,可以在一定程度上反映任务的完成。建议以后在项目中推广XUnit测试技术(Java的JUnit,C#的NUnit/csUnit等)。

需要改进的地方

RUP过程组织不成功

虽然项目是按照RUP方式来组织项目,但并不成功。阶段的划分比较机械,迭代的概念并不完整,阶段的划分和迭代的周期显得不合理,这也跟项目组的计划受外界影响可能有直接关系。从目前看,RUP并没有在我们内部真正使用好,RUP过于庞大复杂,建议以后的项目要慎重,不要机械的使用RUP。

公司内的工程管理活动并没有给项目组带来实际的价值

SQA活动被要求,但并没有太多实际价值(改进活动)。这些活动包括组织(SQA部门)对项目活动和文档的评审,缺少对项目组真正有帮助价值的管理,这些是与组织的过程管理成熟度是有关的,在不成熟的组织,容易成为形式,并带来负面影响。

建立架构组负责项目的关键决策

项目组没有一个明确的架构人员或架构组,以至于在一些项目项目的整体的构架决定上无法及时决策。在关键需求的定义、应用的验证和目录数据库技术引入等决定上都有影响。但对于每一部分具体的逻辑设计,构架组的作用并不明显,因此这里的构架组主要担任一种决策的职责。

但项目组同时也建立了与系统结构一致的组织:引擎组、建模工具组、原型组,这种结构应当是合理的,符合系统的特征,只是在进行的过程中,3个小组的节奏不太一致,难以形成一个整体的感觉,如果存在构架师或构架组也可能使他们更容易形成一个整体。

项目需要就总体计划保持一致

同时也发现项目组成员对整体的计划没有很好的理解。虽然项目经理公布了进度,但因为总体计划经常变化,以至于在成员心中没有了权威性,这一点也可以说与需求的变化和外部的干扰有很大的关系。当然项目组如果能够控制变更的节奏(如两周或一月才基线化变更,而不是一有变化就引入给其他成员)和更有效的方式来反映进度(如进度图表、测试通过率等形式化手段),也可能会有改进。

 

说明: 本文的主要目的仅为帮助项目成员和外部人员通过项目的总结获得有价值的经验,了解项目的情况,并不证明项目本身的成功或失败。内容来自于交流获得的信息,虽然力求真实,但仍然带有很多个人的判断,需要注意。

2007年4月16日星期一

Web Service的异常处理

在 Atlassian Confluence 2.3中,如果用户和组来自于LDAP,当一个用户事先未加入用于授权的组,此时用户访问WIKI页面时会提示“Not Permitted”,但管理员将该用户加入授权的组,访问时仍然会提示未许可。这是因为Confluence使用了缓存机制。以管理员进入Confluence后,点击右上角的"Administration"打开管理页面,选择左边菜单 Administration->Cache Statistics。打开这个页面可以看到系统的缓存信息:


Confluence的缓存使用的是Orachle的Coherence技术,配置在confluence-coherence-cache-config.xml配置文件中定义的(在目录..\confluence\WEB-INF\classes下)。详细的配置定义可以参看
Coherence的配置元素参考,这里简要说明需要调整的配置。

缓存配置的问题

配置文件的根节点包含两个部分:
定义要配置的项,下表中的项是LDAP部分的


<cache-mapping> 
<cache-name>com.atlassian.user.impl.ldap.LDAPUserManagerReadOnly.users_ro</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING>
<cache-mapping>
<cache-name>com.atlassian.user.impl.ldap.LDAPUserManagerReadOnly.repositories</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING>
<cache-mapping>
<cache-name>com.atlassian.user.impl.ldap.LDAPGroupManagerReadOnly.groups</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING>
<cache-mapping>
<cache-name>com.atlassian.user.impl.ldap.LDAPGroupManagerReadOnly.groups_hasMembership</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING>
<cache-mapping>
<cache-name>com.atlassian.user.impl.ldap.LDAPGroupManagerReadOnly.groups_getGroupsForUser</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING>
<cache-mapping>
<cache-name>com.atlassian.user.impl.ldap.LDAPGroupManagerReadOnly.repositories</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING>


schema-name定义了配置表的名称为user,上面的LDAP的配置都使用user配置表,前面提到加入组无效的问题,就是因为"com.atlassian.user.impl.ldap.LDAPGroupManagerReadOnly.groups_getGroupsForUser"缓存的原因。

接下来我们看配置的缓存值,节定义各个schema的缓存值,与LDAP相关的是如下部分:


<local-scheme> 
<scheme-name>default</SCHEME-NAME>
<class-name>com.atlassian.confluence.cache.tangosol.HitTrackingLocalCache</CLASS-NAME>
<high-units>1000</HIGH-UNITS>
<expiry-delay>3600</EXPIRY-DELAY>
</LOCAL-SCHEME>
<local-scheme>
<scheme-name>user</SCHEME-NAME>
<scheme-ref>default</SCHEME-REF>
<high-units>5000</HIGH-UNITS>
<expiry-delay>300s</EXPIRY-DELAY> //定义缓存过期的时间,可以根据需要进行调整
</LOCAL-SCHEME>

user的schema使用default。从这个表看,user的expiry-delay项为300秒,也就是过5分钟后,该用户就可以具有权限访问WIKI页面了,但实际上并非如此,用户依然未授权。后检查发现,这个配置项根本不起作用!而是使用了default的设置,3600秒也就是60分钟才会过期。问题出在cache-mapping的定义上,在Cache Statistics的页面可以看到缓存定义项的名字比配置文件中的定义名多了一级:ldapRepository。这是atlassian-user.xml配置的ldap库的名字,原来Confluence内部的缓存配置项名加上了库的名字,但在默认的配置项定义中却没有,因此上面的配置根本不起作用。Confluence的文档也未提到这一点,让我折腾了很长时间!!


解决办法


解决办法很简单,就是在cache-mapping的cache-name值的com.atlassian.user.impl.ldap.LDAPGroupManagerReadOnly后面加上库的名字即可,新的配置如下:


<cache-mapping> 
<cache-name>com.atlassian.user.impl.ldap.LDAPUserManagerReadOnly.ldapRepository.users</CACHE-NAME>
<scheme-name>user</SCHEME-NAME>
</CACHE-MAPPING


修改后重启Tomcat即可。

随便谈谈——rup、开发过程、cmm

原文:2002/7
修订:2004/2/19

这是以前在XPChina论坛上贴的一些旧文,后又贴在了UMLChina上。当时的公司刚做完CMM3不久,因为自己不但是CMM过程改进组和评估组的主要成员,同时也是一个一线的项目经理和开发成员 。软件开发者是痛苦的,所以他们总是梦想是有像“葵花宝典”一样的神奇功法可以拯救自己,比如说“规范的开发流程”,比如说CMM,但在无数次的尝试之后,我想,也许那不过是个silver bullet。最近一年多在开发中,逐渐使用像FDD和XP的一些实践,开始觉得,也许不提“方法”,那些来源于开发实践中的不那么宏伟的东西似乎更有效,像JUnit之类的东西。不过现在整理以前的旧文,也算是回顾一下当时的思路历程吧。

2002年在XPChina贴出之后,网友grantlee提出了一些质疑,而且《随便谈谈..》似乎意犹未尽,于是又贴了续文《随便再谈谈...》。还有一点需要说明,既然“随便谈谈”,看官需不必太苛刻。


XPChina帖子链接(http://xpchina.smiling.com/group/posts/view_forum.ecgi?group_id=9999&res_message_id=1101784)


现在开发软件的人员说到开发流程,基本上都是rup/xp,xp没使用过,不敢说,这里说一说我对rup的使用经验。

根据我的使用经验(1.5 years),rup并不是一种强方法论,更多的是包括了大量的关于软件开发的流程和设计指南,没有谁会直接使用rup的流程,实际上只有理解好rup的活动思想,自己剪裁、组织合适的流程,才可能使用比较有效,但这需要很长的学习曲线和昂贵的学习费用(包括OOAD/RUP,不是一个人而是一个团队),当然自学也是没有问题,如果本身有很多软件开发的经验。

所有我同Rational的人交谈的时候说,虽然RUP号称适合于小项目,但那是在忽略了学习周期和成本的前提下,也就是说,如果你的团队或组长很熟悉RUP,那么它当然适合小项目(可以有效地剪裁并向成员培训),否则在实际的项目中使用就要小心了。但无论怎样,都可以把它看作关于OOAD的设计指南和流程的一般性建议,就像XP的很多原则也可以适合于其它环境,不过话又说回来,软件开发经验的本来就是怎样,不一定非要属于某种方法。

关于软件流程和CMM

过去我觉得正规的软件企业应当有一套完整的开发过程,现在我更倾向于认为软件开发的最佳实践对项目更有价值,包括开发方法、管理的,如有效的计划、需求方法、同行评审、团队的面向对象的经验。主要我觉得国内软件企业不太可能建立真正有效的角色清晰的资源结构和成熟有效的过程框架,国内软件企业热衷于CMM而不太关注于其具体效果(我参与了我们公司CMM3的评估,评估小组成员);关注于纸质的规范,而不关注约定对质量的提高;盲目追求一套流程,而忽视流程是否真正有用。

作了这么长时间的过程改进和软件开发,我觉得对于国内的企业来说,xp或者其它最佳实践方法更容易给开发团队带来实际的价值,因为这些由一线经理和开发人员,CMM不过是高层管理的招牌,他们大部分人实际上不懂软件开发,或者没有参与一个完整的软件开发过程,谁又会期望他们给开发团队带来的是开发的利器。

grantlee:我也随便说说

XPChina:我也随便说说

1、RUP不是方法论;00才是方法论,RUP/XP只是工作流程罢了。
2、过程的改进不是一朝一夕的事情,流程也有一个制定的过程;在这个过程中应该是首先有流程,然后才是流程的逐渐实施。
3、不管采用什么方法,对于企业的目标是一定的:创造价值;所以使用什么方法还是和当前团队的能力相关,如果带领着一组清一色的学生你无论如何完成不了一个大的项目(当然你通过几年的培训另说了)。

随便再谈谈——rup、开发过程、cmm

UMLChina帖子链接(http://umlchina.smiling.com/group/posts/view_forum.ecgi?group_id=9986&res_message_id=1135234)

我不知道rup是不是不能叫方法论,但我想既有分析设计的方法,也不妨有流程的方法,不过也许你是对的,但我只想把我使用rup的一些感受同大家交流。

我同意grantlee的话“过程的改进不是一朝一夕的事情,流程也有一个制定的过程;在这个过程中应该是首先有流程,然后才是流程的逐渐实施。”,但我分为两个层面来说明一下,一是如何的循序渐进, 二是制定怎样一个流程来执行。

首先循序渐进的做法是没有问题的,开发的流程不可能一下就完善起来(永远也不会有没有机会改进的时候),但重要的是怎样的循序渐进,以什么样的目标的渐进?那国内的企业是以什么来作为改进的目标呢?我想可能主要不是质量目标,而是评估的目标吧,倒不是说企业领导者不愿意看到质量的改进,这里面有些问题:质量的改进并不像证书那样被大家认同,同样也就不被管理者认同。实际上没有几个管理者是真正关心质量和开发的生产率这些话题,他们渴望市场和媒体的认同,这必然会带来一些轻浮的做法和浅薄的目标:通过CMM评估,而且它的高层需要也是基于市场和同行的压力(你说鼎新都过二级了,联想怎能不过呢?),这样的改进能带来什么呢?可笑的是,CMM的宗旨是帮助企业评估它的过程能力或者叫开发成熟度,它不愿意被看作是一个标准或认证,所以它叫评估而不叫认证。当我们通过CMM3我仍然不知道我们的项目的控制范围有多大,测试覆盖率是多少;当我看到众多的国内软件开发企业在2001年接二连三的通过CMM评估时,我知道我们都在玩弄CMM。国外到CMM2极可能需要几年的时间,CMM2到CMM3需要1.5-2年的时间,而我们从一个连SCM/Baseline都没搞太明白的公司,用一年的时间通过了CMM3(实际时间更短,不敢说出来),这又是怎样的渐进呢?

其次说说RUP,当然CMM同RUP并没有多大的关系,但CMM3要求要求组织级的开发过程,项目根据组织级的开发过程进行剪裁来获得项目的开发过程。以前我不知道国内到底有什么成熟的流程,所谓的结构化实际上也是很模糊、不完整的概念,RUP因为具有了开发过程所需要的流程、指南、模版,这些都是CMM,也是软件开发过程所需要的元素。因此我们就通过基于RUP来定制自己的开发过程,但后来发觉使用RUP有些问题:

  1. RUP的活动太多,并且没有严格的活动界限,对于初学者来说很难理解那些活动应该取舍,项目经理在半年后仍然无法熟练地对RUP进行流程的重新组织。
  2. 虽然RUP声称不一定要依赖OOAD,但事实上RUP是离不开OOAD,它本来就是从Objective流程改进而来。但对于组织来说并不是所有的项目都适用于OO的。

但是总的来说,我还是认为RUP比较复杂,并且项目也不能花费太多的学习代价,所以我还是希望能够有更加容易理解、有效的流程。

对于人,我觉得虽然我们并不是实际用应届毕业生(指没有经验的)来开发,但实际上对于严格的流程来说,我们可以说不是训练有素的人员,所有也有人说国内的职业工程师太少,这时候也更需要贴近开发人员,容易在过程中改进的流程。

同时我也觉得CMM确实需要很多管理带来的成本,不是太大的软件企业是否值得付出代价(虽然有些企业是大型企业,但开发团队从整体上讲却是小企业),这时候注重项目成败的流程会更有价值。

Persistent Data Object(PDO)-基于.Net的O/R映射框架


PDO是一个试验项目,已停止开发。O/R映射框架推荐使用NHibernate



持久化问题(永久存储信息的访问)是面向对象开发和企业应用开发需要面临的问题,而关系型数据库是目前保存数据的有效手段,因此如何在对象层(Object)和关系数据库(Relational)之间建立一种好的机制,可以更有效的改进开发的效率和质量。PDO(Persistent Data Object)是适用于Microsoft .Net环境下的O/R映射方式的持久性处理框架。PDO能够使处理持久化数据(需要保持在关系型数据库中的数据)采用"持久化对象"的方式,对于面向对象设计来说是处理对象持久化问题的自然方式。在传统的开发方式中,通常需要书写代码使用像ADO或Microsoft .Net Data Provider等数据访问引擎来读取、更新和增加数据,需要维护关于底层数据库的信息。然而PDO对数据以对象和属性的方式进行处理,而不再需要了解如何处理表的纪录,以及数据的来源等底层的数据库信息。

PDO同其它O/R框架不太一样,使用基于客户端的状态管理,是一种直接支持客户/服务器模式的O/R映射和设计框架,着重于分布式应用。有关PDO构架的详细介绍参看PDO Developer Guide

PDO采用C#开发,开放源码。

Features

1.0.0 2004-5-10
(new)

  • 重新改写底层代码,性能改进
  • Delay Loading支持延迟属性的装载和更新
  • 支持多种数据访问方式
  • 支持多数据源定义
  • 增加XML映射支持
  • 改进的Attribute映射方式使映射定义更简单
  • 删除MCC模式支持库。原来的MCC库管理对象状态使整个结构显得复杂。
  • 在核心库中增加客户段状态管理支持。
  • 支持更加灵活的自动值属性处理。使PDO能够支持包括Microsoft SQL Server和Oracle等主流数据库的自动主键创建方式。

0.4.1

  • 项目从VS.Net 2002 升迁到 VS.Net 2003,单元测试工具改为使用NUnit,以便更好的同其它开源工具集成,如NAnt。

下载

开发

  • PDO需要在.Net Framework 1.1环境下使用。
  • PDO设计时考虑了CLS兼容,但并未进行兼容性测试,如果在C#以外的语言中发现无法正常使用,请通过邮件报告发现的问题。
  • 源代码使用Microsoft Visual .Net 2003开发环境开发。其它的辅助工具参看源代码包的readme.txt。

[lastest updated:2004-5-10]

基于电子邮箱的虚拟文件存储空间:emboxFTP

关于Kangaroo emboxFTP

Kangaroo emboxFTP是一个采用Java编写的基于电子邮箱的虚拟文件存储空间。利用emboxFTP可以方便地在邮箱中保存个人文件。

邮件文件系统MFS(Mail File System)




常规情况下电子邮箱用于收发电子邮件,emboxFTP通过扩展电子邮件的头信息(Header)来保留文件信息,使用附件来保存文件,形成了一个简易的基于邮件的邮件文件系统MFS(Mail File System)(见上图)。

MFS具备一般的文件系统功能,如:
  • 新增/删除文件
  • 通过扩展java.io包的MFInputStream/MFOutputStream读写文件。
  • 列出文件列表
MFS将文件拆成小块发送并存储到邮箱中,MFS支持用多个邮箱组成一个大的空间。
当前版本还不支持目录结构。

FTP服务

为了便于使用MFS,emboxFTP通过FTP服务的方式来提供访问接口。emboxFTP建立一个FTP服务器来访问MFS,通过FTP服务,我们可以使用标准的FTP客户端如NetAnts(下载)、

  • cuteFTP(上传/下载)等管理邮件文件系统中文件,如上传、下载、删除文件。
  • emboxFTP支持FTP端口的用户的配置,参看readme。
版权

本软件开放源代码,可以随意使用本软件的发布版和源代码,但请注意源代码中的声明。

下载
安装
  • emboxFTP采用Java编写,需要运行在JDK1.3以上,如果你的机器未安装JDK,请在http://java.sun.com/j2se/1.3下载。
  • 安装后首先阅读readme,并进行邮箱和ftp的配置,然后运行run.bat。
文档
  • emboxFTP开发特性表(虽然使用的是FDD(Feature-Driven Development,一种敏捷开发方法)的术语,但并不是按照FDD来定义的)。

关于构架的一些观点

12月5日聊天备忘——关于构架的一些观点

2002/12/10
参与者:张玮、魏猪头、marshine

12.5日晚,我和两位同事又来到KFC,来几杯可乐,找一个靠窗的位置,就开始天南海北的神吹。这是我们一些简单的乐趣,高升桥那家KFC是我们常去的地方。
在项目中,平常我们都会谈论关于构架的话题,然而“到底什么是构架?”,有人提出来。是啊,到底什么 是构架呢?有人说构架是项目的关键问题的决策,有人说构架软件逻辑结构的高级抽象;RUP中的构架是4+1视图,XP认为构架可以用隐喻(Metaphor)来表示,FDD看重领域模型在架构中的位置,也就是说,软件开发者时常挂在嘴边的“架构”(Architecture)并不容易找到一个标准的概念。
我个人比较同意“构架是项目的关键问题的决策”的观点,它应当有多个剖面(Profile),也就存在不同的对于架构的描述,取决于关注者的重点。
我们开始在各个方面讨论关于构架的一些概念,以及对项目的影响。后来张玮把它整理出来,这便是这篇小文的由来。


从某种程度上说,系统本身就是架构,那为什么还需要提出架构这种概念甚至由某个或某几个专门的架构工程师或小组来负责主要制作并维护这些架构呢? 实际上,不论是否有架构工程师、专门的制作架构的活动或架构文档,任何一个系统都是有它的架构的。 就好象大多数文章,不管它是否有目录,是否有导读,它都是有自己的结构的。架构是系统的形或系统的神;同时架构(在大多数情况下)还可能影响项目组成员的组织;架构可以起到提纲契领的作用。

人们需要架构来:

  • 了解系统--了解架构的目的就是(从某一个角度)了解系统,因此从这个角度上来说,架构就是为了交流(比如说某人对房子最关心的就是它的建筑面积),既然是为了交流,当然需要一个顶层的描述,从而建立起对系统的总体映象。
  • 做出并表达重大决策--描述架构是为了描述系统,但只有重大的决策才被列入架构。而这种决策是否重大,实际上从决定本身而言,项目中每个决定都是一样重要的,因此我们说某个决策是否重大,实际上是说这个决策对受众影响的重大程度及影响的范围是否更广,或者说要改变这种决策的难度的大小。
  • 平衡协调--预先制作架构是因为架构影响的范围广,程度深。范围广就意味着需要多方协调,同时架构还意味着对多方进行权衡(平衡),比如对多种需求多种技术进行权衡等。架构还可以用于多个小组之间的协调,它是多个小组之间的协议,同时它还包含多个小组共同的守则。有专门架构小组或专门架构师是因为架构是对多方面需求多种涉众要求进行权衡并做出的重大决定,而这些因素互相约束,互相牵制并互相影响,因此需要有专门的人进行统一的协调(集中制),当然也可能所有影响深远的决定由团队所有成员一起做出,统一进行协调(民主制)或有一种解决冲突并协调的机制(如隐喻,民主集中制等)。
  • 屏蔽影响--架构稳定就意味着影响深远及需要多个地方都发生改变的变化不会频繁出现。
  • 目录--就象一本书,需要一节前言来说明书的结构及表达的方式,如果你要想快速找到文章中关心的某一项内容,一般来说,你需要先查目录,架构也可以起到目录的作用。

认识到架构的这些特点,我们可以在应用中更好地使用架构:

  1. 架构就是系统,系统就是架构
    既然描述架构就是描述系统(中的重要元素),因此一切描述系统的手段都可用于描述及表达架构。
  2. 架构是重大决定,因为它影响深远
    1. 因此架构的确定方法与组织中确定大事的文化应相符,比如组织是一种民主制,则宜用民主制的架构方式,组织是集中制,则宜用集中制,如是民主集中制,则宜用民主集中制
    2. 架构元素应捡重要的元素说,所谓重要的元素,是指
      1. 共同的元素,

      2. 变化涉及多个部分的。

  3. 架构用于交流
    1. 因此描述架构的方法应用易于理解的方式来表达架构,比如简单,直接的方式,图画甚至动画等一切手段。
    2. 因此架构应该是面向不同受众的,比如用户和设计人员看待架构的角度就不会一样,他们的关注点也不会一样。比如一幢房子,用户可能比较关心地段、户型、面积等,而开发商可能更关心成本及收益等,而建筑商可能更关心其中的通用件是否多,是否容易施工。换句话说,如果给你十分钟对初次接触系统的人描述系统,对用户和对设计人员,编码人员,测试人员等你所讲的话肯定会不一样,但是它们又都是描述同一个东西的。

  4. 架构用于平衡协调
    1. 因此架构中应包含这两部分内容:划分的部分及各部分之间的接口,各部分之间共同的约定。

    2. 如果项目组本身成员之间的关系就比较和谐,协调比较方便,重大事项的决定可以集体决定,就可以没有专门的人负责维护架构(但并不是没有人维护架构,没有人维护和所有人维护是完全不同的)。

    3. 架构工程师需要有(技术上及人员组织协调的)经验。各种技术的平衡及协调也是架构工程师所需要做的一件重要的事情。

  5. 屏蔽影响一个好的架构应使系统趋于收敛,意即架构使系统变化引起的改变及影响越来越小。


附录:一些关于构架的描述

"Architecture" is a term that lots of people try to define, with little agreement. There are two common elements:One is the highest-level breakdown of a system into its parts; the other, decisions that are hard to change. It's also increasingly realized that there isn't just one way to state a system' architecture; rather, there are multiple architectures in a system, and the view of what is architecturally significant is one that can change over a system's lifttime.From time to time Ralph Johnson has a truly remarkable posting on a mailing list, and he did one on architecture just as i was finishing the draft of this book.In this posting he brought out the point that architecture is a subjective thing, a shared understanding of a system's design by the expert developers on a project. Commonly this shared understanding is in the form of the major components of the system and how they interact. It's also about decisions, in that it's the decisions that developers wish they could get right early on because they're perceived as hard to change. The subjectivity comes in here as well because, if you find that something is easier to change that you once thought, then it's no longer architectural. In the end architecture boils down to the important stuff-whatever that is.

---Introduction,Page 1.Patterns of Enterprise Application Architecture. Martin Fowler. Addison Wesley Published,2003

(2003-2-10)

Abstractly, software architecture involves the description of elements from which systems are built, interactions among those elements, patterns that guide their composition, and constraints on these patterns and interactions among those components. Such a system may in turn be used as a (composite) element in a larger system design.

---Chapter 1:Introduction,Page 1.Software Architecture:Perspecitiv On An Emerging Discipline .Mary Shaw,David Garlan. Prentice Hall Published, 1996 [清华大学出版社影印版,1998]

(2003-2-17)

CodeMetric项目

关于CodeMetric

CodeMetric是一个采用C#编写的代码度量程序,当前版本可以对C++/Java/VB/Delphi语言的代码行进行统计。
在开发过程中采用了TogetherJ 6作为UML的建模工具,并使用csunit作为单元测试的框架(csunit是属于XUnit的C#单元测试框架)。
虽然CodeMetric只是为了熟悉C#而开发的一个小软件,但CodeMetric在设计上还是充分考虑了系统的扩展性,可以很容易的扩展支持的语言和度量项。
同时CodeMetric支持统计的结果作为简单文本方式输出和XML方式输出。可通过制作不同的样式表来改变统计结果的输出效果,但当前版本没有提供确省的样式表。参看体系架构文档

下载

安装

CodeMetric采用C#编写,需要运行在.Net的CLR环境中,如果你的机器未安装.Net的运行环境,首先下载Microsoft .NET Framework Redistributable(.Net运行环境)。

文档

About Marshine

出生于四川安岳(号称石刻之乡),居住在成都,现在北京一家软件开发公司进行.Net应用开发。都说成都生活气息较浓(另说成都人比较闲散),不过我倒觉得有一种自由与轻松的感觉,当然还有美食和好玩的地方。
92年开始就读于郑州纺织工学院电气工程系自动化专业(学校现已改为中原理工大学,本想改得更为大器,却更像一所民办大学,就像什么黄河大学)。大学4年,生活简单却也自得其乐,上课、踢球、喝夜啤酒、看录像。日常生活基本没有异色,倒乐得清闲看了大部分计算机专业的教材。95年通过计算机程序员水平考试,算是大学里觉得骄傲的一件事;平常成绩平平,普遍60出头,偶尔70挂零,颇为中庸;自从小学里当过小队长,在事业上再无建树,大学当过微机原理科代表,只负责收作业、擦黑板,不能算是突破。
96毕业后到成都的一家小研究所从事电子产品开发,9个月设计电子线路,焊接电路板、调试电路,很辛苦,却也有创造的乐趣,后觉得电子产品设计太难,环境又不够好,没有成就感,灰心失意而去。
97年帮一个朋友做一套房地产管理软件,用的是Foxpro。独自呆在一个房间里3个月没日没夜的开发,出来后疯狂的想找一个公司,不为别的,只因这样白天有同事可以聊天。软件中骄傲的部分好像是居然用VF写了一个显示AutoCAD矢量图像文件的程序,要拿到现在,早到网上下载控件去了。这套软件算是我职业软件生涯的开始吧。
之后进入XX集团,开始使用Visual Basic开发一些小型的应用。1999年开始开发企业端的应用,这是我的第一个具有挑战性的项目:在4个月时间内开发一个同城客运车站联网系统(市场人员就是这样,那管开发者死活)。项目要求4个车站能够相互联网售票,并且工商银行能够连接到系统中,利用其终端售票。现在想来那个项目充满了风险,5、6个人,几乎不熟悉所有的关键技术:DCOM结构(当时MTS才2.0,还有很多问题)、SQL Server、分布式系统,Visual Basic语言熟悉的人也很少,按照Edward Yourdon的话就是一个“死亡之旅”。项目最后还是按照计划在2000年1月开始上线,虽然问题不断,系统基本上还是可以运行,紧接之后的2000年,我们利用10个月的时间开发了全新的2.0系统,并对原来的系统进行了升级。“死亡之旅”的结果是一半的人离开了公司,不过现在想来,当时的团队的气氛是很和谐的,积极性也很高,至今仍叫我怀念。
接下来几年,忙忙碌碌,时而关注什么公司发展的大事,有时也为“软件的未来”之类的事情做一些毫无意义的担心。关注软件工程那阵子,先是参加微软的MSF培训(Microsoft Solutions Framework,2000年),觉得没什么意义。然后是RUP,后来参加了公司的CMM3的改进和评估,越来越觉得国内的软件公司浮夸的多,真能进行软件过程改进的少,能有条理做好的我就看不见了(视野不够) ,所以对那些CMM什么的就再没多大兴趣。不过对怎样有效的使用像RUP和XP/FDD这些工程方法来进行小型团队的开发还是有点意思的。 基本上来说我没有固定的流派,有RUP/XP/FDD综合的影子。
2003年有过短暂的创业历程,和一位朋友一起开发基于.Net的字处理风格的XML文档发布系统,因为缺少资金的支持,难以维持生计,与年底时候终止。项目因为开发字处理内核花了大量的精力,希望这部分有机会能够得到发展。
平常主要研究研究OO和互联网方面的技术;喜欢使用Java,现在主要使用C#进行.Net应用的开发i;XML在刚出来时就很喜欢,不过现在被炒得成了时髦用语。因为所开发的项目大部分是企业应用,当然也离不开J2EE和.NET企业架构,以及数据库方面的技术。
跌落到一处枯叶覆盖的深坑,希望那儿曾经有先行者留下的提示;偶有感悟的惊喜,希望能与朋友谈谈,即便那不是真理。一些实践中的心得与各位交流。
联系:
marshine@163.com
http://www.marshine.net/