2009年6月12日星期五

《彩色UML建模》译者序

人们的学习都是从模仿开始的。学习书法时,一种重要途径就是临贴。学习围棋时,一种重要途径就是打谱。学习面向对象分析和建模时,对应的途径在哪里?

开发者希望看到真实世界中企业级开发的例子,而不只是ATM机的UML图。如果您是一名建筑设计师,您有机会看到金茂大厦的设计图纸吗?我们会看到最后的产品,但对产品得到的过程一无所知。模型并不重要,重要的是得到模型的过程。我们希望听到更多的设计大师的自战解说。开源让我们有机会看到优秀的代码,而这本书让我们有机会看到优秀的面向对象分析模型,以及建模的过程。

建模能帮您做到什么?或者说,您为什么要建模?对我来说,建模是为了让软件开发更简单一些。虽然让复杂的事情变得简单并不容易,但这本书却做到了。通过4种彩色的架构型,让我们能够迅速地对复杂的业务领域建立起简单的模型。

系统分析师需要“体察千行百业之要义”,这本书对企业组件的全景式描述,为我们提供了学习的典范。

Booch在他的《面向对象分析与设计》一书中说,所有成功的软件项目都有两个显著的特点:一是很强的架构愿景,二是迭代增量式的开发。彩色UML和FDD做到了这两点。

一本好书会改变您对事物的理解,从而改变您的行为实践。对我来说,这本书就是这样的。每次我看到其他人给出的领域分析模型,都会用彩色UML的方法去印证,结果总是能得到更好的模型。

这本书是一本值得反复临写的贴,是一本值得反复打的谱。

在这本书的翻译过程中,我学到了很多,因此郑重地向大家推荐它。如果这本书对于您改进软件开发实践有所帮助,我将十分高兴。

2009年6月10日星期三

Top down还是Bottom up?

设计程序或写程序,您是习惯自顶向下还是自底向上?

一个朋友曾对我说,很感谢父亲小时候对他的作文训练。训练的方法是这样的:扩写和缩写。把一句话扩写成一段话、一篇文章。把一篇文章缩写成一段话、一句话。这就是所谓的演绎和归纳,也是自顶向下和自底向上的方法。

Alistair Cockburn在《编写有效用例》时也表达了这样的观点:所有的系统都可以归纳为一个最大粒度的用例,即“使用XX系统”。然后我们再把它逐步细化,得到“天空级用例”、“海平面级用例”、“水下级用例”。

这是自顶向下的分析方法。但也有不同观点。

Martin Fowler在《设计已死?》中传达出这样的观点:告诉我一个一个的用户故事,最后我会给你一本故事集。这本故事集就是它存在的意义所在,你不必事先告诉我这本故事集“一言以蔽之”应该怎么说。我可以通过不断的消除重复和重构,得到高级的抽象概念。

但是Grady Booch在《面向对象分析与设计》中告诉我们,人们对客观世界的认识往往是从中间的抽象概念开始的。例如小孩在认识动物时,开始是认识狗,然后会向下认识拉布拉多、吉娃娃,向上认识哺乳动物、动物。

我们在写文章时也是如此。先会有一段素材,让你在脑子里闪过一个念头,涌出一股写作的冲动。然后你开始仔细思考,提练更深刻的主题,寻找更多的素材,向抽象和具象不断探索。

我在设计系统时也是如此。

2009年6月9日星期二

RESTful的codebase

我们的代码集曾经保存在不同的地方,现在终于进入了云端。Bespin已经在云上做IDE了。

毫无疑问,代码是很多人关注的一项资源,而资源可以用RESTful的方式来表现。所以可以有这样的URL:

http://www.google.com/code/com.google.inject
http://www.google.com/code/com.google.inject.Guice.java
http://www.google.com/code/com.google.inject.Guice.java/revisions
http://www.google.com/code/com.google.inject.Guice.java/authors

这样隐藏了代码集的一些实现细节,例如,它使用什么版本控制工具。
对代码的浏览可以有SourceInsight那样的效果,也就是把SourceInsight放到云端。
从此不再需要每个人都从svn下载一份代码拷贝,然后打开SourceInsight来读代码了。

爱因斯坦说:想象力比知识更重要。
熊彼特说:创新,就是新的资源组合方式。

RESTful的课程表

一个连锁健身会所在全国有70多家分店,每个店都要有一张课程表。课程表中包含一周的课程、操房、教练等信息。如果客户感兴趣,还可以进一步了解课程介绍、操房介绍和教练介绍等信息。课程表会不定期进行变更,客户可以在网页上查看,也可以下载PDF文件。

如果按RESTful的风格进行设计,可以考虑这样的URL:
http://www.HealthIsOne.com/schedules
http://www.HealthIsOne.com/shanghai/citychamber/schedule
http://www.HealthIsOne.com/coaches/troy.zhu/
http://www.HealthIsOne.com/shanghai/citychamber/rooms/room2
http://www.HealthIsOne.com/courses/YugaIntro

返回的内容可以协商,根据不同情况返回HTML、XML、JSON、PDF或图片,也可以有这样的URL:
http://www.HealthIsOne.com/coaches/troy.zhu/photo
http://www.HealthIsOne.com/coaches/photos
http://www.HealthIsOne.com/shanghai/citychamber/coaches/photos

课程表会变更,就意味着有历史课程表,所以可以有这样的URL:
http://www.HealthIsOne.com/shanghai/citychamber/schedule/20080305

这样做的好处在于:
1、以业务资源为中心,隐藏一切实现细节
2、应用和数据回归Web本质
3、容易支持扩展
4、容易实现缓存机制

2009年6月7日星期日

临摩

我儿子在跟着一位老师学习书法。老师的教法很简单:在教授了点划的写法之后,就是描红,然后是临贴。楷书临的是欧阳询的《九成宫》,一开始自然是临不像的。笔划弯弯扭扭,字歪歪倒倒,还会把墨弄得纸上到处都是,甚至弄到手上和脸上。老师没有不耐烦,每次发现儿子有写得进步的地方,就用朱笔画圈,写得明显漂亮的字就画五角星。然后要求家长督促,回家以后一定要每天练习,经常说“一日不练三日空”、“一日不练,自已知道;两日不练,师父知道;三日不练,观众知道”。老师的眼睛很厉害,如果一周没练,下一个周末去当场写的时候,他就能看出来。

就这样几年之后,儿子写的字有点像样子了。汉隶临的是《张迁碑》,行书在临《兰亭序》和《集字圣教序》。诗云:如切如磋,如琢如磨,大概就是这个意思。书法艺术有几千年的历史,从二王、索靖、张芝、智永、欧储颜柳、张旭、怀素,到苏黄米蔡、赵孟頫、徐渭、王铎,出现了为数不少的大师。不仅他们的作品值得我们反复临习,他们成为大师的艺术追求之路,也同样值得我们借鉴。

米芾的例子就很典型。米芾习书,自称“集古字”。根据米芾自述,在听从苏东坡学习晋书以前,大致可以看出他受五位唐人的影响最深:颜真卿、欧阳询、褚遂良、沈传师、段季展。米芾有很多特殊的笔法,如“门”字右角的圆转、竖钩的陡起以及蟹爪钩等,都集自颜之行书;外形竦削的体势,当来自欧字的模仿,并保持了相当长的一段时间;沈传师的行书面目或与褚遂良相似;米芾大字学段季展,“独有四面”、“刷字”也许来源于此;褚遂良的用笔最富变化,结体也最为生动,合米芾的脾胃,曾赞其字,“如熟驭阵马,举动随人,而别有一种骄色”。然后他开始学习二王。《宋史》载:米芾“妙于翰墨,沉著飞翥,得王献之笔意;尤工临移,至乱真,不可辨……”。最后,“既老始自成家,人见之,不知何以为主”,完成了自己风格的确立,大概在五十岁以后。

所以,成为书法家的过程可以总结为四个字:入帖、出帖。

张大千则是画家中的典型例子。他早年临石涛临到乱真,号称“当代石涛”。四十多岁时自费赴敦煌,花了大量时间对十六国、北魏、北周、隋、唐、五代、宋、西夏、元各朝的壁画代表作及雕塑进行了临摹。从此,张大千的画风也为之一变,善用复笔重色,高雅华丽,潇洒磅礴,被誉为“画中李白”、“今日中国之画仙”。他走的路和米芾一样:从临摩开始,到乱真,最后自成一家。

我读中学时,正好是聂卫平在中日围棋擂台赛上大胜的时候。全国上下掀起了一阵围棋热。我也去弄了一本电视围棋教程来看,我记得很清楚,我只看到了第37页,就把书一扔,找人对杀去了。杀是杀得很爽,但我那时连征子都还没学到,实战中另一个同学“教育”了我。围棋也有数千年的历史,王积薪、黄龙士、施范、秀策、木谷、吴、六超、聂马、曹李,都是我们可以效仿的大师。

一般来说,要想提高棋力,需要日复一日地做三件事:打谱、做死活题、实战。打谱是一种临摩,做死活题是一种临摩,实战也是一种形式的临摩。

另一点值得强调:实战一定要复盘。不论结果如何,要找出下得好的棋和下得臭的棋。在水平低的时候,如果有条件,最好是找水平高的老师来帮忙复盘。这就是老师的作用:虽然他的水平没有大师那么高,但是他可以告诉你练习的方法(需要去临摩哪些东西),也可以指出你最需要改进的地方。可惜我现在明白了怎么学习围棋,却没有太多的时间去练了。

如果想学习管理,可以去看看朱兰、克劳斯比、戴明、德鲁克、高德拉特、韦尔奇,再找个咨询师。如果想练习游泳,可以去看看波波夫、北岛康介、菲尔普斯的视频,再找个专业教练。

如果您是设计软件的,梦想成为一名伟大的程序员,那该怎么做?您一定已经知道了。

2009年6月6日星期六

面向对象设计与开发(两天培训提纲)

一、好的面向对象系统的特点
  • 高内聚、低耦合原则
  • 关注点分离原则
  • SOLID原则
  • Don’t Repeat Yourself!
  • 加入一个中间层!
  • 复杂系统的架构方式

二、设计模式
  • 设计模式的意义
  • 设计模式的模板
  • 设计模式选讲

三、面向对象系统开发过程
  • 从需求到OOA
  • OOA到OOD
  • 自动化测试和持续集成
  • 敏捷开发方法学

四、领域分析与建模
  • 领域驱动开发
  • 彩色UML方法
  • 案例分析

五、相关技术
  • 对象持久设计
  • 表示层设计
  • 组件化
  • AOP
  • SOA和SaaS
  • RESTful和AJAX
  • MapReduce集群

2009年6月4日星期四

《执行SOA》上市了

执行SOA--SOA实践指南

译者序

几年前,为了尝试JDK 1.5中的并发包,我写了一个多线程的网页爬虫程序,利用线程池来抓取和分析页面。

