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建模很有研究,后来又醉心于敏捷方法学,然而他不能将这二者很好地融合,那么他只能算是一个跟风者,就像那只过玉米地的猴子。我怀疑这些知识是否真正成为他的东西。
  • 接下来是实践,实践出真知。实践可以检验你的理解是否正确,即是否符合客观规律。“纸上得来终觉浅,绝知此事要躬行”。

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

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

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

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