并发200个线程,每个线程从待爬URL队列中取得一个URL,取回网页,进行分析,找出其中的URL链接,再放入待爬队列。开发过程很正常,但在测试中遇到了问题。在爬了7万多个网页之后,程序开始越来越慢。凭感觉判断,有一些线程“死”掉了。

多线程的调试并不是件容易的事。这个问题很“难”再现。这不是普通意义上的难再现,它每次都会出现。但要跑到7万多URL时,才会出现。也就是说,再现这个问题的代价很大。我试过将线程池的大小退化到 1,想找出什么样的URL会导致线程死掉,但是行不通,因为速度太慢。当时的IDE也缺乏对多线程调试的一些支持。而且即便有支持,可能也不太适合这种情况。后来因为种种原因,那个程序就不了了之了。

这本书中SOA治理的思想给了我一些启发:我们需要关注服务执行的健康状况,包括服务执行的时间。例如,我们可以进行这样的改动:

在每个线程领取URL时,记录一个时间戳。在它完成这个URL处理时,再记录一个时间戳。再利用一个线程,对未完成的URL定时检查它的健康程度。如果在很长的一段时间内它还没完成,那么它就有问题。这样我们可以找到嫌疑URL。我们可以对这种URL单独测试,看看是否因为程序的原因,不能处理这样的URL。或者,我们可以把对应的线程任务杀掉,直接跳过这些有问题的URL。

如果您和我一样,是一名开发人员,学习一些SOA的思想是很有帮助的。我们可以在程序中设计一些机制,支持运营维护和故障分析,这正是SOA的一部分内容。

IT运维部门需要SOA。业务部门需要SOA。企业高层需要SOA。设想一家经营固话业务的电信公司,通过兼并和重组,拿到了一个移动网络。公司最需要的是什么?就是SOA。

这个移动网络上跑着多少应用?多少中间件?多少数据库?多少操作系统?多少服务器?它们的使用状况如何?它们由谁提供技术支持?它们是什么配置和版本?它们有哪些参数可以调整?它们支持着怎样的业务流程?它们支持着怎样的业务数据模型?它们提供怎样的QoS?它们在安全性和可伸缩性方面存在哪些风险?

SOA参考框架帮助我们提出这些问题。提出问题比解决问题更重要,真的。企业应该认真考虑向SOA迁移。

2009年5月24日星期日

改进实现

TimeMachine的第一个实现跑起来了,跟预期的行为一样。但进一步想想,有两点不足:
  1. 每睡一小时/一分钟都会输出一条日志,一个周末下来,输出了很多重复的日志。
  2. 端午节要到了,原来的实现只考虑了周末,没有考虑这种节假日休息,周日反而要工作的情况。
解决起来也很简单:
  1. 在WorkTimeControllerImpl中添加一个方法“Date nextStartTime()”,计算下一次开始工作的时间,这样waitTillStartTime()就可以只输出一条日志,然后一直睡到下次开始工作。
  2. 在WorkTimeControllerImpl中添加一个非工作日列表,包括正常周末和节假日。
这个故事有两点意义:
  1. 重复的无用信息是不好的。Don't Repeat Yourself. 无用的信息会干扰有用的信息。
  2. WorkTimeController和TimeMachine接口都不需要改变,这就是区分“做什么”和“怎么做”的好处。
一次就做对是不容易的事。就像书法家写作品,写了很多,满意的不多。

2009年5月21日星期四

TimeMachine

手上在写的程序希望能够跑着就不要人管,但只在工作日的9:00到11:30,13:30到15:30做需要做的事,其他时间休息。

所以我设计了一个WorkTimeController,它有3个方法:
void waitTillStartTime();
void skipLunchTime();
boolean afterEndTime();

waitTillStartTime()负责跳过双休日、国定假日,直到工作日的9:00为止。它在8:00之前,会sleep(ONE_HOUR),再检查一下时间。在8:00之后,会sleep(ONE_MINUTE),再检查一下时间。

skipLunchTime()在工作时间会立即返回,在午休时间会sleep(ONE_MINUTE),再检查一下时间。

afterEndTime()判断是否在15:30以后了。

工作程序有一个doOneDayJob()方法:
void doOneDayJob() {
workTimeController.waitTillStartTime();
while (true) {
doSomeJob();
workTimeController.skipLunchTime();
if(workTimeController.afterEndTime()) {
break;
}
}
}
这样就完成了一天的工作。

现在的问题是,如何来验证这些时间控制动作都是正确的?难道只有让时间来证明?“Time, goes by, so slowly...”

于是我设计了一个TimeMachine接口,包含两个方法:
long currentTimeMillis();
void sleep(long time);
WorkTimeController使用TimeMachine来获取当前时间和休息。

TimeMachine的正常实现是很直接的,我设计它的目的在于,可以实现一个MockTimeMachine。这个MockTimeMachine的sleep方法什么也不做,立即返回。而它的currentTimeMillis方法则依次返回一组预先设定好的时间。这样,我们就得到了一个时光机,可以检测在一些关键的时间点程序行为是否正常。

时间是一个关注点,在这个例子里,我们实现了时间关注点的分离,改善了程序的可测试性。

2009年5月14日星期四

彩色UML总能给出漂亮的领域模型


手上在写的一个程序需要不断抓取实时信息,然后根据既定的策略做出相应的反应。写着写着,就觉得结构有点乱了。等一下!我还没有用彩色UML建过领域模型!

特定的Info触发Strategy创建Executing,Executing在被创建之后可以接受Info,并创建ExecutingDetail。Strategy可以控制在执行的Executing的数目。Executing实际上是一个有限状态自动机的实例。

画了类图以后,感觉好多了。方法学就是习惯。习惯就是不那么做就感觉不舒服。

试着用Netbeans画了个序列图。VisualParadigm接手UML模块后,带来了易用性。

2009年5月10日星期日

最大价值

“如果你的男朋友跟你谈恋爱三个月了,还没有谈到未来,那么你们就没有未来”。这是以前看到的一个笑话。现在我改编了一个新版本:“如果你的项目开始三个月了,还没有提供客户价值,那就永远不能提供客户价值”。

这句话很极端,我相信一定能找出反例。所以如果您不同意,一笑而过就可以了。

早交付、常交付客户价值,是敏捷方法学的核心。这样做的直接结果就是提高了ROI。所以,向商业客户推销敏捷方法是不难的,只要告诉他们可以提高ROI就可以了。

体现客户价值的需求有很多项,先交付哪一项?答案很简单,先交付最有价值的那一项。如果你运气好,最有价值的那项需求碰巧可能是最容易用计算机系统实现的。那项需求如果用人工来做,成本可能极大,精度和时间上可能完全做不到;但对于计算机系统,则是轻而易举的事。

但是,要明白什么是客户的最大价值,需要和客户进行充分地沟通。要让客户了解计算机系统可以做到什么。如果客户对计算机系统缺乏了解,就不能提出计算机系统对他最大的帮助是什么。开发者要尽可能了解客户的业务特点,和客户一起,找出计算机系统能够实现的最大价值。

鼠标的发明者Douglas Engelbart说,计算机的作用是IA(人类智能的扩展)。我们要找到人的弱点和计算机的长处,然后取长补短。

七个习惯说“要事第一”,TOC理论说“找到瓶颈并加以改善”,棋谚说“急所先于大场”,道理都是类似的。可做的事情有很多,做不完,要列出优先级。

宫本武藏在《五轮书》说:以剑法而言,敌一人与万人其理雷同。做一个人的小项目和做一万人的大项目,道理是一样的。

结论:定制开发的成本是很高的,客户的价值也很多,我们要尽可能从最大价值开始。

PS:问:“不太理解这里客户价值指什么。是指客户最关心的feature吗?”

答:要让客户享受到实实在在的好处,挣到钱。这样客户就愿意掏钱支持下一步的开发。大家拿到钱,士气才会高。

 就像医生开药,不能说我的药吃两年,让你壮得像小伙。得是吃了一个月之后,关键症状有明显减轻。然后再说,坚持吃点啥,调理调理。也有可能吃了一个月的药后,后面的药都不用吃了。

 实际上,随着客户业务的发展,不断会有新的业务瓶颈出现,需要克服。IT系统也可以不断改进,提供新的客户价值。于是实现了双赢。 

所以在客户关心的feature中,弄清楚每个feature能帮客户挣(省)多少钱。这是提供解决方案的基础。

2009年5月6日星期三

美丽架构7原则

  1. 一处一事实。一个设计决定,只出现在一个地方。将来当这个设计决定改变时,改动量可以最少。参设计决定、反悔、霰弹式修改和架构污染
  2. 自动传播。有时候出于效率考虑,必须复制一些东西。系统需要保证这些复制很容易自动进行。现在流行的分布式版本控制系统(Git、Hg)就是很好的例子。还记得当初的EJB吗?事实是我们在写分布式应用,然后规范要求我们在几个地方尊重这个事实。EJB3.0改变了这种情况。
  3. 架构也包含构建过程。架构什么都不是,围绕架构的过程就是一切。
  4. 使用最少的机制。够用就好。要明确目标,优化瓶颈,不要沉迷于非瓶颈部分的优化。要事第一
  5. 设计引擎。利用引擎,我们把该放在一个地方的内容放在一个地方。例子有规则引擎、工作流引擎、脚本语言和DSL。但是在项目中自己实现一个引擎要考虑实现的成本和项目的经费。
  6. 支持伸缩。在负载增大的情况下,系统的表现如何?系统是怎样的方式实现伸缩?
  7. 抵制熵增。年轻时很美并不难,难的是一辈子到老都很美。美丽的架构能够经受时间的考验。

2009年5月4日星期一

DSL

不同领域的人讲着不同的语言。这些语言中的概念有特殊的语境,包含一些特殊的概念,是对话者多年潜心钻研的结果。

在考虑系统架构时,一般原则是按领域来分解系统。不同的系统组件由不同的领域专家来负责。例如,我们把系统分成三层:UI、业务、持久。

将不同领域的内容写在一段程序里,是公认的坏事。如果你看到某段代码中既有对HttpRequest的处理,又有业务规则,还有数据库连接和SQL语句,那么有两种可能:写这段程序的人是初学者或超高手(初学者不知有更好的写法,而超高手故意为之,因为写这段代码时的情形令他做出这种选择)。

业务领域可以继续划分,在一个企业里,财务、销售、生产、仓储物流等等都是不同的领域,拥有各自的领域专家。

架构师,是那种对每个领域都懂一点的人。而他懂的那一点,恰恰是这个领域的精华。这样,他就能设计出不同的领域如何组织成一个系统。

企业的CEO,也是那种对每个领域都懂一点的人。他甚至还懂信息技术。所以他知道怎么把属于不同领域的部门组合起来,形成一个系统,去实现整体的目标。

面向对象技术的强大就在于,可以形成自定义的概念抽象。然后,我们可以有一个执行引擎,执行由这些概念所组成的指令。

Groovy/JVM是个好引擎。

if ((new PriceDifference("zn0906", "zn0907") > 108)
      && new Tendency("zn0906").isUp())
{
    new KaiCangBuyIn("zn0907");
    new Delay(ONE_SECOND);
    new KaiCangSellOut("zn0906");
}

没有仔细学过Groovy,如果能把这些new去掉就更清晰了。

Google:广告界最懂信息技术的公司

Google是著名的搜索引擎,但搜索是免费的,它也不搞竞价排名。现在大家都知道了,它的盈利模式是广告业务。开展广告业务的公司很多,Google的核心竞争力在于,它是所有广告公司中最懂信息技术的。

早就听说Walmart建立了强大的数据仓库,通过数据分析来制定各种业务决策:商品的摆放位置、销售价格......,Walmart不记录客户的姓名,但是它的系统被一本讲客户关系管理的书列为重点案例。Tim O'Reilly在推销Web 2.0的概念时说:你们知道吗?Web 2.0最成功的典范不是Sun的员工blog,也不是Dell的产品评论,而是Walmart。因为它能根据销售信息决定采购、物流、上架。也就是说,它能根据环境变化来自动改变业务决策。这些环境变化的信息正是客户所提供的,这就是Web 2.0的精髓。Walmart是所有零售公司中最懂信息技术的。

英文资料常见到这样一个词组:informative decision,意为“信息充分的决定”。然而,环顾四周,这样的决定是少之又少。

做IT这行的人和公司,对信息技术的理解也有高下之分。作为一个软件公司的管理层或一个软件项目的经理,他们的日常决定有多少是informative decision?

谁是软件界最懂信息技术的公司?谁是金融界最懂信息技术的公司?谁是健身行业最懂信息技术的公司?

一般来说,最成功的人是拥有最好信息的人。

2009年4月29日星期三

Endless Test, Continuous Integration

“无尽的测试,持续的集成。”这句话在2000年左右敏捷开发刚兴起的时候大家讲得比较多,这两年已经不大听得到了。但是,好的东西总是让人不断认识到它的价值,直到流淌在血液中,成为自己有机的组成部分。

方法学就是习惯,就是不这么做你就觉得很不舒服。

没有什么比好的理论更可实践的。敏捷开发关于测试和集成的理论正是如此。现在我会:
  1. 让客户确定里程碑。
  2. 写出业务测试用例。
  3. 扫除一切障碍,实现业务用例。

特征驱动开发(FDD)提到,它的一个优点就是制定对客户有意义的里程碑。客户可能不懂IT技术,但客户通常会明白什么对他们自己有价值。作为软件开发人员,你不用猜测、假设什么东西对客户有价值。相反,你直接问客户:您最终希望得到什么结果?您希望一个月得到什么结果?

作为技术人员,我们很容易假定一份完备的需求规格说明书对客户是有价值的,一叠UML模型对客户是有价值的,一个架构设计对客户是有价值的。然而,这种假定是错误的。并不是所有的客户都这样想。现在的商业用户越来越重视上市时间。换言之,投资要快一点看到回报。在造一幢大楼时,恨不得第二层还在造,第一层就已经租出去了。

人们对未来的长期预测能力比较差,但对近期的预测能力还是可以的。“明天的天气跟今天差不多”,大多数时候都是对的。就这样,一天天的差不多,在三个月后会有一个大变化,在六个月后变化更大。

FDD说,作为项目经理,不要去问开发者进度。让开发过程自动报告进度。最好是让客户告诉你,我们的项目完成了百分之多少。昨天,一个客户告诉我,他觉得我们的项目已经成功了30%。听得出,他是满意的。

在询问客户之后,把你的理解写成一个可执行的业务测试用例,跟客户确认。如果你写不出来,或者客户不认同,那就是与客户的沟通还有问题。不解决这些问题就开始开发,做出来的东西一定不能满足客户的要求。

确认测试用例之后,施展你的才华的时候就到了。尽你所能,又快又好地实现它。下个星期(月)给客户去演示、发布、上线。

举个例子,如果你想做一个Web 2.0的网站。打算集成SocialSite、JForum、Roller、XWiki...那么你会怎么做?我会先确定用户管理和单点登录机制,然后是无尽的测试和持续的集成。

远期目标清晰,近期目标明确,辅以无尽的测试和持续的集成,大事成矣!

2009年4月24日星期五

改造多线程爬虫

我曾经为了好玩写过一个多线程的网页爬虫,最近重新思考了一下多线程,想对这个爬虫的设计做一些改动。

程序的基本业务如下:
  1. 从一个待爬集合中取一个待爬URL
  2. 取回这个URL代表的Web页面
  3. 对页面进行解析,找出其中的链接URL
  4. 如果找到的URL不在已爬集合中,就把它放到待爬集合中去
我关心的这些URL都属于同一个网站,所以从一个URL开始(如首页),如果待爬队列中过了一段时间没有新任务,整个网站就爬完了。

由于网络访问的延迟,所以采用多线程是很自然的考虑。但是多线程的设计并不能伸缩到多台机器的集群上。“做什么(爬网页)”和“怎么做(多线程)”混在了一起,未能实现关注点分离。

如果我们决定使用多线程,可以。但要确保这种设计决定没有散布在程序的各个角落。不幸的是,我原来的程序没有做到。由于要使用多线程,我在程序的各个角落使用了synchronize关键字,还使用了线程安全的集合类。

之所以要使用synchronize关键字和线程安全的集合类(手段),是因为我要保证操作的原子性(目的)。实际上,根据实现的设计决定不同,就会采用不同的手段。例如,我们可以通过数据库持久事务来实现操作的原子性。

按照Google MapReduce的设计思路,我们应该设计一个任务主控组件,它负责分发任务和结果合并。这个主控组件管理着待爬集合、正在爬集合和已爬集合。同时它也管理着执行机器的集合。它组装出一个个的Runnable或Task,发送到执行机器上(如果只有一台机器,那就本机了),并监督它们是否正常执行。可以向一台执行机器同时发送多个任务,在执行机器上实现多线程。

这样,通过一个中间层,一个子任务的执行和总任务的管理之间的耦合解除了。

这个架构可以移到GAE上去。写一个RESTful的Web service,负责执行一个子任务。任务主控组件向GAE上的这个应用发起一堆请求,利用GAE的强大计算力、带宽以及伸缩性。也许一万个页面只要几秒钟就搞定了。

你说什么?这个任务主控组件不好弄?本身就需要很好的带宽?把它也弄到GAE上去!

网络就是计算机。这一天到来已经很久了。

2009年4月18日星期六

多线程、Project Darkstar、MapReduce和GAE

通过提高主频来提升性能的时代结束了,我们一下子就被扔进了多核、集群的时代。程序员可以分成两种:1.会分布式并行编程的;2、不会分布式并行编程的

有人声称,继OO之后,程序员下一项需要掌握的技术就是多线程技术。但是我预计,短时间不会有大量程序员掌握多线程编程,就像短时间学不会OO技术一样。

IT业界从来不缺聪明的人,他们已经设计了各自的解决方案。让不懂分布式并行编程的人享受到分布式并行的好处,就像让不懂OO的人享受到OO的好处一样。

Beautiful Architecture”一书的第3章介绍了Project Darkstar,它为多玩家在线游戏和虚拟世界这样的系统设计了一种架构,使得游戏程序员不需要掌握分布式并行程序设计技术。

代码之美》一书的第23章介绍了Google的MapReduce设计,实现了分而治之的古老策略,体现了任务/分布式并行计算的关注点分离。

GAE不让你启动自己的线程,所有自己会启动线程的jar包都不能跑在GAE上。伸缩性和并发由底层基础设施来实现。

我们需要重新考虑一下应用的架构方式了。如何才能够跑在GAE这台巨大的虚拟机上?


2009年4月15日星期三

两个关于架构师的问题

《程序员》第100期(2009.04)最后一篇文章提到了两个关于架构师的问题:

1. 程序员如何成为好的架构师?
2.资浅架构师如何设计出好架构?

我也试着回答一下这两个问题。

1.通过大量的学习、思考、实践,才能成为好的架构师。你能叫出几个世界顶级架构师的名字?有没有听说过Parnas?你对他们的设计思想和作品有什么了解?你做了哪些思考?你进行了怎样的实践?

2.这个,基本上,很难。水平怎样看两点:一、坚持练了多久;二、练的方法对不对。好架构来自好的架构师,冰冻三尺,非一日之寒。

2009年4月14日星期二

“大”项目的关键是集成

一个朋友提到,他们的公司将clear case换成了git。而他的感觉是,由于git是分布式版本控制系统(DVCS),所以在几百人的分布式大项目中,如果弄不好,很快就会乱套。虽然有一个“主代码库”,但是可以想象,提交时的冲突会很多。

确实,Clear Case有很多先进的特征,我也很喜欢,除了它的价钱之外。但我认为,这里问题的关键是集成。

几百个人的分布式大项目,要让所有人的工作能够顺利集成在一起是一件不容易的事情。其中的难度早有定论:这些人需要沟通协作,而沟通时的不一致和冲突将耗费大量的时间和精力。所以,在大项目中,我们常常看到集成的工作量比编码的工作量要大,有时候甚至大得多。

开发人员多是一种类型的“大”项目,另一种类型的“大”项目是小团队,但复用了很多第三方的软件。这种类型的项目在开源软件中相当常见,比如Liferay就是一个例子。它使用了Velocity模板框架,提供与多种应用服务器的绑定,支持多种关系数据库后端,还支持第三方的CMS和用户管理。

这两种大项目,如何来实现集成?

据我所知,以前某些大公司的做法是,项目设置build manager或build team,专门负责集成构建。这样做的含义很清楚:集成工作量很大,我们要专派人手。

但现在业界的最佳实践是持续集成

对于开发人员很多的项目,《持续集成》中介绍的一种集成方式或许可以解决他们的问题。设置两个Repositry,大家向第一个Repositry提交,如果提交后5分钟没有新的提交,就第一个Repositry上持续集成。如果集成失败,自动回退到上一次成功集成的状态;如果集成成功,将新提交的内容再提交到第二个Repositry。开发人员将在持续集成服务器上,看到自己的提交是否成功集成。也会在集成失败时收到通知。

(这个故事再次告诉我们:工具的价值小于过程的价值,过程的价值小于人的价值。人是改进过程和工具的决定因素。)

对于小团队大量复用的项目,持续集成仍是成败的关键。你可以在Liferay项目中看到大量使用Selenium自动化集成测试。

最近我独自一人开发程序时,也明显感到持续集成的重要性:我需要把一周、两周、一月、两月的工作集成在一起。也许,集成一直都比编码更重要吧。

日志设计

记日志是个技术活,需要经过认真的设计。

为什么要记日志?为了在某个时候以某种方式对日志进行分析。Tim Bray在《代码之美》的第4章中,用例子说明了如何分析他的Blog的访问日志,找出最受欢迎的10篇文章。

从本质上来说,日志记录了系统中发生的事件。我们应该将重要业务事件的处理记入日志,并通过记录事件ID,保持事件的可追踪性。

例如,在一个filter-pipe架构中,每一条信息都有一个ID,每一个filter对信息的处理(通过或过滤)都记入日志(What)。在需要时,还可以记下过滤的规则(Why)。由于日志会记录时间,我们就可以写一些分析程序/脚本,统计业务事件的发生情况和处理时间。又是5Ws and an H的思想。

在调试时,这种日志明显是有好处的。这是升级版的printf。在运营时,这为系统的审计追踪性提供了帮助。

如果日志输出能够转向,作为另一个应用的输入,那么我们就可以写一个实时日志分析程序了。你猜到了,这是SOA治理的思想。

唯一的担心是性能开销。设计师要根据具体情况做出折衷。

2009年4月12日星期日

Filter和Pipe是一种实用的架构

手上在写的程序需要不断抓取实时信息,然后根据既定的策略做出相应的反应。写着写着,我的程序就变成了经典的Filter-Pipe结构。

我设计了三个接口:InfoProvider,InfoConsumer,InfoFilter。InfoProvider有setInfoConsumer()方法,InfoConsumer有receiveInfo()方法,而InfoFilter是InfoConsumer和InfoProvider的结合。

另外,我还设计了一个InfoSource,它是InfoProvider,但它还有另外两个方法:start()和next()。start()方法的语义是源源不断地提供Info,直到最后提供null。next()则是单步提供Info。

然后,我利用Guice的实例绑定来组建这个处理管道。再用一些确定的数据来测试整个管道的功能是否正确。

根据SOA的思想,我为每个InfoProvider/InfoConsumer/InfoFilter组件设计了一些运行时监控功能。在开发时,可以利用这些功能来确定程序的正确性。在运行时,可以得知程序的实时运行信息。

如果某个Filter没有设置InfoConsumer,它可以在标准输出中输出Info。

很像Unix的管道吧?推荐一本书“Software Architecture: Perspectives On an Emerging Discipline” by Mary Shaw & David Garlan,其中提到了Filter-Pipe架构。

2009年4月11日星期六

7个习惯

上次在培训时,我的slide中出现了“以终为始”,主要是让学员能在培训之后继续与我保持联系,这样我就好知道他们是否“学到了知识,改变了行为,养成了习惯,改变了命运”。学员们对“以始为终”产生了共鸣,我一问,原来大家都集体培训过“高效能人士的7个习惯”了。

如果一个理论包含3点,那么它很好理解并记忆。例如:
训练动物的秘诀有3点:1、动物没有错;2、动物不愿做这个,你就让它做那个;3、动物做了你想让它做的,就给它奖励。(Tip:也适合“人”这种动物)
Sun的策略有3点:1、技术采用;2、商业跟进;3、处理好前二者的关系。

如果一个理论包含5点,努力一下也能记住。例如:
管理者的工作有5个方面:1、制定目标;2、分配资源;3、监督进度;4、执行奖惩;5、教育培训

如果一个理论有7点,就要采取分而治之的方法来记忆了。如Peter Coad的7项修练,可以分成(语数)(音体美)(情商)。又如高效能人士的7个习惯:(积极主动、以终为始、要事第一)(双赢思维、知已解彼、统合综效)(不断更新)

还有一种办法,就是记首字。如7层网络模型:物数网传会表应。围棋十诀:不入攻弃舍,逢慎动彼势。
得贪胜。界宜缓。彼顾我。子争先。小就大。
危须弃。勿轻速。需相应。强自保。孤取和。


2009年4月2日星期四

低配置机器上的开发环境

如果你有一个上网本,希望出差几天时也能偶尔写点程序。那么装点什么开发工具?
  • Mercurial HG:分布式版本控制系统。
  • JDK:写Java程序,没这个不行。可能还要装Java API doc。
  • vim:带行号显示、语法加亮的编辑器总要一个的。
  • Ant:经常要打ant clean test。
PS:编辑器还可以用Notepad++

怎样才能提高自己的能力?

一个朋友在MSN上问了我一个问题:怎样才能提高自己的建模能力?

子曰:“大哉问!”凡是练功升级的人,都在思考这个问题。一般来说,我们需要:学习,思考,实践。

  • 学习是摆在第一位的。“吾尝终日而思矣,不如不如须臾之所学也”。“三人行,必有我师焉。择其善者而从之,其不善者而改之。”要师从古人,师从外国人,师从周围的人。
  • 学习完了要自己思考。“学而不思则罔,思而不学则殆”。他人的观点纷繁复杂,有的甚至针锋相对。思考的目的就是在自己头脑中建立起一个自洽的知识体系,将新学到的知识与旧的知识融合,使之相互得到加强。如果一个人,以前对UML建模很有研究,后来又醉心于敏捷方法学,然而他不能将这二者很好地融合,那么他只能算是一个跟风者,就像那只过玉米地的猴子。我怀疑这些知识是否真正成为他的东西。
  • 接下来是实践,实践出真知。实践可以检验你的理解是否正确,即是否符合客观规律。“纸上得来终觉浅,绝知此事要躬行”。

以上是一般原则,是泛化。下面结合具体例子,讲一个特化。

学围棋的人,很关心怎样提高棋力的问题。我也请教过多人,总结下来有这样一些方法:

  • 做死活题。吴清源说:围棋最重要的是死活。然而下棋的目标却不是杀死对方的棋或自己做活。死活只是手段,赢棋是目标。不过你若“不知死活”,目标当然遥不可及。看高手下棋时,我觉得必死的棋,忽又活了;我觉得是一堵大厚势,不可能出事的棋,忽又变成了孤棋,最后大龙愤死。不由感叹:满盘尽是死活题。做死活题时,是学习,是思考,也是实践。
  • 打谱。在打谱中,你可以看到中国古棋凌厉的攻杀,秀策那看似平淡无奇却闪耀着万丈光芒的小尖,吴清源和木谷实对布局的新理解,武宫正树天马行空的宇宙流。打谱时,是学习和思考。再在实战中用出来,就是实践了。
  • 实战。没有实战不行的高手。高手是通过无数实战而长成的。实战有输有赢,但关键是要通过复盘,分析输赢的原因,从而提高棋艺。
  • 复盘。当你水平不行时,最好是请高水平的老师复盘,这样进步快。当你到达一定水平时,可以自己复盘或与对手一起复盘,摆出一些变化图,分析实战中决策的合理性。反思是最重要的一项实践。所有实践一律平等,但有些实践更平等。

坚持做到这几点,围棋水平一定蹭蹭地往上长。


2009年3月26日星期四

《持续集成》的译者序

我发现在项目团队中真正实现持续集成是一件不容易的事情,因为需要改变一个团队做事的方式和习惯。这里特别把当初写的译者序贴出来,并郑重向大家推荐这本书。下面是原文:

软件项目开发有两大难题:一是确定软件的需求,即确定目标;二是确定目前离目标还有多远,即确定剩余的工作量。第二个问题就是项目缺少可见性的问题,这对“人月神话”做出了“巨大贡献”。当一个项目经理或一名开发者说已经完成了80%的任务,您必须保持审慎的乐观。因为剩下的20%可能还需要80%的时间,甚至永远也不能完成。您可能迟迟不能拿到可以部署的软件,对此所有的人都无能为力,只能表示深深的遗憾。这确实让专业软件开发者的声誉蒙羞。但是对于大型软件开发这样的复杂工作,我们的经验确实显得有些不够。

《持续集成》这本书向我们介绍了一种增加项目可见性、降低项目失败风险的有效实践。许多软件开发的资深人士认定,这种方法非常不错。我们不必把宝全部押在最后那一次“大爆炸”式的集成上,而是采用“早集成、常集成”的策略。这样做可以减少缺陷引入和缺陷发现之间的时间,提高开发效率,降低风险。您对项目完成百分比的报告将有更大的信心,而且任何时候,您都可以得到一个可以部署的软件。虽然功能可能还没有全部实现,但它是可用的!

这本书向我们揭示了这样一个道理:如果一件事很难,而您又必须做,不妨经常去做,每次做一点点。其实这也是古老的“分而治之”思想的一种应用。滴水穿石,跬步千里。
敏捷软件开发的许多实践都是互相关联的。持续集成在与其他实践结合时,才能将它的效用发挥到极致。这本书除了介绍
CI的基本原则和工具之外,也介绍了如何将测试驱动、代码审查、数据库集成、信息反馈等实践和工具。人(思想)、过程和自动化工具的完美结合,将形成一个和谐的开发生态环境。如果您一直在追求效率更高的软件项目管理方法,我相信这本书一定能给你带来一些启发和灵感。

一本好书使您改变。它将改变您的思想,您看待问题的角度和方式,最终,它将改变您的行为。然而,所有具有重要意义的改变都不会在一夜之间发生。改变随时都在发生,但按照您的意志去领导变革却很难。如果您相信这种变革必须发生,不妨朝着这个方向去努力,经常改变,每次改变一点点。

软件业中没有银弹,不可能有某种东西在短时间内让您的开发效率提高
10倍。但是我们也很容易发现不同个人和不同团队之间的开发效率相差巨大,不止10倍。那些软件高手和明星团队就像职业围棋选手,他们高得惊人的效率是多年用心改进实践的结果。

在这本书的翻译过程中,我学到了很多,因此郑重地向大家推荐它。如果这本书对于您改进软件开发实践有所帮助,我将十分高兴。

2009年3月20日星期五

Socket编程和协议设计

眼下在写的一个程序涉及Socket编程,最初我写的是Java tutorial上推荐的方法,即封装出BufferedReader,再调用readLine()方法。然而请求发出去之后,却阻塞在readLine()上了,没有读到预期的应答数据。

基于TCP协议应用协议有一个好处,可以用telnet来调试。于是我祭起Putty,发起请求,这下看到了预期的应答数据。

我的程序错在哪里?错在readLine(),它要等待一个'\n'。而按照应用协议的规定,不会在一次应答结束时返回'\n'。所以readLine阻塞了。

正确的写法应该是怎样的?只从socket读取一次数据是不对的。由于网络延时是不确定的,所以读一次数据不知道是否读到了完整的应答。一定要根据应用协议的规定,读到结束字段。而这个应用协议规定了用''分隔字段。于是我必须根据应答字段的个数进行读取,直到读完一条完整应答为止。

事情还没完。根据请求是否成功,服务器应答信息的字段数是不一样的。所以应答读取的程序应该先读到成功失败标识,再判断成功失败,读取后面的应答字段。

这导致了协议对话部分的程序相当复杂,而这种复杂本来是可以避免的。为什么会出现这样的结果?

原来的协议还附了一段C程序例子。正是这段例子,让我对问题的原因有了一个大概的猜测。

这段C程序初始化了一个缓冲区,并往里面填充了0。然后从socket中进行了一次读取,放在缓冲区中。不幸的是,这段程序是错误的。因为网络延时不确定,所以读取的数据可能是不完整的应答。写例子的人就当它是完整应答了!这样写是简单,但是却是错的。

我想,如果设计协议和写C例子的人(可能是一个人)如果概念清楚,还是会把协议设计成以'\n'来结束一次应答吧。

2009年3月19日星期四

FDD伸缩性和实现客户价值的TOC理论

FDD在敏捷方法学中最独特之处就在于,它声称可以实现伸缩性和可重复的成功。那么,它的这一特征有什么意义呢?

前两天,我刚好看到James Shore的一份Presentation,其中提到增加客户价值的5种方式,真是敏捷方法学的强人,抓住了客户价值这一点不放。

提到这份Presentation的原因是,他的5种方式中的第一种要求一个团队同时只做一个项目。他的图示说明非常具有说服力,一下子就让我完全同意。但接下来问题出现了,如果是一个千人的公司,同时也只做一个项目?这是不是有点荒唐?他自己也意识到了这一点。

关键之处仍是大部分敏捷方法学的软肋:可伸缩性不好。

FDD声称可以适应很大的项目,于是我们可以设想这样的场景:一个50人15个月规模的项目(新加坡项目的规模),如果给我750人,那么1个月就搞定了!这将为客户提前14个月获得投资回报。如果真能实现这一点,我想世界会震惊的。

干一个月的活,收钱。这是我喜欢的方式。干一天的活,收钱,这是我爱的方式。

好吧,我承认,我身体里理想主义的部分和浪漫主义诗人气质在发挥作用了。事实上,这样的事情还没有发生。存在就是合理。合理之处在哪里?有什么理论支持?

实际上,IT系统为业务带来价值不是只有开发这一个环节。存在着一个“开发-实施-反馈-接受”的循环。假设750个人一个月开发了1500个特征,这将在业务部门带来巨大的冲击。只要想想ERP的实施就知道了。按照TOC的理论,实施、反馈、接受的环节将成为实现客户价值流水线上的新瓶颈。

按照精益制造的思想,一个环节的产能超出系统其他部分的产能太多,绝对是坏事而不是好事。我们应该根据系统的总吞吐量,不断发现新的瓶颈,提升瓶颈的产能。

不管怎么说,这样想想还是挺妙的。现在瓶颈不在我负责的这一块了。也许在不久的将来,企业能够接受一下实施1500个新特征的冲击。

2009年3月17日星期二

Mediator模式


一个朋友问起Mediator模式,为什么类图中具体类之间存在着依赖关系?这样耦合是不是太厉害?是不是“Gang of Four”当年不太严谨所致?

这要从Mediator模式要解决的问题说起。Mediator模式是为了解决对象间多对多的通信协议问题。碰巧的是,我在《基于组件开发》一书中也读到了相关的内容。

如果A组中的M个对象与B组中的N个对象相互之间都需要通信,那么在未使用Mediator之前,它们之间需要M*N个通信协议。这在对象很多的情况下很麻烦,在添加一个对象时要做的工作也很多。

“所有问题,都可以通过添加一个间接的中间层来解决”。所以我们引入Mediator作为中间层,它的工作就是进行协议翻译。设计一个中间表示模式,实现所有对象通信协议到中间表示模式的翻译。这样,原来的M*N个通信协议就变成了M+N个通信协议。在添加一个新的通信对象时,也只要增加一种互译协议。

回到朋友的问题。根据这样的设计,Mediator必须知道如何完成这种协议翻译。因此,它必须了解具体参与通信对象的协议实现方式。所以,Mediator的实现就依赖于通信对象的实现了。

CORBA就是这样的一个例子。当然,我们可以把协议翻译这一块拿出来,做成一个协议适配器,通过再添加一个中间层,消除Mediator的具体实现与通信对象的具体实现之间的耦合,这是故事的续集。

我们还可以让通信对象各自负责实现协议翻译。毕竟,架构就是系统由哪些组件构成以及这些组件之间的关系。猜猜我们得到了什么?MQ(消息队列)/MOM(基于消息的中间件)/ESB(企业服务总线)!

2009年3月4日星期三

免费的UML建模工具

ArgoUML。画出的图中规中矩,不能算漂亮。项目一直没太大动静,据说最近又活跃了。

StarUML。画出的图不够漂亮。似乎是韩国人用Delphi写的。据说停止开发很久了。

Netbeans的UML模块。6.5以后的免费UML模块由Visual Paradigm接手继续做了,易用性方面有明显改进,图也一如既往地漂亮。这是我主要的画图工具。

Eclipse插件eUML2 。没有使用过,看截屏似乎还比较漂亮。

Eclispes插件 MDT-UML2。没有使用过,网页上没找到截屏,看EclipseCon 2008的一些Slide似乎还比较漂亮。

UML图和代码的正、逆向工程是很有用的特征,但敢于展示这项强大特征的工具似乎不多。如何维护UML模型到代码的可追踪性,是项目团队要认真考虑的一个问题。

2009年3月1日星期日

Amdahl's Law与伸缩性(scalability)

读到这篇博客:Hitting the Scalability Wall - Amdahl's Law

Amdahl's Law指出,如果您的系统中的任一部分要求串行化处理,而不能并行化,那么系统就会撞上伸缩性之墙,不能再提升性能,不论您再投入多少硬件。

关于Amdahl's Law的细节,请参考Wikipedia的条目

伸缩性是架构设计中考虑的一个重要方面。基本上,我们是在讨论如何提高系统的吞吐量。这让我想到《目标》一书中的约束理论(TOC),扩大系统吞吐量方法是找到瓶颈并消除。书中有一个例子,新购买的一台强大的加工机器取代了原来的两台旧机器,但不幸却成为了系统的瓶颈,因为大多数零件需要“串行地”经过这台机器的加工。解决办法?找回原来的两台旧机器,让工作能够“并行的”进行!

Prevayler在主页上声称比MySQL快3000倍,比Oracle快9000倍。但不管怎样,它也会撞上伸缩性之墙,因为它的事务是“串行”处理的。硬盘的访问也是串行的。这是否意味着我们无法设计出高伸缩性的系统?

不是的。我们可以通过硬盘阵列,数据库表分区(Sharding),让硬盘访问能够并行地进行。我们也可以用Prevayler实现LDAP的后端,利用LDAP协议本身的复制和分子树的能力,化解串行访问对伸缩性的影响,构造高伸缩性的系统。(做一个支持大量并发的身份证查询系统?)

开发方法学也有伸缩性问题。我们曾听说过,许多敏捷方法“don't scale very well”。不能支持大型团队,在短时期内交付大量的特征(客户价值)。究其原因,很大程度上是因为“隐式的知识”沟通是串行式进行的。

传统开发方法学也存在沟通问题。团队加入新成员后,短期会出现产能下降。因为新成员需要学习,老成员要抽出时间来教他们。

提高产能的方法就是把串行消除掉,让项目能够并行地开发。这一点恰恰是特征驱动开发(FDD)做得比较好的地方。前期经过尽可能短的串行处理(整体建模、特征列表、优先级)之后,后续的开发就可以并行展开了。所以搞FDD的人声称,可以实现可重复的成功,可以适应大型的开发项目。新加坡项目就是50个人15个月的银行项目。

FDD是一系列最佳实践的组合。并行开发的另一个关键在于组件化。使用同样的组件化架构风格,处理子系统、系统、和“众系统之系统”。让开发者熟悉了系统的一个部分,就很容易熟悉系统的另一个部分。这就是Fred Brooks所谓的“概念完整性”。将不同粒度的业务事件,开发成不同粒度的组件,用Peter Coad的话来说,实现了“模型的伸缩性”。

开发完了再测试(瀑布式)还是开发一点测试一点(迭代增量式)?Amdahl's Law告诉我们,要并发!得到详细的需求规格说明书后再开始设计和编码,还是像FDD那样把局部的详细需求放到迭代开发之中去?Amdahl's Law告诉我们,要并发!各模块开发好了之后再集成,还是持续集成?Amdahl's Law告诉我们,要并发!

由此我甚至进一步想到,FDD的前面三项串行工作也不是必须的。在实践中也遇到了这样的情形,特别是在创新的业务领域中。新加坡项目刚好是成熟的业务领域。我们可以让业务模型一点点长出来。清楚一部分,做一部分,但要在同一种架构风格(如彩色UML或5w and an H)指导下,确保整个系统的概念完整性。

和这些前辈高人神交,人生一大快事也!

我想讲的课

以下这些内容,是我这些年来的兴趣所在:

1. 软件需求过程
软件开发中最难的部分是确定需求。需求是一项社会工程。
博学多才的需求分析师再加上得到良好理解的需求过程,一定能找到正确的、创新的需求,实现产品在构建前的品质。

2. 软件测试与自动化
按传统软件工程理论,测试占到开发工作量的50%。而在软件投入使用后的维护和升级工作中,还有不少于一半的工作量要花在测试上。如何降低测试成本,是提高软件质量、解决软件危机、降低软件总体拥有成本的关键。

3. 彩色UML建模与特征驱动开发
哪一种建模方法让许多接触它的人觉得“一旦拥有,别无所求”?哪些大型项目会把UML建模作为项目成功的主要因素之一?彩色UML建模方法让我们了解了这样一种方法和这样一群人。特征驱动开发是敏捷方法学的一种,和其他多数敏捷方法学不同,它声称可以复制成功,而且可以很好地适应大型开发项目(如50人15个月的银行项目)。特征驱动开发与彩色UML建模相映生辉。

4. 持续集成
日构建是微软最值得称道的开发实践之一。持续集成让客户、管理层和项目成员了解真实的软件进度,避免交付期临近时让人崩溃的压力。持续集成是诸多软件开发最佳实践的集中体现。

5. SOA
个人、团体和大型企业都在向其客户提供服务,同时从供应商处消费服务。全球化让我们能够在更大的范围内选择质优价廉的服务。SOA首先是公司治理理念的体现,然后是业务与IT的密切配合。向SOA迁移是企业决策层首要考虑的问题之一,也是企业员工需要明白的趋势。

6. 敏捷开发方法学
敏捷开发方法学是业界顶尖思想家赠予我们的财富。它包含的内容彻底改变了业界对软件开发的认识。在经过多年的实践之后,这些方法学得到了很好的检验,足为我们所效法。

7. 过程改进与CMMI
如果你只想学一个日语单词,那我推荐“Kaizan”,它的意思是“持续改进”。反思和持续改进是取得成功的唯一途径。CMMI为我们提供了一个框架,指出了在提高软件能力的过程中,那些需要持续改进的领域。

8. UML建模
做软件就像开车,你不能设定好一个方向就一直往前冲。模型就像地图,没有它会有麻烦。建筑业、制造业都有丰富的建模经验,设计师基本上都是在和模型打交道。软件业还年轻,建模还不是那么受到重视和流行。一旦掌握了建模的三昧,定然有“向来枉费推移力,此日中流自在行”的感觉。

9. 软件配置管理
版本控制/配置管理是绝大多数开发项目都会执行的一项日常实践,而且它与需求管理、自动化测试和持续集成等实践关系密切。在这方面的一点改进,都会为软件开发带来可观的收益。

10. 软件复用
复用是实现令人满意的效率和品质的唯一途径。SOA的一个目标就是促进企业中的软件复用。要让复用成为企业文化的一部分,必然需要知其当然,不惧困难,而徐为之图。

2009年2月26日星期四

转账Transaction(交易/事务):业务层和持久层设计

在邱郁惠的《系统分析师UML实务手册中》曾读到,Coad的彩色UML模型在早期曾是以事务为中心的。在Coad后来的著作中,仍可以看到事务的烙印:aMomentInterval.commit()。

巧的是,在Prevayler的实现中,强调的也是事务:它要求开发者设计可以序列化的“事务对象”,利用Java的序列化机制实现持久,而所有对象的目前状态则保持在内存中。

三年以前我开始看Prevayler时,觉得它的这种“事务对象”不好理解,别扭,不是我喜欢的东西。在读了彩色UML建模之后,我理解了这种持久设计。下面以银行账户转账的例子来说明我的理解(注意,这个例子是虚构的,不存在于任何真实的系统中。如有雷同,纯属巧合):

业务模型

关于这个业务模型,有两点说明:
  1. 按照“5Ws and an H”的方式设计。MoneyTransferring本身记录了转账的金额和时间,说明了这是个什么事(What),何时发生(When)。MeansDesc是转账方式描述,可能是通过ATM机、柜台、电话银行等方式完成这次转账,说明了发生方式(How)。Terminal说明这次交易在哪里发生(Where)。Staff和Customer说明了参与这次转账的人(Who)。转账的理由(Why)则因为没有很合适的分类方法,可能作为MoneyTransferring的一个字符串属性。
  2. 所有的类,都可以作为分类统计的依据。例如,我们可以根据一个账户的MoneyTransferring事务和存入、取出事务,确定这个账户的活跃程度。或根据MeansDesc来确定用户对转账方式的偏好。“5Ws and an H”都可以在未来成为查询和统计的条件。
通过这个业务模型,我们记录了“流水账”,并为将来的数据仓库分析保留了足够的信息。某本书上曾说,数据库分为两种:操作型数据库和数据仓库型数据库。

持久设计
持久层设计为一个接口,其中的一个方法实现转账事务:
PersistentLayer.transferMoney(MoneyTransfering mt) ;

如果以关系数据库的方式来实现持久,这个事务将包含3条SQL语句:一条插入(INSERT)和两条更新(UPDATE)。更新必然是用原来的账户余额加上或减去转账的金额。数据库事务将保证这个操作的ACID。如果转出账户余额不足,且数据库设置了约束,余额字段不能为负,这个事务将失败。

但是,我们在开发过程中还可以临时使用内存的持久方式,实现一个“不持久的”持久层。这个持久层实现只是简单地将MoneyTransfering对象插入到一个全局的List中,并假定业务层已经将相应的两个账户做了改动。

这两种实现在对业务层的假定方面有一个细微的区别。第一种SQL实现只关注传入的MoneyTransfering对象拖着的两个Account对象的ID,而不关心它们的余额。第二种内存实现则假定业务层已经处理好了它们的余额。如果我们只用内存实现来做一些不严肃的测试,这样也未偿不可,但严格来说,我们对持久层接口的契约理解上,出现了偏差。

所以,正确的做法是,业务层不负责改动账户余额,全部交由持久层实现来完成。这样内存持久实现将做3件事,一次插入和两次更新。这下和数据库持久的语义一致了。

之所以费这么大劲讨论内存持久对PersistentLayer接口语义的支持,是因为有Prevayler的存在。它把“内存+对象序列化”做为一种正式的持久方式,用于生产系统中。如果我们写一个PersistentLayerPrevaylerImpl,那么这个实现对这个事务的处理是:将MoneyTransfering对象插入全局的List,更新两个账户余额,将MoneyTransfering对象通过序列化机制持久到硬盘。内存里保存的是最新的数据快照,持久存储上存放了所有数据变动的历史。这和HSQL是一样的道理。

您可能听说过DB4O,或者听说过BerkeleyDB,还可能听说过Tokyo Cabinet。在云计算时代,我们有很多的持久机制可以选择。为什么不把业务代码设计得灵活一些,可以适应不同的持久机制呢?多维护一个PersistentLayer接口的开销并不算大。

小结
  • 我们用“5Ws and an H”的方式对业务事件建模。
  • “5Ws and an H”成为业务数据查询和统计的条件。
  • 通过PersistentLayer接口来统一持久层语义。
  • 根据不同目的和环境,采用不同的持久层实现。
  • 云计算时代的持久不同于C/S时代的持久。

2009年2月17日星期二

Peter Coad的7项修炼

(本文已在《程序员》杂志2009年第2期发表)

Peter Coad是我喜欢的一位面向对象专家和软件创业者。他在上世纪九十年代与人合著了6本关于面向对象软件的分析、设计和编程的书籍,以《面向对象分析》一书中和Yourdon共创的Coad/Yourdon方法而成名。1999年,他创建了TogetherSoft公司。2003年,TogetherSoft卖给了Borland公司,他成为了Borland公司的副总裁。后来,他离开了Borland,兴趣转向了软件之外,教人读希腊文版的圣经。

他最值得一提的技术贡献,就是提出了“彩色
UML”建模方法,这也是他最后一本书《彩色UML建模》的主题。在这种建模方法中,他使用了4种颜色,代表4种不同设计意图的类,创造性地回答了面向对象分析和设计的一个根本问题:“怎样识别类,并确定方法和属性?”这本书已由机械工业出版社引进版权,出了中文版。

我对
Coad的关注始于十多年前,那时朋友向我推荐了北大出版社引进的《面向对象分析》一书。后来因为学习JavaUML建模、关注Borland公司的产品,又看到了TogetherSoftCoad的名字。再后来,敏捷方法学兴起,我又看到了Coad,他是特征驱动开发(FDD)的主要支持者。向师傅学习,要学师傅练功的方法,而不只是师傅的招式。我想知道的是:为什么Peter Coad会发明彩色UML的方法?或者说,Peter Coad是怎样炼成的?

在他
1994年左右的一篇文章中,对这个问题给出了一些提示。这篇文章标题是“Amplified Learning”,介绍了“7种智能”的理论和应用。

语言是第一种智能。
著名的通天塔的故事体现了自然语言表达的重要性:失去了语言沟通能力,团队协作就变成了不可能的任务。语言能力的习得,有一句话可以概括:“听说领先,读写跟上”。学习本国语言和外语都是如此。语言的重要性,实际上体现的是沟通的重要性。据说,当年
TogetherSoft公司在面试时,会先让被面试的人选一个与软件开发无关的话题讲15分钟。通过这样的面试环节,可以了解面试者对生活的一些理解和表达沟通的能力。这里我还想强调一下中国程序员的英语能力。由于软件方面的最好的信息几乎都是英语的,所以良好的英语能力对程序员有很大的帮助。一般来说,在各行各业,最成功的人都是拥有最好信息的人。

代码的可读性也是语言能力的体现。Knuth1984年提出的Literate Programming,把编程比喻为写文学作品。既然是文学作品,就会有不同的风格。有晦涩难懂,也有深入浅出、生动有趣。所以作为一名好的程序员,需要对自然语言和编程语言都有深刻的理解,要懂得欣赏和创造文章之美,代码之美。

那么什么样的文章是好文章?一个留美回来的英语作文教师曾对我说,好文章首先要有“
something new(新东西)”。新的观点、新的解释、新的视角、新的实践、新的证据......都可以。这是西方鼓励创新的思路,所以我们会看到软件业的创新层出不穷。中国人对好文章的评判标准更注重传统。《文心雕龙》里提出的标准是“本乎道,师乎圣,体乎经,酌乎纬,变乎骚”。把软件提升到道的高度,那就是C++创始人Bjarne Stroustrup所说的,“人类文明运行于软件之上”。

数理逻辑是第二种智能。我们需要对数字敏感,需要具有归纳和演绎的能力。这样的能力虽然也包含在语言学习之中,但更多地包含在数学、物理等理科课程中。在西方,学理科的学生几乎都读过
Pólya的《怎样解题》一书。数学是思维的体操,它让我们的思维更灵活、更缜密。懂得欣赏数学之美的人,会看到简洁的公式和逻辑的力量。许多大学的计算机系都是从数学系分出来的,一些老师以前是学数学或物理的。史书记载:“尧造围棋,教子丹朱”。学围棋也是对逻辑思维很好的锻炼。和数学一样,从围棋中我们也可以看到,从一些简单的规则出发,可以怎样建造一个瑰丽的宫殿群。另外,在软件复杂度测量、软件工程管理方面,也需要许多量化的测量指标。

以上两种智能可能是学校教育中教授得最多的内容,体现在语、数、外三门主课上。能够熟练灵活地运用语言进行沟通,并能够运用逻辑推理来找出问题和解决问题,我们就具备了一些基本的能力。但除此之外,还有一些扩展性的智能,对于我们软件开发者也很重要。

空间色彩是第三种智能。
九十年代是
GUI的十年,在这十年里,软件的美由内而外,扩展到了视觉领域。叔本华说,“美是最高级的善,创造美是最高级的乐趣”。但是,我们不时会看到一些殊无美感的设计,那真是叫“乏善可陈”。Apple公司的操作系统中,利用色彩来表示窗口的控制按钮,给人以艺术的享受。Peter Coad的彩色UML建模方法利用4种色彩来表示4种不同的架构型,通过色彩强化了信息沟通的效果。我还记得当初在Borland公司的集成环境中第一次看到彩色语法制导时,带来的那种震撼。作为程序员,我们经常需要阅读图表、画设计草图、运用图表或幻灯片来传达信息,空间色彩能力也是相当重要的。

“流光容易把人抛。红了樱桃,绿了芭蕉。”“知否?知否?应是绿肥红瘦。”中国人讲究“诗中有画,画中有诗”。软件是散文,是诗,其中也蕴含着色彩和空间之美。

音乐是第四种智能。科学家有音乐特长的例子比比皆是,并且他们都认为,音乐对他们的专业是有启发的。
Spring框架的作者之一Rod Johnson拥有音乐博士学位。旋律和节奏是音乐训练的两个主要方面。很多歌曲要记住歌词不容易,但旋律却让人记忆深刻。我喜欢在工作环境中使用背景音乐,这样能舒缓压力,激发创造力。音乐反映了我们对世界的一种认知模式,反映了道。《庄子·养生主》载,“庖丁为文惠君解牛,手之所触,肩之所倚,足之所履,膝之所踦,砉然响然,奏刀騞然,莫不中音,合于桑林之舞,乃中经首之会。”庖丁是个解牛的专家,是个哲学家,也是个音乐家。

运动锻炼是第五种智能。许多职业棋手都通过运动锻炼,保持有活力的工作和思考,使自己处于最佳状态。极限编程(
XP)在一开始提出“每周工作四十小时”作为一种最佳实践,但是如果你的身体有问题,就会连四十小时都做不到。后来XP将这一实践改成了“保持有活力的工作”,我想大概是因为有些身体好的人,每周工作六十小时也一样很有效率吧。要养成锻炼的习惯,争取做到“每天锻炼一小时,健康工作五十年,幸福生活一辈子”。

以上三种智能,体现在音、体、美这三门副课上。
将这三种智能提升到一定水平,就可以称得上全面发展了。

自知是第六种智能。曾子曰:吾日三省吾身。苏格拉底说:“认识你自己”。蘧伯玉年五十而知四十九年非。正确地认识和评价自己,不断地反省自己,这是进步的基础。《从优秀到卓越》一书的作者
Collins说,你应该问自己一个问题:在哪个领域你可以做到世界前三?人贵有自知之明。

知人是第七种智能。
越来越多的工作,正由团队来承担。有一本书名叫“
Teamwork Is an Individual Skill”,书名就很有启发:团队合作是一项个人技能。要成为团队的一员,你需要读懂他人,并具备一定的社交技能。然后,通过实现团队的目标来实现自我。“己欲立而立人,己欲达而达人。”如果你是团队的领导,那么知人善任就是一项重要的能力。刘邦正因为用对了张良、韩信、萧何而得到了天下。

寻找怎样的人作为合作的伙伴或朋友?益者三友:友直、友谅、友多闻。怎样看人才不会看走眼?子曰:视其所以,观其所由,察其所安;人焉廋哉。人焉廋哉。

以人为镜,可以明得失。见贤思齐焉,见不贤者自省也。所以,知人也可以促进自知。

知人者智,自知者明。以上两种智能,就属于情商的范畴了。(透露一个秘密:一下记住
7件事是有难度的,所以我把它分成3个部分——主课、副课、情商。你能记住吗?)

每个人在这
7种智能上的发展都不尽相同。可能因为各自的成长经历和条件,我们在某些方面的智能没有得到很好的发展。随着年龄的增长和自省智能的提升,你可能会认识到人生就是练功、升级。如果你现在已经走上了一条主动的持续改进之路,那么你打算如何在这些方面提高自己呢?

2009年2月10日星期二

‘5Ws and an H’流水账和组件

在前面的贴子“彩色UML建模、SOA和《目标》”中,提到了‘5Ws and an H’流水账,这里进一步阐述一下。

流水账
  • When:这个业务事件何时发生?
  • Who:谁参与这个业务事件?
  • What:这是个什么事?涉及什么物品?
  • Where:这个业务事件发生在哪里?
  • Why:为什么会发生这个业务事件?
  • How:这个业务事件以何种方式发生?有无细节?

让我们来看一个例子:
特征:为客户打印发票(收据)
  • When:记录下“打印发票(收据)”这个业务事件发生的时间
  • Who:参与这个业务事件的人有两个:客户和业务员
  • What:这个业务事件是“打印发票(收据)”,涉及到一张纸质发票(收据),上面有序列号
  • Where:这个业务事件发生在公司旗下众多连锁店的一个里
  • Why:因为客户付了一笔款,客户付款是前驱业务事件
  • How:用户付款时享受了折扣优惠,需要在发票(收据)上注明

当我们把一个业务事件的这些方面都记录下来时,信息就完备了。值得一提的是,在具体业务事件中,上面的每个因素都可以有多个具体的值。例如,在持续集成中,因为前面有3次提交,然后在5分钟内没有新的提交,这触发了一次持续集成。那么,这次持续集成事件的前驱业务事件就有3个。

组件
这个业务事件构成了一个组件,它对使用的上下文做了一些假设。如果这些假设没有变化,这个组件就不需要改变,可以复用。

也可以把这个组件看成一个独立的数据源,它提供对这个数据源的数据分析统计功能。例如:
  • 这个月哪个业务员开出的发票最多?
  • 从上次领发票本到现在,某个连锁店开出了多少发票(是否要提醒领新的发票本)?
如果从流水账和总分类账的观点来看,可以认为“5W and an H”类似于“科目”,它们对流水账进行了分类。

组件粒度
组件粒度也就是业务事件的粒度。“进行一次销售”这个业务事件可能由“下订单”、“付款”、“打印发票”这3个小粒度的业务事件组成。 于是,我们可以在“进行一次销售”这个业务事件的“How”部分,标明它由3个明细业务事件构成。如果我们还允许一周内无条件退货,那么还可以包含“退货”的业务事件。

小粒度的组件组合起来,就构成了大粒度的组件,系统以这种方式体现伸缩性。这样做的好处在于:
  • 简单一致地伸缩,设计小系统和设计大系统的原则一样
  • 在不同的抽象层面上看系统,如操作层面和管理层面

总账会计师和CIO
总账会计师要能够从企业的总账中分析出企业的盈利能力和风险。CIO要能够从业务事件流水账中按“科目”进行整理,通过分析找到实现企业目标的风险和机会。

2009年2月8日星期日

RESTful的停车场图和开发Dashboard

“REST的原则包括:
  • 以资源为中心的方式。
  • 所有相关的资源都可以通过统一资源标识符(URI)来寻址。
  • 统一通过HTTP来访问:GET、POST、PUT和DELETE。
  • 内容类型协商允许从同一个URI取得不同表示形式。
  • REST风格的服务很容易通过运行在web浏览器、其他客户端或服务器上的代码来访问,这在Ajax的背景下很流行。
  • 完全利用WWW的缓存机制
  • 为同一资源的多种表示层提供服务。
简而言之,这些网络架构原则允许多个网站对用户的无缝展现。由于有了REST,因特网变成了一个动态的网络,而不只是点到点的连接,不只是基于远程方法调用(RPC)技术。”

以上内容引自正在译的新书“Executing SOA”,这启发我用REST的观点来看问题。REST不是新观点了,我现在才有点理解,Shame on me!我的博客中“最近的访客”就是一个极好的REST例子。

如果用REST的观点来看,项目的进度是一项资源。特征驱动开发(FDD)的停车场图就可以作为一个很好的例子。
  • GET:取得停车场图(图或数据)
  • POST/PUT:创建或更新这个停车场图所代表的资源
  • DELETE:删除这个停车场图所代表的资源

然后,我们就可以把这个停车场图无缝集成到别的网站中。集成到哪里呢?项目开发Dashboard!类似像Sonar或maven生成的项目网站这样的。

更进一步,如果所有的资源(版本控制、持续集成......)都放在云端,然后用RESTful的方式发布出来,我们就可以随便用一个内容管理系统(CMS),定制自己的项目Dashboard,最简单的可能就是使用一个wiki。

云时代真的来了。很想写一个停车场图的RESTful Web Service。


2009年2月7日星期六

测试计划

关于制定test plan,我理解的是测试工作的进度安排。

最根本的一条原则:测试工作和开发工作结合得越紧密,效果就越好。

理想中的测试工作安排是这样的:
  • 在项目早期,测试团队的骨干参与需求工作,确保需求的可测试性,同时也帮助消除了需求的二义性。
  • 在概要设计/架构设计阶段,测试团队同步进行测试概要设计,确定测试的基础架构和基础设施。
  • 在详细设计/迭代开发阶段,测试团队同步进行测试用例设计和开发,确保开发团队通过持续集成交付的每个feature都在最快的时间内得到测试。容量测试、压力测试等也要在条件一成熟,就马上进行。

随着产品代码的不断增加,自动化测试的代码也不断增加。这些测试代码在整个软件生命周期中都有用。

测试成本低的的设计是好设计,测试成本高的设计是不好的设计。降低测试成本是降低软件总体拥有成本的根本途径。

人总是会犯错误。尽早发现,尽早更正,确保决不再重犯同样的错误,是我们的追求。

参考书籍:《快速测试》、《持续集成》、《Java测试新技术:TestNG和高级概念

2009年1月26日星期一

彩色UML建模、SOA和《目标》

彩色UML的主要思想是组件化。粉红色的MI(时刻时段)是组件的核心,同时粉红色的MI也代表着事务,这些事务串在一起,组成了流程。

每一个粉红色的MI及其相关的黄色角色、绿色参与方-地点-物品和蓝色描述构成了业务流程中的一个步骤。根据彩色建模的伸缩性设计,这个步骤的粒度可大可小。可以是大粒度的“完成一次销售”,也可以是小粒度的“开发票”或“收款”。

SOA的思想是要把事务组件web service化,再用BPEL来组织流程,以这种方式来组织业务逻辑。
另外,在数据逻辑或信息逻辑的方面,SOA会把一些数据分析和挖掘的方法也设计成可以复用的web service。

彩色UML方法里面的一些思想,比如“评估销售的及时性”,就带有很大的数据分析的味道。

换句话说,我们先对业务过程记上流水账,然后再对流水账进行分析。我们记录下When/Who/What/Where/Why and How(‘5Ws and an H’ ),然后就可以进行各种分析了。你可以在数据库中只记录下最小粒度的业务事件,然后以虚拟数据源或信息服务的方式,提供大粒度的业务事件的相关信息。

为了让流程能够灵活变化,我们可以考虑消除步骤之间的耦合,即在红色的MI之间不建立直接关联,而是利用它们共同的关注的内容,如“订单”,来组织流程。如果我们要“评估销售的及时性”,只要得到“订单”下单的时间和付款的时间之差。设想有一个信息服务,作为一个虚拟数据源,提供一段时间内所有订单的完成时间和各步骤完成的时间。

Goldratt的《目标》是所有管理者都应该读的一本书。其中提到了一个重要观点:所有在原材料方面的投资,如果还没有变成有效产出(产品卖出去并收回货款),都应该看成是负债。这种负债乘以天数,是一个重要的测量指标。我们应该努力减小它。根据这一观点,制造业的企业提出了许多解决方案,如按订单生产、精益制造等等。而制造业的人们学到的东西,看来IT业都要重新学习。

回到我们的主题,这和彩色UML或SOA有什么关系?

关系就在于:我们的IT系统,要能够帮助企业的决策者找到系统瓶颈。看过《目标》的人应该记得,那里面有个神奇的CIO,每次都能告诉厂长系统的新瓶颈出现在哪里。然后大家不断优化瓶颈,使系统的产能和效率不断提高。

如果您就是那位CIO,请你告诉我,我们的订单执行过程中,哪(几)个环节耗时最多?




2009年1月15日星期四

设计决定、反悔、霰弹式修改和架构污染

(老文章,曾在《程序员》杂志发表。用了vim得到彩色的代码,但可惜在《程序员》上没有印出彩色。)


设计决定

“好吧,在这一个项目中我们就使用Google Guice!”我们坚毅、果敢、英明神武的首席架构师宣布了这个决定。

为什么不呢?依赖注入是这么成功的一个概念,我们使用Spring作为依赖注入框架已经好多年了。而Google Guice又给我们带来了许多的改进。

首先,Guice抛弃了XML配置文件,这使它具备了“重构友好”的特征。在Guice中,我们通过Java代码来说明接口(服务)和实现类的绑定,例如下面这一段代码:

public class MyModule extends AbstractModule {
protected void configure() {
bind(Service.
class)
.to(ServiceImpl.
class)
.in(Scopes.SINGLETON);
}
}

这样,当我们需要重构(修改)接口或实现类的名称时,就可以利用集成开发环境提供的重构功能来完成,不需要再手工修改XML中的类名。在重构深入人心的今天,我们不希望改一个类名还需要额外的工作。

第二,在抛弃了XML配置文件之后,应用程序就不再需要一个XML解析器了。这样,Guice就比Spring“瘦身”了不少。高手们总是对减少资源的占用怀有特殊的爱好,不是吗?Guice 1.0Jar包只有544K(高手可能会认为还是大了一点),不依赖于其他的Jar包。另外,由于不需要读取并解析XML配置文件,执行的速度也提高了不少。

第三,Guice使用了JDK 5的新功能,其中一项就是泛型。我们不再像以前使用Spring那样进行强制类型转换了,我们有了编译时刻的类型安全检查,而且代码也简化了。我们像下面这段代码那样取得组件实例:

public class MyApplication {
public static void main(String[] args) {
Injector injector = Guice.createInjector(
new MyModule());
Client client = injector.getInstance(Client.
class);
client.go();
}
}

第四,Guice使用了JDK 5的另一项新功能,annotation!这个词太可爱了,听起来和innovation有点相似,以至于我们一时间不知道如何将它翻译成中文。Guice提供了近乎神奇的@Inject annotation,满足您各式各样的注入需要!(我相信这些注入方式都是在Martin Fowler的那篇著名文章中提到的。)

我们可以实现构造方法中的注入,像下面这样:

public class Client {
private final Service service;

@Inject
public Client(Service service) {
this.service = service;
}

public void go() {
service.go();
}
}

也可以对属性直接注入,甚至不需要设值方法,像下面这样:

public class ServiceImpl implements Service {
@Inject
Emailer emailer;

public void go() {
// Some expensive stuff.
...
// Send confirmation.
emailer.send(
...);
}
}

第五,Guice有在真实应用中成功使用的历史。据说,它曾在Google的广告应用程序中使用,而Google的广告应用程序可能是目前世界上规模最大的应用程序!

最后,更重要的是,品牌就是品质。Google是如此受人尊敬的公司,吸引了许多重量级的人物加盟,已经成为许多IT人士向往的地方。使用Google的荣誉产品,就像使用JDK自带的包那样可靠,我们还有什么可担心的?据说Spring的新版本也向Guice学习,使用了annotation。模仿就是一种肯定,不是吗?


反悔

“新郎,你愿意娶新娘为妻吗?”

“是的,我愿意。”

“无论她将来是富有还是贫穷、或无论她将来身体健康或不适,你都愿意和她永远在一起吗?”

“是的,我愿意。”

“将来看到更漂亮、更好、更合适的,你也不会改变今天这个决定吗?”

如果理性地回答,答案就太不浪漫了。因为未来不可预见的事情实在太多。好在热恋中的人们倾向于不那么理性,所以答案是“永远。如果非要加上一个期限,我希望是一万年”。

我们的架构师正在与Guice开始一段新的感情。这种感觉太好了,上次有这种感觉好像还是在刚使用Spring的时候,那已经是几年前的事情了。现在他压根没想到以后可能会像抛弃Spring一样抛弃Guice

直到有一天,一个刚刚参加工作的小兄弟问了他一个很傻的编译错误。他告诉这个小兄弟,要使用@Inject,必须这样:

import com.google.inject.Inject;

小兄弟满意地继续他的开发,但我们的架构师却觉得有一丝不祥的感觉掠过,空气中似乎飘荡着一种坏味道。

他检查了一下项目源代码。在第一次迭代中,我们写了50多个类,其中大约有一半都包含了@Inject!这种情况不妙,简直太不妙了。这违反了他多年的经验所建立起来的原则信仰。

这违反了Larry Constantine的“高内聚、低耦合”的原则。这条伟大的原则如此简单而深奥,以至于不断受到人们的践踏。

虽然“疯狂的Bob Lee”为Guice设计出了极小的API接口,但在使用@Inject这一点上,却让人不太赞同。他似乎鼓励我们在自己代码的各处插入@Inject

当我们把一个设计决定分散到代码的不同地方,它就会成为难以改变的既成事实。即使是对于JDK自带的类,我们也不能这么做。我们不能在所有的业务类中都引用JDBC的包,使用ConnectionStatementRowSet。相反,我们应该把它们放在持久层中。也许,我们应该使用Hibernate这样的框架,将它们封装起来。也许我们还需要再封装一层,设计一个PersistentLayer接口和一个PersistentLayerHibernateImpl实现。这样,如果我们以后有新想法,只要再写个PersistentLayerJdbcImplPersistentLayerDb4oImplPersistentLayerIbatisImplPersistentLayerHibernateAndIbatisMixImpl

如果我们把使用Guice这个设计决定作为一种默认的假定,让它散布在代码的各个角落,那么将来我们要反悔的代价就非常大。实际上,我们就不能反悔了。

我们会反悔吗?我们需要反悔吗?不能反悔的决定是我们可以承受的吗?

我们的架构师做出了一个痛苦的决定:我们需要能反悔。毕竟,作为一名架构师,他的全部声誉就在于设计出有弹性的架构,可以容纳将来可能的变化。


霰弹式修改

我们的架构师是经验丰富的。办法总比问题多,不是吗?关键是要发现问题。根据“所有的问题都可以通过添加一个间接的中间层来解决”这一原理,他设计了一个Factory,这是一个全局的Singleton

public class Factory {

private static Factory factory;

synchronized public static Factory getInstance() {
if (factory == null) {
factory =
new Factory();
}
return factory;
}

private Injector injector;

private Factory() {
injector = Guice.createInjector(
new MyModule());
}

public T getInstance(Class type) {
return injector.getInstance(type);
}
}

然后,在每个用到服务依赖注入的类中,在构造方法里加上赋值语句:

public class Client {
private Service service;

public Client() {
this.service = Factory.getInstance().getInstance(Service.class);
}

public void go() {
service.go();
}
}

注意,我们没有为service属性加上final关键词,这样您仍可以有setService()方法,可以利用Mock对象对Client类进行单元测试。

剩下的工作就是从项目已有的代码中去掉所有的@Inject。这是一种“霰弹式修改”,不幸中的万幸,我们只要修改20多个类,而不是200多个、2000多个。

由于有自动化的测试类作为保障,这种改动很快就完成了。如果以后我们想换一个依赖注入框架,就不需要“霰弹式修改”了。我们甚至可以很容易改回用Spring,当然,目前还没有这个必要。

再见了,@Inject


架构污染

不久后的一天,我们的架构师在浏览代码时,惊奇地又看到了@Inject

原因很快查到了,一个刚刚结束了婚假回到开发团队的开发人员,因为错过了关于这次修改的讨论,重新在代码中引入了@Inject

我们的架构师设计出的精美架构受到了污染。这种事情以前也发生过,似乎总是有各种各样的原因,导致架构在实现者手里变形。我们的架构设计师能设计故事的开始,却猜不中故事的结局。这种情况是他所痛恨的。

“我们需要有一种手段,能对架构的实现进行监控。”架构师在开发例会上说,“这不是不相信我们的程序员,人都可能犯错的。”

“信任,但要核查。”里根在签署核裁军条约时曾这么说。

一个对敏捷软件开发颇有研究的高级程序员说,“我最近看了一本书,书名是《持续集成》,书中提到了一个工具,好像叫JDepend,可以显示项目中的包依赖关系。正适合解决这个问题。”

后来的故事简单了,通过重构,我们将Factory放在了单独的一个包里,只有这个包才对Guice产生依赖。我们的架构师经常地查看JDepend的输出,看着他设计架构一天天地实现。


尾声

我们的架构师盯着他的二十二寸宽屏液晶显示器,回味着简洁优美的架构所带来的快感。一个念头在他脑海中闪现:“如果Guice不提供@Inject相关的功能,那Jar包就会更小了。”

Guice是开放源代码的,如果有时间,我们的架构师将替他的“新娘”瘦身,去掉@Inject相关的所有类,让这种架构污染永远也不可能再发生。但是现在,还有更多的项目工作等着他。也许等他忙完这段时间,会再来折腾一下这个问题。

生命在于折腾,不是吗?

分析代码和项目管理:Sonar

静态代码分析、测试覆盖率分析是持续集成的重要组成部分,最近看到的开源项目Sonar,带给了我更多启发。

第一,对多个项目的统一管理和集中展示。这适合在开发组织(乙方)中应用,特别是一些做外包的公司的QA部门。

第二,对主要测量指标的时间维度的关注。当前的状况固然重要,但更重要的是发展的历史和趋势:情况是在变得越来越好还是越来越糟?。

第三,单个项目管理的dashboard。把项目的主要测量指标以图表(仪表盘)的方式展现出来。

第四,向下钻取具体信息。允许进一步关注细节。

第五,覆盖云。用一种创新的方式体现被测试代码中各个类被关注的程度。这种方式似乎来自于Blog对Tag的一种管理,例如,Sun公司的博客中的Most Popular Tags。很直观。

关于CMM有一个简单易记的说法:第二级是“Plan the work”,第三级是“Work the plan”,第四级是“Measure the work”,第五级是“Work the meature”。Sonar给了我们一些第五级的启示:我们关注哪些项目测量指标?怎样展现和应对?

Netbeans 6.5和Liferay 5.1.2

自从Sun把Studio Creator合并到Netbeans 6中以后,开发Liferay的portlet就不像原来那么方便了。最近搜了一下网络,新出了一个攻略!我还没亲自试过,不过可信度应该比较高。

Portal和portlet的概念很适合企业应用的组件化开发、SOA和流程再造,为应用程序的表示层提供了灵活的解决方案。是好东西。

2009年1月6日星期二

FDD项目进度管理和停车场图

特征驱动开发有一个好处,进度的管理变成了一件很简单的事情。

每一个特征的实现分成两步:根据特征设计(DBF)和根据特征构造(BBF)。其中包含一些小的里程碑,每个里程碑代表着完成了一定百分比的工作量。领域走查:1%;设计:40%;设计审查:3%;编码:45%:代码审查:10%;提交构造:1%。

接下来的魔法就是由项目管理人员来告诉大家项目的进度。最具创新的就是“停车场图(Parking Lot Char or Parking Lot Diagram)”。下面是一个例子 :


绿色的部分代表已完成的特征集,红色的部分代表已延期特征集,蓝色的部分代表正在开发的特征集,白色的部分代表还未开始的特征集。点击图片可以链接到该图的原出处()。

怎样能画出这样的停车场图?好东东来了,你可以在这里下载一个Excel模板。

每个星期打印一张这样的图,贴在墙上,告诉所有感兴趣的人,我们工作做到哪儿了。另外,还可以打印已完成特征数(或包含总特征数)随时间变化的折线图。看到这样的图表,所有的人都会心情愉快的。