PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf ·...

126

Transcript of PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf ·...

Page 1: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。
Page 2: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

2

JDJ

用 Lucene 给你的应用程序加上搜索功能

作者:Craig Walls

我供职的电子商务

网站用好几种不同的办

法实现过搜索特性。一

开 始 , 我 们 使 用 简 单

的、带有“like”子句的

SQL语句,但这种办法效

率不彰,而且有很多我

们期望的语言特性没有

得到实现,包括派生词

(例如“paint”、“painter”

和“painting”)和同义词

匹配(例如“ c a t ”和

“feline”)等等。然后,

我们又尝试了另一种相

似的解决方案,这次我

们解决了效率和语言要

求的问题,但定制行为

的能力又不强。

随后,我们发现了

Lucene。作为一个框架,

Lucene 提供给你的是用

于构造搜索引擎的构件

块,你可以用这些通用

的构件块搭建起一个能

满足特定搜索需求的搜

索引擎。Lucene 既灵活、

完全可定制,并且还非

常快。

在本文中,我将向

读 者 展 示 “ 如 何 使 用

Lucene 给你的应用程序

加上搜索功能”。Lucene

是如此灵活,以至于你

可 以 在 任 何 应 用 程 序

(包括Web程序、桌面程

序等等)中使用它。

CUJ

对 Interpreter设计模式的另一种解释

作者:Eric Niebler

当 我 在 《 设 计 模

式》中读到 Interpreter模

式时,感觉特别亲切。

“那就是我的代码!”我

这样想道。在对这个模

式一无所知的情况下,

我也用了一个类似于In-

terpreter的方案,而且解

决的问题也跟GoF的“动

机”一样:正则表达式。

Interpreter 可以清楚而准

确地在代码中解释语法

规则,并在运行期对正

则表达式进行“解释”或

执行。但是,出于性能方

面的考虑,这个模式常

常被人们忽视了。就连

GoF,也对该模式在高性

能应用程序中的使用前

景不表乐观。

的确,一个简单的

Interpreter 实现的确在性

能 方 面 有 着 严 重 的 缺

陷。但是,只要加上一些

非常简单的技巧,Inter-

preter实现的速度就可以

与状态机相媲美,同时

又 不 损 失 该 模 式 的 优

点。在实现 GRETA 正则

表达式模板库时,我就

使用了这些技巧——这

个库在很多方面甚至比

Boost 的 regex还要优秀。

在本文中,我将介绍这

些技巧中的一个:借助

模板将虚拟函数调用内

联化。

DDJ

活着的神话作者:Jonathan Erickson

你并不是每天都能面

对面地接触一位活生生的

神话人物。当然,如果你

在 COMDEX或者软件开发

大会的会场上遇到 B i l l

Gates、Linus Torvalds或者J.

D.Hildebrand,他们肯定会

摆一摆架子。但是,如果

把“神话”这样一个修饰

词加在他们的名字上,还

是显得有点不伦不类。

如果你想要见到一位

活生生的神话人物,就必

须到他们可能出现的地方

去,并希望自己有个好运

气。我就曾经在一次漫画

书大会上遇到过 J u l i u s

Schwartz。在他的帽子上写

着“活着的神话”,不过我

并不需要它的帽子来告诉

我这一点——如果你连

Julie Schwartz 都不知道的

话,那么你一定不是科幻

迷或者漫画迷。

1932 年,Schwartz成

为了第一本科幻杂志《时

间旅行者》的编辑。1939

年,Schwartz 参与组织了

在纽约举办的第一届世界

科幻大会。后来,Schwartz

还荣获了Forry奖的“科幻

终生成就奖”、第一届

Fandom Hall奖、Raymond Z.

Gallun“科幻文学特殊贡献

奖”,以及你将在本文中

看到的 Julie 奖。

MSDN Magazine

给.NET应用程序加上模板功能

作者:Adam J. Steinert

在. N E T 框架中,

CodeDOM 对象模型可以

用多种不同的语言来展

现。本文中介绍了使用.

N E T 框架的 S y s t e m .

C o d e D om 和 S y s t e m .

CodeDom.Compiler 名字空

间编写的代码模板是如

何 让 开 发 者 创 建 可 复

用、可在多个项目之间

共享的样板文件源代码

的。通过模板设计的组

件能够缩短开发时间,

从而提高生产率。

在这里,我们模拟

了 C + + 风格的类和模

板,并生成了多种语言

的模板。此外,我还将向

读者解释对象图和格式

化输出的代码。

本文中所介绍的示

例程序能够帮助读者理

解 CodeDOM 的用法。但

是,还有一些地方需要

扩展和改进。一个很重

要的改进就是:严格区

分类型的能力。例如,如

果把“使用String变量生

成的模板类”作为算术

函数的操作数,那就没

有意义。对类型的区分

可以这样实现:将参数

和值通过 XML 传递,并

用 XML Schema 来验证

类型。

WDJ

构造更好的SQL 查询作者:Mo Budlong

在数据库驱动的应

用开发中,“构造 SQL语

句 ” 是 日 常 的 工 作 之

一。但是,有时你也会构

造 出 既 难 看 又 难 懂 的

SQL语句,就像我们可以

看到的查询是针对一个

设计得很糟糕的 SQL 数

据库的,它的用途是提

取出特定年、月、日、小

时的价格和供求信息。

每个小时中,信息被更

新不止一次,因此查询

必 须 确 保 从

PRICE_CALCULATION行中

提取出的结果能够和所

需 的 时 间 相 吻 合 。 但

是 , 数 据 库 又 有

PRICE_CALCULATION_ID

列,这一列的值会在每

个新的行中自增。

它能够正确地通过

编译,但是查询却不能正

常运行,因为这种风格的

查询造成了一个几乎完全

无法发现的错误:FROM

MARKET_CLEARING_PRICE前

面的一行并不是以空格结

束的。当字符串的连接完

成之后,在WHERE和FROM

MARKET_CLEARING_PRICE之

间将没有空格。要想检查

到这个漏洞,你必须自己

去检查最终的字符串。即

使这样都还不一定保险,

因为查询实在太长了,完

全有可能看走眼。

Page 3: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

3

呼唤明星

2002年体育界的头件大事毫无疑问是姚明登陆NBA,在大洋彼岸掀起“小巨

人”旋风。

2002年影视界最引人注目的电影是张艺谋的《英雄》,大投资,大明星,

大宣传,好莱坞的大片模式终于复制到了中国。

回头看看我们所在的技术出版领域,每年到了年末都是销售商的好日子,

图书订货会上这家美酒佳肴不断,那家新马泰风光走来。用户就是上帝,谁给

我钱谁就是我的上帝,市场经济的道理大家都懂,没有经销商,我们的图书如

何销售得出去。

可是我总感觉缺了点什么?

年终评选优秀技术图书,几乎所有入选的书籍都是引进英文版,只有侯捷

是例外,他的《深入浅出MFC》、《STL源码分析》都广受好评。1999年我在CSDN

网站上介绍侯捷,说可惜大陆没有这样的作者,文武双全,既精通技术又妙笔

如花,不少读者愤愤不平,说太武断了,大陆这么多人才,怎么可能?

某家出版社看到《深入浅出MFC》热销后,广发帖子声称要制造培养大陆的

侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈

出现大陆侯捷了。

其实在出版领域,所有人都知道作家是我们真正的明星,可是为什么在技

术图书方面,我们却只有引进版。而且国内软件技术领域也是如此,又有谁能

数出我们的专家明星?

最近这三年来,因为工作关系我接触了更多的技术专家,现在我要修正原

来的话,我们不是没有人才,而是缺乏吸引人才的环境。

一个好作者,至少要过三关。第一时间关,真正的技术高手来自于企业,

而企业人士又很难沉下心来写作,而且写好一本书要花费半年到一年甚至更长

的时间,他们有没有时间?第二是写作关,有了时间但能不能写好,技术好并

不一定能写好。第三是金钱关,有时间又能写,能不能得到合理的回报,技术

越深的书越难写,而且销售量还越少。再加上某些出版社陈旧的体制,对作者

的忽视,我想能挺过这三关的实在是太少了,所以我们在国内看到的是太多的

“剪刀+浆糊”的作品。

我们一直在努力打通这三关。《程序员》杂志打破时间关,写本书花费的

时间太长,不妨就先写一篇文章;CSDN网络专栏打破写作关,写得好不好发在

网站上,看看网友的反映就知道了;最后一关看起来最难,但其实也最简单,

用那只“无形的手”就解决了。

明星是由市场决定的,读者心目中的明星才是真正的明星。只要建立起合

理的商业出版机制,我想明星也就离我们不远了。

2002.12

Page 4: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

4

程序员调查 4

调 查

最爱10 本书揭晓最爱10 本书揭晓作为软件开发人员、项目管理人员提升自己的一个最直接介质——图书,历来是大家最为关注的热点话题之一。为了激

励相关作者和出版社在2003年出版更高品质的好书,《程序员》杂志联合CSDN网站举办了“2002年度最佳开发类图书评选”

调查活动。根据读者、网友的提名和专家推荐,经过严格评审,产生了以下候选书目:

获 得 提 名 资 格 的 图 书(共 85本)(评选范围:2002年国内出版社出版的原创、翻译、影印版开发类图书)

《C#程序设计》

《Microsoft c#Windows程序设计》(上、下册)

《Applied Microsoft .NET Framework Programming》(影印版)

《Programming Windows with C#(Core Reference)》(影印版)

《C# COM+编程指南》

《精通.NET核心技术——高级特性》

《精通.NET核心技术——原理与构架》

《JAVA技术手册》(第3版)

《Java 经典实例》

《精通EJB》(第2版)

《Java与模式》

《Java编程思想(第2版)》

《Java高效编程指南》

《最新Java 2 核心技术 卷I:原理》(原书第5版)

《Java深度历险》

《J2EE核心模式》

《J2EE技术内幕》

《面向对象程序设计——图形应用实例》

《J2EE应用与BEA WebLogic Server》

《EJB 2.0企业级应用程序开发》

《C++程序设计语言》(特别版)

《C++语言的设计和演化》

《C++编码规范》

《C++编程思想(第2版)》

《C++沉思录》

《C++Primer中文版》(第3版)

《C++Primer 题解》

《C++程序设计语言题解》

《C++ Builder深度历险》

《STL源码剖析》

《C++Builder5程序设计大全》

《C++标准程序库—自修教程与参考手册》

《More Exceptional C++中文版》

《C++代码设计与重用》

《C陷阱与缺陷 》

《Delphi6应用开发指南》

《Delphi6/Kylix2 SOAP/Web Service程序设计篇》

《Delphi6企业级解决方案及应用剖析》

《Delphi 第三方控件使用大全(II)》

《XSL技术内幕》

《Visual C++编程深入引导》

《数据库系统导论》(英文版.第7版)

《Transact-SQL权威指南》

《UML数据库设计应用》

《Delphi/Kylix数据库开发》

《算法导论》(第2版 影印版)

《软件开发的科学与艺术》

《计算机程序设计艺术》(英文影印版)

《计算机程序设计艺术》(中文版)

《从规范出发的程序设计》

《程序设计实践》(英文版)

《计算机体系结构:量化研究方法》(英文版.第3版)

《操作系统概念》(第六版 影印版)

《分布式系统——原理与范例》(英文影印版)

《计算机网络》(第3版)

《系统分析与设计》

《系统分析与设计》(英文版)

《软件工程:实践者的研究方法(第5版)》

《设计模式:可复用面向对象软件的基础》

《设计模式:可复用面向对象软件的基础》(英文版)

《分析模式》

《人月神话》

《编写有效用例》

《编写有效用例》(英文版)

《UML和模式应用:面向对象分析与设计导论》

《统一软件开发过程》

《快速软件开发——有效控制与完成进度计划》

《UML参考手册》

《UML用户指南》

《CMM实践应用——Infosys公司的软件项目执行过程》

《个体软件过程》

《小组软件开发过程》

《软件需求管理:统一方法》

《程序调试思想与实践》

《UML精粹——标准对象建模语言简明指南(第2版)》

《软件开发的滑铁卢——重大失败项目的经验与教训》

《ERP原理·设计·实施》

《Win32多线程程序设计》

《4.4BSD操作系统设计与实现》

《TCP/IP 详解 卷1:协议》(英文版)

《TCP/IP详解 卷2:实现》(英文版)

《TCP/IP详解 卷3:TCP事务协议、HTTP、NNTP和UNIX域协议》(英文版)

《Windows 图形编程》

《计算机图形学原理及实践:C语言描述》(英文版.第2版)

《Windows程序调试》

《UNIX网络编程卷2:进程间通信》(第2版)(英文影印版)

《最高安全机密(第3版)》

《黑客大曝光:网络安全机密与解决方案》(第2版)

《编写安全的代码》

《面向对象的软件测试》

《软件测试》

喜爱系列

《Java编程思想》(第2版)《C++Primer中文版》(第3版)《C++程序设计语言》(特别版)《精通EJB》(第2版)《精通.NET核心技术——原理与构架》《设计模式:可复用面向对象软件的基础》(英文版)《人月神话》《TCP/IP详解 卷1:协议》(英文版)《C++编程思想(第2版)第1卷:标准C++导引》《STL源码剖析》

Page 5: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

5

C++类 Java类 软件工程类 .NET类 系统开发类 其他

43%

23%

18%7%

5% 4%

调 查

点 评

从评选结果我们可以看到最受读者欢迎的图书仍然是C++类。三本在C++世界有着极好口碑的著作《C++ Primer》、《The

C++ Programming Language》和《Thinking in C++》都榜上有名。紧随其后的则是Java类图书。

另一方面,读者的品味也有了很大提高,除了前面三本C++经典之外,《Thinking in Java》、《Mastering EJB》、《Design

Patterns》、《The Mythical Man-Month》等在国际上享有盛名的图书译本也受到了中国读者的青睐。随着翻译作品的增加,读

者对译者的要求也更加苛刻,因此侯捷、潘爱民、裘宗燕、汪颖等一批优秀译者才能脱颖而出,形成品牌效应。

但是,“国产”作家的缺乏也让我们感到淡淡的隐忧。10本最佳图书中,只有两本原创作品,真正引起较大反响的只

有侯捷的《STL源码剖析》一本。在新的一年中,希望我们能看到更多优秀的原创技术作家出现。

(本刊编辑部)

欢迎大家向我们提供调查话题,来信请寄:[email protected]  

调查结果如下

1、最受欢迎的10本书: 2、图书分类排行榜:

此次评选活动从策划到实施,再到结果揭晓,共历时一月左右,期间得到了广大读者和网友的极大关注和配合,在CSDN

网站上引起了很大的反响,正式评选阶段网友投票数高达2万4千多张。

评选步骤:(1)编辑、专家、网友提名; (2)CSDN网上投票。

3、技术热点现状:(总投票数:24587张)

C++ 类: 得 10572 张

Java类:得 5655 张

软件工程类:得 4425 张

.NET 类: 得 1721 张 系统开发类: 得 1229 张

其 他:得 985 张

Page 6: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

6

目 录

书评

一枝看上去很美的花 lllllllllllllllllllllllllllllllllllllllll 114

读 C++沉思录 llllllllllllllllllllllllllllllllllllllllllllllllll 117

好书推荐2002 年软件开发类图书市场热点回顾 lllllllllllllllllll 119

名社新书 lllllllllllllllllllllllllllllllllllllllllllllllllllllllll 122

编读往来 lllllllllllllllllllllllllllllllllllllllllllllllllllllllll 124

厂商直击 llllllllllllllllllllllllllllllllllllllllllllllllllllllllll 126

服务&广告

管 理

微软人才专题

微软人才管理探秘

—— 2002微软高级开发管理峰会成果解析 lllllllllllll 33

软件创业

开放源码的风险评估 lllllllllllllllllllllllllllllllllllllllll 46

软件工程论坛

CRM现状及发展 lllllllllllllllllllllllllllllllllllllllllllllll 48

特别策划马不停蹄又一年—— 2002年开发技术大综述 lllllllllllll 10

2002 十实点评 lllllllllllllllllllllllllllllllllllllllllllllllll 15

对话从 Rational被收购谈起 llllllllllllllllllllllllllllllllllllllllllll 18

走向海外菲律宾随笔 llllllllllllllllllllllll lllllllllllllllllllllllll ll l 21

人物专访中国最高智慧之叶天正:没有什么可以骗了我 llllllllllll 25

“C++之父”访谈 lllllllllllllllllllllllllllllllllllllllllllll 28

人物&报道

最爱 10 本书揭晓 llllllllllllllllllllllllllllllllllllllllllllll 4

调 查

封面书摘 lllllllllllllllllllllllllllllllllllllllllllllllllllllll 2

呼唤明星 lllllllllllllllllllllllllllllllllllllllllllllllllllllll 3

马不停蹄又一年——2002年开发技术大综述

书写神话的布鲁克斯 lllllllllllllllllllllllllllllllllllllllll 9

名人堂

2002十实点评

10

28

“C++之父” 访谈

15

最爱 10 本书揭晓最爱 10 本书揭晓4

Page 7: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

7

程序天下事 lllllllllllllllllllllllllllllllllllllllllllllllllllll 51

技术专题:垃圾收集导语 llllllllllllllllllllllllllllllllllllllllllllllllllllllllll 52

Garbage Collection——问题和技术 lllllllllllllllllllllll 53

.NET的自动内存管理 lllllllllllllllllllllllllllllllllllllll 59

一个 C++的垃圾收集框架 llllllllllllllllllllllllllllllll 64

针对 Delphi对象和构件的垃圾收集器 lllllllllllllllllll 67

电脑英语

垃圾收集算法 llllllllllllllllllllllllllllllllllllllllllllll 70

MSDN

WTL初探——完美的 ATL应用程序框架 llllllllllllll 72

Database

嵌入式数据库——朴素但实用的数据库选择 lllllllll 75

我眼中的 Visual FoxPro llllllllllllllllllllllllllllllllllll 78

JavaPersonal Java 程序设计(一)lllllllllllllllllllllllllllll 81

RubyRuby在网络上的应用 llllllllllllllll llllllllllllll llllllllll 84

Design Pattern抽象机模式 lllllllllllllllllllllllllllllllllllllllllllllllll 87

C++C++ 高效程序设计lllllllllllllllllllllllllllllllllllllllllllll 92

DelphiDelphi 中不同常规的接口 llllllllllllllllllllllllllllllllll 96

开发工具使用 AutoDump+ 处理挂起和崩溃 lllllllllllllllllllllll 103

编程擂台搜寻恐怖分子题解 llllllllllllllllllllllllllllllllllllllll 106

专家门诊 llllllllllllllllllllllllllllllllllllllllllllllllllllllll 108

技 术

目 录

Page 8: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

8

关 键 词 索 引

(总第63期)

主管单位:中国社会科学院

主办单位:中国社会科学院文献信息中心

协办单位:北京百联美达美数码科技有限公司

www.csdn.net

出版单位:《Internet 信息世界》杂志社

总 编:黄长著

常务副总编:张悦校

社 长:张悦校

副 社 长:蒋 涛

执行副总编:熊池舟

副 总 编:唐 琦

编 委 会:黄长著 张悦校 陈洋彬 蒋 涛

熊池舟 唐 琦 曾登高

编 辑 部:孟迎霞(主任)

责任编辑:张 里 闫 辉 程 琰 张 力

技术编辑:汤 韬 熊 节 王行舟

美术编辑:关峻峰 孔祥利  吴志民

电 话:010-84540265

投稿信箱:[email protected]

发 行 部:俞 波(主任)

发 行:蔡 莉

电 话:010-84540235

信 箱:[email protected]

广 告 部:武 超(主任)

电 话:010-84540236

信 箱:[email protected]

读者服务部(邮购)

读者信箱:[email protected]

地 址:北京市朝阳区北三环东路 8号

静安中心 26 层(100028)

电 话:010-84540262

传 真:010-84540263

邮 购:读者服务部

国内刊号:CN11-3967/G2

国际刊号:ISSN1008-2255

国内邮发代号(2002 年):2-665

印刷:北京新特文教印刷厂

广告经营许可证号:京东工商广字 0188号

网 址:http://www.csdn.net/magazine

出版日期:每月 5日

零 售 价:人民币 10.00元

本刊图文版权所有,未经允许不得任意转

载或摘编。本刊作者发表的文章仅代表作

者个人观点,与本刊立场无关。

发现装订错误或缺页,请将杂志寄回本刊

读者服务部,即可得到调换。

广告内容

□ Power Java Solution

□ Ebuider

□ KV3000

□名家经典系列图书

□杂志广告报价

□ CSDN网站广告报价

□系列图书

□海淀图书城销售排行

□高校教材

□ China-pub 销售排行

□新风雨网络书城

□培训信息

□ IBM Web Spere

□电子杂志征订

□组件、华表插件

□征订

□ IBM软件认证

公司名称

Borland 公司

清华同方

江民新科技

程序员杂志社

程序员杂志社

百联美达美公司

人民邮电出版社

海淀图书城电子科技书店

华章公司

华章公司

新风雨网站

锐信公司

IBM公司

西安葡萄城信息技术公司

用友华表

中国电脑教育报

IBM公司

页码

封二

1

7

127

128

129

130

131

131

132

132

134

134

135

136

封三

封底

公 司 索 引

公司 页码

R a t i o n a l 1 8

I B M 1 8

趋势科技 2 1

微软亚洲研究院 1 2 6

微软(中国)有限公司 1 2 6

B o r l a n d 公司 1 2 6

神州数码(中国) 1 2 6

微创 1 2 6

金蝶 1 2 6

S A P 1 2 6

I B M 1 2 6

人 物 索 引

人物 页码

布鲁克斯 4

J e n n y L i u 1 8

邵凯 1 8

左春 1 8

曾炼 1 8

张伟钦 2 1

郑奕立 2 1

纪孟宏 2 1

叶天正 2 5

B j a r n e S t r o u s t r u p 2 8

广 告 索 引

关 键 词 页 码

开发类图书 4

技术盛会 15

专栏作家 17

开放源码 46

风险评估 46

CRM 48

垃圾收集 52

GC 52

引用计数 53

标记 - 清扫 55

结点搬迁 55

分代式 56

保守式 56

.NET 59

自动内存管理 59

CLR 60

Rotor 60

C++ 64

智能指针 64

gc_ptr 65

Delphi 67

TObjectSafe 67

TExceptionSafe 69

COM 72

ATL 72

WTL 73

Visual C++ 73

嵌入式 75

数据库 75

关 键 词 页 码

Berkeley DB 75

Visual FoxPro 78

异常处理 78

连接 79

Personal Java 81

Windows CE 81

Symbian 82

Ruby 84

SOCKET 84

SMTP 85

cookie 86

session 86

抽象机 87

虚拟机 87

设计模式 87

C++ 92

Delphi 96

接口 96

生命周期管理 96

对象快照 98

IIS 103

COM+ 103

调试 103

AutoDump+ 103

模式 114

索 引

Page 9: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

9

书写神话的

布鲁克斯

世纪最后一年(也就是1999年)的图灵

奖,授予了年已69岁的资深计算机科学

家布鲁克斯(Frederick Phillips Brooks, Jr.)。在60

年代初,布鲁克斯只有29岁时就主持并领导了

被称为“人类从原子能时代进入信息时代标

志”的IBM/360系列计算机的开发工作,取得辉

煌成功,从而名噪一时。以后他作为硬件和软

件的双重专家和出色的教育家始终活跃在计算

机舞台上,在计算机技术的诸多领域中都做出

了巨大的贡献。在计算机科学领域,布鲁克斯

的名字俨然已经成了一个“神话”。

布鲁克斯1931年4月19日生于北卡罗来纳

州的杜哈姆。1953 年从杜克大学毕业,并进入

哈佛大学深造,1955 年取得硕士学位,1956 年

取得博士学位。他的博士论文课题工作是在哈

佛著名的计算实验室进行的,最终完成的博士

论文题目为“自动数据处理系统的分析设

计”。从博士论文开始,布鲁克斯的一生就与

计算机结下了不解之缘。

在哈佛取得博士学位以后,布鲁克斯进入

IBM公司设立在纽约波凯普茜的实验室当工程

师,并在那里参加了Harvest和Stretch计算机的开

发,任体系结构设计师。1959年,布鲁克斯曾

被调至IBM在约克郡高地的研究中心工作,但翌

年又重新被调回波凯普茜的实验室,并出任

IBM/360项目的主持人。IBM/360的开发总投资5

亿美元,达到美国研究原子弹的曼哈顿计划投

资的1/4。在研制期间,布鲁克斯率领着2000

名程序员夜以继日地工作,单单360操作系统

的开发就用了5000个人年。因此,在IBM公司纪

20

念其成立50周年的庆祝大会上,360系列计算机被称为“公司历史上发布的最

重要的产品”。为此,布鲁克斯常常被称为“IBM/360 之父”。

360成功以后,布鲁克斯离开IBM回到故乡,为北卡大学创建了计算机科学

系,并担任系主任长达20年(1964~1984年)。除了教学以外,他还致力于发

展美国的计算机技术和计算机在国防等方面的应用,有许多社会兼职。1966~

1970年,他是ACM全国委员会的委员;1973~1975年出任ACM体系结构委员会的

主席;1977~1980年布鲁克斯在美国国家研究院计算机科学技术部任职;

1983~1984年他是美国国防科学委员会人工智能攻关领导小组的成员,1986~

1987年是上述委员会另一个攻关领导小组“计算机模拟和训练”的成员;

1985~1987年他担任军用软件攻关小组组长。1987年布鲁克斯当选为美国工程

院院士,他同时也是英国皇家学会和荷兰皇家科学与艺术院的外籍院士。

在授予图灵奖之前,ACM在1987年曾授予布鲁克斯“杰出服务奖”,1995

年曾授予他以纽维尔(A. Newell,1975年图灵奖获得者,1992年去世)命名的

Newell奖。加上这次的图灵奖,布鲁克斯成为继克努特(D.E. Knuth,1974年图

灵奖获得者)之后的第二位同时拥有ACM三个奖项的计算机科学家。IEEE也先后

向布鲁克斯授予了McDowell奖(1970年)、计算机先驱奖(1982年)和冯·诺伊

曼奖(1993年)。AFIPS在1989年授予布鲁克斯Harry Goode奖。数据处理管理协会

1970年授予他“计算机科学”奖,并命名他为该年度的风云人物。1985年布鲁

克斯因在开发IBM/360上的杰出贡献而荣获全国技术奖章。物理学界的富兰克

林学会(Franklin Institute)也曾授予布鲁克斯Bower奖。

布鲁克斯的著作不多,但影响都很大。1963年他和依费逊(APL发明人,

1979年图灵奖获得者)合著了《自动数据处理》一书。他还与荷兰特文德理工

大学的勃芬夫教授合著了《计算机体系结构:概念与发展》。除了学术性著作

外,1995年,他与苏泽兰特(“计算机图形学之父”,1988年图灵奖获得者)

等还合编了一本书,书名是Evolving the High Performance Computing and Communications

Initiative to Support the National Information Infrastructure,论述了有关高性能计算机计划

及信息基础设施(也就是所谓“信息高速公路”)建设的一系列问题。

而布鲁克斯最引人关注的著作无疑是《人月神话》(The Mythical Man-Month:

Essay on Software Engineering)。1975年,他把他历年来所写的有关软件工程和项

目管理方面的文章汇集成这本书,立即引起了轰动。由于本书是他领导IBM/360

软件开发经验的结晶,内容丰富而生动,成为软件工程方面的经典之作。在出

版20年之后,在1995年,他又对书中内容进行了修缮,并加上了后来的《没有

银弹》和《再论“没有银弹”》这两篇文章,出版了一个“《人月神话》20年

纪念版”。一本计算机科学专著能够在盛行20年之后再版,《人月神话》也成

了一个绝无仅有的“神话”。

Page 10: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

10

每到年底,各种媒体难免都要对当年所发生的大事进行

一番回顾,并评出所谓“十大XX”之类,《程序员》也不能

免俗。现在就让我们来回忆一下在2002年整个软件开发领域

所发生的一些有可能对未来产生重大影响的事件。

Windows:巍然不动

没有办法,对于这个控制了全球90%以上终端用户桌面

的操作系统,对于这个整个星球上最有钱的软件公司——微

软而言,它的一举一动都受到格外的注意。

2002年,微软虽然没有推出新一代的操作系统,但是他

却发布了令整个Windows开发者社群所瞩目的新一代集成开

发环境——Visual Studio.NET。这也是继微软提出其规模空前

的.NET战略后,发布的第一款集成开发环境。为了配合其提

倡的.NET架构,微软不惜对Visual Studio进行了大刀阔斧的改

进:加入了C# 这门全新的语言,它由著名语言设计大师

Anders Hejlsberg(Delphi的设计者)所设计并将担负起微软对

抗Java的重任。对古老的VB语言进行全新的设计,使之能够

适合.NET架构,并加入了更多现代OO语言的特征。而Visual

Studio中另一门重量级语言——C++也有了重大的改进。微软上

一个Visual Studio版本是在98年发布的,而那时C++的标准化

才刚刚完成,微软的C++编译器与标准之间的差别是显而易

见的。虽然经过几年的补丁修订,可仍然是公认的对C++标

准支持较差的编译器之一。而随着Visual Studio.NET发布的全

新一代编译器则向着标准化方向跨出了一大步。更值得我们

期待是,微软于2001年底请来C++大师之一的Lippman(第一个

C++编译器作者之一)来帮助改进其C++编译器。这样凭借

Lippman的实力,相信下一个版本的C++编译器将再次带给我

们惊喜。当然,这一版本的C++所带来的变化还不止如此,

同样是为了配合.NET,C++这门“自古”以来的编译型语言

居然也可以被编译成bytecode。即所谓的Managed C++,它可

以被编译为与其它诸如C#、VB一样的Microsoft IL(Microsoft中

间语言)文件。想法是好的:既可以尽量利用C++语言二十

年来所积累的人才和技术资源,还融入90年代后所发展的字

节码/虚拟机技术,能够在更大程度上提高开发效率、降低

错误率。只是这样一种别扭的C++“方言”究竟能在多大程

度上获得向来以执着著称的C++程序员们的认可,还有待时

间的检验。

说到Windows平台的开发工具,就不能不提到另一个重

量级厂商——Borland公司。这个以曾经提供DOS/Windows平台

下开发工具而著名的开发工具商。在经历了几年的低谷、几

次战略调整、甚至还有十分搞笑的几次更名之后,现在又回

来了。Borland现在的战略已非常明显,今年完成几次重要收

购,将自己塑造成为一个不偏向任何阵营,不依赖某个特定

平台,拥有从设计、建模、开发、调试等完整产品线的全能

不停蹄又一年 2002年开发技术大综述

本刊技术编辑:汤韬 熊节

2002:技术回顾

Borland新的“Delphi”神殿还能重塑昨日的辉煌吗?

特别策划

Page 11: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

11

人 物 & 报 道

提供商。为达到这个目的,Borland在2002年发布了具有里程碑

意义的Delphi 7/Kylix 3。这两个工具能够分别在Windows/

Linux上编译相同的源代码,当然要让自己的代码顺利的在两

个平台上编译通过,必须要采用Borland新一代的组件体系CLX

来开发应用程序。Delphi 7/Kylix 3所拥有的这个能力,也使

得Borland成为第一个在主流开发领域能够同时跨越Windows和

Linux平台将GUI应用程序源代码编译为本地二进制代码的工具

提供商。而在此之前,众所周知的,由于Windows和Xwindow

之间巨大的差异,除了使用Java之类的非编译型语言以及一

些Open Source提供的跨平台GUI Framework外,要想开发跨平

台的GUI应用并要编译成本地二进制代码,是一件非常困难的

事。除了讨好Linux下的开发者外,Borland当然也不能忘了更大

规模的Windows开发者社群。为了能紧紧跟上微软的.NET步

伐,Delphi 7也提供向.NET平台进行移植的途径。虽然它现在

还赶不上Visual Studio.NET的紧密集成编译能力,可毕竟多了

一种除Visual Studio.NET之外的另外一个开发.NET应用的选择。

2003年Borland蓄势待发,相信我们很快会看到她的一系列重

量级工具、产品。

Java:来势汹汹

Sun公司这几年来一直都把自己装扮成一个反微软的旗

手,而它手中的大旗正是Java。的确,正是依靠Java这面大

旗,这个反微软阵营还真是聚集不少人气,连诸如IBM、HP、

SAP这样的巨头都赶来捧场。既然大家同在一个战壕,都抱着

同一件“宝贝”武器。那么向大家炫耀一下自己的武器是多么

厉害就是一件理所当然的事情。依靠着虚拟机技术中的垃圾自

动回收和“一次编译,到处运行”(虽然很多时候被人讥笑为

“一次编译,到处调试”)的跨平台能力这两件法宝,依靠众

多“战友”的齐心协力砸下巨资研发了一代又一代虚拟机,开

发了一代大过一代的类库,制定了各式各样的标准,出版了数

不清的各色Java图书,到现在,Java已俨然成为了企业级应用

开发的主流语言。同时也赢得了众多开发者的追捧。

2 0 0 2年J a v a领域几乎都没有什么激动人心的事件发

生。各个主要的Java产品供应商都是在按部就班的升级自

己的产品。

唯一的亮点是SUN于年中左右发布了又一个主要升级版

本J2SE 1.4。在这个版本中,Java语言本身已没什么改变,

已构建起来的庞大的类库结构也没有大的变化。但是一些主

要的类还是做了很多改进。例如新的I/O API,开始支持IPv6

等等。不过1.4最重要的改进还是虚拟机性能的可观提升。

由于改进了垃圾收集等技术,1.4版本的虚拟机不管是对服

务器端程序还是客户端应用性能都有不小提升。另一个值得

庆幸的是1.4全面兼容1.3版,而没有像以前一样每次重大升

级都伴随着语言机制的变革、类库结构的改变等情况。这也

从另一个侧面暗示了Java语言本身以及核心的类库经过了多

年的发展已逐渐成熟,慢慢稳定下来。广大开发者不必为了

适应新标准的变化而不停的一遍又一遍“移植”自己的程

序。各个编译器/虚拟机实现厂商也可以静下心来优化自己

的程序。这样,稳定的结构、逐步提升的性能又能吸引更多

开发者和用户。

Open Source / Linux:摩拳擦掌

不管你是喜欢还是讨厌也好,一个不可否认的事实是

Open Source/Linux已经成为软件开发领域一支极其重要的

力量。Open Source运动依然在全球蓬勃发展,影响着越来

越多的开发者。如今越来越多的开发者习惯于在接到工作

或是遇到问题后,跑到http://sourceforge.net网站去看看有无

可资利用的代码库或相似的解决方案;也有越来越多的开

发者能够自豪的宣称能够不花一分钱,完全利用Open Source

构建起足够强健的应用系统。当然也有很多人对Open Source

抱有敌意,认为Open Source的发展会影响软件产业的盈利

能力,更可能导致其饭碗不保。我在这里无意引入这些争

论,毕竟每个人都有争取自己利益的权利。抛开Open Source

与商业利益的关系,换一个角度,Open Source运动所沉淀

下来的优秀代码不正是全人类所拥有的宝贵财富吗?就如

同人类几千年文明所积累的优秀的思想与文化,这些软件

开发的技术与思想也应该全人类思想财富的一部分,而不

会被某个个人或公司所独占。广大的开发者也正是借助这

些优秀的人类共有的财富才无需一遍又一遍发明轮子。前

一段时间看到的一篇有趣的文章,大意是说我们应该将Linux

Kernel看作是一个定期发行公益出版物,由纳税人向其提供

一定经费来资助其持续的出版活动,就如同政府使用纳税

人的钱来修建公共图书馆一样。

好了,言归正传,看看2002年整个Open Source界有哪些

Linux这只小企鹅真的已经长大了

Page 12: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

12

重要事件发生。

Linux Kernel这本“公益出版物”的下一代核心即2.5的开

发进程终于启动,并很快就开始了全力运转,平均每过几天

就会发布一个新的开发版,在经历了50几次版本更新之后,

终于在年底宣布进行了功能“冻结”。预计在明年第二季度

就可以看到新一代内核2.6正式发布了。与2.5火热的开发相

比,2.4版的修订情况就安静了许多。当然对于稳定版的2.4内

核来说,这绝对是一件好事。相对于2001年的补丁满天飞的

情况,今年仅仅发布了3个版本,而且都不是因为出现重大缺

陷而打的补丁。这也充分说明2.4核心经过近两年的磨合如今

已达到相当稳定性,足以承担起普通企业级应用的要求。

曾经有人说,在Open Source界可以没有Linux,因为我们

至少还有FreeBSD,但是如果我们没有GCC,想想我们还能干

什么?是的,什么也干不了。将GCC称作是整个Open Source

领域的“root”一点都不夸张。正是有了GCC强大的功能、对

众多CPU的良好支持、严格遵循着GPL协议,才使得整个Open

Source运动有了一个坚实的基础。

在2002年,GCC依然延续了2001年3.0发布后的工作,持

续不断对其修订。到如今的3.2为止,已极其可靠。而经过

了这近一年半的考验,各个主要Linux发行商都将其默认的编

译器版本升级到3。这样各位开发者在为能享受GCC3编译器

所带来的先进特性时,再也无需为兼容性而发愁了。

如果说Linux Kernel和GCC在2002年的发展视作可预期的、

按部就班的进行而没有什么意外的话。那么Python/Zope的异

军突起就可以算作是一大惊喜了。在2002年Python语言的作者

Guido Van Rossum获得了自由软件基金会的年度大奖,而有

Python语言kil le r级软件之称的Zope也获得著名的Linux杂志

《Linux Journal》的编辑选择奖,并被赞为下一代应用服务

器。作为一个出色的OO语言,Python和Java在很多地方有着相

似之处,同样使用虚拟机/字节码/自动垃圾收集等技术,

良好的跨平台能力。而且比Java更加灵活,更加“动态”,

开发效率也更高。我预计Python语言在未来几年里将会取代

Perl和PHP成为Open Source界最为流行的解释性语言。并且由

于Python和C/C++天然优秀的互操作性,他们之间组成的混合

开发模式将成为一种潮流,至少是在Open Source领域。

>>> >>>

写完三个软件开发领域的主线,还有点意犹未尽。上述

的这种分类方式只是为了笔者叙述方便而将其分作三个部

分,并不一定合理,大家不必介意。实际上,现代软件开发

技术的趋势是各种技术相互取长补短,相互渗透,彼此的界

限越来越模糊。而融合这些技术的是两张“网”:一张是有

形的互联网;另一张却是无形的Web Service这张“网”。互

联网带给整个软件开发领域的各种影响这里就不必说了,由

大、小于符号所组成Web Service这张“网”带给我们影响也

将是深远而巨大的。它将模糊各种语言、各种平台的界限,

将世界以更简单、更有效的方式连接起来。虽然现在Web

Service/SOAP/JAX-RPC/XML-RPC之类这些技术还无法取

代老一辈诸如CORBA/DCOM/EJB/RPC这些技术的地位。但

是相信“简单就是美的”这条颠扑不破的真理,Web Service

终究会把他这张“网”铺向这个世界每一台电脑!

写到这里,终于该停笔了,这时头儿走来,叫我还是顺

便写写国内的软件开发领域,这样才能为这篇“综述”打上

一个完整的句号。我停止敲击键盘,开始思考这个问题,可

是脑子里一片空白,问问周围的同事,大家也是茫然四顾。

面对那些国产IT“巨鳄”们或是忙着在中关村修着写字楼,

或是忙着在满世界圈地盖着一个又一个“软件园”,我究竟

还能写些什么呢?我不知道⋯⋯

在IT业的领域内,预测未来的技术发展是非常困难的,

尤其是短期的预测就更显尴尬:如果要做一个长期的预

测,我可以放心地谈论“摩尔定律失灵”、“量子计算投

入应用”、“生物芯片和虚拟现实技术全面发展”、“传

统密码学全面崩溃”之类的大话题;但是,如果像这样对

未来一年中的IT技术做一个预测,恐怕有一半要成为明年年

底时的谈资笑料。所以,我决定展望一些最可能对中国软

件开发人员造成影响的技术。这些技术多半已经不是新东

西,其中一部分已经引起了广泛的关注,只是尚未在实际

应用中体现出足够的价值,所以这样的预测会比较保险

(同时也比较实用)。

如果把过去的十年称为“OO时代”,相信不会引起太多

的异议。到目前为止,面向对象(Object-Oriented,OO)技术

得到了最为广泛的应用,并且也被证明不是软件开发的“银

弹”。从Robert Martin到Bertrand Meyer,研究者们把OO的能力

和局限摸了个一清二楚。同时,人们也在传统的OO三要件

(封装、抽象、继承)基础上发展出了更多的新技术,借以

弥补OO的缺陷,使OO能够更好地解决软件开发中的问题。这

些“建立在OO基础上、并对OO作出扩展的新技术”被广泛应

用的时期,我把它称为“后OO时代”——这是一个即将到来

2003:展望后OO时代

特别策划

Page 13: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

13

人 物 & 报 道

的时代。

在我看来,“后OO时代”应该由三个部分组成:对OO进

行扩展的编程技术、更加实用的方法学、覆盖软件生命周期

各阶段的集成开发环境。下面,我将分别介绍这三个部分。

新编程技术:柳暗花明

O O 技术的一大局限性就是:它对软件职责的划分是

“垂直”的。在一个标准的对象继承体系中,每一株继承树

负责软件系统中一个特定部分的功能,对象的行为是在编译

期决定的。但是,在实际问题中,常常有一些“水平”的功

能需求,例如:一个容器(container)对象应该可以容纳各

种各样的对象;一个日志(logger)对象应该可以记录所有对

象的行为。这些“水平”的功能需求有一个共同的特点:它

们的操作对象横跨了多株继承树,并且操作本身对于操作对

象是透明的。这就向软件开发者提出了“功能正交分解”的

要求。在标准OO技术的环境下,人们想出了一些解决方案

(例如单根继承体系和某些结构型设计模式),但结果都不

能令人满意。

OO技术的另一个大问题是接口定义。在传统OO环境下,

对象的开发者没有任何办法确保使用者按照自己的要求来使

用接口。他唯一能做的就是:在接口之外提供一份详细的文

档,说明接口的使用要求。当然,文档可能疏于维护而与事

实不符,使用者也可能根本不看文档,因此这个问题也并没

有得到很好的解决。

如果你也遇到过上面这两类问

题,如果你还没有找到解决的办法,

那么请注意下列的几个术语。它们可

能将很快进入你的生活,成为你需要

学习的技术。

5 Aspect-Oriented Programming

(AOP,面向方面的程序设计)。《程

序员》杂志在2002年第11期介绍过AOP

的概况,这是一种对代码进行再分

析、再处理而解决横切关注点问题的

技术。在2003 年中,除了Asp e c t J之

外,我们应该能看到适用于.NET的AOP

工具出现,甚至可能有适用于Delphi和

C++的版本。另一方面,AspectJ可能

由“处理源代码”转为“处理字节

码”,从而具有更高的实用价值。

5 Generative Programming(生成式

程序设计)。C + + 的创始人B j a r n e

Stroustrup曾经说过:“我以为,自从

OOP问世以来,可称为‘根本性’的新型程序设计技术,惟

有泛型程序设计(generic programming)和生成式程序设计

(generative programming)技术。”generative programming这种

诞生于数值计算领域的技术,很可能在一般程序设计领域中

找到更多的用武之地。

5 Design by Contract(DbC,按契约设计)。2002年,

中国的程序员听到李维先生带来的一个词:CBD(基于组件

的设计)。一时间,“组件”被炒得炙手可热,似乎就快

成为软件业的银弹了。不过,如果没有Design by Contract

(或者类似的技术),“基于组件的软件工程”还将继续

扮演水中月镜中花的角色——实际上,在没有De s i g n b y

Contract的时候,我们居然还做了那么多的面向对象程序,

这才真是件奇怪的事情。到2003年,你还要让DbC从你的视

野中溜开吗?

敏捷方法学:方兴未艾

2002年,随着人民邮电出版社的“极限编程(XP)丛

书”问世,中国的软件开发者们第一次全面、系统地了解了

一种敏捷方法学(agile methodology)。敏捷方法学给人们带

来了巨大的心理冲击,那种张扬个性的主张是任何一个软件

开发者都无法抵抗的诱惑。就连Rational的首席科学家Iva r

Jacobson也曾在接受本刊记者采访时声称“RUP与XP一脉相

承,XP是RUP的轻量级版本”。当为数不少的中小软件企业

尝试了重量级的RUP(并吃到了苦头)之后,可以预见,敏

我们的软件产业能够实现“终极梦想”吗?

Page 14: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

14

捷方法学将在更多的地方发挥作用。

继XP的深入人心之后,在2003年,以下的一些与敏捷方

法相关的“关键词”可能会广泛地进入中国软件开发者的日

常工作:

5 Agile Modeling(AM,敏捷建模)。由Scott Ambler所

提出的“敏捷建模”是一组软件建模阶段的指导性原则。对

敏捷建模最好的使用方法,就是将它结合在一种完整的敏捷

方法学(例如XP)基础上。当XP真正大规模地投入到企业开

发实践中之后,敏捷建模会自然地受到XPer们的关注。

5 Refactoring(重构)。

5 Unit Test(单元测试)。

5 Daily Build(日创建)。

对于这三个词,我不想再废

话。如果你有兴趣了解它们的含

义,可以去看Kent Beck的《解析

极限编程》(eXtreme Programming

Explained)或者Martin Fowler的

《系统重构》(Refactoring)。但

是,对于那些迫不及待地想要一

试身手的读者,我要提醒你:在

没有顺手的工具之前,不要在项

目中尝试这些做法。如果想要重

构、想要单元测试,先装好xUnit;想要日创建,先装好Ant

和CVS。而最好的选择是:先购买一个足够方便又足够强大

的IDE。

集成开发环境:三足鼎立

“工欲善其事,必先利其器”。对于成熟、高效的软件

企业来说,功能齐备且方便易用的集成开发环境(Integrated

Development Environment,IDE)是至关重要的。在2002年底,

Borland收购Together、IBM收购Rational之后,Borland、IBM和微软

三大巨头的IDE都拥有了需求分析、建模等软件工程特性,从

而真正覆盖了整个软件开发生命周期。如果“微软收购

Borland”的流言没有不幸而成真的话,2003年的IDE市场就将

由微软、IBM和Borland鼎足三分,而软件企业的选择也将更加

简单明了:

5 微软阵营。尽管微软的技术从来都不是最先进的,

但微软的工具从来都是最出色的。强大、易用、人性化,

是Visual Studio一贯的风格。在2003年中,我们将看到.NET

2.0的发布。.NET的成熟和Web Service的完善,将使Visual

Studio .NET成为IDE中的“天王山”。不过,我仍然认为Vi-

sual Studio .NET只能是Windows平台上的NO. 1——虽然微

软说.NET将“完全跨平台”,不过我更乐于把这看成微软

的“业余爱好”。

5 IBM阵营。在过去的一段时间中,IBM一边暗示着微软

有垄断行为,一边又生产出一些极其难用的软件把潜在用户

都吓跑。不过,从WebSphere Studio Application Developer

(WSAD)开始,IBM似乎也意识到了一个易用的IDE对于开发

者的重要性。更令人开心的是,在2003年中,WebSphere Stu-

dio将变成一个强大无比的Java开发工具,成为大型、超大型

Java应用开发的不二之选——一个差强人意的Java IDE加上DB2

加上WebSphere,再加上ROSE、XDE和RUP,再加上蓝色巨人的

品牌和服务,你知道这意味着什么

吗?

5 Borland阵营。在开发者的心

中,Borland就代表着最好最纯粹的技

术。在2003年,不论你选择C++还是

Java、.NET还是Linux,Borland都将为你

提供一个好用而且熟悉的开发环境。

而且,比起Visio和ROSE那“可怜”的

建模能力来,Together对UML的运用简

直是出神入化:你不但可以完全无缝

地在代码和模型之间切换,还可以直

接生成一些设计模式的程序框架,还

可以对代码进行自动重构,甚至还可

以从代码导出顺序图!假如Borland能把Together的这些功能全

都移植给Delphi和Kylix的话⋯⋯世界将多么美丽。

如今的IDE都具有了建模、代码生成、编辑、编译、集

成、运行、调试、发布等“全系列”的功能。不过,在我看

来,它们还缺少一项重要的特性:真正的在线帮助。我所说

的“在线帮助”,不是指像MSDN这样的“关键词索引”,而

是从网络上即时下载数据、即时解决疑难的能力。最近Google

加上了“新闻搜索”的功能,据说还将推出“购物搜索”的

功能,但愿Google的成功能给IDE开发商带来一些灵感。

结语:新年快乐

在2002年,我非常高兴地看到:《程序员》在2001年第

10期所介绍的泛型程序设计受到了众多C++程序员的关注,

在2001年第11期所介绍的中间件概念已经实实在在地养活了

为数不少的软件公司。对于一本技术刊物来说,没有比看到

自己引荐的技术受到重视更欣慰的了。在2003年中,《程序

员》将继续关注业界最新甚至是最超前的技术。如果你仍然

喜欢站在技术的潮头,让我们继续结伴前行。

微软的Windows之火是否依然让竞争者退避三舍?

新的一年,祝大家快乐。

特别策划

Page 15: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

15

人 物 & 报 道

微软亚洲 MVP峰会

4月22日,微软公司在上海隆重举办首届微软亚洲“最有价值专家

(MVP)”峰会,向在2002年评选活动中脱颖而出的106名IT专家颁发了荣誉

证书。其中,25位技术精湛、乐于助人的社区“雷锋”代表中国大陆参

加了此次活动。同时举办的微软最新技术讲座和分组讨论则成为众多MVP

和微软技术工程师交流技术、分享经验的论坛。

Intel信息技术峰会

4月25日,以“拓展数字天地”为主题的2002年春季英特尔信息技术

峰会举行。本届峰会共有来宾超过了1500人。在为期两天的活动中,英

特尔介绍了互联网四大基础体系架构的最新技术信息,以及如何在英特

尔架构平台上更好地优化软件产品及解决方案。

Oracle World 2002

6月12日,Oracle在华举办的最大规模的“全球电子商务和新技术大

会”正式拉开帷幕,来自世界各地的工商界领袖、技术专家、IT经理和

软件开发商聚首北京,和上千名中国用户共同分享当今杰出的商业和技

术专家的真知灼见。在四天时间里,与会人士不仅深入了解、亲身体验

Oracle E-Business Suite的应用和解决方案,还了解到当今企业信息化管理各

方面的最新趋势、理念和解决方案。

趋势科技百万程序竞赛

8月28日,总奖金高达100万元人民币的趋势科技“百万程序竞赛”

正式落幕。这次竞赛是一场海峡两岸学子“同时、同题、异地”的竞

赛。经过紧张激烈的决赛后,华南理工大学Acme队从十支队伍中脱颖而

出,一举夺得程序竞赛的桂冠,获得了人民币25万元的高额奖金。

微软技术教育大会 TechED 2002

9月6日,亚洲最大的.NET盛会——微软技术教育大会TechEd2002在北京

国际会议中心隆重开幕。近2,000名专业软件开发人员和IT专业人士尽情

享受了这一“技术盛宴”。大会围绕“激发企业无限潜能,构建企业级集

成解决方案”主题,通过三天近百场的专题讲座,全面阐释了微软.NET 技

术及相关产品如何帮助国内合作伙伴构建面向各行业的企业级应用解决方

案,并进一步揭示了未来信息技术的发展趋势。

Sybase2002 亚太用户会议

10月24日,以中国的京剧艺术来庆祝“Sybase 2002亚太区用户大会”

开幕,充分表达了Sybase对于中国的特殊感情。本次大会以“Everything Works

Better When Everything Works Together”为主题,阐述了Sybase以全球领先的企

业集成解决方案与服务为客户创造价值的企业理念。会上,除了主会场

上近十场精彩的主题演讲,会议还安排了九个分会场、七十多场技术讲

座,除了聆听七十多场技术及行业应用的讲座,参会者还可参观Sybase及

30多家合作伙伴的展览展示。与会者不仅亲身体验到Sybase的最新技术,

而且深入感受了Sybase业界领先的产品及解决方案。

IBM 2002 开发者大会

11月15日,千余名来自全国各地的开发人员再次云集一堂,共赴

“IBM 2002 开发者大会——developerWorks Live!China 2002”,共同聆听IBM

公司发出的软件声音。IBM公司通过32场精彩讲座向国内开发人员介绍了

IBM技术策略和趋势、如何解决应用开发中的问题,以及关于J2EE技术架

构,业务整合方法论等方面的内容。

Borland 中国程序员设计大奖赛

11月28日,Borland新技术展示暨程序员大赛颁奖大会在北京举行。本活

动的宗旨在于为全中国的广大Borland钟爱者提供空间和舞台,激发软件开

发者的创意潜能。大会评选出了“首届Borland专家”,并为获奖者举行颁

奖仪式。李维还就Borland.NET产品发展方向、Borland Java 产品发展方向、

系统开发周期管理(ALM)等方面向与会者作了精彩的讲座和实例演示。

联想技术创新大会——Legend World 2002

12月3日,酝酿及筹备已久的联想技术创新大会在北京联想大厦

隆重召开。本次大会以“创新科技 畅想未来”为主题,来自全球知

名IT企业、国内科研院所、大专院校的顶级技术专家以及国内50多名

院士、教授及各行业学者共同站在了联想的讲台上,一起畅谈科技

未来。在这一周中,专家们将主要围绕新一代互联网的关键技术和

应用发展趋势进行探讨,并有多项前瞻性技术成果的首次亮相。

CSDN系列技术论坛

与上述九大盛会在业界引起的轰动效应相比,CSDN系列技术论坛有

些微不足道,但在开发人员的心目中,我们有理由相信CSDN系列技术论

对国内开发人员的技术进步和影响却是无与伦比。从2002年元月开始,

每月一次的技术论坛每期一个主题,分别邀请相关技术领域的高手、专

家进行主题讲座和讨论,真正做到了让国内开发人员领悟各类技术的精

髓、特性和发展趋势。

2002 十大技术盛会回顾(按时间顺序排列)

本刊编辑部十实点评

一年结束,回顾和分析已经发生的大事,我们尽力去触摸未

来趋势的脉络和节拍。因此,通过读者投票、网络调查、编辑评选,我们对过去一

年发生的十大盛会、十大热点新闻、十大专栏作家一一总结、一

一呈上,以飨读者。不敢说绝对权威,但我们相信一年之后有人再回味这"十实点

评"的时候,会感觉到它的价值和意义。

Page 16: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

16

对 话

倪光南

特别策划

杂谈2002十大新闻(排名不分先后)

IBM,2002 年最大的野心家

要说2002年最大的赢家,还得数IBM了,他的野心在2002年得到了最

大的满足。接连把EDS(Electronics Data System)、Trellisoft、普华永道、Ra-

tional和Tarian等软件制造商和咨询公司抓在手中。IBM是越买越庞大,越买

越强大。如此布局,IBM完成了最完整供应链的打造。从解决方案的咨

询、软件平台的供应到硬件设备的提供,IBM现在不能单纯划拨到软件公

司、硬件公司或是咨询公司了,它成了三者的综合体。

35所软件学院 3 5家实验室

呼呼啦啦一声响,全国35家软件学院就象竖起的一面墙,标榜着自己

可以培养软件金领——年薪几十万的软件工程师。这多少年薪还是未来

2、3年后的事情,现在不好说。但应该搞清楚的是,作为实验室的35家软

件学院有多少存活可能,而作为实验品的

学生的前途又如何。

“经验成问题、师资成问题、教材成

问题、实习成问题、就业成问题”,一大

堆盘根错节的问题如何消化也是问题。学

生们可不是瓶瓶罐罐不会说话,稍不注意

又是一颗定时炸弹。如果这些问题和这颗

炸弹都解决不好,中国的“软件大国”梦

只有先搁到黄梁上去了。

华为与托普对扑

今年中国IT界孱弱的神经着实被捉弄得够

呛。任正非的预言变成了现实,华为的冬天

在一年最热的时候降临。好家伙!洋洋2200

多人的失业大军被推向市场,连裁员的方式

也是那么的帅——末位淘汰,看来华为消肿的

气魄完全与国际接轨了。相形之下,逆势而动的托普可就风光无限,一口气

要招5,000软件工程师。托普就是勇敢,它能够顶着来自各方面的置疑和谴

责一再保证绝不是炒作,现在这样的集团没有几家了。不过,在托普看来,

这5,000人能否消化根本不是问题,反正自己有的是地盘。托普在全国圈了

那么多地,修了那么多的楼都还等着人去填缺。果然年底就爆出问题。

龙芯,“通用”是瓶颈

2002年9月28日,北京长城饭店“龙气逼人”,中科院在此宣布我国

第一款商品化的通用高性能CPU“龙芯”问世。让人不爽的是我们的“龙

芯”基本“通用”不起来。据中科院消息,除了可以应用于服务器领域

以外,它主要用途只在嵌入式领域。这意味着“龙芯”所标榜的“通

用”只能通用局部了。然而,“龙芯”之所以盛名于国内早前推出的其

他“中国芯”,它不能回避的一点是选用了“通用”之名。

不妙的是我们起点太低,这颗“龙芯”只能勉强达到486水平,而Intel

的家伙已经奔到IV了。“龙芯”真地好养活吗?虽然中科院给它取了“狗

剩”的小名,寓意名字贱点好养活,只可惜那终究是一厢情愿。

杀毒厂商群败记

以前有人想过杀毒软件的售价会50元不到吗?这情景在2002年轰轰烈烈

地实现了。

杀毒软件的超低价倾销牵动了金山、瑞星、江民和交大铭泰的心。

为此,四大国产杀毒软件制造商2002年“舞”到了一起,这不是共谋中

国软件发展的盛会,而是由金山挑起的一场刺刀见红的搏斗大赛。

奇怪的是四家公司都说自己是赢家,那我们只好认为市场输了钱。

其实明眼人心里早明白,这种“多败俱伤”的恶斗,四家公司都是输

家,市场也在他们无谓的消耗中越缩越小。结果是大赛至今没有结果,

金山的“蓝色安全革命”却在无奈中草草收场。

Linux 标准之争

Linux还只能说是“乳臭未干”,其涉足领域和能稳固的阵地与未来

IT应用领域相比好象小孩子的尿床,还只有那么一块。而最让Linux睡不

着觉的是自身身份(标准)的难以确定。不同厂商推出不同商业版的

Linux系统软件往往不尽相同,为了各自的商业利益,厂商是否能够确实

执行标准规范尚有待观察。Linux“公说公有理,婆说婆有理”的局面,

反倒成全了微软,在他眼里,Linux不过是“无头苍蝇”,至少在现在难

成大器。

最引人注目的还是岁尾传来暴笑一幕:微软为了重创Linux,不惜重

金收购老字号的市场机构IDC做假杜撰了《权威调查公司指出:安全性最

差的OS是Linux》和《IDC:Windows比Linux便宜!》两文,给纷争不休的Linux再

添躁动,也为即将到来的Christmas Day增喜不少。

中国 IT 盛会此起彼伏

中国IT界今年算是好事不断,也算是真正开了

眼界。数量之多、级别之高、影响之广、规模之大

的IT盛会为历年罕见。微软的“春耕计划”是由微

软公司CEO斯蒂夫·鲍尔默主持,稍后进行的Oracle

“甲骨文全球电子商务与新技术大会”上,首席执

行官拉里·埃利森亲率众高层到会。随即,一群厂

商趁热打铁,先后举办了“Sybase2002亚太用户大

会”、“联想技术创新大会”。时至年底,还有

“2002BEA中国用户暨合作伙伴大会”。

一年中,微软的“TechED2002教育大会”、BEA

的“BEA dev2dev Days”和IBM的“IBM2002开发者大

会”也确实让中国开发者过足了瘾。这些走马灯般

的会议让人目不暇接,虽然各自针对的对象不同,

但其目的都为了一个——中国IT消费者的口袋。

大小 ERP乱成一团

被“不上ERP是找死”吓晕了头的传统企业大都急于找到靠山,于是

靠山们一个一个地浮起头出来,不顾自己脚底下的泥巴有无洗净。

“三、五十万的那叫小单,要干就干千、百来万的,还不许砍价”。

ERP是一个许进不许出的“局”。其它事情失败可以重来,我ERP可

不行。失败的影响可是企业的致命内伤,外传不得。于是,一桩桩肯定

难以挽回和弥补损失被捂住了。

如果那些以为抓到了ERP稻草的传统企业还知道“上ERP是早死”的

话,这个世界就真的清净了。可是谁愿意伸出哪怕是小指头去把ERP泡泡

捅破呢?一个尴尬的数据显示:2002年中国ERP实施成功率还达不到40%。

利玛风波

利玛事件的由来是因为短时间内包括高层在内的60 多人“一拍而

散”引起的。费解的是他们并未去远,反倒是办起了另一家与利玛类似

产品的公司。两家经营同样ERP产品的公司对着吆喝的好戏就这样在北京

机械工业自动化研究所里上演开来。

利玛一不小心地当上了中国软件改制的“试管婴儿”,只是他本已

累累重病,稻草之身难以承担如此重任,因而出现人事哗变不足为奇。

只是企业在“姓计划还是姓市场”的改革中出现的利益分配问题给每个

关注此事的人上了一堂代价不菲的课。

利玛风波尚未尘埃落地,利玛谜团的破解还需要一定的时间,不管

如何,我们希望这是中国软件企业出现的最后一次“集体梦魇”,中国

的软件企业内战不起了。

Table PC来的不是时候

现在是机器换代的高峰吗?好象不是。大家现在都在持币观望吗?好

象也不是。一波又一波的通货紧缩让人都把钱往银行里存,虽然银行的利

Page 17: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

17

人 物 & 报 道

从创刊至今,《程

序员》杂志始终蓬勃成

长,在刚过去的2002年

中更呈欣欣向荣之势。

随着读者群体的日渐扩

大,我们越来越感受到

大家对“名家专栏”的

青睐和好评。

我们高兴,因为有

专栏作家长期辛苦主持自己的“名家专栏”,因为有这样一支忠实又高品

质的作家队伍,我们得以把一期又一期精彩而充实的杂志送到读者的手中。

我们的专栏作家,有多年海外从业经验的“海归”,有台湾知名技

术作家,更有大陆资深专业人士以及在业界崭露头角的后起之秀。基于

此,本刊编辑部根据读者反馈,评选出本年度“十大专栏作家”,让我

们对他们所作出的贡献表示衷心的感谢。

侯 捷

侯捷先生专精技术,更擅文采。读侯先生的文章,非但不觉晦涩难

懂,反倒令人感到神清气爽,有荡气回肠的感觉。而且,侯捷保持着一

种大师的谦逊,他从来不对自己不熟悉、不精通的领域胡乱置喙。作为

一位受到读者信任、尊敬的技术专家,侯捷先生能够经常勇敢地说自己

“不知道”,能够对自己知识范围之外的技术保持沉默,这样的品质弥

足珍贵。

李 维

李维先生的《Borland传奇》系列当属2002年《程序员》杂志最“叫

座”的文章之一。作为Borland公司的资深技术专家,李维对Borland的历史和

技术如数家珍。除了优秀的技术文章外,他笔述的Borland故事也就成了读

者的最爱,甚至出现一期文章断档时,很多读者在CSDN网站上质问原因

并强烈要求继续刊载,这在编辑部一时传为佳话。

裘宗燕

作为教育界的老前辈,裘宗燕教授的技术水平自不待言,而且他古

道热肠,不论是面对来自网上读者的置疑,还是网下后学者的请教,他

总是鼎力相助、有求必应。让编辑们感动的是裘教授的有心之举,他常

常把适合本刊的文章在经过校对之后直接发送到编辑信箱中来,减轻了

编辑的工作负担。从各方面来说,不论是做学问、写文章,还是待人接

物,裘教授都堪称楷模。

蔡学镛

2002年,蔡学镛将他的研究方向从Java转向了.NET,但时时不忘强调

自己研究.NET虚拟机只是为了更好地研究Java。蔡学镛就是这样可爱的一

个人:棱角分明,直率淳朴。目前,大多数读者可能还只领略到他“勤

奋技术专家”一面,而在他即将出版的《Java夜未眠》一书中,大家还将

看到蔡学镛可爱的另一面——那将是令你大吃一惊的一面。

高 展

在整整12期的“高展软件工程论坛”中,高展先生凭借自己多年的

软件开发经验,对企业的信息化管理和软件建模技术提出了自己独到的

见解,给读者带来很多新意和启发。高展的观

点在本刊和CSDN网站论坛中引起强烈的反响,

引发了数场关于面向对象建模技术的大争论。

作为站在企业信息化前沿的专家,读者完全可

以从文章中感受到他对项目成败的切身体会和

认识。

曾 炼(John Zeng)

一系列剖析CRM历史、现状以及未来走向

的《CRM全解》系列文章,使读者认识了“海归”曾炼先生的实力。其

实,这一点毫不惊奇,因为不论是在中科院软件所,还是在美国硅谷

知名软件企业中八年多的工作经历中,他都在从事着企业管理软件核

心技术的研究与开发,所以,不止CRM,曾炼还能道出ERP、SCM、BPR等

相关技术的发展与应用。回国后,曾炼更增加了对国内企业实施信息

化管理的经验和感悟。2003年,我们期待着曾炼先生继续为读者撰写精

彩的篇章。

孟 岩

在过去的一年中,孟岩的文章给《程序员》增色不少。除了过去常

写的书评、技术文章之外,他还常常扮演起“客串记者”的角色:与虫

虫合作的《Andrew Koenig夫妇专访》受到了读者的一致好评;在对Iva r

Jacobson的采访中,几个重要的问题都来自于他;在本期刊登的C++之父

Bjarne Stroustrup的采访以及前几期的《三味书斋》中,孟岩和本刊技术编

辑都做了很好的搭档,配合非常默契。

戴习为

作为老朋友,大家都喜欢尊称他为“戴老师”。“戴老师”亲自撰

写的文章不多,但从创刊开始就有他的身影出现。其实,戴老师的背景

资料很是丰富,他曾“将自己在美国的科研公司和专利技术硬碰硬地卖

给了微软,自己也顺理成章地成了微软公司具有最高技术级别的少数顶

尖工程师之一⋯⋯曾伴随盖茨一起摇摇摆摆访问中国晋见国家领导人,

拜见电脑名流⋯⋯30年间,自觉不自觉地,有幸分别穿过了中美两边IT业

的“底层”与“上层”。(摘自即将出版的、由戴老师撰写的写实性图

书《过河卒》)。

何致亿

何致亿在Oracle、SQL Server、.NET和Java方面都研究颇多,是勤奋又

多产的技术专家,曾经在两个月时间里出书三本,并在多本技术杂志开

设专栏。更难得的是他的文章和书籍的内容都保持了高品质。已取得

OCP、RHCE、SCJP、Borland JBuilder Product Certified、MCSD、MCDBA等十多项国

际认证。

金 尹

这个桀骜不驯的“恶魔”以及他带来的Python语言让很多人眼前一

亮。他的文章总是严谨而不失风趣、注重理论又不脱离实用,所以尽管

金尹介绍的只是一种并不流行的语言,仍然受到不少读者的欢迎。不

过,根据编辑私下里的沟通和了解,恶魔最有心得的还不是语言细节,

而是软件工程和项目管理。

息是越来越少。美国人不买,日本人不买,欧洲人不买,中国人买吗?台

式机也是越买越便宜,移动PC又趁兴而来,笔记本更是一天一个价地往下

滚。那么,12月2日正式在中国亮相的,价值一万多块人民币的Tablet PC有

什么用呢?微软是否后悔当初的莽撞了呢?好象可以买电脑的人早就买

了,正增加着的电脑消费者,大半都是量入为出的现实消费者,感觉不到

他们追求新奇的浓重需求。

看来,Tablet PC虽好,来得毕竟不是时候,只好算是给中国用户的一

种提前预告好了。

2002程序员十大专栏作家

(排名不分先后)

Page 18: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

18

对 话

对 话从 Rational 被收购谈起

Borland大中华区总经理

主持人:闫辉yanh ui@csdn .net

主持人:2002年12月6日,IBM宣布以21亿美元收购软件设计工具厂商

Rational公司。为此,本刊邀请到几位嘉宾,Jenny和曾炼有多年美国工作经

验,对全球软件市场有深刻的了解,而邵凯和左春作为国内大型软件企业主

管,有多年的技术和管理经验,他们一起深入探讨了此次收购给整个软件产

业带来的影响和其深层次的原因。

是地震吗?-给整个软件产业带来的影响

蒋涛(《程序员》杂志及CSDN网站负责人):最近IBM用21亿美元收购Ra-

tional公司,之前,IBM曾经并购过像Lotus这样的大型软件企业,但好像引起的

反应不如此次剧烈。应该说,这次收购对开发工具市场会形成巨大的冲击。

不知各位听到这个消息的时候,是怎样一种感觉?

Jenny:最近很多事情发生的太快太突然。像有些产品去年还有,现在

已经不存在了。此次,IBM收购Rational后,必然引起工具市场的重新整合,以

前Rational是独立的软件开发商,现在已隶属于IBM,这势必会使一些用户,系

统集成商,独立软件开发商,以及其他合作伙伴重新调整他们的合作策略。

邵凯:这次收购和Lotus有很大的不同,毕竟Lotus只是一个应用产品。而

这次收购让IBM可能会真正转型,就像当初从硬件厂商转变为应用供应商一

样,现在添加了软件设计工具,更像是一个非常系统而强大的软件公司了。

转变还会表现在开发人员和软件企业的认知度方面。现在,Rational在大

型软件开发中的影响力使得IBM在人们心中的印象产生了变化,变成了比微软

更高的企业级软件开发工具厂商。企业做高端应用就会联想到IBM,这是本质

的概念变化,已经被IBM装到筐子中了,而这种情况,以前无论如何都包装不

出来的。

左春:Rational的价值在于它是设计标准化的工具,有很多忠实的用户

群,在这个市场上处于绝对优势,这种影响力是巨大的。而且Rational拥有的

这些客户同时也是IBM一直关注的企业级市场。

曾炼:我感觉这次IBM是志在必得,从某种角度上看还是很恐怖的。原来

IBM已经有了企业解决方案,开发工具有Websphere,数据库有DB2,企业集成也

完成了,从上到下形成了完整的一套体系。目前还看不清IBM如何来处理

Rational,如何打这张牌,是面向中小企业,还是只针对大型企业。

蒋涛:我认为IBM可能会通过Rational在开发人员中的影响力来打开其他软

件的市场,比如Websphere,借势推广到独立软件开发商市场中。

邵凯:尽管战局发展很难估计,但这招棋确实非常高明。前些年,王文

京董事长到硅谷考察时发现,只要做大型应用就会采用J2EE。但后来微软推

出.NET之后,人们会考虑一下再做决定。

用友软件高级副总裁

中科软总裁 研究员

百联科技集团技术总监

Jenny Liu(刘珍妮)

邵凯

左春

曾炼

Page 19: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

19

人 物 & 报 道

而现在有了此次收购,假想一下未来的硅谷,很可能提

到企业应用开发就会说用的是“IBM的工具”。因为企业应

用就是复杂、大型的代名词,而Rational就代表专业。

蒋涛:在此之前,Borland也收购了Together软件公司,尽

管收购金额1.85亿美元相比Rational不算巨大,但作为软件设

计工具市场前三位之一,Together营业额从1999年以后增长很

快。看起来国外的企业现在非常重视这一领域, Borland收购

是不是提前预见到了这个方向。

Jenny:我觉得Borland当初收购TogetherSoft还是很明智

的。Borland现在倡导一种理念,叫ALM,Application lifecycle

Management,其实就是开发生命周期管理,包含最初的系统

分析、建模设计到构建,测试,优化,维护,配置分发的全

部过程,所以Borland在今年10月,一个月宣布收购了三家公司

BoldSoft, TogetherSoft,Starbase,完成软件开发的全部产品线。

此次IBM收购Rational之后,很多公司包括微软都考虑在软

件分析方面使用何种工具的问题。现在,Borland公司在这方

面已经处于非常有利的位置。

谁受益了?-深入分析收购动机及各方反应

蒋涛:国外很多大公司都喜欢购买技术。不过不同公司

采用的方式不尽相同。微软就喜欢购买最初版本的产品,只

要认为思想好,那就把全部人马收编过来,比如Frontpage,而

很少花巨资购买大型软件企业。而IBM却喜欢吞象,Lotus就是

例子,现在又轮到了Rational。

左春:这是同企业文化密切相关的,微软往往把收购的

技术变为自己产品的一个部分,而IBM因为其庞大的体系和

深厚的公司文化,它可以容纳各种特色的公司。

邵凯:因为Rational的产品和IBM体系非常互补,所以在

IBM的眼中,Rational是非常值钱的。IBM尽管在软件设计工具开

发方面做了很多工作,但一直没有系统的产品出现。我认

为,首先开发工具的用户是软件公司和开发人员,但IBM一直

以来都直接关注这类客户,没有把软件企业规划进来,因此

IBM没有很好的土壤去发展开发工具。而Rational在这方面就做

的非常出色,我每次参加Rational的年会,都受益菲浅。

蒋涛:应该说IBM的研究力量很强大,在很多领域都有

一帮专家在做。尽管有积累,但名气不算大,所以收购中不

排除包含为了牌子,因为这对企业的影响还是很大的。

曾炼:实际上,这次并购并不只是技术范畴。从资本运作

方式看,并购是在经济低潮时期生存的一种策略。对于Rational来

说,因为美国经济的衰退,硅谷的新项目基本上处于停滞状

态,老项目能维持就不错了。没有新项目,自然也不需要新工

具,所以Rational很难增长。但投资人仍然要看收益,这时最好的

方法就是绑定一个大公司,成倍的现金立刻就可以得到。

Jenny:一般说来,在经济形势好的时候,可以看到很多

公司分离上市,公司分离后,还可以保留这个纽带;在经济不

景气的情况下,则以合并为多,并通过互补产品的并购,增强

竞争力,并维持公司业务的持续发展。

Rational在全球拥有大量的客户群体,IBM收购Rational后,直

接可以得到这些客户群;还可以快速进入到高端发工具的市

场中来,省去了很多的时间和开发成本,可以快速获取收益。

左春:的确如此,财务、市场等非技术因素十分重要。

资本运作中有很多做业绩的方法,购买公司是最直接的,这

样业绩很容易上去,而付的钱可以作为长期投资,这样给投

资方看的报表会更漂亮,CEO也能够得到更大的拥护和收

益。国外的很多大企业如果每年不买几个公司,可能还会有

完不成业绩的担忧。

有机会了?-第三方软件厂商的渴望

蒋涛:UML属于一种标准。这个标准比较成型之后,随着

Rational的被收购,其他厂商的机会是否大大增加了呢?Together和

Rational的产品单从功能上比较,差别会有收购价10倍这么大吗?

Jenny:这和历史有关。UML是由三位信息系统的方法学

家Grady Booth,Jim Rumbaugh和Ivar Jacobson联手创造的。当

年,他们在Rational的旗帜下,推动了UML伙伴联盟的发展,

并于1997年将UML作为面向对象的建模标准提交到OMG,正是

UML成为了标准并推向了业界,才使得其他厂商入有机会进

入这一领域。1999年,TogetherSoft成立,从Rational独占市场

到占据一块份额,这对Rational构成了潜在的威胁。

左春:所以我觉得现在Bor l a nd如果卖的话肯定是好价

钱。因为Rational的产品不是正式的工业标准,只能说是一个

准工业标准,因此接下来控制工业标准就成为了重要的环

节。IBM购买Rational之后,其开放性可能会损失很多,厂家独

断的可能性会大大增加,用户的选择余地就会减小。这些都

会刺激第三方产品的更快发展。

曾炼:从这个角度看,此次收购对小公司和新的idea产

生是有好处的。原来做配置工具有五六个小公司,大家面对

Rational这样的巨无霸,都很困难。现在Rational被IBM购买之

后,这些厂商会感觉在这个专业领域不存在老大了。

对小公司来说,单靠其自身的发展会很慢,也需要风险

投资的介入。而风险投资商也在其中可以寻找到机会,Rational

被收购后,因为IBM的增长是固定的,而如果小公司技术特别

好,发展空间会更大,风险投资商会更愿意投资这样的公司。

邵凯:这些我非常同意。此外,中国的程序员,甚至全

球的程序员,都不想受限制于一种平台,被别人捆住。假如

现在出现了一个小公司的产品,所有的平台都能支持,客户

在内心深处就会有支持它的想法。

Page 20: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

20

Jenny:这就是用户的倾向,而且不仅表现在客户,对

于大公司同样如此,比如Sun和HP,他们会在多大的程度上支

持IBM旗下的Rational产品呢?平台中立的小公司可能会成为更

好的选择。这方面中国市场上体现比较少,美国市场上很多

小公司就是在这种情况下被扶持起来的。

邵凯:但我认为小公司要找到自己的生存空间,仍然需

要定位准确。因为高端应用是非常系统的体系,这个生存环

境是很残酷的。

软件开发工具如果不系统,其价值就会变小。因此这些

小的工具软件公司在大型软件开发中还不足以与Rational竞

争,而是要考虑针对不同规模的团队,并且要加入思想和理

论的支持。(如Rational的工具就非常系统地应用了OO的思想

和UML的理论模型)

软件开发工具更新换代,我们准备好了吗?

蒋涛:IBM收购Rational,对于UML以及软件分析工具市场有

很大的示范效应,至少表明了国外软件开发的发展趋势,国

内在这一块对于UML以及Rational产品理解和应用水平如何?

邵凯:我们从1998、99年开始使用Rational的产品,而且

有专门的小组进行研究。在国内,我们也一直寻找可以交流

的伙伴,但反馈不是很理想。

Jenny:UML最大的功能是定义了一种语言。用户和程序

员采用的是不同的思维,很多项目之所以失败,问题往往就

是出现在程序员总觉得用户的需求在改变,而用户总是感觉

做出来的不是自己需要的东西,不满意。而UML作为一种标

准提供了层次化解决的方法,表层的东西用户都可以看懂,

随着深入,程序员可以看懂,Rational的产品对推倒这层隔膜

起到了重要作用。

左春:UML是需求标准化的工具,原来各家都有自己的

产品,但不统一,Rational出来后基本上统一了。但在国内实

际应用中还不是很普及,我看到有篇文章有个年轻人写自己

对Rational的工具很了解,可实际上还差很多。

UML在教学上的意义更大,应用更广泛,特别是现在学

了理论后,可以使用工具的就只有Rational的产品了,特别是

在面向对象设计,面向对象分析方面。

Rational支持代码和设计的双向生成,在企业应用中,就

我感觉,与生成代码还有相当的距离,在控制流,力度,实

用性方面没有这么强,有些演义的成份。很多方法在逻辑上

可以行得通,但实际中就会发现,很少有人真正这样做下去

的。而且现在软件开发周期的限制也很难要求这样做。

曾炼:国内软件开发人员对UML的理解与国外(特别是美

国)有较大的差距,同样对Rational的重要性还没有足够的认

识。很多开发人员对软件开发的理解还限于编程。在国内大上

软件的同时,同样需要加深对软件开发及管理的理解,希望这

次并购也可以引起人们对UML及软件开发工具方面的重视。

左春:这方面国外一直很正规。但对UML的理解也在发

生一些变化,不会全程使用,这样成本太高,而且也不切实

际。主要是因为竞争加剧造成客户不允许开发周期长,两三

年实施一个项目,时间过长会涉及到公司的生存问题。但这

不是要放弃设计,而且对设计的改进。

曾炼:在美国,稍微上点规模的软件公司,如100人以

上,或者大型的企业,只要做项目需求设计,比较流行采用

UML,而UML工具中比较好的就是Rational的产品,基本上没有

什么例外。它本身是一套全面的体系,从需求开始,到软件

测试,所有过程都涉及到了。但因为其软件价格非常昂贵,

而且美国的版权法非常严格,所以很多企业可能会购买其中

的一部分,比如测试程序。如果公司再小,只有一二十人,

可能会用到Visio。

实际上,尽管Rational从设计到最后测试都有产品线,但

在具体应用中,公司会选择性的使用Rational的工具。如分析

和设计的时候用一段,但很细的情况下可能就不用了。

关于未来的预测

蒋涛:照现在的趋势看,软件产业会像汽车产业一

样,最终形成几个软件巨头。未来会不会出现这样的情况:

I B M会推出各种企业的全面解决方案,逐个行业的进行推

广。会不会出现IBM圈到了大片的地,然后再租给其他人去

种的可能。这对现在的软件企业会造成什么样的压力?

邵凯:国内所有的软件厂商,包括用友,都面临这样一

个现实的竞争。市场法则决定了任何公司在规划企业发展

时,不会考虑到给其他人预留空间。IBM这样,微软同样如

此。微软一直在做CRM产品,准备发布中小型企业的解决方

案,这时候肯定不会考虑给中国的软件公司留一块空间的。

用友做软件,是强在我们了解客户和市场。我们在做基

于J2EE的ERP,IBM也有全套的系统。这就像Oracle、SAP在国际

上也是很好的,我们在给客户推荐数据库的时候,Oracle数据

库还是首选的数据库之一,但越来越多的中国的用户会选择

用友的ERP。

Jenny:Borland的产品是J2EE和.NET大战中的中立者,提

供支持两种标准的产品,客户可以根据自己的需要进行选

择。我对J2EE和.NET之争的看法是在未来五年内最大的可能

性并存。尽管IT巨头像IBM,Oracle等公司都在实行多元化的战

略,纷纷进入向ERP,CRM,中间件等相关市场,但是,只有

独立软件商才能真正给予客户平台中立的信息,所以说这个

空间是永远不会消失的。

对 话

Page 21: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

21

人 物 & 报 道

当北京街道两旁候车的人们将冬装紧裹的时候,菲律宾

还沐浴在三十多度的高温中。经过三千多公里的飞行,感受了

从冬季到夏季的急速变迁,我到达了菲律宾首都马尼拉。

导游告诉我,四五十年前,因为美国等西方国家的支

持,菲律宾经济发展迅速,一度被称为“亚洲首富”。然

而,随着世界格局的变化,菲律宾经济不断下滑,现在除了

菲佣劳动力输出外,并没有其他突出的支柱产业,在亚洲也

相对落后了。夕阳映照下,路旁高大的椰子树好像仍然在回

味往日的辉煌。

然而就在这样一个相对落后的地区,全球市值最大的防

毒软件厂商趋势科技成功的在这里创建了全球防病毒研发暨

技术支持中心-TrendLabs的总部。

TrendLabs在病毒防护研究及技术支持上通过ISO 9002品

质认证,Trend Micro也成为世界上第一家获得国际品质标准

认可的防毒软件公司,这证明了防毒市场不再只是单纯地销

售软件产品,而是一个服务行业。

TrendLabs随时对突发的病毒事件做有效的反应并加以解

决。尤其一旦有病毒爆发的情况发生,TrendLabs将立刻进入

其红色警戒应变程序,在最短的时间内(45分钟)研制出解

药并分发给用户。1999年Melissa病毒侵袭全球时,Trend Micro

是第一位提炼出解药并分发给全球的公司。在2000年10月出

现TROJ_SHOCKWAVE.A病毒后,该特洛伊程序迅速地扩散。

被侵袭厂商通过呼叫器向TrendLabs发出病毒警告后,30分钟

内,TrendLabs的工程师就把新的病毒特征码文件制作完成,

并将把解决方案传送给客户。

到达的第二天晚上TrendLabs将举行当地员工的圣诞晚会,

趋势科技公司高层管理人员汇聚菲律宾。我们直接采访三位

领导TrendLabs的管理人员,他们分别是全球网络安全事业部执

行副总裁张伟钦,全球技术支持执行副总裁郑奕立,全球防

毒研发及技术支持中心总经理纪孟宏。

选择菲律宾

1998年,趋势科技在市场上取得了巨大成功,并准备将病

毒分析工作扩展到其他国家或地区开展,印度、爱尔兰、中国

南京和菲律宾被列为了考察对象,菲律宾并不被看好。

然而反馈的信息显示,印度当时作为软件外包中心,即

将出现的“千年虫”问题让印度软件园中的公司都接到了很

多大单,软件人才处于供不应求的状况。没有足够的人才怎

么做事呢,趋势科技只能遗憾地放弃印度。爱尔兰是欧洲软

件本地化中心,但距离台北需要飞行20多个小时,华人工程

全球知名防毒企业趋势科技防病毒研发暨技术支持中心亲历

菲律宾随笔

记者/闫辉

全球知名防毒企业趋势科技防病毒研发暨技术支持中心见闻

菲律宾随笔

张伟钦 郑奕立 纪孟宏

Page 22: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

22

师都不愿意如此长途奔波。南京当时已经设立有研发中心,

最有可能直接扩大投资,但因为趋势科技70%以上的客户在

美国,语言成为了比较难以克服的问题,不得不放弃。

菲律宾属英语系国家,英语是其官方语言,十分普及,

在语言方面比亚洲其他国家有得天独厚的优势。因为这个原

因,很多跨国公司将Call Center设在马尼拉。而且当地薪资

水平也相对较低。据说,在台湾地区每月能挣到合三万比索

的非佣在菲律宾当地只能挣到一千五百比索,而在美国雇一

个工程师的费用在马尼拉可以雇佣近十个工程师。

这些因素最终刺激趋势科技抱着试一试的心情,将目光

投向菲律宾。而执行的重担落在了时任亚太区技术支持经理

郑奕立的肩上。

郑奕立,1996年作为技术支持经理加入趋势科技,在他

的带领下,台湾地区的客服部门取得了瞩目的成绩。他自然

也成为了去菲律宾的最佳人选。

人才行动

起步是艰难的。菲律宾的教育制度中没有初中,小学之

后就是所谓的四年高中,四年大学。因此,大学生毕业时的

年龄仅在20岁左右,教育质量也有很大不足。即便这样,刚

到马尼拉的时候,因为没人知道趋势科技,招人非常困难。

郑奕立回忆道,到菲律宾一段时间后,董事长张明正询

问招了多少人,回答说有六七个人,张明正又问马尼拉有多

少人口,回答说有一千多万,张明正很急迫说:“一千万人

中找不出一百人吗!”

因为菲律宾IT基础非常薄弱,招聘的工程师都没有防毒

方面的知识基础,因此必须要先到公司总部进行培训,之后

返回马尼拉才能工作。

菲律宾TrendLabs成立一年后,取得了长足的发展,郑奕

立也前往美国,协助建立当地技术服务团队,纪孟宏担任了

总经理。

纪孟宏曾参与开发趋势科技最早的杀毒软件产品PC-

cillin,做过测试和技术支持工作,1998到菲律宾担任病毒分

析部门经理。接手总经理职位后,面临的最大问题仍然是人

才招聘。

TrendLabs招聘过程首先是基础知识的集体书面考试,考

生通过考试之后,部门经理就会直接面试并决定了此人的去

留。最初,每周都会进很多人。

然而很快他们就发现这种方式带来的问题。

语言问题是最早发现的,美国的销售人员反馈说:“这

些人的英语很烂啊,不知道在讲什么!”刚开始,纪孟宏还

很奇怪,这些人从小学英语,为什么会说菲律宾员工英文讲

的不好呢?后来发现,这些员工懂英文,可以听,但不太会

用英文,而且菲律宾的英文单词发音中有特殊的音调,像方

言一样,很难听懂。

采用这种流程招聘时,因为部门经理更重视技术,比较倾

向于留下技术好的人。而有些人尽管技术很好,但不善言辞,

这为以后选择团队Leader产生了很多障碍。

意识到这些问题,TrendLabs便开始改变招聘的流程,并

设立了人力资源经理的职位。书面考试过后,首先要通过人

力资源经理的考核,期间要到专门的评测机构做英文能力鉴

定,没有通过的人员会被自动淘汰。

纪孟宏表示:“我们把最难改变的部分放在最重要的位

置。技术是容易改变的,只要聪明,一两个月之后都可以跟

上来。但讲话的腔调是二十多年养成的,很难改变。我们现

在的考核标准是:英文第一,潜力第二,工作态度第三,最

后才是技术。”

工作之前,新员工还要接受高强度训练,这也是从工作

中得到的教训。当初新员工进来就工作,一旦遇到紧急事

件,大家都很紧张,不知道如何处理。为了让员工学会承受

压力,新员工要接受三个月的严格训练。甚至还给员工放映

一部《魔鬼训练营》电影,告诉他们未来几个月的生活就是

这样。

在这个过程中,很多人无法坚持,但保留下来的员工抗

压能力和各方面的素质得到了极大增强。

选拔条件尽管苛刻,但趋势科技的薪资在当地处于中上

等,而且在菲律宾大学中的影响力日益扩大,因此前来寻求职

位的学生络绎不绝。就在采访当天,几十人还在公司的前台等候

考试,每个人的脸上都充满渴望的表情。

纪孟宏表示:“我们花了很多时间来寻找人才,运气还

不错。”

职业成长规划

菲律宾因为处于相对独立的岛屿环境,而且被西班牙等

员工和老板在一起

走向海外

Page 23: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

23

人 物 & 报 道

国家殖民统治数百年,中北部地区大部分人口信奉天主教,

因此菲律宾人的性格中有很大部分乐于现状的倾向。在月末

发薪水的几天,整个马尼拉市会彻夜不眠。

在这种大环境下,年轻人很少去考虑明天,但趋势科技

要帮他们考虑未来的发展。TrendLabs的高层很早便定下了一

个规定:每个月,老板都要和员工谈话,了解员工的想法,

并帮助其制定未来的发展规划。

TrendLabs制定了细致的技术评级考核制度。根据职能分

成1-20级,不同的职位需要具有不同的技能,有5-10的基本

层次。员工发展到了某个程度之后,可以选择向技术和管理

职能两个方面转变。当然考核评估标准也非常严格,每个阶

段都会有很多考试。如果在一定时间内无法通过级别考试,

甚至有可能被辞退。通过这种方式,在一定的压力下,公司

和个人的发展结合在一起,每个人都可以预期自己的未来。

有些员工还被送到其他国家的开发中心和当地技术人员一起

工作,反馈表示,这些人十分出色。

推行了正规化管理后,病毒分析部门的运营令人满意。

接下来,趋势科技将全球技术支持部门也转移到菲律宾。现

在,美国所有的技术支持热线全部转移到了菲律宾,网络监

控部门也转移到了这里。为了更好的支持网络,他们还租用

了直通美国的海底光缆。

趋势科技还考虑过将一些开发工作也转移到菲律宾,但

因为开发的很多工作需要和其

他地方的开发部门直接沟通,

而菲律宾在签证方面不太方

便,最终不得不放弃。

打造 TrendLabs文化

进入TrendLabs办公室,抬

头就会看到一排显示全球主要

城市时刻的时钟,张伟钦解释

说这里实行的是十二个小时、

隔日上班的制度,不同组的员

工按照不同时刻上班。

最初TrendLabs也是八小时

工作时间,但因为马尼拉交通

非常堵塞,很多员工单程就会

花费两三个小时,很辛苦。而

病毒分析工作,没有经验的员

工往往需要连续工作四五个小

时。如果八小时工作的话,没

有多长时间便会到下班。而且

菲律宾人很重视西方节日,到

节日的时候,协调工作时刻表

成为了棘手问题。

改为十二小时工作时间后,员工比较容易调配自己游玩

和朋友相聚的安排。而且同一个座位可以有两名员工,既节

省空间开支,也很容易衔接,达到24小时不间断服务。

参观TrendLabs过程中,发现公司在各方面为员工提供了

舒适的工作环境。从饮料就可以看出来,除了常见的可口可

乐机之外,还有芒果、菠萝、椰子等各种果汁饮料,五种配

方的咖啡热饮也随时可以免费饮用。

公司成长到一定规模,更重要的是要用企业文化来团结

员工。趋势科技有自己的4C+T的企业文化(创意Creativity、

变化Change、沟通Communication、客户导向 Customer、可信赖

Trustworthy)。但在一个异国他乡的技术支持中心,如何结合

公司的文化创造出自己的特色呢?

纪孟宏认为,首先要通过各种方式向员工传递信息,营

造一种温馨的氛围。因此,每次活动都会拍上很多照片,贴

到墙上。我们看到,公司的布告栏上有上百张的照片,旅游

活动、化妆晚会、家庭小聚都成为了拍摄的主题。

为了拓展员工视野,TrendLabs的管理者从美国订阅了

大量的技术和管理杂志,像《Infoworld》和《商业周刊》等

必不可少。除此之外,他们还自己设计出版了内部刊物

《@TrendLabs》,每季一期,印刷精美。其中会宣扬公司的

战略,回顾取得的成果,表扬一些优秀员工等等。纪孟宏

每期都亲自主笔,其中一篇谈“员工职业生涯成长”的文

章给我留下了深刻的印象。TrendLabs还将这些刊物赠送到

员工的母校,学校和员工都得到了满足感,客观上也宣传

了TrendLabs。

很多公司会组织员工活动,但有何效果没有人关心。纪孟

宏认为,有必要了解做活动是基于那些考虑,做完要达到什么目

的。员工缺乏哪方面的能力,就要做相关的活动来进行提高。

“我们30%的活动项目属于沟通,20%的项目属于让员工谈感

想,每种能力都占据一定的活动比例。”

员工的庆生会上,部门经理还会被要求帮员工切蛋糕和培训流程

按照全球不同地区时刻工作

Page 24: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

24

倒汽水,这样的一件小事为什么还要提出来呢?因为菲律宾

的阶级观念很重,服务生在餐厅会尊称客人为Sir,新进的员

工对仅工作一两年的员工也会用Sir来称呼。考虑到这样做会

造成不必要的隔阂,因此TrendLabs要求员工都要直接称呼名

字,让大家没有尊卑的感觉,同时体验角色交换,让员工处

在更和谐、沟通的环境中。

数字保证服务

2001年7月,CodeRed病毒爆发,2001年9月 Nimda 病

毒窜起,并且以多元化的感染途径开始大量传播;接连两

个破坏性超强的病毒,让所有的企业无不闻毒变色。从那

时起,如何突破传统的防范机制,以更主动、更实时的方

式来控制病毒的扩散,以及更有系统地掌控病毒散播的情

形、更有效率进行整个网络环境的修复成为了防毒软件厂

商考虑的重点。

在这种情况下,趋势科技多年打造的一支服务队伍提出

了EPS (Enterprise Protection Strategy企业安全防护战略),其中最

重要的就是利用趋势科技专业的防毒研发暨技术中心和24x7的

服务提供全面解决方案,这项业务的负责人是张伟钦。

张伟钦是趋势科技的元老,早年他曾在美国负责和Intel

的合作项目,此后又到日本,带领程序员开发出了占据日本

桌面杀毒软件市场70%的日本版Virus Buster/ PC-cillin。他

认为,防毒并不能带来生产率的提高,而只是为了防止生产

率的下降。原来大家都在比拼谁的杀毒软件好,其实用户关

注的并不是这些。他们关注的只是如何预防病毒,如何更好

的解决病毒所带来的问题和灾难。用户可以购买很好的机器

让程序运行的更快,而不可能购买各种杀毒软件完全防止病

毒带来的损害。从产品角度看,杀毒软件已经出现了10多年

了,但现在杀毒软件仍然无法针对病毒提供完全的解决方

案,因此需要服务来实现这种要求。

张伟钦在现场还做了“红色警戒”的演示。虚拟客户发

来一个VB script病毒样本后,立刻被提交到病毒防治系统,

本刊记者和TrendLabs员工合影

如果系统中有该病毒的信息,那自动反馈给客户解决方案。

如果没有,那就会发送病毒爆发信号给实时防毒研发小组,

同时通知技术支持专案经理30分钟内找出病毒码,在这个过

程中,会同客户保持联系,同时在网站上每15/30分钟更新

信息。找到病毒码和提供解决方法之后,会提交至病毒码质

量保证部门,要求15分钟内发布最终解决方案。并根据病毒

的损害程度发出指示,一般一个国家两家企业发现同一种病

毒,那就会亮黄灯,而一旦有三个国家发现同一病毒,那就

属于全球性病毒,红灯会被启动。

在现场,我们看到TrendLabs员工严格遵循着工作操程,病毒

命名,提交信息,病毒分析,反馈信息不断的进行着。很快,解

决方案给提交到了网站上,并发送给了他们的客户。

张伟钦表示,向客户承诺两小时提供解决方案,其实内

部要求一个小时甚至45分钟就要完成。现在和趋势科技签约

这种服务的大企业有六百多个,其中包括澳大利亚电信这样

的大型企业,项目的服务费超过数百万美元。正是这种服务

所带来的价值让趋势科技取得了良好的收益。今年趋势科技

的收入达到3.5亿美元,软件服务占了很大比重。

为了让员工能够按照国际性的标准来做事,TrendLabs在

通过了ISO9002标准认证后,还在通过ISO17199的安全认证,

通过流程来更专业的保证客户资料的安全。

张伟钦表示:“很多公司都宣称自己有个天才,而我们

有的是数百个的经过三年严格训练的工程师。在提供的软件

服务中,只有一个标准,那就是数字,我们用数字说话。”

圣诞节之夜

晚上7点,在马尼拉一个酒吧中,三百多名TrendLabs员工

欢聚一堂,CEO张明正和CMO陈怡蓁夫妇,来自日本的CFO等

公司高层也悉数出席。菲律宾人能歌善舞,欢快的性格展露

无遗。大型音乐剧、舞蹈等节目的演员全部来自内部员工,

表演颇具专业素质,令人惊讶的是,这些节目的排练只是在

几个下午的时间完成的。当张伟钦、郑奕立、纪孟宏上台讲

话时,下面的员工齐声呼喊他们的英文名字。

员工载歌载舞

走向海外

Page 25: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

25

人 物 & 报 道

中国最高智慧之

叶天正没有什么可以骗了我

中国最高智慧之

叶天正没有什么可以骗了我

必须强调的“三个必须”

1994年王玮(George Wang)博士来华策划创立IBM(中国)

研究中心(编者注:以下简称研究中心),1995年研究中心正

式成立。叶天正博士是从1998年下半年来到研究中心担任主任

至今,他见证了研究中心的每一次发展。研究中心在中国和世

界上取得的进步,他都一一看在眼里,铭记在心里。

搬迁到昊海大厦之前,研究中心位于上地六街的华发大

厦,两者之间的距离不远。在华发大厦的日子,叶天正认为

那是一段“痛苦的回忆”。

“研究中心招收的是来自全国各地的优秀学生,户口问

题的限制着实让我们伤透脑筋。早期时候,北京没有形成人

才流动机制,除了博士生的户口,其他学历的学生户口难以

解决。后来随着北京IT人才政策的放开,这些问题才得以逐

步解决。上网也成问题,那时侯网络建设没有铺开,研究中

心要上网只能采用微波传输,依靠微波设备指向IBM中国总

部当时所在的丽都饭店实现网络联系。这在当时属于比较先

进又昂贵的上网方式,麻烦却不少。通过微波传输数据实现

网络联结平时还可以,一遇大风大雨天气,就需要派人不时

校正微波指向,否则通信线路随时会断,实在有些折磨人。

在华发大厦中午吃饭的选择也是少得可怜,除了包子还是包

子,你别无选择,否则就得去老远的地方。”

正是华发大厦留下的难忘经历,叶天正非常懂得维护现

在的大好环境,他知道如何让自己的员工享受到一流的办公

环境。

因为业务发展的需要,研究中心于20 0 0年搬到昊海大

厦,完全占据其中第二层。看着空荡荡的整个楼层。叶天正

依据自己的设计开始布置I B M(中国)研究中心的平面布

局。他认为技术开发是最辛苦的工作之一,要给开发人员提

供尽可能优质的硬件设施。

事无巨细,叶天正都亲自过问,力求做到精益求精。灯

管不能是日光灯,它发出的惨白色光线有时让人晕眩。灯光

色调必须是比较柔合的自然光,因此他选用带有暖色调的灯

具;椅子必须是最好的椅子。开发人员有近1/2的时间在椅

子上度过。因此,他给每位员工买了价值5,000元的椅子,

都是专门从国外进口的;员工的工位空间必须是疲劳后可以

将双脚放在桌上为宜,这意味着研究中心开发人员占据的空

间可能是普通公司员工的两到三倍。

不用完整提问的采访

叶天正博士非常善于琢磨记

者的思维方式,以至于他对问题

反应的机敏让笔者有些吃惊。

每在事实引述和提问问题

的间隙,叶博士已经笑咪咪地开

始作答。他的回答有一种定式,

最初一两句话切中问题实质再围

绕展开。笔者感觉比较轻松,每

一个问题不用解释太多就可以轻

易得到想要的答案,由此也看出

他作为科学家本质的一面——逻

辑推理的严密和连续。

不过也难怪,历经了诸多

不同工作环境的叶天正博士已经

养成遇事多思考的习惯,这是他

最可宝贵的财富之一。

中国最高智慧之

叶天正没有什么可以骗了我

撰文/张里

见证了IBM(中国)研究中心成长历程的叶天正博士

IBM(中国)研究中心员工的工位,其空间必须保证将双脚放在桌上为宜

Page 26: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

26

这是曾经有着同样研究经历的叶天正追求的一种轻松氛

围,他知道开发人员最需要什么。他不喜欢满嘴的口号,他喜

欢通过具体的表现方式让大家感受到所受到的尊重,这体现了

他在研究中心倡导的“以人为本”的人文气息。

“现在不是比较的时候”

虽然IBM(中国)研究中心成立的时间不短,除了语音

产品,他们为人所知的并不多。叶天正把原因归结为自己的

很多成果属于隐形技术,是用户看不见的,只有他们在开发

应用的时候才会感觉出来。

至于为什么当初会选择走这样的路子,叶天正解释说:

“因为最初人手比较少,我们决定介入发展最快的技术领域。现

在已经确定了5个主要的研究方向(多媒体技术(mu l t i m e d i a

techology)、专业运算(professor computing)、自然运算(natural

computing)、电子商务(electronic business)和信息基础设施技术),而

这些都属于飞速发展的隐形技术。”

在叶天正博士的带领下,研究中心人员数量从10余人扩

大到现在的100多人。作为世界级的研究中心,百来人的研

究队伍算不上强大,但人员数量上的劣势并不妨碍他们出世

界级成果。和国外同行相比,研究中心对语音技术的研究投

入并不算早,但凭着孜孜以求的精神,他们在语音领域贡献

出了一系列世界级产品。“比如说iPAQ,如果语言选择中

文,那么里面的嵌入式软件就是我们的成果,它已经行销全

世界。这个中间模块的一个比较重要功能是使基于定位的信

息能够提供后端的信息服务。这样的中间件模块从头到尾都

是由我们来研发的,它已为世界用户所认同”。

衡量一个研究中心的影响,不仅要看他们的研究成果,

申请成功的专利数量也是重要指标。但到目前为止,叶天正

领导的IBM(中国)研究中心所获专利却是不多,在IBM的全

球8个研究中心中位居下游,因此有人质疑中国研究中心的

能力。对此,叶天正有着个人理解。

“专利申请是一个长期过程,专利局差不多要为一项专

利耗费2-3年的时间才有批复。我们今年大概会申请20个专

利以上,但得等到2003或更晚时候才知道有多少专利申请成

功。研究中心今年拿到的专利则是3年前所申请的。IBM(中

国)研究中心3年前人数还不足5 0人,经验的积累也比较

少。从这个角度来说我们和其他研究中心(IBM(印度)研究

中心除外,他们成立于1998年)不具有可比性,因此用专利

数量的多少来衡量一个研究中心的影响不是科学标准。我们

成立的时候只有10个人左右,到现在也仅100来人,而IBM全

球研究中心共有3,000人的规模。”

“我做什么呢?”

“在不同职位做不同事情。我现在只需要考虑一些新的

研究方向和新的策略性方案。”叶天正简单地把自己的工作

描绘了出来。

“如何让管理的思路去建立这种风气是非常重要的。最

重要的目的是希望所有的人在一段时间的发展后都可以来做

我的事情,而不是我去做他们的事情。这才是成功的管理

者。所以如果问我的具体工作,我的回答是非常非常少。”

叶天正喜欢和开发人员一起进行头脑风暴(Brainstorm),

以保持自己有持续不断地有新的想法出来。但当自己的想法

付诸与实验,他就会完全避开,“我不会加入真正的实践研发

中”。叶天正的目的是希望把研究里面的Culture(叶天正特别

解释这不一定理解为企业文化,它有可能是一组人的行为在这

个环境之中的表现,以及对环境的反馈。)一定要完整地延续

下来,它牵涉到不同人文方面的问题。

其实,叶天正的话语里隐藏着自己的另一项工作——对

开发人员的着力培养。这种培养不是“手把手”的技术言传

身教,而是培养他们树立一种良好习惯——遇事多参考。

“如果谁说自己有一个好主意,我马上反驳并指出这技

术早有人涉足。虽然事实如此,但多次这样的话,也没有人

会大胆公开自己的想法。我只能委婉劝说他在做事情之前先

去看看别人是否做过。”

对于开发人员来说,叶天正认为自己发现问题比别人的

劝说效果要好得多。他们走别人的老路只是因为自己没有查

询到足够多的资料。“但你不能打击他,而应鼓励他继续往

深处想。让他们养成遇事多参考的习惯,这样他再提出相同

课题的机会就比较小了。这才是他的主见。这意味着在整个

行为反馈中每个人应该如何去建立一个能够启发的环境。”

“没有什么可以骗了我”

叶天正在台湾念完大学后服了一年兵役,在军营里他作

为教官教士官生的物理课,服完兵役去美国读了博士,在康奈

尔大学取得博士后文凭,然后直接加盟IBM华生研究中心。

在IBM华生研究中心,叶天正博士过去的研究项目中也

不乏有些中途停止,在聊及此事时他并不把这类经历看成失

败。“一切根据实际需要出发”,这是他认为最好的解释,

“研究的目的不是单纯强调结果,经验的积累才最重要。”

这其中就有他在华生研究中心的一次经历。那是他带领的课

题小组在研究超导体制作CPU的可行性。

在70年代,电脑界遇到紧迫问题是如何使信息在电脑里

传输得更快。提高传输速度的方式有两种:一种是距离缩

短,一种是加快速度。由于没有办法把两个原件的物理距离

更为靠近,只能追求速度的提高。采用超导技术做实验的目

的就是希望提高速度的同时降低功耗。虽然后来叶天正带领

的研究小组证明超导技术具有完全可行性,但最终还是不得

不遗憾地结束研究。原因之一是与超导技术竞争的硅技术的

人物专访

Page 27: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

27

人 物 & 报 道

进步,使得CMOS的出现暂时解决了功耗问题。

“那时候用超导体搞研究完全是一种尝试,看是否可以

做成。最后答案是肯定的。但在是否需要继续深入的时候,

我们选择放弃。因为其成本太高了。如果继续往这项研究里

投资,IBM公司将为此投入更大的人力和财力,出产品以后

的效果还不一定好。我们停止该项技术的实用化意味着替

IBM公司节约了一大笔费用。这个时候就要算一笔经济账,

看哪一个在经济上更为合算。”

从这个角度来讲,叶天正认为做研究的人应该了解清楚

问题的实质后再做决定,因而即使得到一个非正面的结果也

不一定就定性为失败。毕竟,经验积累起来了,而经验的多

少是直接导致往后研究成功与否的关键。

叶天正又以研究中心举例。“假如有10项研究成果都成

功实现产品化,这意味着我们失败了,失败之处在于我们对

未来技术制订的标准不够高。好比很多人可以轻易跳过椅子

却难跳过桌子一般。如果有技术因标准太高而不能进入产

品,我们可能遭遇暂时的实用化失败,但毕竟从研究过程中

学到不少前瞻性的东西,经验自然得到提升。”

正因为有了这么多经验的积累,叶天正自豪地说“每次

去参观客户公司或工厂,或去拜访某个国家的研究中心,除

非是比较深入的技术,否则稍微浅显一点的技术解释都骗不

了我,因为我几乎都接触过。”

所以在研究中心,有人问叶天正为什么不做某个项目的

时候,他就会用经验告诉开发人员考虑问题要从全局着手。

“如果仅从这个项目来讲有单方面的效益。但如果从全局角

度来看,其效费比可能并不占优,所以具有不同经验了,一

个人对很多事情看法和别人不一样。选题目和选方向的时

候,如果你的经验用得不好,就会成为你的包袱,否则就是

你最好的参考资料。”

“以我全部叫得出名字为宜”

任何研究中心的目标很简单也很明确,做到世界第一

流。顺应这样的远景目标,研究中心需要多大的规模才算

合适,这是值得每一个研究院的首席负责人认真考虑的问

题,这也是笔者请教叶天正博士的最后一个问题。他的答

案有些出乎意料。叶博士没有给出明确回答,“具体需要

多大,这是需要看研究的进展而定的”。他只认为“最适

合的员工数量以我完全叫得出他们的名字”为宜。在他看

来,当员工数量过多导致自己不能叫出每个人的名字的时

候,就可能存在不能和他们面对面沟通的问题。

叶天正之所以不急于对未来蓝图的勾划,更深层次地

体现了他作为一名科学家本着的宗旨——“人人成功”。

他认为国内开发人员存在着经验严重不足的缺陷,而

他们进入研究中心后又立刻面临尽快实现角色转换(从学

术研究向产品研究)的问题。目前情形是,国内很多开发人

员重研究,轻实用,技术产品化经验不足。再者,工业界和

教育界对技术成果的理解不同,前者以最快达到目标为目

的,如何借用别人的长处实现目标才是真正需要。而后者

则基本上要求学生们自力更生。

“如何让新人从学术环境进入产业研究环境,如何让

他们得到实质性的提高,如何尽可能让他们人人成功,我就

必须让新人做最前沿的东西,训练他们知道如何让客户了

解技术好处并留下印象。为了避免走弯路,我还要带他们

关注IBM全球8个试验室各自的技术和发展方向。因而每个

人进IBM中国研究心都有一个被带领的过程。如果一下子给

我50人,而我的基数只有1 0 0多人,叫我如何去消化他们

呢。这是一个非常严肃且严谨的问题。”

多才多艺的科学家

笔者参观过很多研究中心负责人的办公室,叶天正博士

的(一个小隔间+一间小会议室)稍显局促而简单,却是别

致有情趣。主人个性得到完全体现。

叶博士喜欢摄影,进他的屋子就可以看到漂亮的风景

照,那是他十几年前的作品,取景的精美很有大家风范。

“我喜欢拍照,但没有刻意追求,使用的也是当时的普通相

机。我不会象摄影家一样为了一幅美景起早摸黑。”叶博士

笑谈自己还不到疯狂的地步。

除此之外,叶博士还对音乐存有一份执着。在美国的时

候,古筝、笛子都是他喜欢的中国传统乐器,来中国开始学

起钢琴来,他现在休息时偶弹钢琴。“我弹钢琴的技术差强

人意,只是吓不跑人而已”。

从叶天正玩笑式的自嘲当中笔者听出了一个额外的回

答:做开发人员原来可以这样。他们的生活不光停留在技术

和实验室,多彩多姿的世界同样可以留下他们的身影。

叶天正和他拍的“桂林山水”,那是十年前的作品

Page 28: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

28

新的 C++标准会有什么?

CSDN:博士,您好,欢迎您来到北京!我们都知道您最

近离开您为之工作了23年的AT&T实验室,赴德州A&M大学任

计算机科学教授。能告诉我们原因吗?

Stroustrup:首先,我没有完全离开AT&T实验室。我和我

原来的研究中心保留了永久的合作关系。第二,我女儿进了

大学,我儿子也进了大学,他们都过得很有意思、很活跃,

拥有大量时间。我也很向往!所以,我就到大学来了——当

然我是去当教授的。我认为是开始教书的时候了,我喜欢和

学生们一起工作。我应该做些新的、不同的事情了。这就是

我离开AT&T到德州A&M大学的根本原因。我在AT&T的同事

舍不得我离开,所以就有了这样的安排。我还保留着与AT&T

的关系。我还是AT&T的一员,即使在我于今年十二月到德克

萨斯后,我还会参与AT&T的某些项目。

CSDN:您正在写ARM2,是吗?

Stroustrup:不是,很遗憾,这件事从来都只是一个想

法,没有付诸实践。我和Andy Koenig打算写一本ARM2(即基

于ISO标准的“The Annotated C++ Reference Manual”)。然

而,时机合适的时候我们没有写,现在为时已晚。以后如果

我们去写这样一本书,那肯定是关于下一个标准的。像ARM

这样的书只有和标准同时出现才是真正的好书。那样,它就

可以帮助程序员、专家、教师和实现人员处理新的概念。一

旦这些人了解了新的语言规则及其含义,他们就不需要ARM

的帮助了。我很遗憾没能够在这一个周期内写作ARM来帮助

专家和编译器实现者。也许,这个十年里我们可以写一本

ARM++。

CSDN:下一个标准什么时候能出来?

Stroustrup:可能2005,也可能更晚。我们需要为之辛勤

工作,就每个主要问题做出决策,以便每个人都知道大概状

况。然后还得投票,确定细节——所谓“细节”,就是那些

只有专家才关心的事情。标准之为标准,就在于此。现行标

准是1996就定型了,但直到1998年10月才成为标准。所以,

我不知道新标准公布的确切日期。

CSDN:一个关于C++和嵌入式系统开发的问题。您认

为,要使C++成为实时嵌入式系统开发的主流工具,C++社

区应该做哪些工作?

Stroustrup:C++已经针对嵌入式系统做了大量的工作。

四年前,ISO C++委员会的日本代表报告,当时日本嵌入式

开发有1 5%是用C++完成的。我不清楚现在的百分比是多

少。但是,我相信是增加了。我认为C++在嵌入式系统开发

中用起来更容易。和我交谈过的绝大多数嵌入式系统程序员

都面临着系统程序员十年前曾面临过的问题。他们希望实现

运行效率、内存使用效率以及可预测性。C++可以提供这些

保证。C++被设计成能在资源有限的环境中支持各种意图。

许多程序员错误地认为,只能用C++进行面向对象编程,这

就意味着不可预测的行为和大量奢侈的内存使用。如果这是

真的,C++当然就不适用于

许多嵌入式系统。但是C++

不是这样的。如果你调用

一个虚函数,其开销是可

预测的,完全可预测的。

你还可以写很多完全不涉

及面向对象的C++代码,而

且它们同样是经过精心设

计、高效和可维护的。很

多好代码都不需要大量的

内存分配。如果你需要动

态 内 存 , 可 以 使 用 内 存

“C++ 之父”访谈

2002年10月,“C++之父”Bjarne Stroustrup博士来到了中国。

1 0 月2 2 日晚上,在北京西苑饭店的咖啡厅里,C S D N记者对

Stroustrup博士进行了专访。

记者/熊节

Bjarne Stroustrup博士

发表演讲现场

全世界的开发人员都感受到了C++

所掀起的技术风暴

人物专访

Page 29: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

29

人 物 & 报 道

池;如果没有实时限制,你甚至可以使用垃圾收集器。但

是,我认为C++在嵌入式系统中的主要优势在于更好的类型

检查,抽象类型以及具体类型的运用。当我们在自动生存域

(使用局部变量)或者静态全局域中使用对象时,其行为和

开销能够很好地预测。同时我们仍然可以提高代码的抽象程

度,改进编程技术。所谓“嵌入式系统开发”,其意义相当

泛泛,小到设备驱动程序,或者手持设备应用程序,大到程

控交换设备控制系统,所以各种限制和问题相差很大。人们

提到“嵌入式系统”时,通常他们想到的只是自己那非常狭

窄的领域。但是,一旦你和许多人交流,就会意识到实际上

问题所涉及的范围非常广。我对“嵌入式系统开发”的认识

是,凡是为非传统计算机设备所进行的开发工作,都属于嵌

入式系统开发。这个范围无疑是非常巨大的。

CSDN:下一个标准将为嵌入式开发人员做什么特别考虑吗?

Stroustrup:这个我不知道。但是我知道两件事。第一:

会有更多的标准库组件支持嵌入式系统编程。第二:C++

标准委员会的一些成员(包括我)已经写了一份关于C++

的性能的技术报告,其中对于限制、开销和具体技术进行

了详细的探讨。这份报告还提供了一些提示,讲解了一些

已经被成功运用、又可以在今后的开发中继续运用的系统

优化技巧。在上周的Santa Cruz会议上,这份报告应该已经

被投票通过成为标准委员会的官方报告了,本来我应该出

席的那个会议的,可是我来到了中国。[性能技术报告的URL

链接:http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/

2002/n1396.pdf]

CSDN:Herb Sutter曾经说过标准委员会将考虑将微软Man-

aged C++扩展纳入ISO标准,真的吗?您是怎么看待这件事的?

Stroustrup:我不清楚Herb到底是怎么说的。Herb是个了不

起的家伙,他做了很多非常了不起的工作。显然微软正在做

一些扩展。只要你想想,就会发现每一个平台提供商都会进

行语言的扩展,以便与他们的平台或者操作系统交互。事情

从来都是这个样子,不必大惊小怪的。我知道微软正在试图

使C++与他们的.NET平台更好地交互。.NET的接口与传统的

C接口大不相同。微软为此提供了一系列扩展。现在已经有

一个版本使用了一段时间,听说他们还考虑修订第二版。对

于你刚才说的事情,我的理解是Herb在保证微软将会把那些

扩展提交委员会讨论,这是件好事。在过去的两年中,微软

确实与委员会进行了非常紧密和友好的合作。也就是说,如

果微软提议,委员会会考虑的,但是这不代表委员会一定会

接纳。只有经过了对提案技术价值的讨论和投票后,才能决

定是否接纳。

C++是最好的语言吗?

CSDN:最近我们通过email采访了你的朋友Andrew Koenig。

我们问了他一个问题:他是如何看待Python、Ruby等动态语言

的。他说他正在学习Python,而且很感兴趣。他相信Python是

C++的绝佳搭档。另外,我刚读了Robert Martin写的一篇短文,

他说他相信下一个十年属于类似Python和Ruby这样的动态语

言,因为动态语言更适合于“敏捷开发方法(agile development

method)”。您对这些说法有什么看法?

Stroustrup:我不知道Robert Martin的这篇文章的具体背景。

他现在非常钟情于“敏捷开发方法”,事实上他是这种开发

方法的原始倡导人之一。或许他看到了开发方法和语言机制

之间的联系?或许他正在开发一些不重视性能的项目?如果

这些,你可以从动态类型的语言中得到很多好处。就个人而

言,我更倾向于依靠语言本身提供的保障,而不是依赖于开

发方法学。当然,两者都很重要,但是我对一些强调性能的

领域有着浓厚的兴趣,我认为在这些领域中静态类型的语言

比动态类型的语言更适合。另外,C++通常只是系统的一部

分,而不是全部。

很多人相信存在一种语言,可以用来做全部事情。对于

那些使用动态语言解决无性能限制问题的人来说,这种信念

很普遍。如果你生活在Smalltalk的世界中,你就会拥有一个

Bjarne Stroustrup博士在北京举办C++讨论会

Page 30: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

30

Smalltalk文件系统、Smalltalk编辑器以及Smalltalk GUI等等。有

些使用Python的人也有类似的想法。我觉得Andy不会这样想,

我也不会这样看Python。我觉得,为了性能和可靠性,你的系

统需要一种静态类型的语言。例如,你采用何种语言来实现

动态语言?目前答案通常是“用C++”。另一方面,你的系

统不可能只依赖于静态类型检查,这样做没有意义,而且会

令系统过于刚性。所以,问题不在于是只使用C++还是同时

采用其他的语言,而在于使用哪种语言来配合C++。可以是

UNIX shell,是Visual Basic,是Perl,或者是其他某种动态类

型语言。在众多的选择当中,Python是很有意思的一个。它的

编程模式与C++不同,但是两者的差异不大,你能很容易地

与它交互。Python里有C++类库,因此你可以调用一些语言结

构,看上去像Python,跑起来像C++(高效、可预测)。这是

自然,因为它们本来就是C++。我知道Andy非常喜欢研究

Python以及与C++的交互。我还知道David Abrahams——他是

Boost中的一员——他也对C++/Python的交互非常感兴趣。关

键不在于除了C++外你是否需要使用其他语言;问题是你要

使用的是什么语言?它们与C++的交互性如何?Python是个很

好的选择。

在这里,我很乐于指出,我本人也很喜欢使用多种语

言。多年来Andy和Robert也一直这么做,我不相信他们以后会

只用一种动态语言度日。

CSDN:康乃尔大学和AT&T实验室的一些计算机科学家发

明了一种叫做Cyclone的编程语言,据说是C的一个更安全的版

本,致力于解决C的安全性问题。这段时间以来,安全和保

密是业界最热门的话题,C++总是被认为是“不安全的”语

言,请问C++将如何对待这些问题?怎样才能加强C++代码

的安全性?

Stroustrup:首先,Cyclone主要是在康乃尔大学开发出来

的;只有一位AT&T研究员参与到Cyclone团队中。第二,Cy-

clone实际上并不是C。它是剔除了C中一些不安全的结构,又

添加了一些C所不具备的结构以增强表达能力。所以,人们

才把Cyclone当作一种更安全的语言,同时在某种意义上把它

当作是C的替代者。

安全和保密是一个大话题。我是属于认为安全性必须来

自硬件支持的那一派,因为真正的话题不是类型安全,而是

系统策略。是的,有一门安全的语言是件好事。我指的是类

型安全语言。类型安全是理想境界。但是,如果你能够从这

种语言中轻易地调用C,你就再一次不安全了。你可以努力

证明调用C的语言是“安全的”。但是没有人能够证验证所

有C 代码。所以我不认为你能够通过这种方式实现系统安

全。还有一些所谓的安全语言(其安全性不仅仅指简单的类

型安全),有的运行在虚拟机上,相对于C和C++所能做的

而言,它们都采用了受到严格限制的对象模型(并且原则上

用C和C++也能安全地实现)。

我认为C、尤其是C++在未来仍将扮演重要角色。而

且,我认为对安全性感兴趣的C + + 使用者可以有很多选

择:如果性能不是问题,可以插入一个垃圾收集器,这可

以解决部分(而不是全部)安全问题。还有些系统将代码

验证和垃圾收集器结合起来,从而将系统安全性提升到了

一个很高的水平。我很高兴能看到更多在这方面的研究。

但是,大家应该意识到,从系统整体的观点看,某些宣称

是安全的语言也可能是不安全。无论如何,如果你使用Java

或者C#,就需要一个庞大的虚拟机实现,通常是用数万行

C++代码写成的。因此,你首先得验证虚拟机本身的安全

性。因此,你无法通过软件获得完全的安全,必须依赖于

硬件。对于许多应用程序来说,正确地使用C++标准库是

一种简单的选择,可以很大程度上杜绝讨厌的缓冲区溢出

和野指针问题,正是这些问题给C和世俗的C++编程风格带

来了不良声誉。

CSDN:我认为“新兴”语言,譬如Python和Java,的真

正魅力不在于语言本身,而是库。所有这些语言的后面都

有一个庞大的、可供使用的库。更重要的是,Python程序员

或者Java程序员可以很容易地找到他所需要的东西。比方

说,如果一个Python程序员需要一个正则表达式处理组件,

他就会到www.python.org去搜索。但是,作为一名C++程序

员,我就没有这么幸运。尽管C + + 发展已经将近三十年

了,我们还是没有一个完整的、可移植的组件库,您怎么

看待这种状况?

Stroustrup:喔,实际上C++还没有到三十年这么久。

C++真正的麻烦在于它流行得太快,C++用户社区又被几个

主要的开发商给分裂了。这些开发商都试图提供一个庞大

1993年,Bjarne Stroustrup 博士参加第二届程序语言发展史会议时与主办者合影

人物专访

Page 31: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

31

人 物 & 报 道

的、能满足用户所有需求的库。例如,如果你是IBM的C++

程序员,IBM库就会提供给你所有的东西,如果你是微软的

C++程序员,微软的库也不甘示弱。所以,C++库的开发不

是集中协调的。由于有很多这样大的竞争者,要想创建一

个集中的资源库是十分困难的。一旦你想提供一个集中的

组件库,就将面对来自IBM、Sun和微软的直接竞争。而像

Java和C#这样的语言,它们的力量主要是由于它们有一个

商业后台给你提供全面的程序库, 而且告诉你:“使用我

们的语言吧,这是程序库,它们都是标准的!”其实这没

什么稀罕的,如果你是始终只跟随同一个提供商,也同样

能得到“标准的”C++库。Python则不同,它是开放源代码

的,Python的领导者成功地建立起一个实用组件仓库。遗憾

的是,这种经验已经不可能对C++有任何帮助了,因为C++

领域中早就存在了组件库过于零散的问题。C++使用者总

是有多种选择。你可以说:“我在用Sun的C++”或者“我

在用微软的C++”,还可以使用由这些提供商提供的全部

有用的东西。我们还可以查找第三方商业程序库,或者开

放源代码程序库——其中很多都是平台无关的。你可以从我

的首页上的“程序库FAQ链接”开始去查找你需要的组件

库。还有一个好的搜索点是www.boost.org。事实上有很多

C++库,遗憾的是没有一个集中的高质量组件仓库。这部

分地归咎于我。早些时候我忙于开发语言,没有及时意识

到程序库的重要性。还有部分责任在于商业操作者,他们

拼命提供给各自的使用者独立于其他提供商的“最好的程

序库”。一旦功能类似的各种库都得到了广泛使用,再要

想统一,就非常非常难了。

泛型:聪明,过分聪明

CSDN:您刚才提到Boost.org上的库。随着STL的成功,越

来越多的人认为泛型编程风格是代表了当前最高技术水平的

C++编程风格。我们看到过许多类似Loki和Boost.mpl这样优秀的

库,它们都证实了这些技术。但是,有人认为模板技术被过

度使用了,正如我们在二十世纪九十年代前期过度使用继承

一样。您怎么看待这种说法?

Stroustrup:我赞成。我反对“C++是一种面向对象的编

程语言”的说法,C++是一门通用目的的编程语言,支持包

括面向对象编程在内的许多不同的风格。同样的,如果有人

说“C++是一门泛型编程语言”,我也会一样反对,同时再

次指出C++是支持泛型程序设计的一种通用编程语言,相对

其他绝大多数语言,C++对于泛型程序设计的支持都更加完

备。我认为看待C + + 的正确方式是把它当作一门多范式

(multi-paradigm)的编程语言。真正有意义的问题不是哪种

范式最好,而是哪种最适合具体的问题,以及怎样将这些范

式结合起来为大型系统提供最佳的解决方法。实际上,在这

次中国之行中第一个演讲的副标题就是“C + + 多范式编

程”。演讲中,我展示了一些例子,在这些例子里直接使用

基于对象风格就是最好的解决方案。然后,列举了另一些例

子来展示面向对象的最佳用途,展示泛型程序设计的最佳用

途。最后,我举出了一个例子,在这个例子中我结合了这些

不同的风格,形成了一个我所知道的最精致解决方案——比

我所能想到的任何一个单一风格解决方案都要精致。所以,

如果你问我C++应该走向何处,我会说“向多范式编程发

展”。我们必须发展出一系列规则,用于指导多范式的编程

风格。

CSDN:也就是说成为一个好的C++程序员会困难得多,

对吗?

Stroustrup:和什么相比?

CSDN:与掌握语言相比。譬如,我学过面向对象编程,

知道“Si ng l e t o n”模式是什么,但是我不理解Loki库中对于

Singleton的实现⋯⋯

S t r o u s t r u p:哈,那就是了。实际上我并不怎么使用

Singleton。我认为类似Singleton这种东西,是人们为了试图证

明自己有多聪明而做的,同时当然也是为了探索语言能力的

极限。而Loki实在是太聪明了。我认为它和很多早期的面向

对象编程一样,异乎寻常的复杂,异乎寻常的聪明。人们总

是试图对每个程序都套用时髦的模型。现在这个阶段,我们

还处于对于模板极限能力的探索时期。然而,我们需要尽快

前进到下一个阶段,在那个阶段,我们可以通过简单的例子

来解释简单的技术,并采用这种方式教授这门语言。我在考

虑我们是否需要一些新的语言工具来简化模板代码。那样的

话,使用泛型程序设计技术就会变得更容易。我们必须找准

语言功能和编程技术之间的结合点,从而显著地简化泛型程

序设计和多范式程序设计。

C++的一个问题在于糟糕的教学。典型的C++课程总是

Bjarne Stroustrup博士和追随者讨论在一起

Page 32: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

32

从C开始。更有甚者,老师们常常要求你首先必须理解C的全

部内容和技术,如何用malloc处理你将遇到的问题,如何使

用宏来解决麻烦等等。我认为,今天的C++教学很重要的一

点就是要改变这个过程,告诉学生通过使用简单的技术,使

用C++语言工具和程序库可以使事情变得何其容易。使用程

序库比编写程序库要容易得多。所以可以说“C++的复杂

性”部分是由于糟糕的教育。

另一个原因在于C++是一门通用编程语言。如果你把一

门语言限定到某个狭窄的领域,当然可以简化它。例如,你

可能会说:“好吧,我不关心高性能数值运算,不关心嵌入

式 系 统 , 不 直 接 操 作 硬 件 , 也 不 会 脱 离 这 个 特 定

framework”。如果是这样,你当然可以设计一个只适合于这

个领域语言,可以比C++简单得多。C++适用于很多领域。

这就是复杂性的根源。变通的方法是,为创建大型商业应用

学习一门语言,为开发嵌入式系统学习另一门语言,为高性

能数值计算、图形系统或者类似的东西学习第三种语言。对

于有些人来说,这是一个很好的办法,但这不是C++要走的

路。C++要满足许多编程领域的需求。

如何管理对象?

CSDN:C++是基于值的,这就意味着我放入到容器中的

是实际对象而不是它们的引用。这给处理大对象时带来很大

的麻烦,因为复制和移动操作都要付出很高的代价。您在实

际工作中如何处理此类问题?

Stroustrup:我会把指针放在容器里。

CSDN:指针?那样会不会不安全?

Stroustrup:当然不会。C++一个很重要的特点是,它显

式暴露了很多其他语言暗藏不露的设计决策。例如,即使在

“纯面向对象”编程语言中,类似字符和整数之类的对象也

基本上都是基于值的。当我设计用以支持复数等概念的语言

机制时,对于常规表达形式和优化的需求很自然地引出了基

于值的方案。如果所谓的“复数vector”,实际容纳的只是一

群指针,指向各自独立的复数对象,那么你就不可能进行什

么有效的优化。很不幸,在很多语言情况却正是如此。C++

将“值”与“引用+对象”清楚地区分开了。你可以褒奖:

“这是C++普遍性的体现”,也可以贬损:“这是C++复杂

性的体现”,但是这些评价针对的都是同一个事实。

就我而言,一般不会在容器里放入大对象,而是会放入

指向这些对象的指针。一旦你有了指针,就必须考虑对象生

命周期的问题。如果被指向的对象是全局范围或者静态的,

你就不必再担心生命周期的问题了。更为常见的是,对象是

放到自由存储区(free store)中的。你可以管理这个自由存

Bjarne Stroustrup 博士与本刊记者合影

储区,可以使用一个垃圾收集器,或者让容器来管理。我偏

爱的方式是用容器来管理自由存储区。一旦我通过指针给容

器一个对象,它就是容器的对象了。我不会再擅自删除它了

——这个对象也许还有其他的用户。当容器被删除时,所有

被其元素所指向对象也都会被删除。我们可以制作一个容

器,让它的析构函数删除所拥有的对象,这是我解决此类问

题最喜欢用的方法。

有很多人,特别是在Boost.org中的伙计们,喜欢在容器

中放入一个计数指针(也叫做共享指针,shared pointer),

依靠引用计数来提供自动的管理。在很多方面这也是一个相

当好的技术,比我的技术显然要通用一些,但又不如垃圾收

集机制通用。然而,和计数指针以及垃圾收集相比,我的方

法有一个优点。当容器拥有它所指向的对象时,程序员就拥

有了对析构时机的直接控制权。我喜欢这种方案的原因之一

在于我经常使用对象来容纳那些必须被释放的资源。垃圾收

集器无法提供这种保证。如果我的对象所容纳的是至关重要

的系统资源,比如互斥锁、文件句柄、内存簇(chunk,注

1),垃圾收集能提供什么帮助呢?垃圾收集器只管理内

存,不顾及其他的资源。我的浏览器偶尔会“死掉”,失去

反应能力,而其主要原因是打开了太多文件。目前的主流因

特网浏览器软件都有“文件句柄泄露”的问题。显然,开发

者没有正确地管理他们的资源。当然,有的浏览器是用C++

写的,但是这里真正重要的问题是我们必须认识到,资源并

不仅仅只包括内存。这只是部分答案,据我所知还没有完美

的答案。

【注1】这里所说的memory chunk,是指一大块内存,在其

中可以放置若干对象,在回收时可将整个一个chunk一次性回

收,从而大大提高回收效率,并且减少内存碎片。这种大块

内存区需要手工管理,垃圾收集机制无法提供这种能力。

人物专访

Page 33: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

33

管 理

引 子

2002 年 12 月 4 日,微软高级开发管理峰会在上海隆重

开幕。来自国内软件企业和软件管理机构的 200多位总裁和

高级经理在三天时间里,与微软的资深经理和技术专家们共

享了微软企业管理和人才发展之道,共同探讨了中国软件产

业产品化和规模化过程中的各种问题,以推动中国软件产业

的健康快速发展。

“分享微软成功的秘密,共创中国软件业的辉煌”是这

次峰会的主题。此次峰会由微软中国有限公司和微软全球

技术中心主办,面向国内软件企业和软件管理机构的决策

者、高级软件开发管理人员、高级软件开发和测试人员。大

会集中介绍了微软公司在软件开发过程管理、软件测试管

理、项目管理、团队建设及企业文化等方面的理论精髓和实

践经验。

整个活动分成微软企业管理、微软开发流程管理和微软

高级技术三个系列同时举行。来自微软总部,微软全球技术

中心和微软中国有限公司的资深经理和专家们分别介绍了微

软企业管理之道、软件开发流程管理和微软高级构架、开发

和测试技术。

中国的软件开发人员已经逐渐认识到影响软件企业发展

和软件产品化的最大问题是管理。微软在近30年的软件产品

开发过程中积累了大量的经验,包括企业管理、企业文化、

业绩考核和数字化管理等。通过专题课程,微软资深经理介

绍了微软如何塑造企业文化,如何通过提供合理的职业生涯

来保证人才的稳定性。

这次管理峰会受到了国内软件企业的热烈欢迎,微软

表示以后将定期、有系统地举办此类活动,把微软的成功

经验介绍给国内同行,与中国软件产业共同培养新世纪软

件人才。

人才问题一直是国内软件业争论的热点,也是让众多公

司管理层困惑的难题。这次,微软人才管理面纱的揭开,让

我们看到了成功软件企业的许多价值理念和管理方法,所

以,根据侧重点的不同,本期的专题就分为了如下五个小

节,分别介绍微软的人才观、微软项目团队三驾马车的开发

模式、程序经理、绩效考核以及来自微软全球技术中心的高

级经理华宏伟对国内人才状况的分析和探讨。

选择合适的人,培养合适的人

——打造人才优势的两大法宝

比尔·盖茨曾说过:“在我的事业中,我不得不说我最

好的经营决策是必须挑选人才。拥有一个你完全信任的人,

一个可以委以重任的人,一个为你分担忧愁的人,一个具备

——2002微软高级开发管理峰会成果解析

策划/本刊编辑部 记者/孟迎霞 唐琦

Page 34: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

34

一系列略微不同的技能且其行为对你有所裨益的人,是十分

重要的。”

在接连三天的管理峰会上,无数次的听到了比尔·盖茨

的这句名言。一个听起来非常简单的话语后面,却包含了一

系列的企业文化和思想感悟,这里面,既有微软的人才理

念,微软的纳贤之道,更揭示了微软的育人攻略。

人才理念

微软有非常明确的人才观,微软强调,人才不是微软的

财产,合适的人才才是微软真正的最大的财产,而员工的素

质是对生产力唯一最重要的来源。因此,微软要求所有的员

工都要敏锐、聪明、有激情并富有进取心。

基于这种人才观念和文化氛围,综合分析,微软员工普

遍具备如下特点:

l 对产品拥有充分的热情和好奇心

l 真正的关心用户的需求

l 时刻思考如何改进产品

l 对公司有长期承诺

l 同时具备专长和广博的知识面

l 适应各种工作方式和条件

l 具备商业知识和经济头脑

l 关注竞争对手

l 懂得分析和取舍

l 诚实,正直,努力

纳贤之道

从微软招聘部门的统计数字来看,微软所招聘的新员

工,来源大致分为如下几种:

微软的聘用哲学是:聘用适应性和灵活性强的人。每个

聘用是为全公司而不只为某一个部门,招聘工作是每位员工

的优先任务之一。

据微软全球技术中心商业应用在线部总经理邢志新介

绍,在微软,严格挑选合适的人才,包括三个方面:看重的

是人品而非技能,看重的是聪明而非知识,看重的是潜力而

非经验。

为了招聘到合适的人才,微软制定了一套非常严谨而规

范的面试过程。面试流程有三个特点:

第一,无论对谁,这套面试过程都不存在例外,无论内

部人员换岗流动还是员工推荐人选,只有通过了正规的面试

过程才能聘用。

第二,面试过程非常漫长,需要经过好几个步骤的筛

选,首先是非正式的面试(如简历等)以相互了解,其次是

电话筛选面试,最后才安排正式的面试。正式的面试,也安

排最少有 5至 6个面试人员、一位主管人员,最后经过总经

理最终拍板才算应聘成功。

第三,只有很少的候选人能通过面试。微软人才理念中

有句话,聘用最聪明的前5%,这里的关键词是聪明,而不是

知识和经验。

经过如此密集和严厉的面试过程后,虽不保证所有合格

的人都被聘用,但至少保证了不合格的人很难被聘用。

微软全球技术中心亚洲商业应用部总经理华宏伟透露

说,微软是没有人才储备的。微软在招聘人数上的控制是非

常严格的。甚至最早以前,所有招聘计划都要通过比尔·盖

茨和史蒂夫·鲍尔默的认可。总之,微软希望尽可能选择最

聪明、最能干的人,再进行多方面的培育,以创造尽可能好

的环境,在工作上给他提供各种各样的便利和服务,使他提

高工作效率,也不愿意采用人海战术。

育才攻略

在微软的育才攻略中,有三个非常重要的术语——

Mentor、MSTE 和 Tech Talk。

Mentor意为师傅,在微软称为师傅制度。每位新员工进

入微软的时候,他都会被安排一位富有经验的师傅进行日常

业务的指导和帮助。师傅是新员工学习的榜样,他能帮助新

员工熟悉工作环境并尽快上手;让他们增强信心,消除恐

惧;而且对于新员工的行为和工作情况能得到真实而又积极

的反馈。对于老员工来说,为人师傅,可以培养自己领导和

影响他人的能力,也可以给工作增加新的挑战和活力,而且

还会感觉到别人的重视,感到一种成就感。

另外,还有一个很重要的名词MSTE。MSTE (Microsoft

Training & Education) 是微软为内部员工提供培训的一个

重要渠道。

MSTE有上百个课程,包括网上课程和现场教授。任何

微软人才专题

外国人 20%

猎头公司< 5%

公司网站> 50%

应届毕业生 25%

员工推荐 15%

微软新聘员工的来源

Page 35: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

35

管 理

员工都可以挑选任何课程,在工作时间内上这些课程。这些

课程大部分是免费的,有小部分需要员工所在的部门交费。

微软的王建硕介绍说,这些培训的教师,有些是微软专

职培训的员工,有些则是外聘的讲师,无论前者还是后者,

受训员工都不需要个人付费。

以下是一个课程的简介:

C# Programming

Course:

This course provides students with the knowledge and

skills needed to develop C# applications for the .NET

Platform. It focuses on C# program structure, language

syntax, and implementation details. C# was created to be

the programming language best suited for writing .NET

applications. It is a simple, object-oriented, and type-safe

language based on the C and C++ languages. This course

is based on the MSDN Training course Programming with

C#.

Audience:

Developers, testers, technical PMs, and others who

wish to learn how to write programs in C#. Solid Visual

Basic, C, or C++ programming skills are recommended.

Materials :

file:\\minerva\courses\CSharpProg

第三个术语,则是 Tech Talk。

微软 Windows Client Team 的 Alan分析说,

以他个人的经验,在微软最重要的学习是 l e a r n

from others, learn from the source code。每个大

的组里会有各方面的专家,他们通常知道得比培训

课程的老师更多,直接去问他们是最好的学习方

法,当然最好是问一些比较重要的问题,否则就是

浪费别人的宝贵时间了。

Learn from the code则更为重要,既学经验,

也可以学到很多教训。微软鼓励技术的交流。在一

个组里,定期就有自发的Tech Talk,任何人只要

在某一方面有心得,订个房间,发个邀请,就会有

很多人听。这样的讨论会无论在专业能力还是沟通

技巧上都确实培养了很多的年轻员工。

掌握软件命脉的三种人

——程序经理、开发经理和测试经理

微软团队思想的精髓在于三权分立:设定程序经理、开

发经理和测试经理三个角色相互牵制。每个人都有自己的角

色和观点,通过不断的沟通和 Review来保证最终产品的完

美性。从这点上来说,和人数多少并没有太多的关系,但减

少了沟通的成本。如果人太多,这种模型反而会导致过度沟

通,所以微软一定要把团队控制成一个小团队。

其实,微软也不是一开始就有测试人员角色设置的,最

初的测试甚至是交给 OEM 厂商来进行,从他们那里获得反

馈再对产品做修改。但是随着产品越来越复杂,这样做的代

价越来越高,产品的质量得不到保证。因此在1984年,微软

开始设立了专门的测试团队,一开始由特定的测试组为所有

的产品组进行测试,最后发展到每个开发团队有自己的测试

小组。微软团队模型不是一开始就有的,而是根据这么多年

的经验教训一点点改进而来。

微软的经验证实,如果要保证一个产品的质量,就必须

有一定比例的测试人员,否则这样的产品即使开发出来,也

很难保证其质量,得到用户的认同。测试人力的投入肯定会

获得回报。

三大团队之间有着密不可分的联系,程序经理负责产品

的规格书,确定功能,确定和控制项目进度;开发经理负责

产品功能的实现;测试经理负责产品的测试,有决定产品是

否可以发行的签字权利。

三大团队的比例设置

一般情况下,这三种人员的基本比例情况是:1个程序

经理对应 3-5个开发人员,而 1个开发人员基本上对应 1个

微软的合同工 

合同工也可以说是临时工。他们的真正雇主并不是微软,而是专门提供临时雇员的公

司。比如,Volt Computer Services 公司,专门为微软提供技术性人员(以程序员和

测试员为主)的合同工,而 Kelly Services 公司则专门为微软提供非技术性人员(如

秘书、翻译、行政助理等)。

这些公司正式地雇用这些合同工,并负责他们的工资和福利。他们在微软工作,有自

己的办公室(通常和别人共用),自己的电话和email信箱。每一个合同规定了每人的

工作时间、期限、每人所得的薪金(当然微软实际付给雇员公司的钱要比每人所得薪

金高一截)。薪金是按小时算的,通常程序员合同工每小时可得 30-70 美元。雇用合

同工基本上只是钱的问题。雇用全职员工则牵涉到福利和人力预算,所以很多部门喜

欢雇用合同工来解决临时的人手短缺问题。

合同工给微软在人力资源的利用上带来了很多灵活。不少合同工在期满之前,由于表

现出色,会被转换成全职工。2000年以前,至少1/4在美国微软总部工作的人是合同

工。后来,由于微软打输了一场有关合同工福利的官司,相当一部分合同工转换成了

全职工或被辞退。现在剩下的合同工被硬性规定不能在同一部门工作超过 9 个月,9

个月之后,他们必须等 3个月才能接下一个合同。无论如何,微软公司成立 27年来,

数以万计的合同工在几乎所有部门里都有作用,他们的贡献是不能忽视的。

Page 36: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

36

测试人员。

但在应用于小团队时,微软的这三架马车,也会出现一

人分担多个角色的情况。但是这样的开发方式,对于人员的

素质要求颇高,对基本技能包括分析、设计、编码、测试,

每个成员应当都会一些,可以说是全才,而在具体分工时,

每个人侧重于不同的方面。

据微软工程师黄雪斌介绍,他曾经参与的一个开发团队

的人员分布情况是:团队共11人,程序经理1人,用户界面

1 人,开发人员 4 人,测试人员 4 人,整个开发历时约 3 个

月,Bug 数据库中一共记录了 1000 多条 Bug。最终开发出来

的产品质量确实很好。就他的经历而言,真正的纯编码工作

在整个开发中只占很小的比重,甚至占不到一半的时间,而

更多的时间用于产品的详细设计、解决 Bug。

对于国内很多软件企业一个项目组只有 3 个人的小团

队,黄雪斌认为,很多工作可以重叠,例如,一个人做程序

经理、测试, 两个人做开发(程序经理最好不要兼任测试人

员),但是测试人员和开发人员一定要分开。

三驾马车的协调

产品规格书是由程序经理写的,但并不意味着程序经理

一个人把自己关在一间屋子里写完就算数。在微软开发周期

模型(PCM)中规定,在 M0阶段,程序经理的主要职责是

完成产品规格书,确定产品功能优先级,确定项目日程安排

等等,而开发组和测试组的职责是进行规格书检验,检验规

格书写得是否全面,是否合理,是否可以根据规格书写出具

体的实现规格书和测试规划书。三方之间经常就产品规格书

开会进行交流、探讨。

同样,项目日程表的确定也要开发人员和测试人员共

同参与,而不单单是程序经理一个人说了算。产品规格书

写好之后,在项目的开发过程中会保持更新,开发人员和

测试人员也会在 Bug数据库中给产品规格书创建 Bug。产

品规格书的更新必须要让开发团队中的每一个人知道,任

何人都要严格按照产品规格书来进行开发、测试和编写用

户文档。

程序经理设计的功能在技术上不能实现或者不可取的情

况是很多的,这个时候开发人员有责任指出来。在微软有功

能规格 spec review meeting(通常有多轮)(同一个组的开

发人员、开发经理、程序经理、测试人员、usability、user

assistant 一起参加),很多问题是在这种会议上协商解决的。

也有开发人员和程序经理两个人关起门来讨论的(应该在

spec review meeting 以前)。但实践中,后一种方法效率较

高。特别重要的 spec review meeting也会有高级的经理参

加,例如开发经理和系统架构师。

如果是功能错误或者冲突而不是技术问题,往往会有更

多更长时间的争论。这个时候有的问题需要在更高一级的会

议上(微软称谓作战会议)上解决的。参加作战会议的会有

更高级的经理。有时候会先做 prototype 的 usability test。

不写程序的程序经理

——专访资深程序经理 Jeff Xiong

项目经理与程序经理有什么异同

关于程序经理(Programmer Manager)与项目经理的定

位,有人比喻,程序经理像球队里的明星,而项目经理就像

教练。Jeff Xiong认为,自己基本上同意这个说法,但两者

还是有差异点。

微软公司内部,没有项目经理这一职位,传统意义上的

项目经理职位已经被程序经理所覆盖,这和国内大多企业只

设项目经理,没有程序经理的情况不太一样。虽然微软程序

经理的作用有点类似于国内企业的项目经理,但实际上两者

的差别还是很大的。

一般情况下,很难给微软的程序经理一个准确的定义,

因为程序经理这个词,是微软首创的,在其他公司几乎找不

到这个词和职位,当然,现在的趋势是陆续已经有些公司开

始设置程序经理这个岗位了,但是实际中运用的情况与微软

实际的程序经理还是不太一样。微软的程序经理,从职位上

分,有初级程序经理、高级程序经理等;从工作职责上分,

有负责项目的发行、项目日常管理的程序经理,还有负责产

品功能设计的程序经理两大类。

从微软的角度来看,一个项目成不成功,与程序经理

关系非常密切。一方面,可以将程序经理比作球队的球

星,是因为程序经理在项目的成功与否中是个非常关键的

微软人才专题

微软资深程序经理 Jeff Xiong

Page 37: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

37

管 理

角色,因为他冲锋陷阵,确实有非常重要的作用。如果将

国内软件企业中的项目经理比作教练,那微软的程序经理

则不止于此。一方面,他们要做很多教练类的工作,例如

竞选项目组,安排人员进行技术指导等等,教练所做的细

节性工作,他们一样也不能少。但实质上,微软的程序经

理不但包含了传统意义上的项目经理,而且比他们的工作

更广泛、职责更多。

Jeff Xiong认为,更准确的比喻应该是:微软的程序经理

更象一部电影作品中的导演。电影作品要做成什么样子,怎

么规划,一个镜头要怎么运作,非常类似于程序经理所做的

工作。一部电影的成功,明星演员功能很大,但还与许多工

作人员的努力分不开,比如摄影师、化妆师、美工等等。这

些工作人员对最后电影产品的形成,也起着非常重要的作用。

从软件产品来讲,程序员是演员,而程序经理更象导

演,他要把所有的人员组织起来,不管是做软件设计,还是

做用户手册,或者是做用户界面设计以及法律咨询,他将一

个项目相关的所有工作人员组织起来,让他们形成组,再围

绕产品协调运转起来。

产品组的管理是最基础的管理。程序经理这种运作模

式,也是微软一步步摸索出来的,微软成立于1975年,真正

开始设立程序经理这种模式,则是到 1986年,经过8到9年

不断的开发磨合过程中总结出来,发现程序经理模式比较适

合软件开发,特别是软件产品的开发。

另外,软件企业中的项目经理一般是一个人,可微软的

程序经理,往往是一组人,是通过一个体系来管理一个项

目。第二个形式上更明显的区别是,国内项目经理是领导

者,具备权威性,而微软同一个项目组中的程序经理和程序

员却不是上下级的管理关系,他们分属不同的行政管理,程

序经理必须依赖自身的素质和感召力来获得权威。第三个

区别,项目经理主要负责项目组人员的管理,而程序经理主

要管理整个项目,而非人。从工作上讲,项目经理主要撰写

项目计划,而程序经理则主要负责设计功能说明书。

程序经理做什么

从工作的职责和要求上来说,微软的程序经理主要目标

是负责产品的设计说明书,保持进度,拿出产品。程序经理

组中的程序经理共分三种,但无论如何,对于一个产品或项

目,总有一个程序经理对进度和功能负责。

第一种是负责设计功能规格说明书的程序经理 (Feature

Design PM):负责具体的产品设计,写功能说明书。

所谓功能说明书,只是界定软件而不是软件实现,并不

需要太多的计算机相关领域的知识,需要的只是理解真正的

需求,并且非常清晰的描述出来,在对应到软件中的特定的

功能中。有些写得较好的功能设计书甚至定义出所有的 UI

界面,点击某个按钮会链接到哪一个界面,每个菜单包含哪

些内容等等,看了之后对整个产品做出来会是什么样子心中

就很有数了。在此之前,还会写项目计划书。针对某一个产

品,用户是什么?要达到什么目标?这个产品的盈利点在哪

里?所有这些,程序经理都要考虑。当然,程序经理之中还

有很多级别不同、分工不同的人,但整体而言,这些都是程

序经理必须负责的事。在整个 PM 队伍中,80%的 PM属于

此类。

第二类程序经理主要做日常管理和协调工作,包括发行

程序经理(Release PM)和协助程序经理(Supporting PM)。

发行程序经理负责整个项目的流程和进度管理、制定进

度表等,协调整个团队的工作。大的PM 队伍中有一人专门

做这个。这是整个项目的领头人。大型的项目的成功与否,

常常靠得力的发行经理的领导。而协助程序经理负责其它产

品发行需要照顾到的事情,如客户交流、市场开发人员交

流、负责 beta program (初版试行)等等。大的 PM 队伍中

少不了这样的人。

在整个微软公司里,有近 2 0 % 的程序经理属于第二

类。这一类的程序经理不用写设计规范书。一个项目组里

有很多分工不同的人,要让整体合理运转就需要这些程序

经理来协调。比如程序经理每周要召开项目进度会,许多

产品组之间的交流和协同,甚至包括媒体采访,都需要程

序经理出面。微软打官司,很多情况下程序经理都要出

庭,因为很多材料准备需要他来做。再比如测试经理生病

了,程序经理就得顶上去帮做测试。所以,程序经理,是

真正要到第一线去冲锋陷阵的,他在这方面职责远远大于

国内企业中的项目经理。

比如盖一座房子,在施工的过程中,要有施工现场工程

师,还要有图纸设计工程师,还要有每天负责工程进度的设

计师。图纸设计工程师,类同于功能设计的程序经理,日常

管理和现场管理协调、进度控制的工程师则类同于日常管理

的程序经理。

微软的产品品种很多,每个产品组如何组织程序经理队

伍,要根据产品的具体情况来做决定,并没有统一模式,有

些产品组分的很清楚,做日常管理的程序经理不负责产品设

计,做产品设计的程序经理不负责日常管理,有的产品组则

是混合在一起,程序经理既做设计规划也做日常管理。

“总之,程序经理除了不写程序,其他什么都要做。”Jeff

Xiong笑着介绍说。

程序经理需要具备什么能力

微软在招聘程序经理时,一般情况下并没有学位的要

Page 38: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

38

求。但由于程序经理责任重大,所以对这个职位的要求会

比较高。除了相当的计算机专业能力外,微软更重视以下

要求:

一、足够高的 IQ。

高 IQ 就是说人要聪明、敏锐,微软比较重视这一点。

从能力上说,程序经理要会编写代码,要有架构设计的能

力,有用户界面设计的能力,要能进行 API和 Schema界面

设计能力,还要有洞悉用户的技能,要有写作或口头上、正

式或非正式的沟通能力和沟通技巧,要有一定的表达描述能

力,具备一定的财经知识,基本的商务知识,合同、专利和

版权法律知识以及市场调研能力等,此外,还要有很高的承

受能力。

对于沟通方式,根据 Jeff Xiong 的经验,一般分为 E-

mail、组织会议、项目组内部网站以及当面沟通四种。E-mail

方式发送的,一般是项目当前进度报告、会议纪要、错误报

告以及常见的Q&A,发送的原则是尽量让更多人了解。而会

议沟通要把握一个原则:让真正关系密切的人参加,否则会

降低开会的效率。对一些个人性的问题沟通,可以采用面对

面的单独沟通方式,这样效果会更明显。

二、足够的 EQ 和交流能力。

从程序经理所负责的职责来说,要做得更好还必须有足

够高的EQ(情商)。要有工作的激情,要具备一定的领导才

能,有管理时间的技能,要和很多人交流,对内要协调项目

组人员,对外也要同各种人群打交道,所以,程序经理必须

具备足够好的交流能力。

三、要能处理、把握好各种关系,要有决策能力。

由于微软公司的管理属于三驾马车的开发模式:程序经

理、程序员和设计人员,这三组人在行政上相互独立,负责

人分别为程序经理的大组长,开发人员的大组长和设计人员

的大组长,分别向总经理汇报。项目组人员在行政上并不归

程序经理管理,工资、奖励也不由程序经理来确定,这一点

和国内的项目经理非常不同,但也就存在一个问题,如果项

目组人员不听从程序经理,那怎么办?所以程序经理不能期

望通过至上而下的领导关系来强迫别人跟着走,要有处理和

把握好各种关系的能力。实际中的情况是,程序经理一般经

过自己好的设计、实际工作能力和以往业绩来赢得别人的尊

重,使别人很信服地听从他的安排。

以上三点是对程序经理的通用要求,除了最基本的是知

识外,很关键的要有足够高的 IQ,更高的 EQ,擅长和其他

人打交道,这是非常重要的。

由于分工的不同,对于从事日常管理和产品功能设计的

两类程序经理,分别又有不同的要求:做日常管理的程序经

理更多负责内部的管理,所以更看重沟通、协调能力,另一

个要求是要比较能够从事细节、繁琐事务的管理。而从事功

能设计的程序经理,则要求有非常强的技术背景,比如说一

般都做过很长时间的技术开发、写过一些高层程序,第二是

要有非常强的创造能力,第三要对用户的研究比较透彻,对

市场、对竞争对手的产品功能有深入的了解,能够根据市场

反馈确定产品所需要的功能。相对而言,负责功能设计的程

序经理对微软的产品起着非常关键的作用,对他们的要求也

就更高一些。

对于个人的发展来说,微软公司更强调程序经理边学

边干。微软的程序经理会根据项目的发展做出重组或转

换,要想完全做好程序经理,适应并胜任各个产品,无论是

主动还是被动,都需要程序经理做出个人努力。比如有些情

况下属于内部重组,从一个完成的项目组到新建的项目

组,总的来讲,到一个新的部门,除了程序经理本身要有很

强的学习能力之外,微软内部会有很多的讲座,你可以经常

抽空去学。更为重要的是,平时就要保持对新事物的兴趣。

此外,微软比较强调边学边干,边干边学,发挥个人的潜力

和主观能动性。

程序经理从哪里来

微软公司共有五万多名员工,包括开发人员一万多,测

试人员一万多名,而程序经理大概占到十分之一,也就是五

千人左右。这么多的程序经理从何而来?

一、外面招聘的人员

有些刚招聘的人员,甚至包括大学应届毕业生在内,如

果他的素质和性格中有一些相符的条件,就可以直接安排做

程序经理。比如说,沟通能力非常强,非常有群体观念,虽

然他缺乏产品管理的具体经验,但是公司仍然会将他作为一

名初级程序经理来培养。对于有一定工作阅历的人,当然会

非常看重他的工作经验,而且这一点会比较重视。看看这名

员工过去的开发经验,比如之前做过某一领域的很好的产

微软人才专题

情商最关键的三个要素

沟通

领导 关系

Page 39: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

39

管 理

品、担任过高级程序员、总体设计师之类职位,而且还具备

一定的项目管理能力和经验,这样的人员是非常适合做程序

经理的。

二、微软内部的人员转岗

微软内部的招聘主要有两点:第一,招聘是公开的。公

司想招什么人,一般会在公司内部的网站上公布,任何一个

人都可以到网上去看哪个组招什么人?他可以去申请。第

二,在微软内部职务申请的时候,并不要求做开发的人永远

做开发,他也可以去做其他项目,测试人员也可以去申请程

序经理。机会面前大家是平等的,但能不能通过面试这是另

外一回事了。

微软内部的招聘过程和外面的招聘一样严格。也许你也

要通过一整天、七到八人或者七到十个人、每人一个小时的

面试。这并不因为你已经是微软的员工,就可以很自由地换

到自己想去的产品组。从实际情况看,微软内部换岗的员

工,失败率大约也占到60%-70%。程序经理的转岗中,最多

的是从以前的测试组组长转来的。

测试组长可以转岗胜任的主要原因是他参加了产品开

发的全过程。最初程序经理把设计说明书写出来后,测试

组长就需要马上根据设计说明书写出测试说明书和测试规

划书。最后产品完成,测试也完成的时候,决定这个版本

程序能不能发布,是需要测试组长签字通过的,这时,即

便程序经理签字通过也没有用,所以测试组长已经具备了

很多程序经理所具备的技术把握和产品负责能力和素质。

从整个产品开始开发到产品结束,测试组长都在和程序经

理打交道。所以他们也需要具备一定的沟通交流能力,而

且和程序经理相同的是,测试组长也不写程序,他做的很

多工作和程序经理非常相近。微软公司中,测试组长有很

多人,从发展来看,他们会成为非常优秀的专司发行和日

常管理工作的程序经理。

还有很多开发人员也可以转为程序经理,由于其具有较

强的技术能力,很多人会成为主要负责产品功能设计的程序

经理。其次,有些是由产品经理转成程序经理,也有从事市

场工作的人员转为程序经理。

这样一看,似乎每个人都可以转做程序经理,Jeff Xiong

评论说,实际上微软公司内部的五千多名程序经理,真正合

格的、优秀的、在产品里起关键作用的程序经理还是占很少

的比率。“当然,这种说法也可能有些不公平,因为能进微

软工作的,个个都比较优秀。但可能一个产品有七个功能,

最为关键的功能也就那么两三个,所以负责那两三个功能的

程序经理一般就显得更为优秀,他通过做优秀的东西得到锻

炼,经验积累,会变得更加优秀。”所以,很多优秀的程序

经理也会自己选择一些更富挑战性的项目产品来锻炼自己并

积累经验。

导致程序经理失败的最关键因素

微软有一千多个产品组,每年有上千个产品要开发,真

正做成功的能拿到市场上去推广的产品几乎不到一半。

微软的很多产品失败,原因会是各种各样的,例如法律

因素,例如市场还没有培育成熟等等。但从程序经理的角度

来讲,有些是程序经理可以控制的,但有些则是不能控制

的,例如法律方面的因素等。

从程序经理可控制的角度来讲,Jeff Xiong 认为最影响

的因素有三个:

首先是沟通交流能

力。一个产品能不能运作

起来,就靠程序经理的沟

通能力。程序经理可以有

很好的想法,但你得把这

个想法和市场部经理沟

通,和你的上司沟通,和总经理沟通,甚至和媒体沟通,让

媒体对你的产品有一个正确的理解以便市场支持这个产品。

有时候,有些开发人员不喜欢和程序经理沟通,觉得程

序经理不称职,他设计的东西根本不现实,总是改来改去

的,或者是由于程序经理刚来,没有经验,或者是程序经理

自己性格方面有不太适合的原因等等,总之,沟通得非常

差,弄得开发人员情绪也很差,程序写得乱七八糟,最后进

度完成不了,项目失控导致整个项目失败。所以沟通能力是

非常重要的。

第二,容易影响产品失败的是程序经理对产品前景预测

的洞察力不够。如果程序经理的预测不对或者不够准确,或

者不符合产品发展趋势,那他肯定就会失败。

例如,早些时候微软想做一些基于网络的日常应用,这

个规划刚提出来的时候大家非常兴奋,觉得三五年内就可以

把网络服务送到每家每户,后来实践证明,当时是过于乐观

了。原因分析,最主要是因为网络基础还没有打好,网络本

身的基本功能还没有完善,就想着在上面做一些应用,而且

前景预测 成本估算

沟通能力

Product Unit Manager

Group PM Dev Manager Test Manager

PMLead Devlead Testlead

PM Dev Tester

Product Unit Report Structure

各职位的行政所属关系

Page 40: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

40

还跟日常生活紧密联系。用户非常质疑,个人信息、银行帐

户、身份证明全存在网络上,这怎么能让人放心呢?做为产

品思想来说,确实很好,可失败原因却在于太超前市场。

第三,容易失败的原因是程序经理对开发产品成本的估

算出现失误。

国内企业在产品开发成本方面也经常会出现这种失误。

例如进度出现问题,本来计划一年出品,可能三年都没有做

出来,原计划需要 10个人的工作量,而结果 50个人还做不

出来。这都是成本估算的问题。成本估算确实很复杂,很不

容易。微软对程序经理这方面要求的非常高,虽然微软慢慢

摸索出一套具体程序和过程管理规范,但程序经理有时也会

出现差错。

对于微软产品来讲,如果产品经理经验不足,就会出现

估算失误。例如虽然他在微软工作了很多年,但由于之前一

直做 WINDOWS系统开发,这时忽然换到另一个产品线上,

因为两个完全不同的产品其成本估算是不一样的,所以这就

存在着经验不足的问题。

微软项目估算主要分两种,一种是按功能块来驱动的

产品开发,不管一年两年还是三年,要保证产品质量,多长

时间也要做出来,当然这种成本估算就需要大的规划和适

时地调整。第二种是按时间驱动的,例如在最初做 IE网络

浏览器的时候,有一段时间需要和网景争抢市场,那时候,

如果网景要做一个新版本出来,那项目组马上就需要改变

原定计划,所以一般三到六个月就会做调整。那时产品的进

度就完全按时间来控制进度,目标是抢在竞争对手前面做

出新产品。不管什么类型的产品,微软的项目基本都是按这

两种方式做的成本估算,技术的进度、控制也是按这两种方

式进行。

要避免成本估算出现问题,一方面对程序经理有很高的

要求,在产品的规划阶段就尽可能地做正确的估算,另一方

面,除了前期的估算外,更多地还是在产品开发过程中的管

理、调整。

例如,像WINDOWS这样的大系统,不可能在三五个月

内完成,一般情况下都是两年以上,而现在技术发展非常

快,两年之后,技术形势可能又会有很大的变化,所以程序

经理要跟踪当前的形势并进行调整,有些功能需要增加进

去,有些功能又要舍弃,然后就要重新评估计算项目成本,

看看产品需要多长时间,需要多少功能。比如说要有10个功

能,对这10个功能程序经理必须要追根究底,不能只说这10

个功能都必须有之类的话,而最少要分三个等级:

第一级,必须有的功能,即使产品推迟,这些功能也要

具备。

第二级,最好具有的功能,有可能这是用户的反馈,希

望有这些功能,但这些功能还不是最重要,但是如果最后来

不及了,就可以把这些功能划到下一个升级版本里。

第三级,可做可不做的功能,有这项功能当然会更好,

但它对用户的要求、市场的销售影响不会太大,一般来说,

这样的功能也许根本就不做了。所以程序经理要对这些功能

模块进行一一排列。

这三个方面的原因很难有量化的数值来考察和培养,

Jeff Xiong说,一方面靠个人能力,另一方面也和运气、机

遇有关。

程序经理中国化的障碍

微软是以一个个小的团队来运作的,每个团队基本上在

30到 40人之间,针对中国的小公司,基于公司规模以及特

殊国情,确实很多方法都不能用。但是,要注意的是,小公

司运作的共性使得微软的一些理念、思想可以通用。

Jeff Xiong 分析,对于国内的软件企业来说,如果要

培养程序经理,最重要也是最大的障碍在于企业还没有形

成自己的专有文化。从这个角度说,微软公司开发研究的

企业文化的界定是很重要的,国内企业一直在培养项目经

理,这也是一种企业文化。但企业文化,是一种意识,要

培养起来,是最困难也是最关键的。要想真正培养出像微

软程序经理这样的人才,既需要长期的过程,更需要实践

的磨合。

另一个最重要的工作是要加强国内软件企业自身的管

理。由于各个企业、各个产品都不一样,微软的管理方式或

许并不适合于所有的国内企业,但是这些软件企业可以从微

软的实践中了解并借鉴到这些管理思想和管理方式。很多的

中小企业更多的着眼点在于赶项目、赶进度、占市场,其他

的比如文化氛围、过程管理等等方面根本忽略不计或者视而

不见,从长期来说,这些基础性的环境是一个很大的缺失。

企业文化和规范科学的工具管理都很重要,这是真正的

土壤,这个土壤与产品的根基是同在的,国内的企业应更早

微软人才专题

微软丰富的管理课程让很多人深思

Page 41: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

41

管 理

地了解到软件开发绝不是纯技术的研发,它有更多的管理成

分在里面,有很多艺术文化的成分在里面,如果能这样考

虑,那对企业的发展会有一些帮助。

悲喜交织在八月

——微软绩效考核揭秘

微软每年的考评是从该年的8月份开始,设定下一年度

的目标,然后是一个全年的跟踪、反馈过程。每年的2、3月

份,微软内部会进行关于员工的未来,以及员工的发展状态

的探讨。然后,在第二年的 7、8 月份,对员工和经理这一

年的工作表现进行正式的绩效考核。

优秀员工的升迁,是随时进行的,并不只限定在8月份

的考评阶段。

一、绩效管理的起点:设定 SMART 目标。

微软公司在每个财年末(每年8月份)的时候,都会做

一个绩效管理,除考核上一年的业绩外,还要确定下一年度

的目标。员工的目标,是由员工和部门经理一起制订的。这

个目标,被微软内部称为SMART目标,是微软绩效考核中

的一个专用名词,它的具体含义和来源是:

S = Specific 明确的

M=Measurable 可衡量的

A = Attainable/Achievable 可达到的

R= Results based/Realistic 基于结果的 / 现实的

T = Time bound 有时间限定的

微软希望给员工设置可衡量的、清晰的目标,也希望员

工自己的职业目标同公司的目标结合在一起的。这一点是通

过绩效表来进行沟通。

对于员工来说,他会有自己的兴趣爱好以及将来想发展

的方向,例如,测试人员现在虽然工作尽心尽力,但他希望

以后能从事程序经理的工作,而技术工程师则希望从事项目

管理之类的工作,这时,员工都可以将自己的职业发展目标

写进绩效表里,填写你所希望从事的工作是哪一些或哪一个

方向。这样,部门经理会在以后的工作中,帮助员工提供这

样的工作机会或者安排相关的培训。

设定目标之后,员工在一年的工作中,可能要从事很多

的事情,但哪一件工作最重要,需要最先做,哪些可以放到

后面进行,这些,员工可以与部门经理进行商量,在执行的

优先级方面取得一致。

在软件企业,员工的工作并不是那么容易明确衡量的,

需要部门经理慎重考虑。比如做客户服务部门的,你要满足

多少的客户满意度,而做技术开发的,这一点就需要部门经

理和开发人员一起分析确定。

目标确定以后,还需要部门经理进行目标的跟踪和反馈

计划。比如说,同时进公司的两个人,水平也许并不一样,

这样就需要设定不同的目标。但在限定的过程中,也许这两

个人的工作努力程度不一样,结果也会不一样,但这样的反

馈和跟踪,并不是到年底了才做,而是中间过程中员工和部

门经理进行周期性的、一对一的面谈,这样才会确定目标情

况并进行支持。这样的沟通和跟踪,需要在年度中间经常性

的进行。

二、微软绩效评分等级

微软对员工的绩效评分共分五个等级:

5.0分 超常的绩效,很少有人达到

4.5 分 一贯地超出所有该职位的要求与期望

4.0分 一贯的超出大部分该职位的要求与期望

3.5 分 超出部分职位的要求与期望

3.0分 达到职位的要求与期望,达到大部分或所有

的目标,某些技能需要进一步的提高 

2.5 分 低于该职位的要求与期望

微软上海人力资源部经理陆华女士笑着介绍道,员工真

要能达到 5.0分,那肯定要被比尔·盖茨接见了。一般能得

到 4.5 的评分,也定是非常优秀的员工,4.0分已一贯地超

出该职位大部分的要求或希望,而 3.0分达到基本的要求或

目标,但某些技能需要进一步的提高。

实际打分的过程中,很多员工其实都是在 3.0分和 3.5

分范围之间,基本达到职位的要求,但有些技能还需要提

高,或者有些部分还没有达到,这时,部门经理会想,员工

工作得很努力,如果给了3.5分,会不会没有被激励这种感

觉,所以,在微软人力资源管理中,规定了 3.0和 3.5分两

者相加的人数所

占部门人数的上

限比率和下限比

率。这样既能保

证激励努力工作

的员工,又能督

促进步较慢的员

工快马加鞭。

在微软公司

内部,到了绩效

评估的时候,会

有一套系统,系

统会给出一个数

员工驱动Employeedrives

经理协助Managerassists

微软支持Microsoftsupports

3 向合作Three-way Partnership

微软强调三方力量的汇合以取得个人和公司发展的最大值

Page 42: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

42

据统计的报告,公司打 3.0分的是多少比例,打 3.5分的是

多少比例,这都是有一定的比例限度,虽然这样的打分有些

残酷,因为在打分过程中,部门内一定有人是打了3.0分的,

所以打分的过程也是一个比较的过程,这样,部门内的员

工,肯定会有这样一个从好到差的排列顺序。虽然有些评分

较低的员工,年度表现可能没有那么差,但打分却只能得到

3.0分或者 2.5。

如果某位员工不幸得了2.5分,那可能临离开公司不远

矣,除非他在近期内有很大的回升,能很快主动意识到自己

的缺点或问题,那样还有挽留的机会。但这已经是一个危险

的边缘了,是需要接受挑战了。

三、员工绩效评分的实施

绩效评分考核的操作,分为三个部分:

(1) 每位员工填写自己的绩效表格。

主要考察点有两个,一是过去一年的工作回顾,另一个

是根据自己对微软价值观的实施程度,填完这个表格后,员

工都要给自己打分,如哪些满意,哪些不满意,哪些欠缺。

员工自己的打分,仅作为考评打分的重要参考,而不是考评

打分的最终值。但员工通过这个过程,可以衡量自己是不是

符合公司的要求、部门的要求,自己的工作技能是否满足职

位的要求等。

微软的价值观主要指:正直与诚实,对客户及合作伙伴

和技术充满激情,坦率的、尊敬的以及致力于使别人变得伟

大,接受更大的挑战并且尽心完成,自我批评同时致力于个

人的卓越,对影响客户、员工、合作伙伴以及股东的结果富

有负责感。绩效考核表基本模式请参见附表。

(2) 部门经理会根据员工整年的表现,对员工打出另外

一个分值。

一般来说,经理和员工打分相差最多不应该超过半

分,如果两个分值相差较多,人力资源部会认识到经理同

员工之间没有足够的沟通,或者沟通有误区。这样双方都

是有责任的,一方面,员工对自己的职业是负责的,他需

要自我激励,出现误差应该主动去找经理沟通,而作为经

理,如果缺乏对部下必要的沟通,这样的经理是需要接受

进一步培训的。

(3) 绩效确认并存档

当所有分数评好以后,数据会进入一个叫微软协调的系

统,分值和人员比率会有一个百分比的控制,也许人力资源

会根据公司总体进行微调,并最终确定一个结果。

评分结果反馈回来之后,部门经理会将完整的绩效表格

发送给员工确认,如果员工无异议,部门经理会将表格上报

到经理,经理再上报总经理,最后汇总进入员工档案数据系

统。微软每年都会将员工的绩效结果汇总到系统。

在微软内部,这个绩效考核记录是比较重要的。例如,

如果某位员工调到其它部门,或者中国的员工调到美国公

司,那对方的部门经理只需要从系统中调出这位员工以往的

历史绩效考核表,他就可以有计划地安排他的工作了。

四、对员工排序

正如前面所说,根据打分情况和绩效评比,部门内员工

之间也会有一个排序。但公司里面,这个排序是不公开的,

只有管理层、人才资源部和总经理知道,当然部门经理肯定

是知道这个排序结果的,因为他了解整个排序过程。整个机

构的排序过程,也仅有总经理和人力资源部知道。

但对于一个部门来说,员工排序是采取自愿方式的,员

工排序的操作步骤包括如下:

(1) 设置标准。

如果要做员工排序,那管理部门首先需要开会,确定排

序的标准,通常需要考虑创新精神、技术含量等等。针对个

人,则会考虑员工是不是乐于助人、合作精神如何,个人能

力等方面,因为排序往往是一个挑选的过程,只有确定排序

的标准之后,部门才可以对员工进行比较。

合同工也可以说是临时工。他们的真正雇主并不是微

软,而是专门提供临时雇员的公司。 比如,Volt Computer

Services 公司,专门为微软提供技术性人员(以程序员和测

试员为主)的合同工,而 Kelly Services 公司则专门为微软

提供非技术性人员(如秘书、翻译、行政助理等)。

这些公司正式地雇用这些合同工,并负责他们的工资

和福利。他们在微软工作,有自己的办公室(通常和别人

共用),自己的电话和 email信箱。每一个合同规定了每人

的工作时间、期限、每人所得的薪金(当然微软实际付给

雇员公司的钱要比每人所得薪金高一截)。薪金是按小时算

的,通常程序员合同工每小时可得 30-70美元。雇用合同

工基本上只是钱的问题。雇用全职员工则牵涉到福利和人

力预算,所以很多部门喜欢雇用合同工来解决临时的人手

短缺问题,

合同工给微软在人力资源的利用上带来了很多灵活。不

少合同工在期满之前,由于表现出色,会被转换成全职工。

2000 年以前,至少 1/4 在美国微软总部工作的人是合同工。

后来,由于微软打输了一场有关合同工福利的官司,相当一

部分合同工转换成了全职工或被辞退。现在剩下的合同工被

硬性规定不能在同一部门工作超过 9个月,9个月之后,他

们必须等3个月才能接下一个合同。无论如何,微软公司成

微软人才专题

Page 43: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

43

管 理

PERFORMANCE REVIEW

STANDARD REVIEW FORM

Please complete all four parts of this review form:

1. Performance Review and Planning

2. Competency and Career Development

3. General Comments

4. Overall Rating and Signatures

A minimum of two one-on-one feedback sessions during the next

review period is recommended.

Name ReviewerEmail Name Dept. NameTitle DateEmployee ID#

PART 1. PERFORMANCE REVIEW AND PLANNING

A. Evaluate Performance Against ObjectiveslList each performance objective in priority order

lGive constructive suggestions for how performance could be improved

EMPLOYEE'S EVALUATION AND RATING:

REVIEWER'S EVALUATION AND RATING:

B. Identify Performance Plan for Next Review PeriodlList 5-7 specific, measurable performance objectives in priority order

for the next review period

lIdentify keys to success for achieving each objective, for example:

stand and value diversity in your organization

EMPLOYEE'S PLAN:

REVIEWER'S COMMENTS:

PART 2.COMPETENCY AND CAREER

DEVELOPMENTAt Microsoft, each employee is responsible for owning and driving his/her own

development. The employee's manager is responsible for providing appropriate

mentoring and guidance. This section of the performance review process provides

a framework for a useful employee-manager discussion. Ratings are not used in

this part of the review.

A. Identify and Discuss Strengths and WeaknesseslIn this section, the employee should briefly evaluate his/her

articulating strengths and weaknesses.

EMPLOYEE'S COMMENTS:

REVIEWER'S COMMENTS:

B. Identify Development Plan for Next Review PeriodlIdentify 1-2 development objectives for the next review period-

lTraining or personal development needs

EMPLOYEE'S PLAN:

REVIEWER'S COMMENTS:

C. Discuss Career Interests and GoalslThis section is for discussion only. Written comments are not required.

PART 3. GENERAL COMMENTS

A. Employee Comments:lFeel free to comment on work assignment, the review process, or

the company as a whole.

B. Reviewer Comments:lNote any additional comments regarding employee's accomplishments

PART 4. OVERALL RATING AND SIGNATURES5.0 4.5 4.0 3.5 3.0 2.5

Employee Overall Rating (employee's opinion of the overall rating):

Reviewer Overall Rating

Employee

DateYour signature does not necessarily mean you agree, but affirms this review has

been discussed in detail between you and your reviewer.

Reviewer

Date

RESOURCES AND HELPTo access the following performance review instructions and other review-related

documents, please visit the Review web site:

附:微软绩效考核表基本模式

Page 44: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

44

立27年来,数以万计的合同工在几乎所有部门里都有作用,

他们的贡献是不能被忽视的。

对于程序经理来讲,考核标准可以简单地分为三个方

面:第一,该年度所做的项目或产品,是不是按时完成,有

哪些产品推到市场去了。第二,对于功能设计经理而言,有

没有大的、关键的功能块、是不是负责最核心模块的程序经

理,还有程序经理的沟通能力、协作人力、是否帮助他人,

是否合作较好等等。对于开发人员来说,最粗略考核的也有

两点,是否实施了最关键的模块,是否按时完成了自己的工

作,此外还帮别人实现了哪些模块?编写的程序出错率有多

少?对于测试经理,标准制定就更明确了:程序错误中有哪

些是优先级最高的?这个程序错误是否对该产品影响最大?

等等。

绩效评估的时候,还需要考虑其它的因素,例如对于开

发人员的评分,需要结合两个因素一起考虑:一是技术评

分,一是管理人员对他的评分。此人最终评分,是这两个分

值的结合分,也就是一个综合打分。

这里面,涉及到微软的价值观和企业文化因素在里面。

因为微软对于员工的考核,并不仅仅看他的工作能力和工作

成绩,微软更看重他在工作过程中是否帮助了别人等等。这

也符合微软价值观中的一点“使别人变得伟大”。

员工如果保持自己的技术独享,这会阻碍公司的成长,

所以微软把员工的互相帮助放到很重要的位置,在做员工排

序的时候,要着重考虑是不是乐于帮助他人,所以,在公司

内部,如果一个人独善其身,那他将来发展的机会也会比较

少。举个例子,有两位员工,一个帮助他人,另一个则不,

虽然后者的技术能力没有前者高,但他经常帮助他人,那他

得到的机会会更多。因为公司鼓励这样,而且公司里有很好

的氛围:我帮助他人,我也可以得到别人的帮助。

(2)通过特定的标准来比较,确定分数。

(3)首先确认分数,然后排序来确认。

(4)沉船法则。这一点,虽是排序的步骤之一,但也是

关于排序的最实际的用途。

根据公司的标准排序,排序最实际的准则就是当公司裁

员时,只需将排序表拿过来,就知道是最后面的几个人被裁

掉了。实际的操作也就是这样一个过程。

反之,如果部门经理希望某位员工离开,人力资源部会

要求他拿出足够的证据,证明这位员工不符合公司的要求。

有时,即便部门经理拿出证据,但人力资源也会根据他的前

期表现进行协调,例如要求部门经理对其做相关培训或提供

其它机会,或者重新调整岗位等。

五、 对经理的绩效考核

同员工的考核一样,每年的8月份,微软的人力资源部

也同样会发给每个部门经理一个经理反馈表。对经理来说,

这是非常惴惴不安的时刻。因为他不知道员工会给自己打多

少分。

对经理的打分,实际上也是两部分,一是员工对上司直

接打分,这个绩效表格会直接上交给经理的经理,经理自己

并不知道;另外一个部分,是经理的上司对下属经理的评分

考核。这样一级级上交上去,直到总经理的下属给总经理打

分为止,这是一个层层上递的过程。

对经理的打分也有一些规定,有直接打分,也有选择打

分项,打分项共有五种状态:非常不满意、一般、放弃、满

意、非常满意。

比较典型的经理反馈表问题有:确保下属员工有明确的

目标,真诚地关注下属的职业发展,消除本部门和其他部门

之间协作的障碍,确保整个部门朝着明确的目标努力,支持

下属努力平衡工作和个人生活,等等。

经理反馈表的实施,可以帮助经理客观地了解自己整年

的情况,了解到自己对下属管理的有效程度,帮助经理提高

认识和工作能力。经理反馈表完成以后,人事总监会将这份

表格的反馈回人事部,人事部需要和每个经理沟通,提醒他

有哪些地方做得不够好,态度有待于进一步的提高,在管理

员工的过程中,他有哪些地方缺陷或者忽视⋯⋯。

通过这样的反馈和沟通,经理就会对自己有更客观和全

面的认识。这样他就会在下面的一年里,调整自己的工作方

法。

人才缺乏制约着国内软件企业的发展

——专访微软全球技术中心亚洲商业应用部总经理 华宏伟

微软是从一个很小的企业做起的,在 1975 年的时候公

司也只有比尔·盖茨和艾伦两个人,到了今天在软件行业已

经是世界第一了,但是微软每天仍在担心自己。比尔·盖茨

说过,微软离倒闭永远只有 18 个月。

微软现在的成功是一个非常不容易的过程。整整27年的

过程,是风风雨雨拼出来的一条血路。这个发展过程中,外

面的生存环境和中国软件行业的发展实际上非常相似。外部

的竞争异常激烈,在这种竞争的环境中时刻要想着不断地优

化自己,不断地创造更多更好的产品。

高级人才缺乏是关键

面对国内软件企业在这种竞争环境下发展缓慢的现状,

微软全球技术中心亚洲商业应用部总经理华宏伟先生做了这

样的分析,一般而言,制约软件行业发展的因素有四个:资

微软人才专题

Page 45: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

45

管 理

金、需求、技术和人才。对于资金,应该说问题不大。一方

面国务院有18号文,有很多风险投资机构,很多上市公司,

愿意在软件行业投很多资金,包括各个软件园区,都愿意花

大力气进行软件企业孵化。虽然这方面还有一些问题亟待解

决,尤其对一些中小软件企业是需要一些资金的扶助,但资

金不是大问题。

第二个因素是需求,也不是问题,中国的软件市场有巨

大的潜力,这一点得到了世界软件业的认可。

第三,技术也不是问题。随着互联网的发展,技术的交

流和传播非常迅速。国内和国外使用的开发技术已经不相上

下,很多最新的东西,可能中国的软件企业用得更早一点。

因为国外一些软件企业开发的历史比较悠久,在尝试新工具

的时候还有点限制,还是希望看看再用。对于很多中国的软

件企业来讲,没有这种限制。

从表面上看,人才似乎也不是问题,因为国内对软件行

业特别重视,全国各地有各种各样的软件培训机构、很多家

软件学院,都在专门从事软件工程以及软件技术这种专门人

才的培养。计算机系一直是各个高校里最热门的专业,报考

计算机专业的一定是那些尖子中的尖子。但从另外一个角度

来看,企业却又感到很缺人才,其中的差异在什么地方?国

内软件企业到底缺哪些软件人才?可以肯定的说,缺的是高

级人才!

缺软件公司的经营者,缺开发经理、测试经理和软件架

构师。这个层面的资源是最缺的。有很多软件企业的老总,

以前是技术出身或者是做市场出身,对技术或者市场很熟

悉,很了解,解决一些技术问题或者签一个单可能对他来说

非常容易。但是作为一名管理者,特别是作为一个软件企业

的管理者,他可能还没有意识到自己身上所负担的管理责

任。作为软件企业的管理者,需要非常多的个人素质以及业

务素质。软件开发经理、测试经理同样如此,这个层次的人

就是软件项目的主持者,软件工程的实施者。国内人才的缺

乏,就是指这些人的缺乏,尤其是高端人才,这才是制约中

国软件行业发展最大的限制。

潜力激发最长久

二十多年前,微软的创始人比尔·盖茨提出了一个宏伟

的目标:让每个人的桌面都有一台个人电脑。经过短短27年

的发展,微软的这个梦想实现了。现在微软作为在软件行业

最有影响力的公司之一,又提出了新的目标:激发个人潜

能,实现企业潜力。

很多国内企业的管理层认为员工从事工作的激情不够,

或者说很难长期保持,根据在微软多年的工作经验,华宏伟

认为这个问题要客观分析。

首先,人的潜能和工作激情并不随时间流驶而变迁,就

是说工作激情和年龄是没有关系的,因为华宏伟在美国总

部,见过很多年龄很大的员工,从他们的眼神,从他们说话

时的神态,做事情的专注,就感觉特别有活力,特别热爱这

一行业。

第二点,激情和工作时间长短也没有关系。激情是一种

信念,一种不出结果不罢休的态度,并不是说成天加班到深

夜,那就有激情。总之,有效率的工作才叫激情。

第三点,保持和发展激情是经理的重要职责,并不仅是

员工个人的事情。员工对公司,技术和工作的激情是一个公

司最宝贵的财富,作为经理一定要保护好这种积极性,一定

要花时间关注员工的个人兴趣和精神状态,并结合公司发展

战略作阶段性调整。

一个人的职业生涯和发展要受很多因素的影响,最重

要的一是榜样力量,二是团队信念。一个企业里一定要有

榜样,一到两个,精神领袖也好,典范也好,都可以带动

其他的人。第二点是团队文化,大家都充满了工作乐趣,

而只有一个人不符合公司文化,那他肯定会感到失落,大

家也会觉得这个人不合群。做为一个领导,要能去建立一

种风气,要让群体的力量和风气来带动个人,这对于一个

企业也是非常重要的。

总之,国内软件企业如何在规范化管理的同时,激发

人才的潜能,这里面存在着很多的难题和困惑,这也成了

摆在管理层眼前最迫切的难关。希望微软的这套人才管理

模式能给国内软件企业带来新的活力和契机,这既是微软

中国公司的心愿,也是我们这期专题的初衷。微软全球技术中心亚洲商业应用部总经理 华宏伟

Page 46: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

46

开放源码的风险评估 撰文 / 陆恒

开放源码(open source)软件是指源代码对公众开放的

软件。这样的软件不特定于某个平台或系统,可以被免费使

用、修改和分发。从历史角度看,开放源码软件发源于同

Unix、Internet相关的编程,主要由分散在世界各地的编程人

员以个人名义开发而成。开放源码的倡导者Eric S. Raymond

在其著名的“大教堂和市集”(The Cathedral and the Bazaar)

一文中写道:“Linus Torvalds软件发展风格的出现如同一个

惊奇, 没有令人肃然起敬的教堂, 甚至 Linux 的同好们似乎

组成了一个有不同流程和不同方式的大市集, 以这个风格发

展出来的 Linux 既一致又稳定, 表面上看来真是一连串的奇

迹。”这代表了软件发展的一个走向,即从集中的、庞大的瀑

布式开发向分布的、利用小工具集的快速原型法开发的转

变,因为开放源码有利于实现软件复用和组件式开发。

今天,当企业或组织致力于降低获取软件许可的成本

时,尤其在Java应用开发的相关领域,开放源码的第三方软

件成为一种很好的选择。目前,大约已有 60% 的站点使用

Apache Web Server,有 14%的站点使用开放源码的 Java服

务器。由于人们对开放源码的呼声不断增长,许多软件开发

商也开始顺应这个潮流,商业软件同开放源码的自由软件之

间的界限也越来越模糊。传统的软件开发商经常提供免费的

服务器版本或测试版本,而开放源码软件的开发者则通过培

训和提供各种支撑服务来达成商业目的。从目前的趋势来

看,随着互联网的发展和开放源码软件的使用许可制度的完

善,越来越多的商业软件包开始集成源代码开放的第三方软

件,这势必对提供纯商业服务并制定行业标准的传统软件企

业形成强大的挑战。

开放源码和封闭源码,哪个更安全

与此同时,业界也逐渐开始重视开放源码软件的可靠性

和安全性。关于开放源码和封闭源码哪个更安全的问题,业

界的讨论也很激烈。一般认为,封闭源码意味着隐藏、保密,

因而也意味着安全。尤其在传统的软件开发商看来,“封闭”

是一道隔离黑客和商业对手侵袭的屏障,或者至少能够延缓

入侵者的破坏速度,把可能的损失减到最小。不过,如果换

个角度来看,对于一个源码封闭的软件,我们并不知道里面

是什么,无法验证其开发者的设想,更不能迅速地检测出其

中的错误和漏洞;也就是说,除了听取开发商的自述,我们

无法检查一个封闭源码软件是否真正安全可靠,正如

Raymond 所说的,“封闭源码并不能导致真正的安全性,而

会产生一种虚假的安全感”。而对于开放源码软件,虽然开

放的代码使黑客编写攻击性的程序更容易,但同时也使维护

人员更方便地检测出程序中的错误和安全隐患,而且能够杜

绝源码编写者安插的隐藏后门。

是否使用以及如何使用开放源码软件,对于不同的企业

或组织,都要根据不同的情况来评定。一般来说,使用他人

开发的应用软件包含了版权、专利权、可靠性、安全性和质

量等方面的风险因素。相比传统的商业软件,免费获取的开

放源码软件毕竟引入了额外的风险,因为传统商业软件通过

特定的授权、法律限制和提供支撑培训服务等手段来建立某

种安全机制、约束使用者的风险,而开放了源代码,这些限

制和约束就会大打折扣甚至不复存在,风险的承担者就只能

是企业自己。因此,一个企业或组织必须建立某种机制和过

程来评估其使用开放源码软件的风险。

开放源码的风险评估

随着开放源码软件的广泛使用,建立一套评估和检测源

代码的策略将变得十分必要。评估使用开放源码软件或第三

方软件的风险,包括了软件来源、作者以及软件可靠性和安

全性等各方面的内容。

一、法律角度的评估

从法律角度来看,在获取开放源码的软件后,评估策略

应该首先包含以下步骤:

(1)确定源代码的来源;

(2)检查版权信息和软件许可证;

(3)检查软件作者的授权。

每个软件都有版权和使用协议。开放源码的版权和使用

协议一般包括编写者的身份公告、注意保护开放源码的状

态、以及对再开发的控制。在使用这个软件以前,企业或组

织的法律顾问应该详细核查这些内容。如果使用一段从非正

规地点取得的代码,势必大大增加风险,而可靠的评估将有

利于风险的降低。因此,企业或组织在获取开放源代码的软

件之后,对其进行的法律范畴内的评估应成为综合风险评估

软件创业

Page 47: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

47

管 理

的重要组成部分。显然,这方面的开销应该列入使用开放源码

软件的成本。不过,在某些情况下,譬如使用具备Apache许

可证的一系列版本时,就不必每次升级都重复这些步骤。

二、源代码的安全评估

除了法律方面的相关问题,许多企业更关心的是软件

的安全性。破坏安全性的方式很多,但我们可以将其归结

为人为破坏和系统缺陷两种。在这里,人为破坏指黑客蓄

意利用源程序中的一段代码来破坏安全性,包括病毒、特

洛伊木马和黑客程序等。系统缺陷通常由内存出错引起,

是由程序设计本身导致的,但是为黑客开启了一道通往系

统核心的门。

针对由潜在的程序设计缺陷而可能导致的内存出错,最

常用的办法是使用各种测试工具。这类工具,比如Rational公

司的 PurifyPlus和 Compuware公司的 DEvPartner Studio等,

能跟踪到许多常见的设计缺陷,例如内存块未申明、缓冲区

溢出、内存泄漏等。PurifyPlus 是一个代码运行时刻的分析

工具,它将运行时刻查错、性能瓶颈识别和沿路径分析等模

块集成在一个工具包内,并能对源程序的每个模块单独进行

分析。DevPartner Studio 是一个大型开发调试工具。以

DevPartner Studio for Java 为例,其 JCheck组件使用事件

调试技术,可以收集Java程序运行中准确的实时信息。由于

Java 程序中经常要使用线程,而关于线程的常见错误有死

锁、系统崩溃、同步问题等。JCheck通过监视和分析当前内

存中所有线程的运行状况,找到出错的根源,并定位到具体

是程序中的哪个方法出错,错误位于程序的哪一行等。

这些针对代码设计缺陷的测试过程不仅是软件开发生命

周期中不可或缺的组成部分,而且也应当成为评估开放源码

软件的重要组成部分。

相比系统缺陷而言,由人为破坏导致的安全性问题更加

难以对付。就我们的经验来看,检测一个程序的安全漏洞远

比自己写一段安全的代码复杂,更何况经常要面对一些源代

码开放的大型复杂的应用程序。由于源代码中通往系统内核

的后门几乎不可能被检测到,对这些软件被攻击风险的评估

似乎变得不着边际。不过,黑客如果想通过软件的安全漏洞

对系统实施攻击,必须首先登陆远程主机,而企业的内部网

通过制定访问权限可以避免来自外界这方面的攻击。当然,

如果是服务器程序本身存在安全漏洞则有相当大的风险。

和人为破坏有关的风险还包括有人通过开放的源代码来

传播有害数据,如病毒和木马程序等。由于传播自由软件是

一种公众行为,有人可能会恶意地在开放的源代码中添加有

害代码,并且四散传递。虽然这种可能性的确存在,并且具

有相当大的危害,但是我们并不认为其概率大于有人用非开

放源码的程序传播病毒的情况。譬如,软件开发者自己或者

雇人在程序中添加有害代码,然后传播出去,同样会造成很

大危害。当然,这里并不是说使用开放源码的软件不需要有

这方面的顾虑,而是指开放源码软件这方面的风险性与非开

放源码的软件相当,因此不需要因为可能传播有害数据而排

斥开放源码软件。

无论是法律因素还是源代码的安全因素,整个评估过程

的关键还在于彻底分析所有获取的源代码,譬如,代码中是

否还存在文档中没有描述的API,资源在何处被分配,程序

是否通过网络传递消息,等等。像Cigital和Secure Software

这样的开发商不仅提供安全性分析工具,还提供安全咨询服

务,以帮助客户编写安全代码。例如,Cigital公司通过软件

风险管理的机制,并以软件全面测试、评估第三方组件、寻

找并发现安全隐患等技术手段来帮助客户提高产品的可靠性

和开发的效率。Secure Software宣称其目标是“软件中坏代

码的核心症结”,并且提供包括培训在内的全套安全软件解

决方案。其它一些软件度量工具(m e t r i c s t o o l s ) ,如

McCabe&Associates公司的产品,则可对源代码进行复杂度等

各种特性的测试。这些工具不仅用在软件开发过程中,在评

估和测试开放源码的软件中也十分合适。

评估开放源码的意义

无论是嵌入式系统、实时系统还是自治系统等技术的应

用,都对软件产品的可靠性和安全性有着很高的要求,而这

也是第三方软件流行的领域。因此在这些产品的开发过程

中,应更注重对集成的开放源码的第三方软件的风险评估和

管理,并且采取方法和措施以降低风险。在成本因素可满足

的条件下,采用形式化方法是检测和控制风险的有效手段。

形式化方法采用数学和逻辑原理,使软件设计规范化,并检

验程序的一致性和完备性。对于所获取的开放的源代码,可

以采用反向工程解析出谓词模型,精确地检测出程序中所有

的不一致性,理清在分布式环境下的多进程、多线程和并发

机制,从而有效地应对第三方代码的复杂度,从根本上验证

程序的可靠性。形式化方法对成本和开发者的要求更高,但

是可以通过只对最重要、关键的组件采用这种方法来降低成

本。目前形式化方法在业界已经开始推广使用,其本身也在

不断适应新技术和新方法学的要求,例如,形式化语言从早

先的 VDM、Z、Petri Net 等发展到后来的支持面向对象的

Object-Z等,而且已经同CASE等工具集相结合,以提高实

用性和易用性。

随着开放源码软件的广泛应用和传播,企业和组织需要

尽快制定对源代码开放的第三方软件的风险评估体系,使用

包括软件度量工具、安全性分析工具和内存检测工具在内的

各种手段来全面检测所获取的源代码,以降低商业风险。

Page 48: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

48

《CRM 全解》系列之四

CRM现状及发展

撰文 / 曾炼

CRM的环境

CRM Today(今天的CRM):经济和企业的动荡使人们

重新认识 CRM。

近几年来,所有从事计算机等高新技术的业界人士一定

能体会到,由互联网泡沫带来的世界经济的大起大落就如同

游乐场的过山车!

CRM的高速发展也正是在互联网的高潮期。在整个经济

大环境影响下,CRM 也不能例外。CRM 在全球的发展已开

始放缓。主要体现在以下几个方面:

(1)技术创新开始放慢

(2)企业接受 CRM的频率降低

(3)用户更关心其 CRM与现有资源的整合

但在中国的情况有所不同,企业才开始了解 CRM 的威

力。再加之 WTO 的压力,CRM仍然保有良好的发展势头!

CRM的产品及市场现状

虽然全球经济陷入低潮,无论在美国还是在中国,CRM

《CRM全解》系列是由曾在美国亲身参与 CRM系统开发的曾炼先生撰写的,整个系列共分四大部分,分别为《CRM历

史回顾》(2002-09)、《CRM理念和实际运用》(2002-11)、《CRM在中国的应用与实施》(2002-12)以及本期的《CRM现状

及发展》。

仍然是科技领域的一个热点,尽管热度较前两年有所下降。

我们将现在的CRM流行总结为:返朴归真,回归自然。

软件业界对 CRM 的理解已较为成熟,新概念出现的越

来越少。厂家发挥自身的潜力和优势,对已有的 CRM 功能

进行深化。

以最新的 Codie奖(由美国软件信息行业协会颁发,授

予各个领域的优秀软件产品,每年颁发一次)为例,这是一

个计算机应用软件和系统方面的评奖。其中 CRM 类的获奖

者是:

l ACT! 6.0:老牌的客户管理软件,以实用见长。

l KnowledgeBase.net Hosted Ed. 2.10:知识管理为

核心,以自助服务为特点。内容有客户支持、帮助台、FAQ

管理、文档管理、接触中心等。

l Salesforce.com Enterprise Edition ASP:解决方案的

领军人物。最新版包括客户服务、销售力自动化、营销自动

化及报表等功能。

l Maximizer Enterprise 7:面向中型企业或大公司部

门的软件工具,集成了销售、客户、市场、客户服务及支持

工具等。

l ChannelWave 5.0:销售渠道管理,包含售前、售中

及售后的渠道活动管理。

l Kana Response 7.5:交互式 CRM平台,使全球型企

业快速响应多种语言的要求。并有多渠道服务和分析功能。

在CRM应用软件领域,不同的企业有不同的做法。企业

里曾经流行有一句话,叫做“小公司做技术、大公司做市

场”。意思是说小公司的竞争优势在于技术,大公司的竞争

优势在于市场。当然好的公司应当二者兼备。但很多公司却

是从某一端起步的。

软件工程论坛

Page 49: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

49

管 理

在 CRM 的领域也是如此,除了一些很有技术特点的小

型公司如 ChannelWave 等,大的软件公司如 Oracle、SAP、

PeopleSoft、JDW 等利用其市场优势也已进入这一市场。老

牌公司如 Sibel 的地位已受影响。

在目前的市场环境下,小公司已很难生存,很多大公司

仍在进入 CRM领域,微软将在最近推出旗下CRM产品,这

是目前最新最显著的进入者。

CRM之困惑

流行未免有几分炒作的成分,尤其在中国,经济发展如

此之迅速,人们有时会头脑发热。

面对当前的全球经济,在科学技术革新放慢的同时,如

何看待 CRM 这样带有技术及管理色彩的事物,是现在很多

企业感到困惑的问题。人们在担心,CRM会不会像互联网泡

沫一样消失。

但这种担心是不必要的。正如我们在前面的系列文章中所

介绍,CRM历史悠久,并非一两天炒作出的概念。CRM是多

年技术和管理的结晶,就如ERP一样,有其存在的长久价值。

CRM技术现状

根据多年 CRM的研发与实施的体会,我认为CRM仍有

很大的提升空间,技术的发展潜力和现在的应用水平之间仍

有很大的距离。具体表现在:

(1)CRM应用的标准化

从以往的经验来看,技术及理论到工业界的推广都有一

个标准化的过程。

目前 CRM 应用的最大问题之一是缺乏标准。标准通常

有几个来源,例如深入而系统化的理论研究,标准化组织的

规范制定,行业组织或厂家联合体制定的标准等等。如ERP

的长久不衰与 MRPII 理论的完善有很大的关系。

在 CRM 的范畴,厂家更多从技术角度和使用角度定义

产品。其核心应用如客户服务、销售力管理、营销管理、变

更管理等没有一套统一的

规范,更多是厂家根据自

身的经验和积累进行演绎

发挥。从厂家对产品的描

述中,用户很难界定自身

的需求,并确定厂家的优

劣。

因此,要让 CRM得

以推广,制定相关的标准

将是一个极大的推动力。

(2)CRM应用技术

在过去的两年中,产生了许多的新技术、新概念。如

XML、Java、J2EE、EJB、WebServices、微软.NET等等,在

CRM中有个性化一对一、商务规则、工作流等技术,但这些

技术具体到用户的使用过程仍有很大问题。如何提高技术的

应用水平,消化这些新技术,是目前厂家和开发商急需解决

的问题。

(3)CRM的商务逻辑

目前宣称自己是 CRM 产品提供商的厂家越来越多,但

用户并没有感到他们的差异,其中一个很大的原因是这些厂

家没有对其 CRM 产品进行细分。很多人都知道,在以客户

为中心的解决方案的各个阶段,会运用不同的技术和管理思

想,随着应用的深入,用户会有不同的需求。

没有细分的产品及技术会导致用户无法对产品及技术正

确理解和判断。

商务逻辑的细分除了系统结构的支持外,还需要对商务

逻辑本身进行分类和研究。有一些商务逻辑可能通用性较

强,与行业等环境无关,便可以将这一类逻辑放入公用构件

或服务,与行业有关的部分则可放入应用逻辑。

CRM的发展

作为未来的CRM,其实用性将会更强,更能满足企业的

需求。以下是一些未来系统的特点。

(1)多种接入和交互手段

CRM是以客户为中心的现代企业管理系统,要借助各

种先进的技术手段。例如:

l 电话

l VoIP

l 传真

l 呼叫中心或 IVR

l WWW (Web Chart、Web Callback、Web Collaboration)

l E-Mail

l VIDEO

l 移动电话

l 其他手持设备

(2)实时性

CRM将有更强的实时处理能力。可以将客户的各种需求

及时地处理并反馈给用户。

客户

客户资料

Page 50: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

50

现在有一些 CRM系统已具有一定的实时处理功能,如

实时任务派送、实时报警、实时分析等,但还未成为一个普

及型的应用功能。

(3)集成性

目前的 CRM 没有考虑和已有资源的集成,这对系统的

应用带来很多问题。因为企业的信息系统有相当的复杂度。

很可能有不同的资源在运行。如何将这些资源有机地结合起

来,是 CRM 集成要解决的问题。

CRM的集成有两种方式,一种是 CRM系统提供环境和

API 界面,将 CRM集成到一个统一的环境中,一个典型的

范例是企业信息门户(EIP)。

企业信息门户(EIP)可将企业的ERP系统、SCM系统、

CRM等系统资源统一到一个界面下,以方便操作和浏览。

另一种集成方式是 CRM 通过已有的集成界面如适配器

(Adapter)将别的资源如 ERP资源集成到 CRM系统中,以

解决资源的共享问题。

(4)协同操作

协同是系统应用对象之间的相互协作。如何对系统在商

务逻辑层面进行实时互操作,是协同型CRM要解决的问题。

下面是一个协同服务的例子:

图中的应用服务器提供 CRM系统功能。

协同服务器具有以下功能:

l 使两个应用的对象及商业过程可以协作

l 利用协同服务器传送对象及其相关对象、事件、操作等

l 应用服务器可以操作传送的对象集合,并将其融入自

身的商务逻辑及工作流中

l 可以对共享数据的安全性进行控制

l 可以对共享数据的映射进行控制

(5)知识管理

CRM系统虽然主要是以客户为中心的管理系统,但实

际系统的运行却需要很多产品、用户及解决方案方面的知

识。因此 CRM与知识管理系统有密不可分的联系。

常见的 CRM知识管理包括:

l 用户档案管理

l 产品信息管理,包括产品自身描述及配置,产品相关

信息,产品解决方案等

l 行业及竞争对手信息管理

l 市场信息管理,包括市场动态,价格变化,历史数据

等等

l 解决方案管理,包括问题的搜集,答案的选择,相关

参考资料等等

(6)电子商务

CRM将与电子商务结合得越来越紧密。现代的电子商

务系统已经不同程度的将 CRM融入到整个系统之中了。

一些企业如 Amazon、FedEx已成功的使用了这种组合。

(7)系统化的开发与实施

目前 CRM 的开发和实施存在很多问题,其中一个主要

问题是缺乏系统化的分析和设计。这样很难保证开发与实施

的质量。现在有很多工具可以提供帮助,如 UML、ARIS、

IDEF3 和 RAD 等。

可能很多读者已经对 UML有所了解,UML是系统分析

的工具之一。这里简单介绍一下 ARIS。

ARIS“集成信息系统体系结构”(“Architecture of In-

tegrated Information System”)自 1992 年首次提出后,

获得了广泛的应用。用ARIS标准软件进行业务模型的设计

已被证明是一个巨大的成功。IDS公司在ARIS概念的基础

上开发的ARIS Toolset目前已成为全球范围内流程重组工

具市场中的领先者。ARIS Toolset已被北美、欧洲、和亚

太地区的很多企业所使用,是和种有效的业务过程设计工

具,可用于 ERP、CRM、呼叫中心等大型系统的开发。

ARIS建模方法首先在对企业全面调查分析的基础上对

企业的组织结构、各种业务职能、所涉及的各种数据、业务

流程和控制关系进行全面科学系统的描述。在此基础上发现

现行系统的缺陷,构造新的系统,并建立未来企业模型。

下图表示了 ARIS 的基本框架。

SAP的 ERP产品已使用 ARIS来进行过程设计并支持实

施,因此可以认为,用 ARIS企业建模开发方法来开发大型

企业 CRM 系统将是一个很好的选择。

Web 浏览器 Web 服务器

应用服务器

对象

协同服务器

协同应答

协同请求对象

应用服务器

需求定义

系统设计

实施描述

组织图

网络拓朴图

网络图

需求定义

系统设计

实施描述表图

数据视图 控制视图 功能视图

应用系统图

控制图

软件工程论坛

Page 51: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

51

技 术

继 Borland收购 TogetherSoft之后,开发工具厂商继续上演收购好戏。IBM和 Rational Software Corp.于美国时间 12月

6 日宣布两家公司达成最后协议,IBM 以大约 21 亿美元现金或每股 10.50 美元收购 Rational。

Rational为开发企业应用和开发软件产品提供开放的,工业标准的工具、最佳实践和服务。通过收购Rational,IBM将能够提供

一个全面的开发环境,帮助公司整合业务流程和软件基础架构。这次收购是 IBM 的 "e-business on demand" 策略的重要组成部分。

Rational 成立于 1981 年,是世界上最大的软件公司之一,在全世界范围有超过 3,400 名雇员。

美国当地时间 12 月 10 日,W3C 发布了用于 XML文件的加密标准“XML Encryption Syntax and Processing(XML

Encryption)”以及电子签名相关标准“Decryption Transform for XML Signature(Decryption Transform)”。

美国当地时间 12 月 19 日,微软和 IBM 两家公司共同发布了以前承诺过的一些 Web 服务规范。此次由 IBM和微软共同

提出的 Web服务安全性规范包括WS-Trust、WS_SecureConversation和Ws-SecurityPolicy。另一组规范则是 Web服务业务策

略规范,包括 WS-Policy、WS-PolicyAttachments 和 WS-PolicyAssertions。

日本微软于 12 月 12 日对外告知,在 Windows 操作系统和 Internet Explorer(IE)附带的 Java 虚拟机“Microsoft VM”

中存在 8 处安全漏洞。危险程度最大的为最严重的“紧急”,如果被恶意利用,机器可能会被攻击者控制。

会受到影响的是安装了 Microsoft VM Build 3805 以前(包括 3805)版本的 Windows 电脑。在使用会受到影响的 Build 时

必须要立即采取对策。对策之一就是使用补丁。运行“Windows Update”后,选择“810030 : Microsoft VM 安全问题的修正

程序”,即可安装已修补了安全漏洞的Build 3809。并且安装Build 3809还可以消除过去公开的微软虚拟机安全漏洞“MS02-052”。

香港文化传信集团 17 日公布,该公司与 IBM合作成功研发“文传 CSCS IA-3210 CPU”,这是全球第一颗专为 Linux

而开发的中文 Linux 单晶片中央处理器(CPU),也是针对中文 Linux IA(Intelligent Appliance)业的技术解决方案。

据介绍,“文传CSCS IA-3210 CPU”首次采用中文单晶片系统技术,是目前全球独一无二采用32位结构的中文单晶片。它的特性主要包

括内建多类接口和内置“中文字形产生技术”。这样,中文就脱离PC软件,直接嵌入CPU,使各类IA产品都能具有高效能的中文处理能力。

第一种基于Symbian有限公司操作系统的3G手机已由Fujitsu公司推出,型号为F2051,不久将可供NTT DoCoMo公司的3G网络用户使用。

Symbian公司的操作系统已经向很多手机厂商颁发了使用许可证。除了Fujitsu公司以外,Nokia、Motorola、Siemens、Sony

Ericsson Mobile Communications AB以及 Samsung Electronics 都先后获准使用这种操作系统。

在 Linux Format 杂志进行的一个评选中,KDevelop 2.1 击败了其他六个商业或开放源代码的竞争对手,被评为“最佳

Linux C++ IDE”。这次评选的参赛“选手”包括 Kylix Open Edition、Studio Gold、Anjuta、Code Forge 等等。

在正在进行 alpha测试、即将正式发布的 KDevelop 3.0中,提供了更多的新特性,例如支持 C/C++之外的语言、支持

qmake、支持 C++ 代码自动补全等等。可以预料,KDevelop 3.0将是一个更加优秀的 IDE。

程序天下事《程序员》杂志每月在“程序天下事”栏目报道当月的重要技术新闻,直击最新技术动态。欢迎读者提供新闻线索,

投稿信箱:[email protected]

IBM收购 Rational

XML & Web Service 安全新规范频频出台

微软虚拟机爆出严重安全漏洞

全球首颗中文 Linux芯片面市

第一种基于 Symbian OS 的 3G 手机问世

KDevelop 当选“最佳Linux C++ IDE”

Page 52: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

52

主持人 / 透明

垃圾收集导

技术专题

不久之前,我在CSDN论坛上发出一个名为“谈谈GC”

的帖子,引起了不少朋友的关注。围绕着 GC的思想、技术

和对程序设计的影响,许多网友都提出了自己精彩的见解。

在这篇帖子的基础上,我们组织了这次以“垃圾收集”为主

题的技术专题,希望能让读者更加清楚地了解垃圾收集

(Garbage Collection)台前幕后的技术细节。

对于“垃圾收集的利弊”这个问题,可以说是众说纷纭。

不过,谁都无法否认:垃圾收集是一项非常重要的技术。面

向对象巨著《面向对象软件构造》(Object-Oriented Software

Construction)的作者、Eiffel 语言的创造者 Bertrand Meyer

甚至说:

“自动垃圾收集的重要性是由面向对象软件构造所提倡

的动态模型带来的。如果没有垃圾收集机制,会在两个方面

造成严重的缺陷:首先,在面向对象的动态模型中,手动管

理内存不仅麻烦而且危险;而更重要的是,垃圾收集机制的

缺乏会将面向对象技术能够带来的所有好处全都抵消掉。”

由此,你可以看出“学院派”的对象理论研究者对于垃

圾收集机制是何等重视。然而,另一方面,垃圾收集也带来

了一些负面效应——天下总是没有免费的午餐。一般认为,

垃圾收集的副作用也主要体现在两个方面:首先,垃圾收集

必定会造成时间性能上的损失(甚至还有这样一个笑话:一

架飞机的控制软件是用 Java 编写的。有一天,飞机正在降

落,机长按下“放下起落架”的按钮,屏幕上跳出一行字:

“正在进行垃圾收集,请稍候”⋯⋯);另外,在多数情况

下,垃圾收集进行的时机(以及是否确实进行垃圾收集)是

不确定的,因此一般不能借助垃圾收集机制来管理一些对时

间要求较高的资源(例如文件句柄、进程锁等等),而必须

对内存和其他资源采取不同的管理策略。

如果只是要在论坛上讨论一下垃圾收集的话题,那么有

这样的了解就已经足够了。但是,如果想要说服自己或者老

板使用(或者不使用)一种带有垃圾收集机制的语言,单凭

这么一点感性的认识是不够的。你有必要知道,垃圾收集为

什么会慢?慢到什么程度?垃圾收集的时机为什么不能确

定?“不能确定”到什么程度?要探究这些问题,就必须了

解垃圾收集的实现原理和技术。

在本期技术专题中,我们为读者准备的就是这方面的内

容:垃圾收集的实现原理和技术。或者说,如何实现垃圾收

集机制。在看过这几篇文章之后,读者未必可以自己实现出

一个垃圾收集机制来,但至少可以了解自己手上用的垃圾收

集器是如何工作的。知其然,也知其所以然,用起来才更顺手。

本期技术专题共有四篇文章:

v 《Garbage Collection——问题和技术》。本文将简

要而系统地介绍产生GC问题的根源及其基本技术,讨论GC

的存在给软件系统的影响,也就 GC与其他资源管理的关系

问题谈一些认识。裘宗燕教授从事 GC方面研究已有多年,

兼之又是教育界的前辈,由他来写这样一篇文章无疑是最适

合的。

v 《.NET 的自动内存管理》。综观近年出现的语言,

往往都具备GC,也因此有越来越多程序员都已经在享用GC

所带来的好处了。但是对于这个默默在背后运作的机制,大

部分的程序员不太清楚它的原理。本文章以言简意赅的方

式,为各位剖析了.NET CLR 和 Rotor 的 GC。事实上,大

多数的 Java VM也都采用这里所叙述的 GC方式。蔡学镛先

生对 Java和.NET虚拟机都有深入的研究,这篇文章也写得

深入浅出,清晰易懂。

v 《一个 C++的垃圾收集框架》。这篇文章介绍了一

个很有趣的 GC for C++解决方案。一般情况下,用 C++

类库的形式实现的 GC只能选择“保守式”的回收方式。但

本文中介绍的 gc_ptr类却采用了 mark-and-sweep的回收算

法,不但新颖,而且有效。如果你也想给自己的C++程序加

上垃圾回收特性,这篇文章不可不读。

v 《针对Delphi对象和构件的垃圾收集器》。“怎样对

对象进行内存管理”是面向对象编程的众多基础问题之一。

对于这个问题,不同的语言有着不同的解决方法。本文介绍

了一种适用于 Delphi的自动内存管理机制。

由于篇幅所限,我们本次技术专题只能局限于“垃圾收

集的原理及实现”这部分知识。而另一方面的知识——垃圾

收集的应用及其对编程方法学的影响——对于程序员来说甚

至可能更加重要。因此,希望读者能针对这方面内容积极投

稿,投稿信箱:[email protected]

Page 53: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

53

技 术

撰文 / 裘宗燕

关键词 GC 引用计数 标记 - 清扫 结点搬迁 分代式 保守式

Garbage Collection(GC)是一类重要的动态存储管理技术。随着面向对象的语言和程序设计方法的日益广泛使用,GC问题也

日益受到实践性程序工作者的重视。本文将简要而系统地介绍产生 GC 问题的根源及其基本技术,讨论 GC的存在给软件系统

的影响,也就 GC 与其他资源管理的关系问题谈一些认识。

Garbage Collection——问题和技术

Garbage Collection在中文专业文献中主要有三种译法。

80年代起就被称为“废料收集”; 有些讨论数据结构等的

计算机书藉中称为“无用单元收集”;近年一些计算机工作

者常用“垃圾回收”。本文在谈到Garbage Collection时将简

单采用 GC,但在谈论 garbage时则将沿用作者的长期习惯,

称其为“废料”,取“废品回收”之意。

许多读者可能不会想到,GC 技术的研究和应用其实已

经有40多年的历史。GC的发明应归功于Lisp语言的创造者,

图灵奖得主,大名鼎鼎的“人工智能之父”John McCarthy,

他在 1960年有关 Lisp语言的论文中第一次提出了自动回收

流失的存储结点的问题,并提出了后面将要介绍的标记-清

扫算法。有趣的是,正是在同一年,其他研究者也提出了引

用计数技术。三十多年后,由于面向对象的语言和程序设计

方法的发展,尤其是 C++ 语言和 Java语言的广泛应用,这

些技术逐步受到了实践性程序工作者的广泛关注。《程序

员》杂志推出关于 GC问题的专栏,也是这种情况的一个具

体表现。

1.动态存储分配与废料

讨论 GC必须从动态存储管理问题谈起,这样做也是为

下面的讨论提供一批基本术语。这里的简短介绍以C等常见

语言为背景,力图给出一个框架,并不深入讨论特殊情况。

在典型情况下,一个程序可用的存储空间被划分为三个互不

相交的部分:静态存储区,栈存储区和动态存储区(堆)。

存于不同区域中的数据对象在许多基本方面性质各异。

程序中使用的命名变量安排在静态区、栈区或寄存器

里,程序里通过名字访问这些变量。静态存储区的分配在程

序执行前已完成,其中的数据对象具有长生存周期,其存储

位置和大小在整个程序执行中是固定的。栈区分配子程序

(过程和函数)的局部数据对象,如参数、局部变量和局部

常量等。它们生存周期也很明确,随着子程序的进退而自动

创建和撤销,每个子程序里有哪些局部对象,共占据多少存

储量的情况在编译时都已确定了。

从动态存储管理的角度,我们可以将一个程序分为两部

分,一部分是完成实在工作的工作例程(人们常用英文词

mutator来代表它),另一部分是动态存储的管理系统。在程

序运行中,管理系统掌握着一些可供申请的动态存储,通常

称为自由空间(free space,是动态存储区中的一部分)。工

作例程申请了一些结点,通过静态区、栈区和寄存器中的命

名指针变量索引它们。这些结点形成了程序的(动态)工作

空间(working space,也是动态存储区的一部分)。在程序

执行过程中,工作空间和自由空间之间存在着两条结点流:

工作例程通过分配操作(new、malloc 等)将结点取到工作

空间,驱动着从自由空间出发的一条结点流;通过释放操作

(free、release、delete等)将结点送回自由空间,形成另一条

结点流。这就是 C/Pascal等经典语言的动态存储管理模型,

如图 1 所示。

索引着结点的命名指针变量称为根指针,对所有结点的

访问只能通过它们间接进行。结点内可能还有指针,这些指

针又可能索引其他结点,这样就可能使工作空间形成复杂的

索引结构,如图1所示。可能存在多个命名变量索引同一结

点,多个结点内指针索引同一结点,一个结点内的不同指针

索引着多个不同结点等情况。易见,一个特定结点能被访

问,就要求存在至少一条从某命名指针变量出发,通过指针

形成的,到达这一结点的直接或间接访问路径。存在多条访

问路径的情况也完全可能出现。对工作空间明确说法是:它

图 1 基本动态存储管理模式

Page 54: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

54

技术专题

由所有存在访问路径的结点组成。我们也可将这种可访问结

点称为“活结点”。

如果结点使用方式简单,程序员就可能通过细心安排程

序里的分配和释放操作,保证在程序运行过程中,流入工作

空间的每个结点或者仍在工作空间里(存在访问路径),或

者已被送回自由空间。如果不能保证这点,动态空间中出现

了已分配的结点没有释放但却失去了访问路径的情况,工作

例 程 中 就 再 也 无 法 访 问 它 们 了 , 这 种 结 点 就 是 废 料

(garbage,图2)。废料结点就是失去了访问路径的已分配结

点,人们也称它们是“死结点”。

产生废料的原因很多,除了程序员的失误(人的失误难

以避免)外,最重要的是程序中的数据共享问题。我们常需

要将动态区里的结点从程序的一部分传到另一部分(例如传

入传出函数),或者在不同访问路径之间传递结点(例如将

属于某一数据结构的结点加入另一数据结构)。这类情况可

能造成动态结点的共享,造成结点归属不清,也使写程序时

无法确知某结点何时失去最后一条访问路径,因此就不敢释

放它们(因为需要避免另一危险情况:悬空引用,dangling

reference)。结点在失去最后的访问路径之后就变成了废料。

对于小的或短时间运行的程序,少量流失结点的情况通常不

会成为问题。而那些需要长期运行的系统,就根本不能容忍

不断流失结点的情况了。

废料收集(Garbage Collection,GC)的想法就是实现某

种自动过程,使之能驱动动态存储空间里的第三条结点流,

将流失的动态存储结点送回自由空间(图 2)。由于GC的许

多本质性问题(下面会逐步介绍和分析),人们首先考虑的

是避免 GC 的技术。

2.防止废料的技术

为防止废料产生,人们开发出许多有用的程序和语言技

术,其中的许多技术读者们比较熟悉,因此,这里只分析讨

论一些许多人可能没有注意到的问题。

完全复制,不共享工作空间中任何结点。如果我们能保

证,在程序运行中的任何时刻,到每个结点只有一条访问路

径,那么工作空间中的结点就形成了一组树形结构,每棵树

以一个命名指针变量为根。此时每个结点都有清楚的归属,

一旦根指针的生存期结束,或者需要它去引用另一棵树,这

就是销毁相应树结点的正确时刻了。按这种管理方式细心写

出的程序完全可以保证不丢失结点。当然,这一方式也有致

命弱点:完全复制可能造成很大的运行时间和存储代价;况

且,在许多情况下,程序里确实需要通过链接结构共享信

息。当然,实际程序里完全可能适当放宽这一限制,并仍然

可能保证结点都具有清晰的归属。但无论如何,要保证结点

归属的清晰性,常常难以避免大量的结点复制。

将动态分配约束于静态作用域的技术。一些动态分配结

点实际上只在某个函数过程的局部使用,在退出之前应该释

放。一些编程环境为这种使用方式提供了专门的存储管理功

能。例如一些 C 系统里的库函数 alloca,由它分配的存储在

退出分配动作所在函数时将自动回收。这些技术可能减少废

料的产生,但不可能解决所有问题。

引用计数技术,这是大家熟悉的技术。引用计数的最大

优点是将存储管理的开销自然分摊到正常执行中,存储回收

通常无须占用大段时间,可以较好地支持需要“实时响应”

的应用。实际上,简单的引用计数还是可能导致程序执行中

出现无法确定时间的停顿:某个结点计数为 0并要求释放时

(可能在任何指针赋值时发生),有可能导致一连串的结点释

放动作。人们也提出了解决这种问题的技术。

长期以来,需要管理复杂链接结构的系统(如Lisp等语

言系统、符号计算系统等)并没有广泛使用引用计数,原因

主要有两条:第一,引

用计数不能回收循环

引用结构,参见图 3。

假定所示指针是指向

该循环结构的唯一指

针(图3(a)),修改这

个指针后,该循环结

构失去所有访问路径,但结点的计数值仍然为 1(图 3(b))。

因此,引用计数只能看作一种“延缓”废料产生的技术,最

终还要依靠GC技术。第二,采用引用计数,每次指针赋值都

要付出几倍的开销。一些指针赋值极频繁的系统可能无法容

忍这种情况。当然,并不是所有程序里的指针赋值都非常频

繁。人们还发现,绝大部分局部指针并不需要做实际计数,

因为它们总是临时指向某个结点,而后很快就移开。可以利

用这一特点减少引用计数的开销。

C++的动态存储管理技术。C++通过其语言机制(构造函

数和析构函数、自定义类型和运算符的能力等)提供了管理动

态存储的丰富手段。其中的一个基本想法就是将所有动态结点

都约束于行为良好、生存周期明确的数据对象(主要是各种局

图 2 废料的产生和废料收集

图 3 循环引用结构

技术专题

Page 55: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

55

技 术

部变量),这样就可以借助这些数据对象的销毁动作去释放约

束于它们的结点。人们还通过auto_ptr一类技术明晰动态结点

的所有权;通过引用计数管理更加复杂的链接结构,以避免过

度复制、实现结点共享,如此等等。然而,由前面讨论可知,

这些技术都有局限性,不可能解决所有动态存储管理问题。此

外,对于某些系统而言,采用这些技术去管理存储的开销也会

过大。

3.基本 GC 算法

GC的根本问题在于它本质上就很难做,它本身就是个矛

盾。我们无法直接找出废料,所有从根指针出发能找到的东

西都在工作空间里,而 GC要找的正是那些找不到的东西。

应该看到,在整个动态存储区里,存储管理系统掌握着

自由空间;如果知道了所有根指针,顺藤摸瓜就可以将工作

空间的所有结点找出来(这不过是一个以动态结点为结点,

以指针链接为边的有向图遍历问题)。所有废料结点的集合

就是自由空间和工作空间之和相对于整个动态存储区的补

集。看到这一点,下面的两种基本算法就很自然了。

标记-清扫(mark-sweep,MS)算法

如前所述,标记-清扫算法是最早的 GC算法,其基本

思想很简单:弄清了哪些东西有用,剩下的就是应收集的废

料结点。实现这种算法的收集器(collector)分两阶段工作:

在标记(mark)阶段,收集器从静态区和栈区的所有根指

针出发,沿指针链访问所有可达结点,并在它们的特殊位置记

录达到信息(如设置一个标记位)。

在随后的清扫(sweep)阶段里,收集器需要对整个动态区

做一次遍历,在遍历中将所有未加标记的结点送回自由空间。

MS 收集器的最常见工作方式是在存储耗尽时启动,在

它工作期间要求工作例程挂起等待,直到一遍 GC完成。这

种工作方式很容易实现,只要令存储分配函数在无法满足要

求时调用收集器,等到收集器返回后再做所需的分配。这一

方式的优点是实现比较容易;缺点是工作例程需要较长时间

的等待(具体时间依赖于动态空间的大小等因素)。

MS收集的一个特点是:有用结点总保持在原位。这一特

征的正面作用是能保持原有的位置信息(有些程序可能利用

这种信息);缺点是回收得到的存储块与活结点交错分布,

如果结点大小不等,就可能造成存储碎片问题,给随后的存

储分配带来困难。当然,存储碎片是动态存储管理的公共问

题,并不是GC造成的,人们也为此开发了许多技术。MS算

法的一种变形是标记-紧缩(mark-compact)算法:将标记

了的结点紧缩排列到动态区的一端,这样就使收回的自由区

连成一大块,为随后的分配提供了方便。

MS收集还有两个主要缺点。第一,完成一遍GC的工作

量正比于整个动态区的大小,无论实际有多少活结点,能收

集到多少空间。第二,由于回收的存储块与活结点交错分

布,重新分配会造成老结点与新结点交错的局面,易于造成

同一数据结构的有关结点的非局部化分布。在分页式存储管

理的系统中,这种情况可能造成很大的效率损失。

结点搬迁(move)算法

70年代一些研究者提出了基于结点搬迁(或结点复制)

的 GC算法,并成功地应用于若干重要的 Lisp系统。这种算

法的基本想法也很自然:将一个动态块里的所有活结点搬

走,整个块就自由了,其中的废料也回收了。图4显示了基

本搬迁算法的工作方式:将整个动态区分为两个半区,收集

器的工作就是逐个将活结点从一个半区(称为from区)搬到

另一半区(to区)。最简单的实现方式是在 from区满时开始

结点搬迁,直到 from区搬空后,就可以在 to区的空位继续

分配了。一旦to区满就交换两个半区的地位,开始另一轮结

点搬迁工作。将所有结点搬迁到另一半区实际上是一种图拷

贝,研究者为实现这一过程提出了有效而简单的算法。

人们发现结点搬迁是一种意义清晰的操作,很容易实现

一个算法,让收集器与工作例程并行地或交错式工作,在收

集器不断将结点从 from区搬到 to区的同时,也让工作例程

同时工作,并不断在 to 区分配所需结点。同样,直至 from

区搬空的时刻交换两区的地位,系统进入下一个执行周期。

搬迁式收集的一个重要优点是回收后的空间是连续的,

这对自由空间管理和分配很有利。还有一个优点是其工作开

销正比于工作空间中活结点的数量。然而,移动结点有可能

改变程序语义(如果工作例程的某些工作依赖于结点位

置)。如果系统里有一些规模很大而且生存期很长的数据对

象,收集器也需要将它们搬来搬去,做的完全是无用功。

上世纪 70年代到 80年代人们设计开发了一些 Lisp机和

其他类似机器,其重要特点之一是设法为 GC 提供硬件支

持,许多机器支持的就是结点搬迁式 GC。随着通用微机的

发展和价格的迅速下降,有关这类专用机的研究现在已基本

上销声匿迹了。

上面讨论的是最基本的 MS算法和结点搬迁算法。出于

对这些算法各种不同侧面的不同考虑,人们提出了许多修正

的算法。这些算法在时间空间消耗,实现复杂性等诸多方面

图 4 基于结点搬迁的 GC

Page 56: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

56

各有千秋。可以说,每种算法都有各自的长处和短处。

4.GC 的一些基本问题

本节将讨论一些与 GC有关的基本问题,以帮助读者进

一步理解 GC 的性质和影响。

应该注意到基本 GC算法所要求的一些基本工作条件。

首先,收集器必须知道并能访问所有的根指针,以便从它们

开始标记或结点搬迁工作。第二,收集器必须理解所有结点

的内部结构,知道一个结点里的哪些位置是指针,需要沿着

它们进一步向前检索。第三,收集器必须知道整个动态区的

结构和范围。我们可以将满足这些要求的工作环境称为 GC

的友好环境。友好环境里的 GC相对而言更容易些,常规的

函数式语言或符号计算系统里的收集器通常是在友好环境里

工作。有关非友好环境里的 GC 问题将在下一节讨论。

易见,相对于工作例程而言,收集器处在另一个更低的

层次中。例如,为能做标记或者结点搬迁,收集器必须能访

问所有的根指针变量,这就可能需要打破常规的作用域规则

(例如,某些指针可能是过程和函数的局部变量)。此外,MS

收集还需要以某种低级方式遍历整个动态区,以“物理方

式”检查其中的所有单元。

对GC算法的基本要求包括可靠性和完全性两个方面。可

靠性要求GC的存在及其工作不会改变工作例程对自己所掌握

的所有信息的认识:它所使用的数据结构不会因为GC而破坏

或改变,即使这些数据结构被移动,其基本语义也应保持不

变。显然,如果收集器执行中移动了工作空间里的结点,它

就必须正确更新所有有关的指针。完全性可分为强完全性和

弱完全性两个级别。强完全性要求在每遍GC完成之时系统里

已不存在废料,这就保证了全部废料的回收;弱完全性要求

动态区出现的每个废料结点最终都会被送回自由空间(未必

在随后的第一次收集中)。

显然,工作例程的工作对收集器有干扰(这正是人们是

将工作例程称为mutator的原因。因为,从收集器的角度看,

工作例程不过是另一个程序或者进程,它常常以自己无法预

料的方式修改收集器需要遍历和处理的数据结构)。对收集器

而言,最佳工作方式是命令工作例程停止在当时的状态中,

自己独立地连续工作,直至完成一遍GC。前面关于搬迁式GC

的讨论中提到人们对并行工作的考虑。对于 MS 收集,我们

当然也可以实现某种增量式标记和增量式清扫。例如,可以

将整个标记过程划分为一些小阶段,完成一段后唤醒工作例

程。当然,要采用这一做法,就必须保存标记过程的执行状

态。如果需要保证工作例程不因存储耗尽而被迫暂停,还需

要考虑收集器与工作例程之间的执行时间分配问题。

随着动态存储分配和 GC的进行,自由空间、工作空间

和废料所占存储量呈现出一种有趣的脉动特征。以基本 MS

方式为例的情况如图5所示,其中的横坐标是时间,纵坐标

是存储量。全部存储被划分为三个区域,自上而下分别是自

由空间,工作空间和废料所占据的空间。假设收集器按最简

单的方式运行,图中显示了三个 GC工作周期中各部分存储

量变化的情况。周期开始于一次 GC完成之后,此时动态区

中只有自由空间和工作空间。随着工作例程的工作,自由空

间逐渐消耗,而废料(下面部分)也逐渐积累起来。直至自

由空间耗尽,系统里启动了另一次 GC,收集工作完成后系

统进入了另一个工作周期。显然,通过 GC回收的存储并不

是一点点逐步得到的,而是在一次 GC的最后一下子得到一

批存储。容易想见,结点搬迁式 GC也有类似表现,这一特

征也是由 GC 的本性决定的。

不难发现 MS收集的另一异常性质:在一次 GC工作中,

如果空间里的废料越少,收集所用的时间反而越长。因为工

作空间里结点越多,标记所用时间也越多,而清扫的收获却

越小。结点搬迁算法也有类似特证:在from区需要搬的结点

越多,搬迁工作量也就越大。而这轮搬迁后to半区空余可用

空间也越小,收集器就很快又需要投入下一轮结点搬迁了。

上面分析说明,GC 工作的效率需要靠一定量的多余动

态空间来保证。举例来说,假设一个程序里采用了搬迁式收

集器,工作空间中结点所占据的平均存储量为N。显然,动

态区总存储量必须大于2*N,否则系统将会在某个时刻进入

一种状况,在这一状态中,收集器将不断地把结点在两个半

区之间搬来搬去,而工作例程根本得不到运行的机会。简单

考虑可能认定在空间达到4*N以上时,程序可能形成一种合

理有效的工作局面。如果采用的是 MS收集器,最低空间的

需求没有那么大,但也是大一些更好。

5.分代式 GC 和保守性 GC

这里介绍其中两种重要的 GC想法和相关技术:分代式

GC 和保守性 GC。

分代式GC

80年代研究者对一些采用 GC的系统里的结点行为方式

做了许多模拟研究,发现了一种明显倾向:在许多系统里,

尚存的创建比较久远的(老)结点趋向于继续生存下去,而

新建的年轻结点则倾向于具有较短生存期,多数很快就将被

图 5 动态空间的时间特性

技术专题

Page 57: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

57

技 术

抛弃。从实际情况看,这些老结点常常链接在更具有全局性

的数据结构上,因此不常变动;而许多新建结点保存的是临

时性数据。而在基本 GC算法中,所有结点都被统一看待,

并没有利用结点的这种活动特性。

人们将这一认识应用于搬迁式 GC系统,由此形成了一

类称为分代式 GC的方法。其基本做法是将动态区分为多个

块,对每个块记录其中所分配结点的时间。进而设计一套GC

策略,其原则是优先在存放最年轻结点的块里做 GC,将块

中的活结点搬出去,达到收回其中空闲空间的目的。位于一

个块里的结点越老,对这个块做 GC 的频繁程度就越低。

这一工作方式有多方面优点。按上面说法,年轻结点常

常很快被抛弃,因此年轻块中的废料结点比例可能更大,这

样需要搬动的结点少,回收的空间多,因此可能提高 GC效

率。第二,避免了将大量有用结点反复搬来搬去的时间开

销。第三,通过将空间划分为多个块,降低了整个动态空间

的脉动幅度,实际上也减少了总的空间需求量。实现分代式

GC 的关键是设计好一套合理的工作策略,除了基本设计之

外,还需要做一些试验和统计分析。

显然,普通搬迁式 GC同样可以采用将动态区分为多个

块的方式。例如采用一种简单的循环搬迁策略。当然,这样

做将不能得到分代方式的优势,而且也将遇到下面问题。

动态区分为多个存储块之后,搬迁一个块里的结点时会

面临一个重要问题:进入这个块的指针不仅可能来自命名变

量,还可能来自其他存储块(这种指针可称为外来指针)。

在搬迁一个块里的结点时,外来指针和根指针都必须考虑,

而扫描所有存储块去找这些指针的方法根本就不可取。为解

决这一问题,就需要为每个存储块附一个外来指针表,并解

决一系列复杂的更新问题。这些情况都会大大增加算法的复

杂程度,也会降低系统的工作效率。

即使存在上述问题,人们仍然看到了分代式 GC的性能

优势。人们所做的一些实例测试表明,采用分代式 GC,在

许多典型系统(函数式语言系统或 OO语言系统等)里,可

以将完成 GC 工作所占用的时间减少到整个系统开销的 20%

甚至10%以下。而在类似测试中,在采用MS方式的系统里,

GC的开销甚至可能达到整个系统开销的40%。然而,这种比

较的合理性也有待考虑,因为采用复杂 GC算法时,已经将

一些开销转嫁到了工作例程。

保守性GC

前面的各种 GC技术实际上都假定收集器是在一个友好

的环境里工作的,也就是说:根指针集合已知,所有结点的

结构(关键是其中所包含的指针个数和位置)已知。这样,

收集器就能确切地知道从那里出发,怎样去找可达结点。如

果现在的工作是自己实现一个包含 GC功能的系统,其中存

在一个明确的根指针集合,系统里的结点类型不多,而且所

有结点内的指针布局结构都可以在设计系统时确定,上面的

GC 技术都是可行的。

然而,有时情况并非如此。例如,假设我们要开发一个

带有 GC 功能的 C(C++)动态存储管理库,希望用它代替

标准库的 malloc/free函数(或 new/delete操作),支持人们

利用它写各种程序,并能自动完成动态存储的回收工作。此

时上面的假定就都不存在了,在这种情况下应该如何做 GC

呢?针对这类实际问题,人们提出了保守式 GC 的思想。

保守式 GC的“保守”包含两个方面的内涵。首先,在

这种 GC过程中不移动结点,以防因为移动结点造成程序语

义的改变。因此,这类 GC收集器一般采用 MS算法。其次,

由于这里并没有认定活结点的确切依据,保守的做法就是将

所有可能活的结点都当作活结点予以保留。

具体做法就是将扫描中遇到的可能是指针的单元都当作

指针看待和处理。首先,在确定的系统里,指针分配方式也

是已知的。例如在某系统里指针占 4 个字节,从地址为 4的

倍数的单元开始存放。另外,程序中正在使用的整个动态区

是已知的,我们知道动态区的地址范围。这样,如果有一个

指向动态区的指针,它一定“保存于从 4的倍数地址开始的

4 个单元里,其中所存二进制模式表示的地址值位于动态区

范围地址范围中”。保守的(保险的)做法是,只要遇到内存

中的这种情况(“可能指针”),就将它当作指针看待。当然,

这里不必考虑有意隐藏指针的情况(例如,有意将一个 4字

节指针分别存放于两个分离的 2字节变量里。收集器的设计

策略应该是告诉使用者不要隐藏指针,否则后果自负)。

保守式 GC需要有存储分配的配合,在结点里以已知可

访问方式保存有关结点大小的信息。此外,保守式 GC需要

一种方式去扫描静态区和栈区。在一次 GC开始时,保守式

收集器遍历整个静态区和栈区,将遇到的所有“可能指针”

都当作根指针看待,从它们出发开始标记“活结点”。对于

遇到的每个“活结点”,也同样采用保守的方式去遍历它,

将其中所有的“可能指针”都当作指针看待。这样工作下去,

直至将所有“可能”正在使用的结点标记完毕,随后就是正

常的清扫收集(还可以做相邻结点合并等)。

由于在非友好的环境里工作,保守式 GC必须尽可能对

环境做最少的假定。保守式 GC的首要目标是可靠性,应该

尽可能保证 GC不破坏工作例程的工作。显然,采用保守式

GC 方式就不能保证完全性。例如,假设静态区或者栈区中

的某个整型常量里保存着一个整数,而这一数值恰好能解释

为一个合法的动态区地址,它就会被收集器看作一个指针,

而被它“指向”的结点即使早已失去访问路径,也不可能回

收。当然,假定指针长 32 位,而实际动态区的可能地址数

目远远小于232,那么(从统计上看)出现这类偶发情况的可

能性将是很小的。也就是说,无法回收的死结点数目通常不

Page 58: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

58

会太多。

6.对 GC 的进一步研究

GC技术的发展和软件实践需求促使人们研究了许多与GC

有关的新问题和新技术。本节简单介绍和讨论其中的一些情况。

GC与资源管理

GC 是一种自动存储管理机制,其直接目标是自动管理

动态存储。由于 GC取得了一些成功,人们就希望能将它的

作用延伸到一般性的资源管理。是不是可能要求 GC为我们

回收所有不慎流失的资源呢?首先,事情总可能自动做,但

问题是怎么做,需要多大开销,结果是否能令人满意。

应该看到存储资源的几个特点:首先,存储是计算机里的

一项大宗资源,值得认真为它提供一种自动回收机制。其次,

存在一种合理而明显的方式确定一个结点是否废料,通过指针

链,很容易判断一个结点是否在用。第三,存储块的使用可能

形成复杂的链接结构,其中一些结点的流失时刻可能无法在编

程时确定,因此必须通过结点的可访问性去间接确定。正如前

面所述,GC是不得已而为之的事情。从这几个方面看,系统

中需要管理的其他资源的性质都与此不同。

首先,人们常常提出的其他资源都是数量很少的个体,专

门提供自动管理是否值得?除非这些资源的管理难度极大,无

法在编程中判断和处理,在迫不得已的情况下才应考虑通过类

似GC的方式。第二,对于这些资源而言,并不存在一种(或

几种)合理方式去判断其在用或者已经弃之不用。如果想通过

GC的方式处理,只能利用某种模式约定,而这类模式约定必

然需要额外的空间和时间开销。第三,这些资源通常不会形成

相互“链接”和共享,通过编程技术来管理应该可行。实际上,

这些资源的使用通常都有很明显编程模式,采用C++的“资

源管理即初始化”一类技术,处理这种问题远比GC有效得多。

还有一点也值得注意:GC 的发生有其内在的非确定性

特征。因此,各种语言里依赖于 GC的终结处理机制都提出

了一些警告,还会告诫程序员:绝不能依赖于有关终结动作

的执行顺序(你无法保证哪项资源先释放)。

GC 研究的其他问题

有关 GC的研究已经继续了40多年,除了设法修改已有

算法或研究新算法之外,重要原因是应用发展提出了许多新

问题。下面简单介绍其中的一些情况。

不同系统里的 GC。早期 Lisp 系统里的 GC比较容易做,

因为那里的结点种类很少,而且都具有同样大小,也比较容

易研究结点活动的规律性。现在复杂软件系统里的 GC则困

难得多:结点大小不同,给分配和回收都带来很多麻烦;使

用情况错综复杂,难以发现一般性的可利用模式。由于 GC

过程牵涉到太多因素,很难实现具有自适应性的 GC系统。

与底层存储管理的相互作用(与分页系统的相互作

用)。现代计算机系统常常是在分页等底层存储系统之上工

作的,GC 要完成动态存储区的各种操作,必须与大范围的

内存打交道,因此与分页系统的相互作用就非常重要。

实时问题。复杂的嵌入式系统常常需要长时间不间断工

作,因此可能需要GC。而这类系统中的许多又是在实时环境

中工作,对系统响应时间有明确要求。GC可能在任何时候发

生,为保证系统的实时响应,就要求 GC 工作不能导致工作

例程的长时间停顿。为此人们提出一些实时 GC 算法,基本

想法或是采用增量式工作方式,或者通过并发进程完成GC。

并行GC模型。人们一直希望GC工作能与工作例程并行

进行,最好是有专用的硬件去完成。

分布式系统里的 GC问题。在分布式系统里,根指针集

和结点都可能分布在一组物理上分布的存储器中,需要一组

在不同存储区上工作的 GC进程协作完成收集工作。人们认

为这方面的研究很重要,但还没有太多成果。

其他GC模型。人们也提出了一些基于其他要素的GC方

法,例如基于类型的 GC模型。虽然这些想法还不是实际系

统的主流,但提出的新思想也值得注意。

超大空间中的 GC 问题。在超大内存中做 GC 产生了新

的规模问题。GC 工作的代价正比于存储区的大小,而服务

器的工作又具有一定的实时性质。这些情况都使超大空间里

的 GC 成为一个必须研究的问题。

为减少 GC而进行的静态程序分析。我们可能看到有人

说某个系统会做静态 GC。我个人认为这应该看作是用词不

当,因为废料的产生本质上是动态的,不可能静态处理。这

些人所说的应该是指通过静态程序分析,设法在编译时确定

将要丢失存储的程序执行点,在目标代码里自动插入有关回

收动作(以避免废料产生)。这方面的研究已经进行了许多

年,有效利用这些技术可能降低发生 GC的频度,减少系统

中 GC 的工作量,也是很有价值的想法。

7.总结

从本质上,可以认为动态存储管理是想在常规计算机线

性编址的存储器基础上模拟一种结点存储器,程序中需要的

各种结点可具有所需的大小和结构,可以根据需要随意申请

和使用,根据需要随意释放。GC 研究则是希望使人完全摆

脱确定合理释放时机的负担。

总结一下,本文对GC的讨论中包含了如下认识:1)GC是

一种极重要的软件技术,将在今后的许多软件系统中发挥重要作

用。因此我们需要了解其基本情况和性质。2)GC不是免费的午

餐,也不是万灵药,它在解决一些问题的同时也会带来一些问

题。3)GC的引入将给一个软件系统的设计开发和内在特性带来

深刻影响。一个系统是否应依赖于GC 则需要做具体分析。4)

GC还是一种正在发展的技术,有许多新问题需要解决。

技术专题

Page 59: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

59

技 术

有必要借助系统自动的 GC 机制。

GC 不只是给程序员带来方便,也让系统变得更可靠,

即使遇到 bug 或恶意搞破坏的程序所导致的错误仍可回复。

大多数的时候,.NET CLR以及JVM都可以让程序员放下内

存管理的重担,也因此不再有直接处理内存位置的必要,间

接地,对于安全(Security)的提升也有助益。

内存配置种类

内存配置方式可以概略地分成三种,分别是:

u静态配置(static allocation):是最早出现的内存配

置方式,将内存区域绑到(bind)某个名称,一直到程序结

束为止。这类的内存,常被称为全域变量(global variables)。

u堆栈配置(stack allocation):堆栈配置使用在堆栈

框(stack frame)中,内存配置的生命周期受到其区块范围

(block scope)的影响,当进入区块时,会自动配置内存,

当离开此区块,内存配置也随之消失。这类的内存,常被称

为区域变量(local variables)。

u动态配置(dynamic allocation):动态配置可以随时

配置,随时释放。配置当然是由程序员负责,但是释放则有

不同的作法。对于 C/C++这一类传统的系统语言来说,释

放仍是由程序员负责;对于 Java与 C#这类 VM-based现代

语言来说,释放是由 VM(虚拟机)负责。这种用来进行动

态配置的内存,称为 heap。请注意,这里 heap和数据结构

的 heap没有任何关系。

上述三种方式,哪一种最好?简单地说,耗费的内存成

本比越低越好,也就是说“越晚配置,越早释放”最好。堆

栈配置的速度又快,配置的时间点又很晚,释放的时间点又

很早,一切全自动所以程序员的负担又很轻,诸多优点使得

堆栈配置成为最佳选择。祸福相倚,堆栈配置的内存也因此

受到范围的影响,严重地限制了程序上的弹性。所以我们有

时候必须使用动态配置。只有在堆栈配置与动态配置都不适

合的情况下,才使用静态配置。

对象的内存究竟要如何配置,其实这方面深深受到对象

Why GC ?

硬盘容量越来越大、内存容量也越来越大,大部份的原

因在于:软件体积越来越大。几百 KB内存就能打发一个程

序的时代,已经不复存在。现在软件内部的组织方式看起来

就像是许多对象彼此之间盘根错节,互相牵扯不清,所以这

类软件的内部指针(pointer)管理相当复杂而为人所诟病,

也因此容易导致内存管理不佳。

计算器科学家一直在寻找最佳的内存管理方式,每个人

都有自己喜好的方式,长期以来争论不休。有一个老掉牙的

笑话是这么说的:C语言的程序员认为内存管理太重要了,

所以不应该由系统来管理;Lisp语言的程序员认为内存管理

太重要了,所以不应该由程序员来管理。对同一件事,有同

样的目的,看法却可以如此南辕北辙。

有人很反对 GC,他们认为只有水平不够的人才会将就

着使用 GC,他们讥笑 GC的效率不彰,认为 GC会导致程序

常常“呆住”好一阵子。

有人很赞成GC,他们认为GC可以减低软件开发过程中

的负担,虽然因此少了一些控制权,但是就像是“蜘蛛人”

(spiderman)电影中所说的:“责任伴随着权力而来”。换言

之,想要少负一些责任,就要放弃一些权利。

暂且不理会这些人的说法,而单纯地从市场的角度来

看,GC 似乎越来越普及了。新一代的语言(诸如 Eiffel、

Python、Ruby、Java、C#)都是支持GC的。究竟这样的趋

势是怎么造成的?

GC受欢迎其来有自。首先,GC已非吴下阿蒙,经过这

些年的演进,非但硬件效率提升可以让 GC的接受度提高,

连 GC算法也更聪明、快速,更甚以往。况且,开发现代化

软件是很复杂的事,程序员总是希望能把时间精力花在刀口

上,系统能代劳的事,能别插手就别插手。毕竟,自行管理

内存可不是一件易如反掌的小事,一个不小心还可能捅出大

搂子。研究显示,C++项目有超过50%的开发时间在处理内

存相关的议题。所以,在这个时代,多数应用软件的开发都

.NET的自动内存管理 撰文 / 蔡学镛

关键词 .NET GC 自动内存管理 CLR Rotor

自动内存管理也就是俗称的垃圾收集(garbage collection,GC)。综观近年出现的语言,往往都具备GC,也因此有越来越多

程序员都已经在享用 GC所带来的好处了。但是对于这个默默在背后运作的机制,大部分的程序员不太清楚它的原理。本文

章以言简意赅的方式,为各位剖析了.NET CLR 和 Rotor 的 GC。事实上,大多数的 Java VM 也都采用这里所叙述的 GC 方式。

Page 60: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

60

生命期的影响。以.NET 来说,线程(thread)以及应用域

(application domain)等程序的控制结构(control structure)

都有各自的方式来管理对象的生命期和内存:线程利用堆栈

方式来储存数据,应用域利用静态方式来储存数据。储存的

方式由这些机制所决定,然而组件生命期和资源配置必须与

控制结构的生命期一致,但是有些情况下势必无法做到这一

点。还好,除了堆栈和静态配置,我们还可以在heap进行动

态配置,控制对象的生命期。

在动态配置的作法中,如果是由程序员控制内存的释

放,会造成一些问题:

u应该释放的时候忘了释放,造成“内存泄漏”(memory

leak):程序执行时会一点一滴地把内存吃光。

u不应该释放的时候却释放了,造成“悬空指针”

(dangling pointer)。一般认为,这是最糟糕的事。

在动态配置的作法中,如果是由 GC负责控制内存的释

放,就不会有这些问题了。下一节简单地介绍一个最常用的

GC 算法,并以一个范例以为说明。

标记、清扫、缩并

.NET CLR、Rotor以及许多Java VM的GC都使用“标

记、清扫、缩并”的算法。

GC会追踪(tracing)所有活着的对象。那些不是活着

的对象就是该被GC清除的垃圾。追踪活着对象的方式,就

是到所有的静态配置、动态配置、以及堆栈配置的对象内找

寻有哪些指向 heap 的指针;每找到一个指针(pointer),

都要顺着再继续找下去,以找出更多的指针,一直到全部找

过一遍为止,这就叫做沿着根指针追踪(t r a c i n g t h e

roots)。根指针的取得方式比较复杂,牵涉到JIT Compiler

等.NET CLR的子系统(sub-system),所以本文章不对此

部分进行说明。

在追踪期间,活着的对象被标记起来,然后未用的内存

就可以被扫除,这个方法就称为“标记 -清扫”(mark and

sweep collection)。

标记且清扫的作法固然简单又有效,但是一段时间之后

内存就会支离破碎,出现许多缝隙,容易导致内存不够用。

为了解决此问题,我们可以采用缩并收集(compa c t i n g

collection)的方式。最简单的缩并手法是:移除垃圾对象腾

出空位之后,将下一个活着的对象往前搬移到此位置,紧邻

着上一个活着的对象。对象只要有被搬移,就必须把所有参

考到此对象的指针也更新成新的地址。如此的缩并收集等于

是把所有可用的内存通通集中在一起,放在heap的后面,连

带造成的正面效益是:后续建立的数个对象都可以集中在一

起。如此的内存区域性(loca l i ty),可以减少虚拟内存的

paging,对于执行效率很有帮助。

通常程序中会充斥着许多短命对象,当对象的“存活

率”不高时,缩并收集尤其能发挥效益,因为搬移的次数少,

且缩并出来的空间大。试想,当对象一口气全死光了,连一

次的搬移都不必了。

为了帮助理解,下面以一个简单的范例作为说明:

1. 一开始 heap 是空的

2. 配置一个对象 A

3. 配置一个对象 B

4. 依序配置对象 C、D、E、F、G、H、I

5. 配置一个对象 J

6. 当准备配置一个对象 K时,发现所耗费的内存已经

超过段落限定的大小(下一节会介绍何为段落),所以无法

配置成功。必须开始进行GC,然后才能配置K。GC的第一

步骤是:假设一切都是垃圾。

7. 开始沿着根节点追踪

8. 追踪到根节点A,A是末梢节点,回到下一个根节点

9. 追踪到根节点 D

10. 透过根节点 D,追踪到 H

11. 透过H,追踪到J。J是末梢节点,回到下一个根节点

12. 追踪到根节点 C

技术专题

Page 61: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

61

技 术

13. 透过根节点 C,追踪到 D。D 虽然非末梢节点,但

是 D 已经被标记过了,所以 D 往下的节点都已经被标记过

了。不用继续从 D 追踪下去,回到下一个根节点。

14. 追踪到根节点 F

15. 全都追踪完了,没被标记的对象就是垃圾,开始清扫。

16. 扫除 B,搬移CD。由于 CD被搬移,所以必须更新

指向 CD 的根节点指针、并更新指向 D 的 C 指针。

17. 扫除 E,搬移 F。由于 F被搬移,所以必须更新指

向 F的根节点指针。

18. 扫除 G,搬移 H。由于 H被搬移,所以必须更新指

向 H 的根节点指针、并更新指向 H 的 D 指针。

19. 扫除 I,搬移 J。由于 J被搬移,所以必须更新指向

J 的 H 指针。

20. 更新 NextObjPtr 指针。

21. 终于完成了 GC。配置新对象 K。

缩并收集的另一种变形是复制收集(copying collection)

作法是将活着的对象搬到一个全新的heap,然后将旧的heap

内的东西全都丢掉,变成下次的新 heap。

和只用一个heap的作法比起来,复制收集的方式有数个

优点:因为所有的对象都会被复制到新的heap,在配置新对

象时变得很简单,不需要寻找最适合容纳的内存空位,直接

就可以配置。而且,因为缩并到新的heap,可以具备不错的

内存区域性。复制收集主要的缺点是:复制对象以及修改指

针的代价太高,而且因为使用两个heap,所以需要的内存空

间比原来多出一倍。

分代收集

透过引进分代收集(generational collection)的技术,

收集的成本是可以被有效地降低的(或者说是分次摊还)。

分代收集的方式将对象随着时间先后的次序分成多个

“代”,分代收集比起前述的各种方法都来得更复杂,但是已

Page 62: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

62

经被大多数的GC所采用了,因为它通常所耗费的时间比其

其它方式更短(毕竟不是整个 heap都要被处理,不是全部

的对象都需要被复制)。因为使用方式的差异,所以对象的

生命长短不一,有些很长,有些则很短,而分代收集则利用

了这一点特性。除了生命长短不同,对象的体积也不尽相

同。将 heap切割成不同的区域,不同区域放置不同特性的

对象,不同的区域使用不同的频率来进行GC,如此一来对

于 CPU和内存的运用会更有效益。

heap 分成数个段落(segment),从年老段落到年轻段

落。活得越久的对象,就会出现在越年老的段落中。段落的

年龄次序不见得和地址次序相同,因为段落是视需要而取得

的虚拟内存,而且 GC 算法也不需要在代之间保有地址次

序。在heap加入新的区段之后,对象会被建立到此一新的区

段。Rotor和.NET CLR的作法总是将最新建立的段落当作

最新的分代,一个分代只用一个段落。每个heap段落内的对

象,可被视为相同年纪。Rotor只使用两个分代,.NET CLR

则使用三个分代。

请注意:最老的对象“通常”可以在最低的地址处发现,

但是不保证一定是如此,因为被钉住对象(pinned objects)

会导致次序改变。所谓的被钉住对象,指的是有特殊理由而

不能被搬移的对象。.NET 允许对象被钉住,但是 Java则不

允许。

如果纯粹采用分代收集的方法,对象一开始会被配

置在最年轻的分代。经过一段时间之后依然存活的对

象,会被升级到新的分代(搬移到新的分代),这种技巧

加诸于缩并收集可以带来很大的好处。年轻分代的存活

率很低,年老分代的存活率很高,因为不同的分代被置于

不同的位置,所以不同的分代可以采用不同的作法来进

行GC。年老分代不太适合使用压缩收集,因为存活率太

高了,搬移的效益不高(缝隙不大),但成本很高(要搬

移太多次)。但是,在最年轻的分代,缩并搬移的作法就

相当适合。

Rotor 使用适应性分代(adaptive generation)的方式

来进行GC,它使用了两个分代,正因为两个分代,所以有

时候也称为半空间(semi-space)。除此之外,Rotor也会特

别隔离大型对象。当配置空间或内存满了,就会驱动GC;

当 heap的空间所剩无几,就会沿着跟指针追踪,可以收集

一个分代或同时收集两个分代的垃圾,可以视情况决定缩

并与否。

为了帮助理解,下面以一个简单的范例来说明分代

收集:

1. 程序执行的过程,产生了对象ABCDE,它们归属于

0分代。

2. 经过一段时间之后,CE变成垃圾。对于 0分代进行

GC之后,只剩下ABD,将ABD升级一个分代,成为1分代。

3. 程序继续执行,产生了对象 FGHIJK,它们归属于0

分代。

4. 经过一段时间之后,HJ 变成垃圾。对于 0分代进

行 GC 之后,只剩下FGIK,将 FGIK升级一个分代,并入

1 分代。

5. 程序继续执行,B变成垃圾,但未被清除,因为B是

属于 1 分代。

6. 程序继续执行,产生了对象LMNO,它们归属于0分

代。经过一段时间之后,GLM变成垃圾。现在的垃圾有1分

代的 BG,以及 1 分代的 LM。

7. 对于 0 分代进行 GC,但是不对 1 分代进行 GC,因

为 1分代占用体积尚未达到分代限制。GC之后,0分代只剩

下 NO,将 NO 升级一个分代,并入 1 分代。

8. 程序继续执行,产生了对象PQRS,随后0分代的AK

以及 1 分代的 PR 变成垃圾。

9. 由于 0 分代和 1 分代的体积都已经达到各自的分代

限制,所以同时对两个分代进行 GC。1 分代剩下 DFINO,

技术专题

Page 63: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

63

技 术

升级为 2 分代。0 分代剩下 QS,升级为 1 分代。

外部资源管理

事实上,Java 和 C# 程序员就算不懂 GC 的内部作法,

影响也不大。程序员只要注意下面两件事,即可在大多数的

情况下让 GC 发挥效率:

u不用的指针要及早设定为 null,以在下一次的 GC中

被清除。

u许多程序员制造了大量的短命对象,仍然浑然未觉。

例如:Java 和 C# 的“字符串加法”。

倒也不是说程序员可以就此高枕无忧。GC可以让程

序员免于注意内存管理细节,但是GC的触角并未伸及外

部资源管理,毕竟外部资源的生命期超出VM的掌控,而

是由 OS 来掌控。甚至,GC 的存在,会使得资源释放的

问题益形复杂。所以,对于外部资源管理,程序员必须戒

慎恐惧。

这里所谓的外部资源包括了文件、窗口句柄(window

handle)、网络socket等。从程序员的观点来看,那些代表外

部资源,或者使用外部资源的类别,必须要能取得或释放资

源。以文件为例,用来代表外部文件资源的类别,必须能取

得其文件句柄(file handle),必须能调用 close()来关闭文

件。资源的取得很简单,只要写在constructor内就行了;资

源的释放却很麻烦,因为在 GC插手的情况下,程序员不能

主动去释放对象。

但是无论如何,系统还是得提供一个方法让我们释放

外部资源,而对于.NET 和 Java 来说,清除资源的过程称

为终结(finalization)。简单地说,终结就是:VM 调用对

象的某method来释放外部资源。在VM发现对象变成垃圾

之后,且对象内存被释放之前,这个空档正是对象被终结

的好时机。

.NET 有个名为 Finalize()的 method(Java也有类似

的method),不需要传入参数和传出值,它做的正是终结

的事。当.NET CLR GC 运作时,如果发现某对象有提

供Finalize(),GC就会调用它。被标示为垃圾,且必须被

CLR 终结的对象,会被纪录在终结队列(f ina l i z a t i o n

queue)中。

请注意:不是所有的对象都必须被终结,只有那些有

提供Finalize()来释放外部资源者,才需要被终结。如果没

有外部资源,就不要提供Finalize(),就不会被终结。当可

终结的对象一被建立,立刻会被纪录在一个弱参考清单

(weak reference list)中。GC会监视着此清单,当所有

指向某“可终结对象”的强参考(strong reference)被

释放,GC 就会立刻将此对象的参考从弱参考清单中转送

到终结队列(finalization queue),对象持续活着。终结

线程(finalization thread)每隔一阵子会一一造访队列中

的对象,然后调用每个对象的终结方法(f i n a l i z a t i o n

method)。如果在终结的过程中,该对象没有再度被强参

考所参考到,此参考于是终于被释放,而对象也会依正常

方式回收。

请注意:终结不是一个万无一失的作法,在许多情

况下,程序员必须小心配合,以达到最佳的资源管理。

G C 发生的时间是由算法和系统的负荷所决定,也因

此,有时候我们有需要改用传统的处置方式(disposa l

pattern),而不要完全依赖刚刚所提的终结机制。也就

是说,程序员需要明白地调用Dispose()或Close()来“关

闭”资源。

提醒各位,不管是 Java或.NET,在终结时,都有可能

造成对象“死而复生”。这个议题很复杂,感兴趣的读者可

以阅读 Weak Reference 相关的数据。

结论

除了上述的 GC 之外,Java和.NET 其实还有另一个全

然不同的 G C ,用来管理分布式运算(d i s t r i b u t e d

computing)的对象生命周期,但是这不在本文讨论范围。

另外,执行时JIT编译所产生原生码(native code),

也会占用内存,毕竟新产生的原生码总得放在某个地方

吧!尽管 m e t a d a t a 必须一直处于随时可用的状态,

method却不然,method可以随着需要而编译产生。每次

需要执行某method,就编译一次,却不管先前编译过与

否,这会降低整体执行效率,但可以节省内存(因为不需

要 储 存 先 前 的 编 译 结 果 ),且 可 能 会 有 较 佳 的 地 域 性

(locality)。

Rotor 所使用的 JIT 编译器在这两种作法之间取得折衷

点,这就叫做程序代码丢弃(code pitching),也算是GC的

一种形式,只不过回收的不是数据,而是程序。当编译出来

的程序代码超过 code heap的最大容量,缓冲区内的全部内

容就会被丢弃(pitch),而堆栈内的全部返回地址都被改成

JIT编译器的地址,这使得后续的所有method都会被重新编

译。作法同GC一样,什么method的程序代码该被丢弃,考

量因素有许多,例如:原生码的体积、IL转成原生码所膨胀

的比率,或者需要耗费 JIT的时间等,这些都可以作为考量

因素。

不管是本地对象的 GC、远程对象的 GC 还是原生码的

GC,都是很复杂的主题。GC固然复杂,但是这一切都是值

得的,对于程序的可靠度和生产力提升都有帮助。如果你对

此主题感兴趣,你可以去研究Rotor和 JVM的Source Code,

以及 Richard Jones 所著的 Garbage Collection: Algorithms

for Automatic Dynamic Memory Management 一书。

Page 64: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

64

智能指针

C++ 程序员们已经习惯于用 RAI I 的技巧来管理内存

了。他们把指针封装到一个对象中,并重载这个对象的特定

运算符(例如“->”和“*”),使它能模拟普通指针的行为。

这样的包装对象就叫“智能指针”,因为它们不仅模拟了普通

指针的行为,而且还加上了更多的、聪明的行为。一个最典

型的智能指针的例子就是 C++标准库中的 std::auto_ptr类。

这个智能指针的设计思路是:当有异常出现时,保证内存能

被释放。但对于更复杂的内存管理问题,它就无能为力了。

例如:如果你使用std::auto_ptr来管理内存,那么对于一个

对象实例,只能有一个“拥有者”(即智能指针)指向它;std::

auto_ptr 的拷贝或赋值都会导致所有权转移、原来的 std::

auto_ptr 被设为 NULL。由于有了这样的限制,所以 std::

auto_ptr也就无法在标准容器(例如std::vector)中使用了。

很多其他的智能指针实现则致力于允许多个“拥有者”

(即智能指针)指向同一个对象实例。它们通常都在内部保

存引用计数,当智能指针被拷贝时则增加计数值,当智能指

针被销毁时则减少计数值。如果引用计数减少到了 0,则真

正销毁智能指针对象。在 CodeProject 网站上,有两个智能

指针实现使用了这种思想。1,2 另外,在 Boost 库中也有一个

使用引用计数思想的智能指针。3

这种“引用计数型”的智能指针实现起来很简单,而且

在很多情况下也的确适用。但想靠它们来实现真正的垃圾收

集是不大可行的,因为它们的灵活性还不够。一个重要的问

题就是“循环引用”的问题:如果对象A中有一个引用计数

的指针,该指针指向对象 B,而对象 B中也有一个引用计数

的指针,该指针指向对象A,那么这两个对象都将永远不会

被销毁,因为它们的引用计数永远不会减到 0。显然,想要

完全杜绝循环引用是很困难的。

解决这个问题的一条途径是:使用“弱指针”。所谓“弱

撰文 /William E. Kempf 编译 / 绯雨闲丸

一个C++的垃圾收集框架

在所有对C++的抱怨中,声音最响的可能就是“C++没

有垃圾收集机制”了。那些使用过更先进的语言(例如Java)

的程序员会发现:C++既难学又难用,而且还很容易出错,

因为你必须手工管理内存的使用情况。在某种意义上来说,

他们是对的;但另一方面,那些死忠的C++程序员们却又对

“把控制权拱手交给垃圾收集系统”的做法恨之入骨。

垃圾收集背后的思想是:由运行时系统来帮你管理内

存。在这样的一种体制之下,那些“必须要频繁地动态创建

对象的复杂系统”的代码复杂度会大大降低,因为你无须再

去操心对象的销毁。这听起来很诱人。既然如此,为什么那

些死忠的 C++ 程序员不肯接受它呢?

问题在于:内存仅仅是资源中的一种。除了内存之外,

其他种类的资源还有很多。可是,GC(Garbage Collection,

垃圾收集)系统只关心内存的问题,却对其他的资源置若罔

闻。在大多数支持 GC的现代编程语言中,程序员都仍然必

须手工管理其他的资源。与之相反,C++程序员却可以将资

源封装到一个使用 RAII(Re s ou r c e Acqu i s i t i o n I s

Initialization,初始化时获得资源)技巧的对象中,由此轻松

地管理这些资源:当包装了资源的对象离开了自己的作用域

之后,析构子会自动地释放其中封装的资源。

乍看上去,如果给支持 GC 的语言中的对象再加上析构

子,世界就完美无缺了。这个想法很好,可惜不能实现。大多

数支持GC的语言(例如Java)根本就没有“栈数据”的概念,

所有的对象都是在堆上创建的,只有在垃圾收集器运行的时候

才可能被销毁。而垃圾收集器运行的时机是不确定的,你无法

准确地知道它会在什么时候开始运行,因此也无法知道对象所

容纳的资源将在何时被释放——这对于很多资源来说是个非常

严重的问题。所以,你真正需要的是一种既支持GC、又支持

栈数据、同时还支持析构子的语言。现在,你知道那些死忠的

C++程序员为什么不肯接受垃圾收集系统了吧。

技术专题

在所有对 C++的抱怨中,声音最响的可能就是“C++没有垃圾收集机制”了。那些使用过更先进的语言(例如Java)的

程序员会发现:C++既难学又难用,而且还很容易出错,因为你必须手工管理内存的使用情况。在某种意义上来说,他们是

对的;但另一方面,那些死忠的C++程序员们却又对“把控制权拱手交给垃圾收集系统”的做法恨之入骨。本文介绍了一个

兼顾易用性和安全性的 C++ 垃圾收集框架。读者可以在 CSDN网站《程序员》频道下载本文示例代码。

关键词 C++ 智能指针 垃圾收集 gc_ptr

Page 65: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

65

技 术

指针(weak pointer)”是这样的一种指针:它指向一个对

象,但并不增 / 减引用计数。普通的 C++ 指针就是“弱指

针”,但使用它有一个缺点:当引用计数被减到0时,对象就

会被删掉,而被当作弱指针使用的普通指针则可能就此“悬

挂”起来。出于这个原因,我们往往会使用另一种智能指针

来作为弱指针。这种新的智能指针可以和引用计数的智能指

针彼此协作,这样当对象被删除时,“弱指针”就会被自动

地设为 NULL,从而避免了指针悬挂的问题。

这种办法的确有效,但仍然需要程序员来负责找出并修

正循环引用的情况——有时这并不容易,甚至不可能做到。

因此,有时 C++程序员们依然希望 C++能有一个 GC系统。

保守的 GC 库

此前已经有人实现了一些免费或者收费的 C/C++垃圾

收集器,可以用它们来取代 C/C++ 传统的内存分配方式。

这些垃圾收集器都被称为“保守的”垃圾收集器。也就是说,

在试图判断“是否仍有指针指向某个对象”时,它们都过于

谨小慎微了。之所以会这样,是因为在 C/C++中,指针有

可能被保存在其他的数据类型(例如联合体、整型数)中。4

遗憾的是,这就意味着这些垃圾收集器有时会无法回收那些

已经确实不再有用的对象。

这样的库还有另一个问题:它们只对那些用malloc或者

new创建的对象才有效,而那些用第三方库分配的内存空间

则无法被回收。

gc_ptr

那么,还有更好的C++解决方案吗?本文(以及附带的

代码)就是要提出一个更好的解决方案:将智能指针与传统

垃圾收集的概念结合而成的 gc_ptr。

gc_ptr 类解决的第一个问题就是“过于保守”的问题。

在 gc_ptr类看来,唯一需要纳入考虑范围的“对象引用者”

只有gc_ptr对象,所有其他类型的对象引用都被视为“弱指

针”而不予考虑。使用智能指针的另一个好处则是:实现起

来容易得多,因为垃圾收集器无须去寻找对象的引用——所

有引用都已经在系统中注册了。

第二个问题解决起来就比较困难了。gc_ptr的实现最初

是基于一个与之相似的类 circ_ptr 的(circ_ptr 类已经由

Thant Tessman 提交到了 Boost3,5)。在这个实现中,只要一

个指针所指的对象是通过operator new创建出来的,就可以

用这个指针来创建 circ_ptr对象。不幸的是,这个实现遗留

下了一个严重的漏洞:当 circ_ptr对象最初创建时,它会记

录所指对象的大小,并用这个数值来判断该对象是否包含了

其他的 circ_ptr对象,从而找出循环引用。但是,如果用一

个派生类的指针来初始化circ_ptr<base>对象,那么circ_ptr

记录的对象大小就是sizeof(base),因此无法准确判断所指对

象中是否包容了 circ_ptr 对象,从而过早地回收一个对象。

如果在原来的实现基础上加上模板化的构造子,可以减少这

种情况出现的几率,因为当使用者把指针类型传递给构造子

时,circ_ptr 对象就可以计算被指对象的大小。但是,在下

列代码中,错误仍然会出现:

base* factory(int i){

switch (i){case 1:

return new derived1;case 2:

return new derived2;

default:return 0;

}

}

circ_ptr<base> ptr(factory(1));

为了确实地避免出现上述情况的可能性,我们需要提供

一种途径,让使用者能够定制“哪些对象需要被回收”。在

gc_ptr的实现中提供了一个new运算符的重载版本,它接受

类型分别为 gc_detail::gc_t 和 size_t的两个参数,从而提供

了我们所需的定制能力。在这个重载版本中,待建对象的大

小被保存起来,使得gc_ptr能够判断一个对象是否包容了另

一个gc_ptr对象。于是,上面的例子就可以被安全地写成下

面这样(请注意其中不同寻常的 new 语法):

base* factory(int i)

{switch (i){

case 1:return new(gc) derived1;

case 2:

return new(gc) derived2;default:

return 0;

}}

gc_ptr<base> ptr(factory(1));

现在,gc_ptr所指的对象必须通过“new(gc)”这样的语

法来创建。传递给new运算符的“gc”变量是在一个未命名

的名字空间中声明的,这是为了方便使用起见。如果把“通

过标准的new运算符创建的对象”用来构造gc_ptr对象,就

会抛出一个 std::invalid_argument异常,因此在运行时找到

错误并防止误用也很容易。由于这样的要求,gc_ptr并不能

原封不动地直接取代普通指针,但仍然算得上一个相对简单

的替代者。对于使用者来说,唯一需要做的就是修改“创建

对象”的部分代码。

Page 66: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

66

接口

使用gc_ptr的接口还算简单。其中有两个全局方法,用

于控制垃圾收集和 gc_ptr 类本身。void gc_collect()

这个方法是垃圾收集器的核心。当这个方法被调用时,

它会使用 mark-and-sweep 算法来找到所有已经不被使用的

对象,并将它们销毁掉。程序员可以在任何时刻调用这个方

法以开始垃圾收集。但是,程序员并不是必须主动调用这个

方法的。如果从上次gc_collect方法被调用算起的内存分配额

达到了一个特定的上限(由程序员指定,详见下文),或者存

储空间被耗尽,那么 new(gc)运算符就会自动调用 gc_collect

方法。在大多数情况下,这种自动的收集就应该足够了。void gc_set_threshold(size_t bytes)

这个方法用于设置内存分配额的上限,new(gc)运算符将

根据这个上限值自动调用 gc_collect 方法。上限的初始值是

被设为1024字节的,但使用者可以调用这个方法来修改上限

值,以获得最佳的性能。在调用这个方法时,有两个有趣的

值可以尝试一下:如果你传入0,那么每次调用new(gc)运算

符都会引发垃圾回收,这会降低系统性能,但保证内存利用

率最高;如果你传入std::numeric_limits<size_t>::max(),则

会收到相反的效果(除非内存被耗尽,否则不进行自动垃圾

收集),这样可以获得最高的性能,但导致内存利用率最低。

另外还有一个有趣的技巧:你可以给这个方法传入一个

std::numeric_limits<size_t>::max(),将自动垃圾收集关掉,

然后在程序中新开一条线程,让它周期性地调用gc_collect方

法。这样,你就获得了“并行的垃圾收集”。在多线程的程

序中,使用这种技巧可能得到最佳的性能和内存利用率。gc_ptr::gc_ptr()

用 NULL 作为参数,构造一个 gc_ptr 对象。explicit gc_ptr::gc_ptr(T* p)

构造一个 gc_ptr 对象,使其指向 new(gc)运算符构造的

一个对象。template <typename U>explicit gc_ptr::gc_ptr(const gc_ptr<U>& other)

从一个gc_ptr构造另一个gc_ptr。这个模板定义允许相

关类型之间的拷贝,但是在目前的实现版本中,只有单继承

关联才能保证安全。如果能够找到支持多继承的解决方案,

它将会被加入未来的版本中。目前,你应该避免在gc_ptr中

使用多继承的类型。gc_ptr<T> gc_ptr::operator= (T* p)

让 gc_ptr 指向由 new(gc)创建的一个新对象。template <typename U>gc_ptr<T> gc_ptr::operator= (const gc_ptr<U>& other)

让gc_ptr指向另一个gc_ptr所指的对象。同样,这个模

板定义允许相关类型之间的拷贝,但是对多继承类型进行拷

贝是不安全的。T* gc_ptr::get() const

返回一个真正的 C++ 指针,这个指针指向 gc_ptr所指

的对象。这是一个显式转型操作。出于安全原因,gc_ptr不

提供转型到真正指针的隐式转型操作。T& gc_ptr::operator*() const

让 gc_ptr 能够以与真正的 C++指针相同的形式解引用

(dereference)。T*gc_ptr::operator->() const

让gc_ptr能够以与真正的C++指针相同的形式使用“箭

头运算符”。

优点

为C++程序员提供了简单的垃圾收集机制。gc_ptr的实

现代码很简短,只有两个文件,因此可以轻松地包含进你的

项目中。

避免了传统的引用计数型智能指针在循环引用的情况出

现时会引起的内存管理错误。

摆脱了“保守垃圾收集”的束缚,同时提供了安全的实现。

提供了一个灵活的接口,允许程序员控制垃圾回收的时

机,并且可以用一种简单的方式实现“并行垃圾收集”。

容易使用。

线程安全。当使用者在多线程程序中调用 gc_collect 方

法时,如果另一条线程正在进行垃圾收集,则对gc_collect方

法的调用可能被阻塞。实际上,这应该不会造成任何可观察

的速度差异,因为垃圾收集的操作本来就(相对地)比较慢。

缺点

效率与手工内存管理或引用计数的智能指针不可同日而语。

对于那些需要被回收的对象,要求使用重载的 new(gc)

运算符来创建。

对于使用了多继承的类型,有可能出现错误。

如果某个由 new(gc)返回的对象指针不被任何 gc_ptr对

象所指,那么这个对象就一定会泄漏。

注解

1 A Simple Smart Pointer,CodeProject 网站上的一篇文章。

2 A Smart Pointer Capable of Object Level Thread Synchronization

and Reference Counting Garbage Collection,CodeProject网站上的一

篇文章。

3 Boost,一个程序员组织,致力于开发高质量的类库。

他们的类库中,有很多有希望被加入 C++ 标准。

4 C++ 标准并没有指明可以用整型数类型来容纳指针,

但实际上大多数平台都允许程序员这样做,这已经成为了一

个很常用的技巧。

5 你可以在如下地址下载circ_ptr的源代码:http://www.

egroups.com/files/boost/circ_ptr.zip

技术专题

Page 67: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

67

技 术

个已经产生的对象“受托管”,Dispose方法则是手工释放一

个“受托管”对象。该接口通过 TObjectSafe 类具体实现。

IExceptionSafe = interface

procedure SaveException; // 记录发生的异常信息

end;

上面代码定义了 IExceptionSafe 接口,通过接口中的

SaveException方法来记录使用过程中发生的所有异常信息,

该接口通过 TExceptionSafe 类具体实现。

function ObjectSafe : IObjectSafe; overload;function ObjectSafe (out aObjectSafe : IObjectSafe)

: IObjectSafe; overload;function ExceptionSafe : IExceptionSafe;

function IsAs (out aReference {: Pointer}; const aObject : TObject; const aClass : TClass) : Boolean;

以下是实现部分的主要源代码:

//实现 IExceptionSafe 接口

TExceptionSafe = class (TInterfacedObject, IExceptionSafe) private FMessages : String;

public destructor Destroy; override; procedure SaveException;

end;

通过TExceptionSafe来实现IExceptionSafe接口,每一次

调用SaveException方法都会把产生的异常信息存储到私有字

段FMessages,当析构事件发生时则引发一个异常把所有的记

录下来的异常信息显示出来。

TInterfacedComponent = class (TComponent) private FRefCount : Integer;

protected function _AddRef : Integer; stdcall; function _Release : Integer; stdcall;

public procedure BeforeDestruction; override;end;

撰文 /Rossen Assenov 编译 / 凌晨

针对 Delphi对象和构件的垃圾收集器

Delphi提供了三种对象管理的方法:

1、配合着 try..finally语句来Create/Destroy某个对象。

2、使用TComponent的派生类——创建一个构件,让它

的所有者(owner)来释放它。

3、接口(Interface)——当某个接口的引用计数为 0时实

现它的对象就会自动释放。

接口是一种非常好的开发方法(我就经常使用它),但

是有些时候它也会成为一种负担,因为同一个事物可能有两

处声明。而且,对于大多数基础的 V C L 类(T L i s t 、

TStream⋯⋯)来说,构件和接口并不能作用于它们,因此

我们必须显式地调用它们的 create/destroy方法。

TObjectSafe

Delphi的帮助告诉我们不要让 TComponent的 owner类

实现任何接口,但是“禁果”总是最甜的。我们会看到一个

实现了接口的 TComponent的派生类是非常有用的,因为接

口的引用计数也发生了变化,因此当它离开作用域时它就会

自己释放自己,同时也会释放所有以它为 o w n e r 的构件

(Components)。我们来扩展这个极有用的功能使它能够维持

一个对象列表并且能够释放他们。

我们把这个接口命名为IObjectSafe,把实现这个接口并

且具有引用计数的 TComponent 派生类命名为 TObjectSafe。

以 下 是 接 口 部 分 源 代 码 ( 全 部 源 代 码 见 附 件

SafeUnit.pas):typeIObjectSafe = interface

function Safe : TComponent; function New (out aReference {: Pointer}; const aObject : TObject) : IObjectSafe;

procedure Guard (const aObject : TObject); procedure Dispose (var aReference {: Pointer});end;

上面的代码定义了 IObjectSafe接口,其中Safe方法使一

个祖先是 TComponent 的对象“受托管”,New 方法产生一

个祖先非 TComponent 的“受托管”对象,Guard 方法使一

关键词 Delphi 垃圾收集 TObjectSafe TExceptionSafe

“怎样对对象进行内存管理”是面向对象编程的众多基础问题之一。对于这个问题,不同的语言有着不同的解决方法。

本文介绍了一种适用于 Delphi 的自动内存管理机制。

Page 68: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

68

TInterfacedComponent 类是从 TComponent 类派生出来

的,之所以要这么做是因为我们要对_Release方法进行扩展。

由Delphi中的VCL源代码可以知道(Classes.pas)从Delphi 6

开始 TComponent类完全实现了 IInterface接口,这也就表明

由TComponent派生出来的类都具有“引用计数”的功能,而

我们所要实现的扩展也就是要其他的类(祖先非TComponent

的类)也能够通过这种“计数”功能实现自动释放,我们通

过重新改写 _Release 方法是的当引用计数为 0 时就自动调用

Destroy 方法(析构函数)来实现“垃圾回收”的功能。TAddObjectMethod = procedure (const aObject : TObject) of

object;

TAddObjectMethod是一个方法指针,用来指向类中的某

个方法。

//实现 IObjectSafe 接口

TObjectSafe = class (TInterfacedComponent, IObjectSafe) private

FObjects : array of TObject; FEmptySlots : array of Integer; AddObject : TAddObjectMethod; //变量类型是一个方法指针

procedure AddObjectAtEndOfList (const aObject : TObject); procedure AddObjectInEmptySlot (const aObject : TObject);

procedure RemoveObject (const aObject : TObject); public constructor Create (aOwner : TComponent); override;

destructor Destroy; override; function Safe : TComponent; function New (out aReference;

const aObject : TObject) : IObjectSafe; procedure Guard (const aObject : TObject); procedure Dispose (var aReference) ;

end;

TObjectSafe类由TInterfacedComponent类派生而来,并

且实现了 IObjectSafe 接口。所以 TObjectSafe 类也就具有了

“引用计数器”;覆盖了Destroy方法,具体实现了垃圾释放

的过程,当其引用计数为 0时会自动调用该析构方法。私有

区域里声明的方法和指针主要是用来管理那些祖先非

TComponent的类,通过FObjects和FEmptySlots这两个动态

数组来维护这该类的实例。

由此读者可以清楚地看到TObjectSafe类就是所有“受托

管对象”的管理者,对于那些由 TComponent 派生而来的对

象其 Owner就是 TObjectSafe类,而对于那些非 TComponent

派生而来的对象则由TObjectSafe类的FObjects和FEmptySlots

这两个动态数组来维护。由于我们把Delphi中所有的类分为

“从TComponent派生而来”和“非从TComponent派生而来”,

因此对于每一个实例的创建和释放都要有两种处理方法,下

面的就是源代码中两个很明显的处理方法

{--------------------------------------------------- TObjectSafe 的析构函数,当引用计数为 0 时执行该函数

----------------------------------------------------}

destructor TObjectSafe.Destroy; var aIndex : Integer; aComponent : TComponent;

begin with ExceptionSafe do begin

//释放祖先非TComponent 的对象

for aIndex := High (FObjects) downto Low (FObjects) do try

FObjects [aIndex].Free; except SaveException;

end;

//释放祖先为TComponent 的对象

for aIndex := Pred (ComponentCount) downto 0 do try aComponent := Components [aIndex];

try RemoveComponent (aComponent); finally

aComponent.Free; end; except

SaveException; end;

try inherited Destroy; except

SaveException; // 记录发生的异常信息

end; end;

end;

从这个析构函数中读者可以清楚的看到对于祖先非

TComponent的对象释放的时候是通过遍历FObjects这个动态数

组直接调用Free方法来进行的;而对于祖先为TComponent 的

对象释放的时候,由于TObjectSafe 派生于TComponent,所以

调用的是RemoveComponent ()这个方法。

{------------------------------------------------ TObjectSafe 的Guard 方法使一个已经产生的对象 " 受托管 "

------------------------------------------------}

procedure TObjectSafe.Guard (const aObject : TObject);begin try

if aObject is TComponent then // 对象的祖先是TComponent

begin if TComponent (aObject).Owner <> Self

then InsertComponent (TComponent (aObject)); end else AddObject (aObject); // 对象的祖先非TComponent

except aObject.Free;

raise; end;end;

从这个方法中,读者也可以明显地看到:先通过aObject

is TComponent来判断对象是否是由TComponent派生而来,

技术专题

Page 69: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

69

技 术

如果是则直接调用 InsertComponent ( )方法,否则调用

TObjectSafe 中的 AddObject ()方法。

主体代码介绍完了,那怎样应用?下面就是一个简单的实例:

procedure TestTheSafe;

var aMyObject : TMyObject; aMyComponent : TMyComponent;

begin with ObjectSafe do begin

New (aMyObject, TMyObject.Create); // 针对祖先非TComponent的对象

// or // aMyObject := TMyObject.Create; Guard (aMyObject);

aMyComponent := TMyComponent.Create (Safe);//针对祖先是TComponent 的对象

end;end;

注意“With"语句,你可以安全地使用 Safe而不需要专

门为它声明一个本地变量。以safe为所有者(owner)调用构造

器创建一个构件(Component),当代码执行到与With相对应

的那个end时,IObjectSafe的引用计数就会变为0,TObjectSafe

的析构函数(destructor)就会被调用,那么它所拥有的所有构

建(Components)和对象(objects)就会被释放。现在你就得到了

一个在Delphi中最好的方法:在你需要的时候创建对象,它

会精确地知道自己在什么时候不再被使用而自动释放自己。

New/Dispose是IObjectSafe的方法,它们的参数是一个无

类型指针,返回的是一个指向某个对象的引用,如果这个引

用的类型与对象常见的实际类型不匹配则会引发一个异常(但

这并不会引起内存泄漏)。但是这是一个灵活、松耦合的类

型,如果你想更安全地使用它,请用 Guard 函数来替代它。

你同样可以在一个内部有很多对象的复杂类的构造函数

(constructor)中创建一个IObjectSafe接口,这样一来你就不

需要在它的析构函数(destructor)中显式释放它们了。

注意在实现(implementation)部分中 TObjectSafe 的内

部 AddObject过程,它是一个指向方法的指针。我们大多是

用它来把一个对象加入到队列的最后,同时也有极少的情况

用来把一个对象加入到空的队列中,而你不需要时时关心已

经加入队列的对象什么时候需要释放。

TExceptionSafe

在TObjectSafe的实现(implementation)部分,另一个可以安

全使用的是 IExceptionSafe。当你对一系列的对象进行某个操作

时,你可能会遇到异常。这时,你通常会忽略遇到的异常。但

是最好的做法是记录下这些异常信息,并且稍后显示这些信息。

这就是 IExceptionSafe 的用途。它其实就是一个无参数

的SaveException过程,它通过使用系统的ExceptObject函数

来得到一个指向当前异常的指针。在你需要记录异常信息的

那个模块的起始处创建一个ExceptionSafe接口,当代码执行

到 with 语句相对应的那个 end 时 TExcetionSafe的析构函数

(destructor)就会检查有多少个异常信息被记录,并且引发

一个新的异常,其中含有被记录的所有异常信息。

with ExceptionSafe dotry

for aIndex := 1 to 10 do try //可能会引发异常的操作

except SaveException; end;

for aIndex := 10 to 20 do try

//可能会引发异常的操作

except SaveException;

end;

//可能会引发异常的操作

except SaveException;end;

IsAs运算符

function IsAs (out aReference {: Pointer};

const aObject : TObject; const aClass : TClass) : Boolean;begin

Result := (aObject <> Nil) and (aObject is aClass); if Result then TObject (aReference) := aObject;

end;

end.

我们经常需要检查某些对象的类型,通过使用is和as运

算符来使某个引用与改对象相匹配,如下面的代码:

if aSomeObject is TMyObject then

begin aMyObject := aSomeObject as TMyObject;

// do something with aMyObjectend;

使用IsAs函数,仅用一行代码就可以实现上述所有功能:

if IsAs (aMyObject, aSomeObject, TMyObject) thenbegin

// do something with aMyObjectend;

写在最后

使用上述技术,你可以很方便地对Delphi的对象和构件

进行内存管理,从而使你的应用程序更安全。

Page 70: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

70

电脑英语

Garbage collection algorithms

The JVM's heap stores all objects created by an ex-

ecuting Java program. Objects are created by Java's "new"

operator, and memory for new objects is allocated on the

heap at run time. Garbage collection is the process of

automatically freeing objects that are no longer referenced

by the program. This frees the programmer from having to

keep track of when to free allocated memory, thereby

preventing many potential bugs and headaches.

A great deal of work has been done in the area of

garbage collection algorithms. Many different techniques

have been developed that could be applied to a JVM. The

garbage-collected heap is one area in which JVM design-

ers can strive to make their JVM better than the

competition's.

Any garbage collection algorithm must do two basic

things. First, it must detect garbage objects. Second, it

must reclaim the heap space used by the garbage objects

and make it available to the program. Garbage detection is

ordinarily accomplished by defining a set of roots and

determining reachability from the roots. An object is

reachable if there is some path of references from the roots

by which the executing program can access the object.

The roots are always accessible to the program. Any

objects that are reachable from the roots are considered

live. Objects that are not reachable are considered garbage,

because they can no longer affect the future course of

program execution.

In a JVM the root set is implementation dependent

but would always include any object references in the

local variables. In the JVM, all objects reside on the

heap. The local variables reside on the Java stack, and

each thread of execution has its own stack. Each local

垃圾收集算法

Java虚拟机(JVM)的堆上保存了Java程序创建的所有

对象。对象通过 Java的“new”操作符被创建出来,新对象

的存储空间都是在运行期分配在堆上的。所谓“垃圾收集“,

就是“自动释放不再被程序引用的对象”的过程。由于垃圾

收集机制的存在,程序员不必再担心“应该在何时释放已分

配的内存”,因此就避免了很多潜在的错误和麻烦。

垃圾收集中大量的工作都由垃圾收集算法来完成。在这

方面,人们开发出了许多不同但都能适用于JVM的技术。“支

持垃圾回收的堆”是JVM设计者们可以致力研究并可能因之

获得竞争优势的一个领域。

任何一种垃圾收集算法都必须做两件基本的工作:首

先,它必须检测到垃圾对象的存在;其次,它必须回收垃圾

对象所占据的堆空间,并将回收的堆空间归还给系统,让应

用程序能够继续使用。一般来说,实现垃圾检测的方式是:

定义一组根结点,并从根结点出发检查其他结点的可到达

性。如果存在一条引用路径,使得执行中的程序能够从根结

点出发访问到被检查的对象,则该对象就是“可到达”的。

所有从根结点可到达的对象都被认为是“活”对象,而不可

到达的对象则被认为是垃圾,因为它们不会再对程序未来的

执行造成任何影响。

在JVM中,根结点集的规定是依赖于实现的,但当前的

局部变量所引用的对象总是包含在根结点集之中的。在JVM

中,所有对象都生存在堆上,局部变量则生存在栈上,每条

线程都有它自己的栈空间。局部变量可能是对象的引用,也

可能是内建类型(例如int、char、float)的实例。因此,任

垃圾收集算法编译 /CSDN

Page 71: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

71

技 术

variable is either an object reference or a primitive type,

such as int, char, or float. Therefore the roots of any

JVM garbage-collected heap will include every object

reference on every thread's stack. Another source of

roots are any object references, such as strings, in the

constant pool of loaded classes. The constant pool of a

loaded class may refer to strings stored on the heap,

such as the class name, superclass name, superinterface

names, field names, field signatures, method names, and

method signatures.

Any object referred to by a root is reachable and is

therefore a live object. Additionally, any objects referred to

by a live object are also reachable. The program is able to

access any reachable objects, so these objects must remain on

the heap. Any objects that are not reachable can be garbage

collected because there is no way for the program to access

them.

The JVM can be implemented such that the garbage

collector knows the difference between a genuine object

reference and a primitive type (for example, an int) that

appears to be a valid object reference. However, some

garbage collectors may choose not to distinguish between

genuine object references and look-alikes. Such garbage

collectors are called conservative because they may not

always free every unreferenced object. Sometimes a garbage

object will be wrongly considered to be live by a conser-

vative collector, because an object reference look-alike

refered to it. Conservative collectors trade off an increase in

garbage collection speed for occasionally not freeing some

actual garbage.

Two basic approaches to distinguishing live objects from

garbage are reference counting and tracing. Reference counting

garbage collectors distinguish live objects from garbage ob-

jects by keeping a count for each object on the heap. The

count keeps track of the number of references to that

object. Tracing garbage collectors, on the other hand,

actually trace out the graph of references starting with the

root nodes. Objects that are encountered during the trace

are marked in some way. After the trace is complete,

unmarked objects are known to be unreachable and can be

garbage collected.

何一个 JVM 的垃圾回收堆的根结点集都一定包含所有线程

的栈中的所有对象引用。另外,被加载的类的静态池中的所

有对象引用也都会成为根结点,这样的例子包括类名、父类

名、父接口名、值域名、值域签名、方法名、方法签名等,

它们都是 string型的对象。

根结点所引用的所有对象都是可到达的,因此是活对

象。另外,所有被活对象所引用的对象也都是可到达的。程

序能够访问所有可到达的对象,因此这些对象必须保存在堆

中。所有不可到达的对象都可以被当作垃圾回收,因为程序

没有办法访问它们。

在实现JVM时,可以让垃圾收集器知道“真正的对象引

用“和“看起来像是合法对象引用的内建类型(例如int型)

实例”之间的区别。但是,某些垃圾收集器可能选择不区分

这两者。这样的垃圾收集器被称为“保守的”垃圾收集器,

因为它们可能无法释放所有不被引用的对象。有时候,某个

垃圾对象可能会被一个类似于对象引用的实例所引用,从而

被保守的收集器错误地判断为活对象。因此,保守的收集器

偶尔会无法释放某些真正的垃圾,但换来了垃圾收集速度的

提高。

将活对象和垃圾区分开来有两种基本的办法:引用计数

和遍历。引用计数型垃圾收集器会在堆上的每个对象中保存

一个计数器,用这个计数器来记录指向该对象的引用个数,

并以此来区分活对象和垃圾对象。另一方面,遍历型垃圾收

集器则是从根结点开始切实地遍历整个引用图。在遍历过程

中遇到的对象就被做上某种标记。遍历结束之后,未被标记

的对象就是不可到达的,因此可以被作为垃圾回收。

Page 72: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

72

control,而 Nenad 负责的就是其中对于窗口机制部分的支

持。这时,Nenad就开始想,是不是可以把同样的技术应用

到更为广泛的窗口机制中,以获得更丰富的UI控制、组件、

以至于应用程序呢?于是,作为 ATL的一部分,WTL被开

发出来了。它将ATL进行了扩展,以使得它可以支持各种类

型的与 UI 相关的组件或者应用程序。然而,它并没有随着

ATL 一同集成在 Visual Studio 中被发布,于是 Nenad就决

定将它作为一个单独的 ATL 扩展库发布出去。

WTL提供了各种类来支持各种各样的用户界面元素:顶

级窗口、MDI、标准控件和通用控件、通用的对话框、属性

表以及属性页、GDI对象、UI更新、可卷动的窗口、分割窗

口、命令条⋯⋯ WTL 的实现使用了和 ATL 一样的模板架

构,所以对于ATL开发者显得很自然。同时它并没有改变或

者是隐藏那些 Windows 相关的结构,那些 Windows程序员

在使用 WTL时也不会感到很吃惊。WTL的一个主要设计原

则就是避免在没有引用到其他 WTL 类时,出现不必要的内

部依赖。这意味着我们的程序将只包含有我们实际上所使用

的代码,除此之外再无其他的东西。加上了模板的使用后,

这样做得到的结果就是一些非常小的、不依赖于运行库的程

序。WTL 专注于用面向对象的方法来编写 Windows 的用户

界面程序,同时保持代码的尺寸很小。同时,它也为开发者

提供了一个很好的基础,可以编写新的类来扩展 WTL。

二、 安装

如果你有微软的 PlatForm SDK,那么 WTL 就在其中,

在 PlatForm SDK的安装向导中可以选择安装 WTL,如图 1

所示:

如果没有PlatForm SDK,也可以到如下网址下载,最新

版本是 7.0。

撰文 / 王志刚

WTL初探― ----完美的 ATL应用程序框架

一、 背景介绍 

学过COM(Component Object Model,组件对象模型)

的人都知道,ATL(Active Template Library)是一个神

奇的工具,它使 COM 开发人员从地狱走向了天堂。使用

ATL能够快速地开发出高效、简洁的代码,同时对COM组

件的开发提供最大限度的代码自动生成以及可视化支持。

然而人们不得不承认,ATL只适合开发轻量级的、适合网

络传输的几乎没有或者根本没有用户界面要求的 COM 组

件。要想使用窗口控件必须手工编写一些包装代码。另外不

要忘记ATL只是一个组件开发的模板库,并不是应用程序

框架(framework)。因此,当你编写应用程序的时候仍然

要面对应用程序框架的选择问题:MFC或者SDK。显而易

见,每一种都不是理想的选择。MFC与 ATL混杂在一起,

则抹杀了ATL的轻量级的优点;使用SDK则似乎又回到了

原始社会(虽然这有点夸张),因为你必须自己手工添加框

架类。面对这种局面,WTL适时浮出了水面。

那么WTL是什么呢?WTL全称为:Windows Template

Library(又是模板),与ATL只有一字之差。它是微软ATL

开发组成员 Nenad Stefanovic 先生在 ATL Windowing 机制

上发展起来的一整套 GUI框架,运用模板(template)技术

组织和创建GUI对象,构筑了精致的面向对象框架,使面向

对象与模板达成了精致的融合。虽然其使用者人数很少,但

确实是“用过的都说好”,有人甚至说,这是微软有史以来

推出的最优秀的一个应用程序框架。不过具有讽刺意味的

是,WTL并没有得到官方支持,技术文档相当稀少。至于为

什么微软没有支持 WTL,我想可能是微软不想让WTL彻底

终结 MFC 吧;亦或是怕影响 C#的推广(当然只是我个人

主观臆断)。WTL 是 Nenad在从事 ATL开发工作时的产物。

那时 ATL 开发组正在扩展 ATL,使之得以支持 ActiveX

MSDN

关键词 COM ATL WTL Visual C++

WTL 全称为 Windows Template Library,是微软 ATL 开发组成员 Nenad Stefanovic 先生在 ATL Windowing 机制上发展起来的一整套

GUI框架。它运用模板(template)技术组织和创建GUI对象,构筑了精致的面向对象框架,使面向对象与模板达成了精致的融

合。虽然其使用者人数很少,但确实是“用过的都说好”,有人甚至说,这是微软有史以来推出的最优秀的一个应用程序框

架。本文将向读者概要介绍这个应用框架。

Page 73: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

73

技 术

h t tp://down load.mi c r o s o f t . c om/down load/

VisualStudioNET/Install/7.0/WXP/EN-US/WTL70.exe

下载回来的文件是一个自解压的 zip 文件,解压缩到一

个目录中后应该有AppWiz60、AppWiz70、Include、Samples

等四个文件夹和一个 Readme.htm 文件。其中 AppWiz60是

用于 VC++6.0 的 WTL 应用向导,而 AppWiz70 则是用于

VC++.NET 的应用向导;这个向导和 MFC 应用程序向导

(MFC AppWizard)十分相似,有过 MFC编程经验的人应

该不会感到陌生。

用手工方式安装VC++6.0的向导,就是将AppWiz60目

录 下 的 A t l A p p 6 0 . a w x 文 件 复 制 到

“%VC60Dir%\Common\MSDev98\Bin\IDE”目录下

(%VC60Dir% 根据你的 Visual C++ 6.0安装目录而定);

VC++.NET的向导安装稍有不同,首先将 AppWiz70\Files

目录下的所有以 WTLApp70 命名的文件,复制到 VC++.

N E T 的 项 目 ( P r o j e c t s ) 目 录 中 , 即 :

%VC70DIR%\VC7\vcprojects(其中 %VC70DIR% 是你的

VC++.NET 的安装目录),然后用记事本打开 WTLApp70.

vsz文件,编辑 ABSOLUTE_PATH(绝对路径)这一项如:

Param="ABSOLUTE_PATH = %WTLDIR%\AppWiz70\Files"

,这里的 %WTLDIR%是 WTL 的文件所在的目录(如:C:

\temp\WTL)。接下来安装 WTL 的类库。在 VC++ 的安装

目录下(如 D:\P r o g r am F i l e s \M i c r o s o f t V i s u a l

Studio\VC98)新建一个 WTL 文件夹,然后将整个 Include

目录复制到其中,再在 VC++IDE中包含这个目录,这样编

译器在编译项目时可以找到这些文件。至此我们就完成了

WTL 的安装。

三、例程

我们知道 ATL是一个 COM组件开发工具,它的设计初

衷就是简化 COM编程,使程序员“快乐地”进行COM组件

开发。然而如果要充分地挖掘ATL的潜力,开发出更灵活、

强大的 COM 组件,则必须对 COM 有比较深入的了解。而

WTL 则是构建在 ATL 之上,要掌握 WTL 必须先学习 ATL

的知识。因此有人说“如果你不是一个好的 ATL 程序员的

话,你就不可能成为一个好的WTL程序员;如果你不会COM

编程的话,你就不可能成为一个好的ATL程序员;可是一旦

你决定开始学习COM,你就迈出了踏向地狱的第一步。”这

话听起来虽然有点危言耸听,但 COM 很难理解和掌握却是

不争的事实。那么我们是不是非得掌握了 COM 之后才能学

习和使用 WTL呢?其实相对于COM来说,Win32用户界面

(UI)的知识对于理解WTL显得更为重要。不过毫无疑问的

是有 COM 的知识会更好一些,但它并不是必需的。下面我

将以一个例子来说明如何用 WTL 编程。要看懂这个例子,

前提是你必须有 Win32 的界面编程知识和 ATLWindows

Class 的知识。

在这个例子里,我们将创建一个单文档界面的应用程

序。它可以打开位图文件并显示出来,另外还实现了最近文

件列表功能。希望通过这个例子能使你对 WTL 有一些感性

的认识。准备好了吗,Let's Go!

启动 VC++6.0,单击 File->New,如果你正确安装

了 WTL,那么ClassWizard向导列表第二项就是“ATL/

WTL AppWizard”。 选择 ATL/WTL AppWizard项 ,

在 Project name 中输入 BmpView(如图 2 所示),单击

OK。来到 ATL/WTL AppWizard -Step 1of 2,出现

如下画面(图 3):

缺省的应用程序类型就是 SDI Application(单文档界

面)。接受缺省设置,单击 Finish 结束。

这时按 Ctrl + F5,稍等片刻一个单文档的窗口就会出

图 1 平台 SDK 安装向导

图 2 WTL 向导

图 3

Page 74: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

74

现了! 现在我们看一看 AppWizard 生成的框架(如图 4 所

示),它包含一个框架类 C M a i n F r a m e 和一个视图类

CBmpView 以及一个对话框类 CAboutDlg。图 5是生成的文

件,BmpViewview.h是视类接口文件,mainframe.h是框架

接口文件,而 BmpView.cpp 则为主程序文件。

为了实现我们预定的功能,需要对AppWizard生成的框

架进行修改。缺省的 CBmpView Class(视图类)是从

CWindowImpl 这个模板派生来的,而我们需要一个能够显

示位图的视图,另外位图的尺寸可能大于视图窗口客户区

域,这样就需要视图具有滚动功能,因此CBmpView应该从

CScrollWindowImpl这个模板派生。CScrollWindowImpl定义

在 atlscrl.h中,因此在 BmpView.cpp中还应该包含 atlscrh.

h文件。然后再为CBmpView添加一个CBitmap 类公有成员

变量 m_bmp(用于操作位图)和一个 SIZE结构变量m_size

(用于保存位图的尺寸)。作为 CScrollWindowImpl的派生类

CBmpView必须实现DoPaint方法,在DoPaint里实现位图的

显示操作;再为CbmpView类增加WM_ERASEBKGND响应

函数,打开ClassView,右击CBmpView,选择Add Window

M e s s a g e H a n d l e r . . . ,在消息列表中选择

WM_ERASEBKGND,然后单击“Add and Edit”按钮,其

余 的 消 息 采 用 基 类 链 接 的 方 法 让 其 父 类

(CSc r o l lW i n d ow Imp l)处理。手工添加属性设置函数

SetBitmap,以便于给 m_bmp赋值。在 CSDN网站《程序员》

频道可以下载 CBmpView 的完整实现代码。

下面我们来实现“最近文件列表”功能。W T L 的

CRecentDocumentList 为这一功能提供了支持。首先,为

CMainFrame增加一个 CRecentDocumentList 公有成员变量

m_mru。因为 CRecentDocumentList类定义在 atlmisc.h文件

中,所以应用程序主文件 BmpView.cpp还需要包含 atlmisc.

h。因为CRecentDocumentList类会将文件列表信息存储在注

册表里,所以要定义一个全局字符串常量g_lpszRegKey,用

于存储注册表子键名称(如:Software\\Microsoft\\WTL

Samples\\BmpView),以便于 CRecentDocumentList类使用。

编辑菜单资源,保留File菜单下的Open..和Exit,其余全部

删除;添加 Recent File 菜单项,钩选 Pop-up 复选框,在

Caption中输入Recent File 。再为其添加一个子菜单项,ID

为ID_FILE_MRU_FILE1,Caption 为“empty”,钩选Grayed

项,最后如图 6 所示:

然后,为 CM a i n F r a m e 类添加菜单消息映射。在

CMainFrame.h文件中找到BEGIN_MSG_MAP(CMainFrame)

在 MESSAGE_HANDLER(WM_CREATE, OnCreate)下面添

加如下两行:

COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen)COMMAND_RANGE_HANDLER(ID_FILE_MRU_FIRST,ID_FILE_MRU_LAST,

OnFileRecent)

COMMAND_ID_HANDLER宏可以将选择指定菜单项

时所发出的 WM_COMMAND消息映射到指定的处理函数;

C O M M A N D _ R A N G E _ H A N D L E R 宏将

ID_FILE_MRU_FIRST 至 ID_FILE_MRU_LAST 连续的

WM_COMMAND消息映射到 OnFileRecent函数,用于处理

打开文件和打开最近文件列表。然后添加 OnFileOpen 和

O n F i l e R e c e n t 函数,这两个函数的实现和

CRecentDocumentList 类的使用都比较简单,读者可以参看

源码。最后再添加一个函数UpdateTitleBar,每当我们打开

位图文件时(使用 Open或 Recent File),调用这个函数来

更新窗口的标题,使之显示打开的位图文件名。完整的代码

可以在CSDN 网站《程序员》频道下载。

到这儿,我们总算大告成了! 怎么样,WTL 不难吧。

以上程序在 VC++ 6.0 + WTL7.0 + Win98 环境下编

译,在 Windows 98、Windows 2000及 Windows XP下运

行通过。

四、 展望

微软正在推行.NET 战略,而 C# 又是其新宠。Java

也正在步步紧逼,C++到底能走多远,构建其上的ATL、

WTL 等模板库又能支持多久?著名的 C++ 元老级大师

Stanley Lippman 加入了 Microsoft 并成为其 VC.NET 开

发小组中的一员。这显示了 Microsoft 对于促进 C++ 编译

器以及语言继续发展的决心。WTL、ATL轻量小巧的特性

对于目前嵌入式软件开发也是非常合适的;而.NET 也是

对 COM 的某种形式的进化和发展。不用怀疑,来到WTL

的世界中来吧!

图 6

图 4 图 5

MSDN

Page 75: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

75

技 术

嵌入式数据库 ——朴素但实用的数据库选择

撰文 /Anton Okmianski 编译 / 林崇亮

宽带设备供应涉及设备配置信息的存储、配置文件的生

成以及按需将这些文档通过因特网传送到设备。在某些大型

部署中,单个系统就可能需要处理五百万以上的设备,并且

每个设备在重新启动时都需要请求配置信息。另外,区域性

电力的突然短缺又引入了附加的性能需求。

为了满足供应宽带设备时的这种高性能的需求,思科系统

(http://www.cisco.com/)将宽带服务管理设备(Broadband

Provisioning Registrar,BPR)设计成一个分布式体系结构。系

统核心是一个用来存储设备配置数据的中央数据库。然而,我

们没有使用标准的关系数据库,关系数据库太臃肿了,很多特

性我们都不需要,性能和管理开销也不合我们的期望。我们最

后选择了 Sleepycat Software(http://www.sleepycat.com/)

的 Berkeley DB,这是一种轻量级的嵌入式数据库。

对于 Berkeley DB,我们的第一个挑战是设计数据库层

以将我们较复杂的数据模式映射到 Berkeley DB的相对简单

的键/数据对。第二个挑战是以较普通的硬件配置提供至少

每秒 1 5 0 笔供应事务,下面是一种较典型的硬件配置:

2x750-MHz Sun Fire 280R、10,000 RPM的硬盘、RAID5。

通过本文,我将解释我们是如何应对这些挑战的。

Berkeley DB

Berkeley DB是一套开放源代码的嵌入式数据库系统的

接口库,该接口库向应用程序提供可扩展的、事务保护的数

据管理服务。它提供了多种存储/访问方法,包括动态哈希

表、B+ 树、持久性队列、编号纪录等。简而言之,这是一

个用来编写定制数据库的工具箱(toolkit)

Berkeley DB并非一个典型的开放源代码的软件项目。

尽管源代码是免费提供的,并且可自由用于其它开放源代码

项目,但 Sleepycat控制着 Berkeley DB的开发并对之提供商

业支持。因为我们的项目并不开放源代码,所以要得到许可

和技术支持我们就得花钱。尽管如此,它的免费(royalty-

free)分发机制使得我们可以尽可能多地在我们系统的分布

式模块上部署数据库,这对我们而言很为关键。

标准的数据库一般是作为独立服务工作的,这一点上与

Berkeley DB这样的嵌入式数据库是不同的,嵌入式数据库

是软件开发库,开发者可以将它嵌入到自己的应用程序中。

应用程序本身可以是一个服务器,而只是利用嵌入式数据库

开发库来实现定制的数据库逻辑。

Berkely DB与通用数据库相比具有性能优势,因为与应

用服务器之间的进程间通信(IPC)开销是不存在的。Ber-

keley DB也未提供像 SQL那样的较复杂的查询语言。但是,

开发者可以为特定的访问模式而定制数据库。

在我们的应用中,供应API是与中央服务器通信的唯一

途径。因此,我们事先知道数据库需要支持的不同的查询方

式。这使得我们可以针对这些特定的查询而优化数据存储和

数据库层逻辑。

技术挑战

我们现在还是面临着如何将复杂而潜在可动态调整的数

据模式映射到 Berkeley DB的键 / 数据对的问题。我们的逻

辑数据模式包含超过30个不同的实体类别及它们之间的无数

的关系。很明显,既然我们的模式可能在开发过程中或后续

发布版本中迅速的升级,我们就需要某种设计来保证这种重

要的模式适应性。为了使我们的解决方案能被最终用户定

制,我们希望让他们在我们的数据对象中存储潜在的类型复

杂的定制属性。其中最重要的一点是,我们每秒必须能处理

至少 150笔供应事务并能在一个数据库中容纳平均对象尺寸

为约 1KB的 15,000,000个记录 /对象。典型的事务将包含

对数据库的读写访问以创建一条设备记录并将它关联到某个

给定的服务级别。既然我们的数据库大小已经超过目标硬件

所能提供的内存量,我们必须能支持对硬盘的高效读写访问。

解决方案:物理规划

我们的解决方案是一种面向对象和网络模型数据库设计

关键词 嵌入式 数据库 Berkeley DB

Berkeley DB 是一种轻量级的嵌入式数据库。本文将介绍如何用这种数据库满足高性能的需求。

Page 76: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

76

方法的综合。由于中央服务

器是用Java编写的,所以我

们利用了Berkeley DB 的

Java API。我们的数据库层

将数据暴露为 Java 对象—

—命名属性和关系的集合

(图1)。关系提供了一种机

制来查找相关联的对象,而

索引属性则提供了一种通

过属性值查找对象的能力。

对象在内部是通过唯一的对象ID(OID)来标识的,这一点跟

OODBMS的实现非常像。这种方式可以将对象的ID和自身的属性

分离开来,并且使得当属性值更新时更加容易且有效地更新逻辑。

在设计中,我们仅使用了 Berkeley DB的 B+ 树存储 /

访问方式。有一种这样的 B+ 树索引,称之为“主索引”

(MainIndex),在一个集合中以OID为主键的串行的形式包含

了所有的对象。更确切地说,我们使用OID作为Berkeley DB

的主键并且将 BLOB 作为串行化数据项。我们使用优化的

Java 串行器对运行时对象和 BLOB进行双向转化。

还有附加的B+树被用来维护索引属性。这些索引提供了

一种从属性值到对象OID的映射,从本质上讲,是作为对象查

找的辅助索引而存在的。只要你提供定制的主键比较函数,

Berkeley DB支持任何主键类型。比如,在我们的BPR产品中,

通过定制的比较Unicode字符串的函数我们对索引属性提供了

外国语言字符串支持。尽管Berkeley DB将主键和数据作为字

节串(byte strings),但这并不是一个限制。事实上,这迫使

应用程序控制数据的串行化,进而有益于版本控制和优化。

主索引和属性索引的组合使得可以通过OID或索引属性

值进行对象查找。一旦对象数据被取出来了或者对象被反串

行化了,它们的属性和关系就向应用程序暴露了。简而言之,

我们的属性是名字 -值对的集合,其中的值可以是任意的串

行对象。由于对象的属性结构可能很轻易的改变,这样做就

提供了很大的弹性。事实上,数据库层只显式的知道被设计

为索引的属性。当索引属性值改变,我们的数据库层代码自

动更新属性索引。在 Berkeley DB 4.0中,Sleepycat 提供了

辅助的索引特征,可以用来对记录进行基于辅助主键的查找。

最后,每个对象有一个与其他对象的关系集合。每个关

系有一个名字(比如:Owner to Modem),并且包含一个

相关对象的OID的集合。在这个例子中,给定一个Owner对

象,数据库层就能决定相关的 Modem 对象的 OID 的集合,

然后通过 OID 从 MainIndex 中获取 Modem对象。

小基数关系是直接用PersistentObject串行化的,而大基数关系

则因为可伸缩性起见而用 Berkeley DB B+ 树索引代表。在后面的

那个例子中,关系简单地定义了包含关系OID列表的索引的名字。

我们也用一些大基数关系索引存储关联对象的额外数据,比如外

键。这使得我们可以不用为了进行常规的查询而将关系另一边的

所有对象都去串行化。比如,Modem关系的ClassOfService可能包含

5个mln Modem OID。为了扩展到这个大小,我们使用B+树索引

来存储该关系的 OID的列表。另外,由于我们常常需要查询属于

给定 ClassOfService的 Modem的 MAC地址列表,我们将 Modem的

MAC地址与Modem OID在关系索引中一道存放。这使得能对给

定 ClassOfService的Modem的 MAC地址列表进行快速的顺序访问。

图 2展示了三个关联对象的图是如何在B+树索引中获得持

久性的例子。图2(a)展示了三个实例对象的实体关系图表。从

ClassOfService到Modem的关系是多基数的,这意味着多个Modem

可以被关联到同一个ClassOfService。剩余的关系都是小基数的。

在图2(b)中,我们展示了每个类的实例。如图1所示,name,

FQDN以及 OwnerID参数存储在每个对象的 Map属性中。

图 2(c)显示了 Berkeley DB中对象是如何被存储在B+树索

引中的。所有三个对象都被加入到MainIndex中,主键是OID,数据

是各对应对象的串行化BLOB。每个对象在自身索引属性(name,

FQDN和OwnerID)的相应属性索引中的有一个条目。最后,因为

ClassOfService到Modem的关系是多基数的,另外还有一个关系索引

保存与该 ClassOfService实例相关的 Modem的 OID的列表。

锁管理

Berkeley DB缺乏记录一级的锁,它是在页面一级上做锁定

的。为了获得更好的并发性能,我们在 Berkeley DB上实现了一

套记录 / 对象水平的锁系统。为了做到这一点,我们没有使用

Berkeley DB的锁子系统并且单线程化所有对Berkeley DB的访问。

更新也带来了挑战。因为我们没有使用 Berkeley DB的

锁,我们不得不保证任何时刻都只能有一个 Sleepycat 更新事

务。同时,我们也不希望使服务器的客户仅限于使用一个并发

图 1 数据库层

图 2 物理规划

Database

Page 77: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

77

技 术

的更新事务。我们的解决方案是允许用户级别的并发更新事

务,批处理所有的更新,直到提交再把所有的更新以单线程的

方式写进数据库。实现上,用户级别的事务是Sleepycat上的一

个瘦层。用户级事务产生的所有更新都被我们记录在内存中的

一个修改集里。在用户级事务提交时,我们启动一个新的

Sleepycat 事务执行Berkeley DB写操作,将所有的修改更新入

数据库。在实现时,我们为每个 B+ 树索引维护一个修改集,

并使用修改集来跟踪用户级事务的每次修改。任何对索引的数

据库访问都必须是通过修改集进行的,以保证通过B+树索引

访问引起的事务性改变都能得到正确地反映。比如,如果用户

删除了一个对象,没有提交,那么同一事务中后续所有的对该

对象的查找都将失败。事实上,这是因为修改集已经有记录表

明对象已经被这个正在执行中的事务删除了。

单线程访问可能运行起来有些慢,但是在我们的应用

中,却看不到任何大的性能下降。这得归功于Berkeley DB的

相对快速的原子操作。我们仅使用 B+ 树的 get, set, and

get-next操作,而且没有复杂的SQL查询。我们还能够满足

甚至超出每秒钟 150笔事务的性能需求。

我们自己操作锁策略也有其它的好处。我们通常使用记

录/对象一级的锁粒度,有时,即使是这种粒度也可能显得

太粗糙了。因为我们自己控制锁粒度,我们就可以做得非常

精巧。定制的锁管理使得我们提高性能,并使我们能在模型

和访问模式变化时以更好的适应性来应对并发热区。

解决性能瓶颈

在我们的应用程序中我们要解决的一个主要瓶颈是磁盘

读写,以及存储和运行时格式之间的数据传输(串行化)

Berkeley DB有一个内存中的高速缓存,用来记录最常

用的页面。然而, 面对随机的访问模式和一个大型数据库,

这个高速缓存所能做的也就这么多了。结果就是磁盘访问时

间(主要是寻道时间)成为性能约束的一个因素.

我们的设计利用了本地引用来改进缓存。本质上,通常一

起使用的对象一般是接近同时创建的。对于Berkeley DB的B+

树实现,顺序创建的有顺序主键的记录有很大的可能是存储在

同一个B+ 树节点中的,因而也就在相同的磁盘块中。我们的

对象存储在用OID作为主键的主索引中。我们的OID是简单的

顺序数字,是跨多个 JVM invocations 唯一的。这样,接近同时

创建的两个对象,其OID在数值上也是接近的。这就意味着很

大可能这两个对象将被存储在相同的磁盘页。如果这些目标同

时使用, 该页面也只要被从磁盘读取一次就可以了。

另一种改进读性能的途径是改进缓存效率。缓存效率取决于数

据的紧密程度和B+树页面的利用情况。当键集确定时,顺序增加才

能使利用情况最好。这就是当树增加时,B+树页面分开的原因。

在我们的实现中最大的索引就是主索引,因为它存储了

串行对象数据。因为该索引中的主键是 OID 且 OID 是顺序

的,为达到理想的利用率,我们所要做就是保证OID的产生

顺序与对象提交到数据库的顺序是一致的。为了达到这个目

标,每个新的对象起先都被赋予一个临时的OID,接着提交

时这个临时 OID 将会被一个持久 OID 代替。

写磁盘也是我们要解决的一个瓶颈。在提交时,事务日志一

定要写到(flush)磁盘以保证持久性。另一方面, 写缓冲也使得

磁盘以磁头运行最有效的方式来安排时间进程。其中的挑战是如

何能写缓冲多个事务而同时又保持事务持久性,这种持久性要求

数据在提交操作返回前必须被存储到在持久存储器中。

Berkeley DB有一个有用的功能,它支持异步写入事务。

通过使用TXN_NOSYNC标记,提交事务时,不必立刻将数据

写入到日志中。接着你可以用log_flush()函数将这些缓冲分别写

入。这个特点使得我们可以在单个磁盘写中处理多个事务。

Berkeley DB 4.0引进了“组提交”功能,这样一来,

应用程序要完成相同的功能就不需要做别的动作了。

我们处理的最后一个瓶颈问题涉及到串行化开销。数据

串行化是一个将内存中对象表象转换到二进制形式的过程。

在我们的例子中,我们需要串行化一个Java PersistentObject

和所有它的关系和属性,这些关系和属性可能是复杂类型。

解决这个问题时,我们实现了定制的串行化,并且为所

有的重要的类提供了紧凑的预定义的对象类ID。这就使得存

储更加紧凑,紧凑的存储又导致更佳的串行化性能,更快的

对磁盘的读写,更好的缓存效率,因为现在更多的数据可以

被保存在缓存中了。

防止数据库错误

在关键任务系统中,必须不惜代价地避免数据库错误。

Berkeley DB具有内在的防错误功能,如日志记录的校验和。然

而,对进程内数据库最大的风险来自于应用程序可能不小心的

改变了嵌入式数据库的内存,而这些页面可能会被写入磁盘。

幸运的是,事实上这种错误的可能性已经被排除。因为我

们用Java写的应用并且运用Sleepycat提供的JNI来桥接到Berkeley

DB库中。由于 JVM堆是和 JVM提供给Berkeley DB的本地堆

分离的,Java 应用一般不会与Berkeley DB产生内存错误冲突。

我们也努力从一个高的层面来防止逻辑上的数据矛盾(数

据库错误的另一种形式)。我们实现了在每次提交时自动验

证关系基数,以保证不会违反逻辑层约束策略。

结论

表面上没有多少功能的嵌入式数据库,却是强有力的工

具。对我们的需求而言,Berkeley DB已被证明是一个明智

的选择。仅仅使用15000行Java 代码,我们建立了一个灵活

的数据库层,并取得了每秒几百个读写事务的性能。

Page 78: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

78

我眼中的Visual FoxPro 8 撰文 / 陈纯

关键词 Visual FoxPro 8 异常处理 CursorAdapter 连接

今年 10 月,微软公司如期发布了 Visual FoxPro 8b版,这是自 Visual FoxPro 3 以来又一个精彩的版本,一下子吸引住了全

球 Foxers 的眼球。本文是一位资深 Foxer 对 Visual FoxPro 8b的一些感受,愿与读者分享。

结构化异常处理

经常有人问我:你认为 Visual FoxPro 最需要改进的地

方在那里。我总是不假思索地回答:异常处理!我一直期盼

它能拥有结构化异常处理机制,把异常及其处理限定在最小

的范围之内,使代码的封装更优。

Visual FoxPro 8以前,对异常处理还限于全局On Er-

ror例程和对象的Error事件。这些机制都有这样一个毛病:

一旦出现了异常,程序就必须跳出正在执行的程序模块,执

行异常处理例程。这时发生异常前执行的程序模块的上下文

已经丢失殆尽,系统很难调整到正常状态或者给调用接口一

个明确的答复。

Visual FoxPro 8 的 Try...Catch...Finally...End Try

结构让人激动不已,它帮助我们方便地抓住“异常”,即时

针对各种异常做出反应,缩小异常的影响范围,保护系统的

运行状态,也使得程序模块的封装性更好。想想以前应付“异

常”而设计的各种方案,一夜之间成了明日黄花、显得那样

苍白无力,真是“沧海变桑田”啊。

仅此一项增强,Visual FoxPro 8就已经光彩夺目,相

信任何开发人员都经不起“结构化异常处理”的诱惑,要立

刻升级到 Visual FoxPro 8。

CursorAdapter 类

Visual FoxPro 8提供的CursorAdapter类也是大家所津

津乐道的。CursorAdapter是一个基于松散耦合思想设计的、

对象化的 Cursor 处理模型。

VFP8以前的数据处理

在 Visual FoxPro 8之前,我这样评价在Visual FoxPro

里的数据处理:灵活而强健,面向记录处理为主、面向集合

处理为辅,采用过程化编程模型,但可以利用面向对象的

XBase 语言自行封装数据处理对象。

说到Visual FoxPro处理数据的灵活和强健,归根到底是

因为 Visual FoxPro同时支持 XBase 语言和 SQL语言。XBase

语言善于对记录的处理,这种处理往往是基于“行”的模式;

SQL语句对数据的处理是根据“集合”的概念,按照条件取

得“集合”,然后处理。无论采用XBase语言处理数据,还是

使用 SQL 语言,Visual FoxPro 默认提供面向过程的数据处

理编程模型,而不是流行的面向对象的编程模型。(当然,

Visual FoxPro并不限制开发人员编写自己的数据处理对象。)

那么在Visual FoxPro中所谓的“数据”到底是什么呢?

是Cursor!在Visual FoxPro中整个数据处理是围绕着Cursor

进行的(而不是 DBF),所以对象化数据处理模型也应该从

Cursor 着手。

面向对象的数据处理器

仅仅对Cursor进行对象化是没有意义的。我们用典型的

对象化游标——ADO组件的 RecordSet做一个分析。可以发

现,RecordSet 中定义了数据集合的来源、数据集合的结构

(表结构)、数据的更新回送方式,另外 RecordSet还能够对

数据集合进行操作,诸如 Move、Find、Delete 之类的方法。

在 Visual FoxPro 里对象化 Cursor,是不是要照搬

RecordSet的一套呢?如果由我们来设计这个构架,我们如何

取舍呢?我觉得没有必要实现对Cursor本身的封装。刚才已

经说过了:整个 Visual FoxPro对数据的处理就是对 Cursor

的处理,如果封装了 Cursor,就背离了整个体系的构架思

路,同时也放弃了 Visual FoxPro 对数据处理的“灵活与强

健”的特色。这就是前面提到的“在 Visual FoxPro 设计对

象化的 Cursor 是没有太大的意义的”。

对象化 Cursor 没意义,那么我们究竟需要什么呢?

用一个面向对象的 Cu r s o r 的处理(管理)器来管理

Cursor的数据来源、Cursor的数据结构、Cursor中数据变动

的更新回送;而Cursor依旧是传统意义上的Cursor,没有任

何改变,依旧可以用 XBase 或者 SQL 语言对直接 Cursor

进行处理——这才是我们需要的。

说到这里,大家会问了,这不就是DBC中的视图吗?它

们在实现的功能上确实有点像,但实质上是有差别的:首

先,视图是DBC的对象(组件),利用视图设计的系统对DBC

的依赖性大,不符合多层体系构架的思路。而我们的对象化

Database

Page 79: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

79

技 术

Cursor(处理)管理器与DBC无关,完全是程序设计级别上

的概念。从多层体系设计的观点看,属于业务逻辑层次。再

者,视图是数据库中的概念,灵活性差、可控制性差;而对

象化Cursor(处理)管理器就不一样了,作为对象,具备了

面向对象程序设计的一切好处,很容易定义、改变、维护,

更能够继承使用,这都是使用视图所办不到的。

说到底,视图是数据库的概念,具备的是数据库对象(组

件)的特性,而不具备编程语言的特点。对象化Cursor(处

理)管理器解决的就是这个问题。

松散耦合

在视图时代或者是 RecordSet时代采用的是紧密耦合的

思维。说得直白一点就是:数据从哪里来就更新到哪里去,

程序员并不能在其中做什么干涉,一旦执行了 TableUpdate

(),就自动更新数据源,没有商量的余地。

松散耦合提供了一种开放的模型,它以 Cursor 为中心,

从本质上认识到 Cursor与数据源(关系型数据库)的相互关

系是:数据获取和变动更新回写。数据获取就是SQL-Select操

作、数据更新回写就是SQL-Insert、SQL-Update、SQL-Delete。

松散耦合模型提供Cursor数据来源的Select子对象、Cursor数

据添加回写数据源的Insert子对象、Cursor数据修改回写数据

源的 Update 子对象、Cursor数据删除回写数据源的 Delete子

对象。由于从构架上把 Cursor与数据源的各种互动操作分割

开来,再辅以事件模型,这就给了开发人员很大的编程余地。

相比之下,视图虽然也采用类似的思路与数据源互动,

但是它把整个互动过程作为一个整体看待,严严实实的包装

起来。开发人员无从参与这个过程。

VFP8的CursorAdapter 对象

回过头看 Visual FoxPro 8提供的 CursorAdapter对象,

它为开发人员提供了一个基于松散耦合思想设计的对象化的

Cursor 处理模型。

默认情况下它支持四种数据来源,分别是:Native(本

地数据)、A D O、OD B C、XM L。请思考一下,为什么

CursorAdapter能够支持几种性质完全不同数据来源呢?原因

就是采用了松散耦合模型,让开发人员用自己的代码参与数

据的获取,使得整个体系的扩展性大大增强。

概括VFP8中的CursorAdapter,它是一个基于松散耦合

思想设计的对象化的 Cursor处理模型。CursorAdapter既保

留了 Visual FoxPro对 Cursor处理的传统优势,又引入了先

进的编程模型和构架。它的特色包括:

u Cursor 本身没有被对象化,依旧是传统意义上的

Cursor,可以使用一切传统语句处理 Cursor。

u Cursor的管理方式对象化了,可编程性更好,有利

于代码的封装。

u 采用了松散耦合的设计思维,以 Cursor 为中心、

但又分割了Cursor与数据源之间的各种操作、再加入事件模

型,为开发人员提供了很多参与的机会。

全新的连接

Visual FoxPro 8在异构数据库编程方面的增强,至少

体现在两个方面:一个是通过 CursorAdapter 对象提供一种

比远程视图更加灵活、更容易编程的Cursor管理对象;另一

个是在原有的基础上革新了连接的概念,使得连接处理更加

独立、连接管理更加方便、连接更容易被共享。

前面我们已经对 CursorAdapter作了分析,接下来就让

我们看看连接的变化吧。

DBC中的连接对象和连接句柄

以前我介绍 Visual FoxPro里的连接时,经常强调这样两个

概念:DBC中的连接对象(组件)和连接句柄。DBC中的连接

对象(组件)只是一种定义,描述怎样通过ODBC连接到数据源;

连接句柄是实例化的连接对象(组件)。我们可以通过USE远程

视图或者使用SQLCONNECT()打开连接对象,得到连接句柄。

在Visual FoxPro 8以前,连接句柄并不独立,主要是远

程视图自己管理连接句柄,其他远程视图或者SPT需要共享

连接句柄是非常麻烦的。远程视图之间共享连接句柄,需要

定义所有参与共享的远程视图的ShareConnection属性为.T.;

SPT 要取得远程视图的连接句柄必须在 USE 远程视图以后,

使用 CURSORGETPROP() 函数取得连接句柄。除了麻烦以

外,还有不能实现的功能,如先期已经存在的连接句柄不能

被远程视图共享。这一切都是因为远程视图太过“自主”所致。

Statement

Handle Vs. 连

接句柄

为了解决

远程视图的“独

断专行”,Visual

FoxPro 8提出

了远程视图设

计时与运行时

分离的思路,引

Page 80: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

80

入了一个新的关于连接的概念——Statement Handle,同时也保

留了连接句柄概念。

从Visual FoxPro 8开始,开发者直接处理的任何Handle

都是 Statement Handle,而不是连接句柄。也就是说,我们

在DBC里建立、设计远程视图使用的连接与远程视图运行时

的连接可以没有任何关系,我们可以在USE语句中即时指定

远程视图将要使用的Statement Handle。这就是Visual FoxPro

8 针对 C/S 编程中连接的一个改进。

Statement Handle 与连接句柄的关系。我以为:

u 连接句柄是 Statement Handle 的基础,Statement

Handle 是连接句柄的衍生;

u真正与数据源连接的是连接句柄,而不是 Statement

Handle;开发人员直接操作的是 Statement Handle,而不是

连接句柄;

u某一条连接句柄可以衍生出一条或者多条 Statement

Handle,而某一条 Statement Handle只可能对应到一条确定

的连接句柄;

u新建一条连接的实例,将(可能)产生一条新的连接

句柄和一条新的 Statement Handle,这条 Statement Handle

对应到这条连接句柄;

u新建一条连接的实例,如果当前 Visual FoxPro系统

中已经存在一条符合以下两个要求的连接句柄,这两个要求

分别是允许被共享,并且连接语句的定义一致(可以认为来

源于 DBC的同一个连接对象),这时不会生成一条新的连接

句柄(不会再与数据源建立一条新的 ODBC 通道),而是直

接由已经存在的连接句柄衍生出一条新的Statement Handle,

这条 Statement Handle 共享原先那条连接句柄。

u当一条连接句柄衍生的Statement Handle全部被释放

了以后,连接句柄才会释放。

u能够衍生出多条Statement Handle的连接句柄一定是

根据 DBC 中连接对象(组件)所创建的。使用连接字符串

(SQLSTRINGCONNECT())或者直接引用操作系统 DSN 创

建的连接句柄只能衍生出惟一条 Statement Handle。这是因

为,Visual FoxPro 判断不同 Statement Handle是否“同源”

的依据是 DBC的连接对象(组件),而不是像 ADO.NET那

样根据连接字符串定义区分。

从上面的表述中,我们发现:因为连接句柄与Statement

Handle 的关系是“一对多”的,如果拿 SQL Server 作为数

据源,我们可以建立一条连接句柄、根据它衍生多条State-

ment Handle,实际上连接到 SQL Server 的只有一条连接,

而应用程序中却可以有多条 Statement Handle,这就是一种

新概念的“连接共享”。

一个实例

让我们通过下面的例子来具体了解一下Statement Handle

与连接句柄。

设 DBC 里有一个“连接对象”(组件),名为“CD1”,

连接到 SQL Server 数据库。Con1=SQLCONNECT("CD1",.T.)

连接成功,返回Con1=1。Con1表示 Statement Handle

,以后就可以通过这个 Con1 打开远程视图或者执行 SPT命

令。由于此时系统中并没有存在连接句柄,所以同时产生一

条连接句柄,需要获取连接句柄可以通过:ODBC1=SQLGETPROP(Con1,"ODBChdbc")

这里的 ODBC1=49551184。这个 ODBC1 并不需要开发

人员关心,也不需要通过程序维护。接着,我们就根据

ODBC1 衍生其他的 Statement Handle:Con2=SQLCONNECT("CD1",.T.)

返回 Con2=2。Con2表示 Statement Handle,以后就可

以通过这个 Con2 打开远程视图或者执行 SPT命令。我们获

取 Con2 的连接句柄看一看:ODBC2=SQLGETPROP(Con2,"ODBChdbc")

这里 ODBC2=49551184。说明ODBC1和ODBC2是一个

东西,从中衍生了两条 Statement Handle。虽然在编程中我

们可以使用 Con1 或者 Con2,但是真正与 SQL Server 的连

接只有一条,就是连接句柄。

接着,我们需要关闭连接句柄衍生的所有的 Statement

Handle,才能真正断开Visual FoxPro与SQL Server之间的连接。SQLDISCONNECT(Con1)SQLDISCONNECT(Con2)

通过这个简单的例子,也许有利于我们来理解新的连接

机制。虽然,Visual FoxPro 8在连接这一块动了大手术,

但是这对于旧的系统影响并不是很大,因为编程接口并没有

变化,只是在更底层的地方加了一层概念。话要说回来,如

果要真正用好这个新特性,还是应该改变一下编程思路。

关于连接的话题谈得差不多了,不过有一个问题是需要

注意的:一个连接句柄可以衍生多个 Statement Handle,在

开发中,如果有一个 Statement Handle 进入了事务处理状

态,这时所有相关的Statement Handle也进入了事务处理状

态。当然,不是由已经进入事务状态的连接句柄衍生的

Statement Handle 则不会进入事务状态。

小结

终于完成了我在 Visual FoxPro 8中的“首航”。细细

揣摩一些 Visual FoxPro 8 的新特征,我深深体会到一个指

导思想贯穿着整个Visual FoxPro 8的构架,这个思想就是:

支持更完美的对象封装。由此推断:用 Visual FoxPro 8进

行对象化编程、多层开发一定比以前更加容易、更加灵活。

本文只涉及到 Visual FoxPro 8 的三个新特性,事实上

还有更多令人愉悦的东西没能讲到,希望以后能有机会与大

家共同探讨。

Database

Page 81: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

81

技 术

Personal Java 程序设计(一) —— Personal Java程序设计入门

撰文 / 王森

关键词 Personal Java Windows CE Symbian

您想用 Java 来设计手机与 PDA的应用程序,可是嫌 J2ME MIDP 功能太少,无法让您尽情发挥吗? 笔者建议您试试 Personal

Java。Personal 可以让您设计的 Java程序得以在以 Windows CE与 Symbian OS 为操作系统的机器上执行,包括市面上已经可以买到

的 PocketPC、Nokia 9210c、SonyEricsson P800 都是 Personal Java 可以发挥的范围。

前言

本文将介绍如何使用 PJEE(Personal Java Emulation

Environment)来开发可以跨平台的Personal Java应用程序,

让大家学习 Personal Java 程序的开发与测试方式,并熟悉

Personal Java 所提供的类别函数库。

另外,Personal Java 所提供的 API 和 JDK 1.1.8 所提

供的一样丰富,不过有少数地方不太相同,这些地方请参阅

Personal Java 规格。

本文最后将着眼于Personal Java关于图型用户界面的部

份,介绍 AWT 库和 Swing库的用法。

何谓 Personal Java

Personal Java 的发展其实已经有一段时间了,无数的公

司根据其规格生产实现品,而它所扮演的角色也在J2ME推出

之后更形尴尬,所以接下来我们将针对Personal Java做说明。

Personal Java的规格其实并没有定义在CLDC或是CDC

底下,虽然最后它将会被归到 CDC的Personal Profile之中,

但是目前其规格还是继续在演进之中。之所以有这种情况,

是因为长期下来已经有许多围绕着Personal Java的规格所实

现出来的商业产品,造成尾大不掉的情形,所以 Personal

Java 的规格短期还是会继续演进。

前面在介绍 Java版本的演进时,大家一定发现 Personal

Java的规格其实是从Java 1.1之中所分支出来,也因此Per-

sonal Java的规格是根据许多 Java 1.1的规格而制定的,但

是并非 Java 1.1 的全部规格都包含进来。Personal Java 特

别适合用在具有丰富图形显示能力的消费性电子产品上面,

于是我们可以发现Sun Microsystems网站上对于Personal Java

的参考实现是建立在 Windows CE(或 PocketPC)上头的。

或许大家将 Windows CE 当作比较偏向于 PDA 的产品(它

本来就是 PDA),可是 Windows CE 本身的确符合 Personal

Java 规格之中所规格的目标平台之条件,比方说具有连接

Internet 的能力、而且对于图形的显示能力非常强大(彩色

LCD)。另外一家公司 Symbian 所推出的操作系统 EPOC

R5、R6 以及 R7 上的 Java 版本也都是衍生自 Personal Java。

目前市面上支持 Personal Java 的移动电话与 PDA 有:

u NOKIA 9210。Nokia 9210搭载的是 Symbian OS

6.0 Crystal 版。

u SonyEricsson P800。它搭载的是 Symbian OS 7.0

Quartz 版。

如何开发 Personal Java 应用程序

目前,使用 Personal Java 最多的平台为 PocketPC 与

Symbian OS。就如同开发其它 PDA 程序一样,您不一定要

购买一台PDA来作测试,Sun Microsystems网站上也提供了

Personal Java 的仿真器,让您可以在 Windows或 Solaris 操

作系统之下测试您开发给 Personal Java环境执行的应用程

序。此工具名为 P J E E ( P e r s o n a l J a v a Em u l a t i o n

Environment),您可以在 http://java.sun.com/ products/

personaljava/pj-emulation.html 下载 PJEE。使用 PJEE 所开

发出来的 Personal Java 程序可以保证能在 PocketPC 与

Symbian OS 之上顺利执行。PJEE 遵循 Personal Java 1.2a

规格所设计,目前最新的版本为 3.1 版

使用 PJEE时,您必须使用 JDK 1.1.7以上的版本来开

发可以在 Personal Java执行环境或 PJEE上执行的应用程序。

Tips

由于从 JDK 1.4.x开始,Sun 变更了类别文件的某些设

计,同时也更新了版本号码。如果您使用 JDK 1.4.x 开发

Personal Java 应用程序的话,所造出的类别文件格式将无法

被仿真器与其它各种执行环境所接受。为了解决这个问题,

我们必须在编译时加上指令 -target 1.1 ,这样就可以造出

Page 82: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

82

Java

仿真器与其它各种执行环境都能够接受的类别文件格式。

在下载 PJEE 时,您会注意到,目前您所能取得的参考

实现分成两种,一种叫做“Touchable”版本,另一种则是完

整的版本。所谓的“Touchable”版本,意指“Personal Java

版本的最小实现”。其实 Sun Microsystems网站上也建议我

们,如果要开发Personal Java的应用程序,最好是以Touch-

able版本为基础,除了很多商业版本的 Personal Java实现是

根据 Touchable版本来开发的之外,也可以保证您的应用程

序可以在所有的 Personal Java 实现上顺利执行。

如果想开发 PocketPC 上 Personal Java程序的话,只要

使用 PJEE 即可。

如果想开发 Symbian OS 上 Personal Java程序的话,由

于 Symbian OS 为 Java 实现了许多丰富的 API (例如

JavaPhone),这些 API 在 PJEE 或其它 Personal Java实现中

无法使用,所以我们必须藉助 Symbian OS的 SDK才行。您

可以在 Forum Nok i a 网站下载 Nok i a 9 2 1 0 SDK。

SonyEricsson Developer World网站可以下载SonyEricsson P800

SDK。Symbian Developer Network官方网站也提供Symbian

OS 6.1 Quartz SDK 的下载。

实机上 Personal Java 应用程序的执行

我们所开发的Personal Java应用程序,在实机上执行时

必须仰赖 Java 执行环境(Java Runtime Environment)。

Symbian OS 内建 Personal Java 执行环境,所以我们只要把

开发之后的程序直接拷贝到实机上就能够执行。但是 Win-

dows CE并没有内建 Personal Java 执行环境,所以我们必

须先安装 Personal Java 执行环境才行。

Sun提供官方的Personal Java执行环境(Personal Java

run-time environment)。您可以到http://java.sun.com/

products/personaljava/下载。不过比较可惜的是,1.0版的

Personal Java 执行环境只支持 Windows CE 2.11 版,而

且只支持 MIPS 与 SH3 这两颗处理器。目前有很多执行

Windows CE的 PDA并非采用这两种处理器,尤其是到了

PocketPC之后,几乎所有的机器都是使用Strong-Arms处

理器。而 1.1 版的 Personal Java执行环境已经支持 SH3、

SH4、MIPS以及 Strong-Arms处理器。如果刚好不巧您的

PDA并非使用上述处理器,那么您可能要到网络上寻找其

它厂商所提供,可以在其它处理器上执行的 Personal Java

执行环境了。

准备工作:JDK的安装

JDK 是所有开发程序的基础。本文假设您安装了 JDK

1.4.1 于 d:\jdk1.4.1这个目录底下,因此,所有的基础开

发工具都位于 d:\jdk1.4.1\bin 目录之下。

安装 PJEE

PJEE 只是一个符合 Personal Java 标准的 JRE(Java执

行环境),安装前不需要安装任何软件。请直接点选pjee3_1-

win-nonrom.exe 以进行 PJEE的安装。请选择欲安装的目

录,本文假设我们将 PJEE 安装在 d:\pjee3.1 之中。

使用 PJEE

请编写下列程序代码

Main.javaimport java.awt.*;

public class Main{

public static void main(String args[])

{Frame f = new Frame(" 窗口 ") ;f.setSize(200,200) ;

f.add(new Button(" 按下我 ")) ;f.addWindowListener(new MyAdapter()) ;f.show() ;

}}

MyHandler.javaimport java.awt.event.*;

public class MyHandler implements ActionListener

{public void actionPerformed(ActionEvent e){

System.exit(0) ;}

}

MyAdapter.javaimport java.awt.event.*;

public class MyAdapter extends WindowAdapter{

public void windowClosing(WindowEvent e)

{System.exit(0) ;

}

}

完成之后,请使用下列指令进行编译:javac -bootclasspath d:\pjee3.1\lib\classes.zip -target

1.1 Main.java

编译完成之后,请使用下列指令执行程序:d:\pjee3.1\bin\pjava Main

执行结果如图 1:

窗口中间出现方框是因为字体尚未设定,稍后将解决这

名称 所在目录

JDK 1.4.1 d:\j2sdk1.4.1

JDK 1.4.1 内附工具 D:\j2sdk1.4.1\bin

Page 83: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

83

技 术

个问题。按下中间的按钮和关闭窗口钮都能够结束程序的执行。

设定字体文件

由于字体设定的问题,所以中文无法出现,请建立一个

名为 font.properties.

z h _ T W 的文字文件

(该文件可以在 CSDN

网站《程序员》频道下

载),并将它复制至

<PJEE 安装目录>\lib

底 下 , 重 新 执 行 即

可。重新执行后的结

果如图 2:

根据 Java Inter-

n a t i o n a l i z a t i o n

(Resource Bundle)的设计,系统搜寻自行设定文件的顺序

为 font.properties.zh_TW==>font.properties.zh==>font.

properties。

使用扩充函数库(Swing)

Swing 1.1.1 版是针对 JDK 1.1.x 而设计,所以也能

够架设在 Personal Java之上,请解开 Swing 1.1.1,我们需

要的只有 swingall.jar。请编写下列代码:

Main.javaimport javax.swing.*;

public class Main{

public static void main(String args[])

{JFrame f = new JFrame("´°zÚ") ;f.setSize(200,200) ;

f.addWindowListener(new MyAdapter()) ;JButton btn = new JButton("°´DÂÎN") ;btn.addActionListener(new MyHandler()) ;

f.getContentPane().add(btn) ;f.show() ;

}

}

使用下列指令进行编译:javac -bootclasspath d:\pjee3.1\lib\classes.zip -classpath

swingall. jar;. -target 1.1 Main.java

使用下列指令执行仿真器:d:\pjee3.1\bin\pjava -classpath swingall.jar;. Main

执行结果:

Tips

由于正式版的 Swing 存有 Bug,PJEE 无法正常执行,Sun

官方的 Personal Java 执行环境与 Jeode也无法执行。目前只

有 CrEme 才能正确地支持 Swing。其它都要修正 Swing 的 Bug

才行。Bug 的修正方式请参考网址:http://www.blueboard.com/j2me/notes/2002_7_26.htm

或h t t p : / / f o r u m . j a v a . s u n . c o m / t h r e a d . j s p ?

forum=56&thread=280486

修改后的 Swing函数库为 swingall_fix.jar使用下列指令

执行仿真器:d:\pjee3.1\bin\pjava -classpath swingall_fix.jar;. Main

执行结果如下图所示:

总结

在本文中,笔者为大家介绍了基本Personal Java开发工

具的安装与开发环境的建置,并简单介绍了Personal Java所

支持的 AWT和 Swing函数库。仅仅是仿真一个环境,您是

否觉得意犹未尽呢?

下一次,我们会将场景移到Windows CE(Pocket PC)之上,

让我们的 Java程序也能在 PocketPC上执行,咱们下次见。

图 1

图 2

Page 84: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

84

RUBY

Ruby 提供两个层次的网络访问:通过基本 SOCKET在

操作系统层之下访问网络,编写面向有连接的和无连接服务

器和客户端;同时,Ruby提供了高层网络访问,比如各种应

用级的网络协议 HTTP、FTP、SMTP等。这些网络类库使

复杂的网络编程变得方便简单。

1. 低层网络访问

右图是 Ruby 的网络模块中的类,以及它们之间的继承

关系。

BasicSocket是所有 socket

类的抽象类。

IPSocket 是基于 IP 传输的

s o c k e t 基类。它提供了

getaddress 方法取主机地址。A= IPSocket.getaddress('www.

pku.edu.cn')

A >> 162.105.129.12

TCPSocket是通过TCP传输

的Socket类。其中提供new方法

创建主机连接。而后可以使用

recvfrom 接受来自主机的数据。

SOCKSSocket是支持Socks

协议的 socket 类。

TCPServer用于创建基于TCP连接的socket服务器。它

提供的 new 和 accept方法用于创建主机和接受客户端连接。

下面是一个简单 web服务器,其基本功能是显示当前时间。

require 'socket'

port = (ARGV[0] || 80).to_iserver = TCPServer.new('localhost', port)while (session = server.accept)

puts "Request: #{session.gets}"session.print

"HTTP/1.1 200/OK\r\nContent-type: text/html\r\n\r\n"

session.print"<html><body><h1>#{Time.now}</h1></body></html>\r\n"

session.close

end

UDPSocket 是通过 UDP 传输的 Socket 类,它传送

和接受数据包。要接受数据就必须把 socket 绑定到一个

特殊端口。发送数据可以有两种选择:要么首先创建与远

端 UDP socket 的连接,而后向那个端口发数据;要么

每次都定制数据包的远端地址和接受端口。下面是两种

使用的实例:UDPSocket.open.send("ad hoc", 0, 'localhost', $port)

或sock = UDPSocket.opensock.connect('localhost', $port)sock.send("connection-based", 0)

sThread.join

UNIXSocket和UNIXServer是基于Unix域协议的socket

类和它的服务器类。

Socket类提供了socket的最低层操作,其中提供了很丰

富的方法,使用它可以灵活地编写各种网络要求的程序,但

是用它编程比利用上述各个类复杂得多。

2. 高层网络访问

为方便编写基于网络应用协议的客户端程序,Ruby提供

了一些简单易用的类。

2.1. Net::FTP

FTP 是 Internet 两站点(计算机)间传送文件的协议,

包含一套控制传输文件的命令,以登录 Internet站点收发文

件。Ruby在 Net模块的FTP类提供了所有 FTP操作的方法。

u new 或 open 或 connect 方法,创建于主机的连接。

u login 登录。

u list 或 dir 列目录。

u server commands向服务器发送各种操作命令,比

如创建目录,改变目录等。

u putbinaryfile、getbinaryfile上传和下载二进制文件。

Ruby在网络上的应用 撰文 / 隗刚 张欣 裘宗燕

关键词 Ruby 网络 SOCKET HTTP FTP SMTP cookie session

Ruby是完全面向对象的脚本语言,它不仅有丰富的基本类库,更提供了功能强大丰富的网络模块,有关Ruby的基本介绍

见前文。本文将集中介绍 Ruby 在网络方面的功能及其使用。用 Ruby 语言可以编写各种网络应用程序,甚至可以用它写出功

能复杂的网站。

Page 85: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

85

技 术

u puttextfile、gettextfile 上传和下载文本文件。

u close 关闭连接。

下面是一个使用 FTP类的例子,它完成从一个 ftp站点

上下载一个文件的工作。

require 'net/ftp'ftp = Net::FTP.new('ftp.lib.pku.edu.cn')

ftp.loginfiles = ftp.chdir('/pub/TextUtils/UltraEdit/8.10a')files = ftp.list('*')

ftp.getbinaryfile('UEDIT32I.EXE, 'UEDIT32I.EXE', 1024)ftp.close

2.2. Net::HTTP和Net::HTTPResponse

Net:: HTTP包提供了基于HTTP协议的简单客户端访

问方法,可用于取得Web内容和头信息等各种信息。以下列

出该类的一些常用方法:

u new 和 start 创建与 Web服务器的连接。

u get 方法取得一特定 URL的头信息或网页内容。

u head 方法取得一特定 URL的头信息。

u post方法通过使用HTTP POST方法向服务器发送数据。

Net::HTTPResponse进一步提供了对头信息的分析处

理。下面是一个使用 HTTP类的例子,它从一个 Web站点

取来一个文件的首 56 个字节:

require 'net/http'h = Net::HTTP.new('www.pku.edu.cn', 80)

resp, data = h.get('/index.html', nil )p data[0..55]

2.3. Net::POP 和Net::POPMail

POP 是 e-mail 软件从 e-mail 服务器获取邮件所遵循的

一种协议。Net::POP 提供了如下等方法:

u new 和 start 创建与邮件服务器的连接。

u each和mails方法取得所有的邮件,返回POPMail对象。

u finish 关闭连接。

POPMail 提供 all、delete、uidl、size、head、top等操作

邮件的具体方法。下面是一个使用POP类的实例,它完成从

21cn.com 读取用户 user 的 Email。

require 'net/pop'pop = Net::POP3.new('pop.21cn.com')

pop.start('user', 'pass') do |pop| msg = pop.mails[0]# Print the 'From:' header line

puts msg.header.split("\r\n").grep(/^From: /)# Put message to $stdout (by calling <<)

puts "\nFull message:\n"

msg.all($stdout)end

2.4. Net::SMTP

使用 Ruby的Net::SMTP中的函数可以很方便地发出电

子邮件。以下列出该类的一些常用方法:

u new方法创建实例。

u start 方法连接到 SMTP服务器。

u ready方法输入发送方和接受方的Email地址,之后

就可输入邮件内容而送出。

下面是一个SMTP类的例子,完成从www.is.pku.edu.

cn 的 SMTP 服务器向 [email protected] 发送邮件。

require 'net/smtp'smtp = Net::SMTP.new('www.is.pku.edu.cn',25)

smtp.start('www.is.pku.edu.cn')smtp.ready('fromuser', '[email protected]') do |a|a.write "Subject: Test1\r\n"

a.write "\r\n"a.write "And so is this"end

2.5. Net::Telnet

使用 Net::Telnet 类可很方便地实现远程登录访问。以

下列出该类的一些常用方法:

u new方法创建实例。

u login方法登录成功后,就可以发送命令远程访问了。

下面是一个使用Telnet类的实例,它完成登录到本机并

执行 date 命令。

require 'net/telnet' #连接本机执行 date命令

tn = Net::Telnet.new({})tn.login "guest", "secret"tn.cmd "date"

3. CGI 开发

Ruby还提供了服务器端CGI开发包。在Redhat7.3上选

择安装 Ruby模块后即可以编写 CGI程序。用 Ruby写 CGI脚

本也很容易。例如要生成“Hello,World!”网页,只需写:

#!/usr/bin/env ruby

print "HTTP/1.0 200 OK\r\n"print "Content-type: text/html\r\n\r\n"print "<html><body>Hello World!</body></html>\r\n"

3.1. CGI类

CGI 程序常用来创建表单和处理表单。

3.1.1. 创建表单

CGI提供大量方法生成HTML,针对每一个标识符提供

了一个函数。为了使用这些方法,必须先用new方法创建一

个 CGI 对象。下面例子创建了一个表单:

require "cgi"cgi = CGI.new("html3") # add HTML generation methods

cgi.out{cgi.html{

cgi.head{ "\n"+cgi.title{"This Is a Test"} } +

cgi.body{ "\n"+cgi.form{"\n"+cgi.hr +cgi.h1 { "A Form: " } + "\n"+

Page 86: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

86

cgi.textarea("get_text") +"\n"+cgi.br +cgi.submit

} }}

}

这些方法返回字符串,包含相应的标记符,并用方法的

内容作为标记内容。产生:

Content-Type: text/htmlContent-Length: 302<!DOCTYPE HTML PUBLIC"-//W3C//DTD HTML 3.2 Final//EN">

<HTML><HEAD><TITLE>This Is a Test</TITLE></HEAD><BODY><FORM METHOD="post" ENCTYPE="application/x-www-form-urlencoded">

<HR><H1>A Form: </H1><TEXTAREA COLS="70" NAME="get_text" ROWS="10"></TEXTAREA><BR><INPUT TYPE="submit"></FORM></BODY></HTML>

3.1.2. 处理表单

CGI提供处理HTML请求串参数的两种方法。如果表单

以 GET方式提交,则可以直接用[]操作符进行访问,如下所

示(假设 U R L 是 / c g i - b i n / l o o k u p ?

user=Zhang%20San&year=1978):require 'cgi'cgi = CGI.new

cgi['user']

输出结果为 "Zhang San"

cgi['year']

输出结果为 "1978"

3.2. CGI::Cookie

cookie 可以用来在客户机上记录用户信息等各种信息。

Ruby 中可以使用 CGI::cookie 对象,处理对 cookie的操作。

可以使用 new 方法创建 CGI::Cookie 对象:cookie = CGI::Cookie.new("www.myweb.com", "ID=123",

"Part=ABC")

用[]操作符取值:cookie = cgi.cookies["www.myweb.com "]

3.3. CGI::Session

Ruby用 CGI::Session处理 session。这里使用了 Cookie,

但提供了更高层次的抽象。下面例子设置了一个名叫myweb

的 session。

sess = CGI::Session.new( cgi, "session_key" => "myweb","session_id" => "1234",

"new_session" => true,"prefix" => "web-session.")sess["ID"] = 123sess["Part"] = "ABC"

这给用户发送了一个名为 myweb、值为1234的 session,

同时创建磁盘文件 $TMP/web-session.1234,记录了 ID 和

Part的名、值对。当用户返回时,要使用该session只需知道

它的 ID值。本例中是rubyweb=1234,有了它就可以得到其

中全部的 session数据。

sess = CGI::Session.new( cgi, "session_key" => " myweb "

,"prefix" => "web-session.")

4. eRuby

Ruby 不仅可用作 CGI 生成 HTML,还可以作为网页的

脚本语言,能嵌入到HTML中。使用eRuby包就可以在HTML

文档中嵌入 Ruby语句了。eRuby有几种不同的实现,包括

eruby 和 erb。eruby是 Shugo Maeda实现的。HTML中的嵌

入式Ruby是非常强大的。它不仅能提供象ASP、JSP、 PHP

的功能,还可提供 Ruby 本身的强大功能。

4.1. 简单实例

eRuby起到一个过滤器的作用,简单明了。除了以下表

达式,其它文字是保留不动的。

<% ruby code %> 分隔号中的 ruby代码用代码执行的

输出代替。

<%= ruby expression %> 分隔号中的ruby表达式用表

达式求出的值代替。

<%# ruby code %> 分隔号中的 ruby 代码将被忽略。

例如:The Number is <% a = 100; puts "#{a}!" %>

产生:The Number is 100!

可以通过配置Apache Web服务器,使它能自动分析嵌

入了 eRuby的文档。配置过程同配置PHP类似。嵌入Ruby

的文档应以“.rhtml”作为文件后缀,配置后的 Web服务

器将要求 eRuby 执行程序解释这些文档,产生所希望的

HTML 输出。

对于绝大部分用 Ruby写的 CGI程序而言,缺省服务器

配置将要求每个 cgi-bin页访问都建立一个新的 Ruby拷贝,

这将使 Web访问变得非常慢。Apache Web服务器采用载入

模块的方式解决这一问题。这种模块被动态加载,成为Web

server运行的一部分。因此完全没必要重复创建新的解释器

去处理服务请求。mod_ruby 就是一个这样的 Apache 模块。

一旦安装配置好了,在运行Ruby脚本时与没有mod_ruby一

样,但是运行速度快得多。

总结

本文介绍了 Ruby 在网络方面的功能及使用方法。在

低层访问方面,介绍了各socket类;在高层方面,介绍了

FTP、HTTP、POP、SMTP、Telnet等网络协议的相关类;

最后介绍了 Ruby 写 CG I 程序和用作网页脚本语言的包

eRuby。关于Ruby的强大网络功能,读者还需要在应用中

进一步体会。

RUBY

Page 87: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

87

技 术

抽象机模式 撰文 /Julio Garcia-Marin Miguel Sutil-Marin 编译 / 马维达

关键词 抽象机 虚拟机 设计模式

由于现代高级编程语言和现有硬件之间的差异日渐增大,常常有必要引入中间语言、并在原始硬件之上构建抽象机。本

论文描述了抽象机(Abstract Machine),一种对抽象机的本质特性进行捕捉的结构型模式;这些特性给出了对抽象机的定义。

该模式将抽象机的静态特性和动态特性作为分离的组件进行描述,并且还考虑了指令集和这些指令的语义,以及此模式的其

他一些重要组件。

意图

为抽象机的设计定义通用模板。该模式捕捉在抽象机之

下的本质特性(也就是,数据区、程序、指令集等等),将它

们的相互分离的、松耦合的组件封装进结构模式中。此外,

本论文还提供了抽象机的各组件进行交互的协作结构。

别名

虚拟机(Virtual Machine)、抽象状态机(Abstract State

Machine)。

动机

在今天,Java是计算机科学中的一个时髦话语。尽管Java

是一种用于分布式和GUI应用开发的面向对象语言,但几乎

每天它都在增加新的非常有用的编程特性和工具。作为一项

事实,在Java的成功中,虚拟机技术的使用有着极为重大的

意义。众所周知,Java虚拟机(Java Virtual Machine,JVM)

是基于软件的抽象机,可在不同的微处理器机器上工作(也

就是,独立于硬件)。JVM 的设计者必须遵从 JVM 的规范,

进行必要的处理,将JVM虚拟环境桥接进具体的操作系统和

微处理器中。这个在虚拟环境之后的“桥梁”允许软件开发

者“一次编程,到处运行”(Write Once,Run Anywhere)

[1],因为不管底层的微处理器是什么,JVM都必须根据JVM

的标准规范以同样的方式工作。

由于现代高级编程语言和现有硬件之间的差异日渐增

大,常常有必要引入中间语言、并在原始硬件之上构建抽象

机。但是,在许多情况下,差异是如此之大,以致于我们难

以看出源语言是怎样与中间语言相关联的,或者,难以看出

中间语言是怎样与硬件相关联的。遗憾的是,在许多情况

下,抽象机仅仅被表示为一些寄存器、内存区和机器指令集

的集合,只有很少的、或没有与源语言的对应性。

在过去,在Java的兴盛很久以前,许多有关说明性编程

(declarative programming)的工作已经指出,抽象机作为常用的

实现技术的一项替代在编译语言上的成功。像 Prolog[7]或 SML

[ 6 ]这样的例子是通过抽象机来实现高性能编程语言的好例

子。在今天,使用“虚拟机”进行表达已经非常普遍。

该项工作的目标是双重的:a)首先,为抽象机的设计发

明一套方法学;其次,b)使用此方法学来描述一个基于模

式的构架,用于设计编程语言编译器。对这样的实现技术(也

就是,抽象机)的使用允许将编译任务规划为渐进的优化过

程,这样,就可以根据中间的抽象机之间的关系来一步一步

获得高性能特性。通过此过程还保持了一个抽象机与下一个

抽象机之间的同一性。每个编译步骤都明确地生成一些新特

性和变动,并将其增加到全局编译过程中(见图 2)。

该过程给出了更为抽象和系统的构造编译器的方法。此

外,它还促进了对编译过程的理解,并简化了对先前的设计

进行优化和复用的任务。一个(原型)编译器或多或少有可

能作为抽象机设计的边际效应自动导出。

适用性

抽象机模式适用于下面的任意一种情形:

图 1 抽象机的例子:Abstract-WAM [3]

* 也可以是“定义”

图 2 通过虚拟机渐进优化进行的编译过程

Page 88: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

88

u当我们想要定义抽象机时。该模式提供了编写高级规

范的通用骨架,允许程序员将注意力更多地集中在定义抽象

机的各组件上,而不是去担心它们的合并(这可以由此模式

“免费”提供)。所编写的规范可以是形式化或非形式化的。

u当我们想要通过使用抽象机技术来编译语言时。抽

象机提供了适当的构架来描述通过中间语言的渐进优化进

行的编译过程(也就是,原型式开发)。在这样的过程中,

各中间语言(也就是,抽象机)之间的关系定义了整个编

译过程。

u当我们想要定义通过应用抽象机获得的编译器时。这

是对上面的两种情况进行结合的结果。

u当我们想要测试同一指令的不同语义时。可以为同

一指令定义不同的语义,同样,也可以获得同一语义的不

同实现。

u当我们想要测试同一抽象机的不同指令集时。可以为

同一抽象机定义不同的指令集。而且,编译过程的渐进优化

意味着每个中间的抽象机都定义了比前一抽象机所管理的指

令集更为优化的指令集。

u当我们想要对我们的程序的执行进行模拟时。程序模

拟将可视化工具和调试工具强制性地结合进程序中。很容易

将这些查看组件和其他特性视为抽象机模式的参与者。特别

地,与可视化或调试有关的执行代码可以通过增强抽象机的

指令来定义。

结构

抽象机模式的结构如图 3 所示。

参与者

抽象机可抽象地定义为两个部分的联合:(1)静态部

分,由与状态(state)有关的组件组成,(2)动态部分,确

定与抽象机的行为(behavior)相关联的要素。抽象机模式

将两个部分的参与者组织和定位为它的结构的组件。

抽象机的状态由以下组件组成:

1. Abstract-Machine Factory(抽象机工厂):它为创

建 Abstract DataArea和 Abstract Program的操作声明接口。

2. Abstract DataArea(抽象数据区):它为数据区对

象类型声明接口,用以配置抽象机。它声明两种抽象操作:

u Init 操作,用以确定数据区的初始配置;

u Stop操作,确定抽象机的执行是否已结束。如果结

束条件不依赖于数据区,Stop操作就返回 true。

3. Concrete DataArea(具体数据区):它定义具体

的数据区对象。具体数据区可以是简单对象或复杂对象结

构(对象容器)。该组件必须提供 Abstract DataArea接口

的实现。

4. Abstract Program(抽象程序):它为汇编程序声明

通用接口。它的定义必须处理一系列指令(A b s t r a c t

Instruction)及一个指令集(Abstract Instruction Set)。此

外,它还声明了四种抽象操作:

u Init 操作,用以确定汇编程序的初始配置;

u Stop操作,用以确定抽象机是否到达它的最终阶段。

如果终止条件不依赖于任何程序配置,Stop 操作返回 true;

u LoadProg操作负责构造抽象机程序的汇编指令。该

操作从输入流中读取文本表示,并将每条指令翻译为 Con-

crete Instruction对象;

u CurrentInst 操作返回要由抽象机执行的指令。

5. Concrete Program(具体程序):它定义具体的程

序对象。它被定义为具体指令集和一些程序计数器的集合。

它必须实现在 Abstract Program 中定义的 Init、Stop 和

CurrentInst 操作。

6. Abstract Instruction Set(抽象指令集):它声明用

以表示一组抽象指令的通用接口。

7. Concrete Instruction Set(具体指令集):它通过

Concrete Instruction对象来定义一组对象。具体指令集与具

体数据区及具体程序相联系。它必须实现Abstract Instruc-

tion Set 的各操作。

8. Abstract Instruction(抽象指令):它声明用以表示

抽象机指令的通用接口。

9. Concrete Instruction(具体指令):它定义具体的

指令对象。具体指令直接与具体数据区和具体程序相联系。

它必须实现 Abstract Instruction的各操作。

在抽象机的行为这一方面,它依赖于在静态组件之上工

作的一些操作。这些操作是抽象机状态的定义的一部分,它

们负责描述在执行过程中抽象机所到达的不同状态。这些操

作描述如下:

1. Abstract-Machine State(抽象机状态):作为汇编

Concrete Instruction的执行结果,它对DataArea和Program

图 3 抽象机模式(结构)

Design Pattern

Page 89: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

89

技 术

之间的交互进行协调。因而,它有着与 MEDIATOR模式[4]

类似的角色。抽象机状态可以到达三个不同的阶段(initial、

executing 和 final),它们与以下操作相联系:

uInit 操作,在程序开始执行时用以确定抽象机的初始

状态;

uStop操作,用以确定执行是否到达了抽象机的最终

状态;

uTransition 操作,用以完成抽象机程序的执行。它

被定义为 while循环控制结构;每一轮循环都执行程序计

数器(CurrentInst)所指向的指令。Transition操作从初

始的抽象机配置状态开始执行,并持续直到到达最终阶段

为止。

2. Abstract & Concrete Instruction(抽象和具体指令):

uSI操作(语义函数)确定抽象机的配置怎样随着指令

的执行而演变。每条指令都定义它自己的SI语义函数,并确

定抽象机的状态怎样变化(更改数据区和 / 或程序)。为了

完成这些变动,各指令必须能够访问当前状态上的组件。这

是通过抽象机状态的copy-reference(拷贝引用)来完成的;

该 copy-reference 被作为参数传递给指令。

协作

抽象机在执行过程中可能到达三个不同的阶段:initial-

ization(初始化)、transition(迁移)和 ending(结束)。图

4、5、6 勾画了这些阶段。

阶段 1:Initialization

抽象机在这一阶段被初始化。结果DataArea和Program

都会被初始化。Program 的初始化是由 Init操作完成的,并

涉及将其组件设置为初始值(也就是,程序计数器、指令阵

列,等等)。然后,负责汇编程序加载(也就是,将来自

inputStream的汇编指令的文本表示翻译为程序的指令对象)

的LoadProgram操作被执行。另一方面,Init操作对DataArea

中的组件(也就是,数据寄存器或控制寄存器,等等)进行

初始化。

阶段 2:Transition

如前面所说的,Transition操作完成抽象机执行。因而,

Transition操作(通过 CurrentInst操作)从程序那里请求所

要执行的指令,即由程序计数器所指向的指令。各指令的执

行构成抽象机的执行,直到到达结束状态为止。每条抽象机

指令负责定义它自己的语义。因而,每条指令都提供SI操作

——取决于指令是否更改程序和/数据区——其执行对抽象

机的状态(也就是,程序和/或数据区)进行更改。更改被

完成的次序仅由指令语义来决定。

阶段 3:Ending

抽象机在它的两个组件中的任何一个(或两者同时)到

达结束状态时(例如,执行了一条具体的停止命令,数据区

溢出,等等)到达结束阶段。该状态对DataArea和Program

的 Stop 操作进行求值,并合并它们的结果。

效果

抽象机模式显示出以下优越性:

u 提供了用以开发抽象机的框架。该模式定义了用以

开发抽象机的高级而灵活的设计。

u 提供了用以通过抽象机的渐进优化来开发编译器的

框架。在编译过程中的优化步骤可以根据抽象机的结构或其

组件的优化来表述。

u 使抽象机各参与者之间的关系去耦合。Abstract

图 4 初始化阶段(协作)

图 5 迁移阶段(协作)

图 6 最终阶段(协作)

Page 90: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

90

Machine Factory 帮助使 Abstract Machine State 和具体

的数据区及程序实现隔离开来。Abstract Machine State通

过各实例的抽象接口来对它们进行操作。在 Abstract In-

struction Set 这一边,它被用作由程序进行管理的指令集

的抽象工厂。

u提供用以测试不同指令集的框架。该模式使得变换

指令集变得更为容易,允许数据区和程序独立于任何具体

的指令集进行维护。要改变具体的指令集,只需重新配置抽

象机。

u提供用以测试同一机器指令的不同指令语义或实现的

框架。因为语义封装在指令(SI操作)中,我们可以重新定

义它们,而不会影响数据区和 / 或程序。

u提供更高水平的抽象机复用。在大多数情况下,全部

或部分的抽象机,可在新的开发中被高度复用。

u提倡一种用于最为系统的抽象机开发的方法学。通过

使抽象机的参与者分离在各组件中(也就是,数据区、程序、

指令集和指令语义),我们引入了一些设计约束,强制用户

遵循系统化的方法。

抽象机模式也显示出以下不足:

u产生出低效的实现。

u 在 Abstract-Machine State和 Abstract Instruction之

间引发通信开销。

实现

考虑以下实现问题:

1. 创建具体的 Data Area和 Program。Abstract-Ma-

chine Factory只为创建 Data Area和 Program对象声明了

一个接口。遵循为 ABSTRACT-FACTORY 模式所给出的

类似提示[4]。

2. Concrete Data Area可以具有复杂的对象成分。遵

循 COMPOSITE 模式中的同样的实现问题的解决方法[4]。

3. 将 Abstract Data Area、Abstract Program、Ab-

stract Instruction和 Abstract Instruction Set定义为接口。

例如,遵循 Java约定:

public interface AbstractInstruction{

public void SI (AbstractMachineState state);public String Name ();public int NumArguments ();

public String ToString ();public void Process (String args [])}

public interface AbstractInstructionSet{ ... }

public interface AbstractProgram{

public AbstractInstruction CurrentInst ();public void Init ();public boolean Stop ();

public void LoadProgram (Stream assemblerCode);}

public interface AbstractDataArea{public void Init ();

public boolean Stop ();}

Abstract Instruction接口提供了两个方法来确定与指令

有关的信息(也就是,Name、NumArguments);方法SI声

明指令语义;以及最后,方法 Process 将指令参数的文本表

示编译 /翻译为该指令所管理的信息。SI方法将对 Abstract

Machine State 的引用作为参数进行接收。

Abstract Instruction Set 定义可由程序进行管理的指令

集。该组件可以是目录名、指令名的枚举集、包、模块,等

等。在 Abstract Instruction Set接口中定义的操作只关心所

选择的表示法。

Ab s t r a c t P r o g r a m 定义汇编程序的通用接口。

Current Ins t 操作被用于访问将要被执行的指令。此外,

LoadProgram操作负责构建将要被加入程序中的 Concrete

Instruction对象。

4. 作为模板参数的AbstractDataArea、AbstractProgram

和 AbstractInstruction。在类 C++ 语法中,如下所示:

class AbstractInstruction{ .. }

templateclass AbstractProgram <class AbstractInstruction>

{ .. }

class AbstractDataArea

{ .. }

template

class AbstractMachineState < class AbstractDataArea,class AbstractProgram <AbstractInstruction>>{ .. }

5. 省略 Abstract-Machine State 类。它与 MEDIATOR

模式的情况类似[4]。

6. 原始操作(Primitive operation)。在 Abstract

DataArea、Abstract Program 和 Abstract Instruction中定义

的一些操作是原始的。因而,它们必须被重新定义。例如,

它们可以被声明为纯虚函数(C++方法),或是接口的一部

分(Java 方法)。在 Abstract-Machine State 中的操作 Init、

Transition和 Stop 不能被重新定义。

示例代码

Design Pattern

Page 91: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

91

技 术

在“实现”部分,我们已经定义了对应于 Abstract

DataArea、Abstract Program 和 Abstract Instruction

的接口。我们以 Abstract Program 程序为例,给出其

Java 实现代码。读者可以在 CSDN 网站《程序员》频道

下载此代码。

抽象机模式的其他组件,可参照此例来加以实现。

已知应用

本文所介绍的模式已被用于形式化及实现Prolog语言的

一种抽象编译器[2]。作为此工作的成果,我们已经获得了一

种基于抽象机技术的多阶段编译方法。此外,我们还发现抽

象机模式十分适于以一种非侵入式的方式、轻易地在抽象机

中包含可视化工具和调试机制及工具[3]。

相关模式

在[5]中介绍的工作研究了用于构建虚拟机的模式语言的

定义。与[5]不同,我们的建议更为聚焦于提供更高级的设计

构架,而不是那么多地描述虚拟机的低级层面。

抽象机模式结合了在[4]中介绍的一些模式:

u Abstract Machine State,MEDIATOR模式的一种

变种,就属于这种情况;

u Abstract Machine Factory和 Abstract Instruction

Set 属于 ABSTRACT FACTORY 模式;

u 另一方面,在 SI 操作(在 Abstract Instruction中)

(上接 107 页)蓄水池。后面的 n 行每一行是 4 个用空格隔开

的非负整数:b, h, w, d,分别表示蓄水池的底面高度、

蓄水池的高、宽和厚度,且满足 0≤ b≤ 106,1 ≤ h× w×

d≤40000。最后一行是一个整数V,表示需要灌入的水的体

积,1 ≤ V ≤ 2 × 109。

输出:

输出文件为 cistern.out。输出文件有 k行,每一行对应

一组输入数据,表示水平面所能达到的最大高度,精确到小

数点后面2位小数。如果灌入的水的体积超过所有蓄水池的

总容积,则输出“OVERFLOW”。

输入示例

32

0 1 1 12 1 1 11

411 7 5 115 6 2 2

5 8 5 119 4 8 1

的后面有着一种隐藏的 STRATEGY 模式;

u 最后,在 Abstract Machine State中的 Init、Transi-

tion和 Stop 操作是 TEMPLATE METHOD模式的明显实例。

参考文献

[1] Arnold & Gosling. The Java Programming Language. Addison-

Wesley, Reading, Massachusetts, 1996.

[2] Garc í a J. & Moreno J.J. Visualization as Debugging :

Understanding/Debugging the WAM, Automated and Algoritmic Debugging

(AADEBUG'93), Lecture Notes in Computer Science (LNCS 749), Springer-

Verlag, 1993.

[3] García J. & Moreno J.J. A Formal Definition of an Abstract

Prolog Compiler, AMAST'93. Workshops in Computer Science, Lecture

Notes in Artificial Intelligence (LNAI), Springer-Verlag, 1993.

[4] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides.

Design Patterns. Elements of reusable object-oriented software. Addison-

Wesley, Reading, MA, 1995.

[5] Jacobsen, E.E. & Nowack, P. A Pattern Language for Building

Virtual Machines. 2th European Conf. Pattern Languages of Programming,

Irse (Germany) , July 1996.

[6] Reade, C, Elements of Functional Programming, Addison-

Wesley, 1989.

[7] Warren, D. H.D, An Abstract Prolog Instruction Set. Tec. Note

309, SRI International, Menlo Park, California, October 1983.

132411 7 5 1

15 6 2 25 8 5 119 4 8 1

78

输出示例1.00

OVERFLOW17.00

运行时限:

在赛扬 1G CPU 上所有测试数据运行总时间不超过 10

秒。请注意,测试数据量很大,如果你的算法不好,可能运

行时间会很长。

参赛要求:

1. 选手可以通过EMAIL方式([email protected])参赛;

2. 具备良好的编程风格,程序中有适当的注释,输入

输出请严格地按照题目要求的格式;

3. 要有详细的算法分析。

Page 92: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

92

C++

C++高效程序设计 撰文 /Joris Timmermans 编译 / 许一汀

摘要

不管是否愿意承认,每个人都希望程序的运行速度越快

越好。每天人们都你追我赶,好像明天就是末日。而同时,

公关部的那些家伙则不停的吼叫着,说他们的新引擎比其他

人的更“快”更“好”。

我并不打算告诉你如何让你的代码跑得比别人的快。我

只是想告诉你,如何让你的代码更快、更高效——当然,是

跟你原来的代码相比。

我讲述的内容主要涉及三个概念,这三者之间的关系相

当复杂:

1、代码执行时间

2、代码 / 程序大小

3、程序设计本身的开支

我始终坚信应该保持这三者之间的平衡,尤其在某些情

况下,2、3 两项直接影响了代码的执行时间。

在本文中,我将讲述一些可能有助于你提高代码执行效

率的方法。我会从最简单的优化方法开始,然后逐渐深入到

那些比较复杂的技术。现在我们首先从一个不太显眼的地方

开始:编译器。

考虑到读者中有一些经验丰富的程序员,我的叙述会尽

可能简单,以避免因为细节太多而显得杂乱不堪。

一、工欲善其事,必先利其器

这一节的内容似乎不说也罢,不过仔细想想,你对你手

中的编译器到底了解多少?你知道它可以为哪些处理器生成

代码吗?你知道它可以进行哪些类型的优化吗?你知道它的

语言不兼容性吗?

当你想要写出点什么的时候,尤其是当你希望你的代码

运行如飞的时候,了解这些内容将是至关重要的。

举例来说,最近在 GameDev 的讨论组里有人问关于

Microsoft Visual C++ 的“Release Mode”的问题。这是一

个标准编译器选项,如果你使用特定的编译器,你就应该知

道它的意思。如果你不知道,那很遗憾,你并不真正会使用

你花费了大量的金钱买来的东西。简单来说,“R e l e a s e

Mode”会删除所有debug用的代码,进行所有可能的编译代

码优化,生成更小的可执行文件,还让这个文件运行的更

快。它可能还会有一些其它的功能,如果你感兴趣,请阅读

编译器的相关文档。

看到了吧,如果你以前并不知道这个“Release Mode”,

我现在就可以告诉你一个让你的代码运行更快的方法,而且

这个方法不需要你修改任何代码!

目标平台也是非常重要的。现在,你遇到的最低档的可

能就是 Intel Pentium 处理器了,不过如果你使用 10 年前的

编译器,那么它不会做任何针对 Pentium的优化。去找一个

最新的编译器,它可能会大大提高程序的运行速度,同样,

也不需要你对代码做任何的修改。

另外还要注意一些事:你的编译器有没有代码分析

(profiling)工具?如果你连这个都不知道,那么你就不

要指望编写出更快的代码了。如果你还不知道什么是代

码分析工具,那么你还需要更多的学习。一个代码分析工

具就是一个用来获得程序的运行时间的东东。你在代码

分析器(profiler)中运行你的程序,做一些操作,然后

再从你的程序中退出,就可以获得一个关于每个函数耗

时的报告。你可以根据这个报告找到代码的运行瓶颈—

—就是你的代码中花费时间最多的部分。对这些部分作

一些特定的优化比随随便便的在每个地方都做一点优化

效果要好多了。

不要说“但是我知道我的瓶颈在哪!”它们可不是

光用脑子就可以找到的,尤其是在使用第三方API和程

序库时。几个星期前我还遇到一个类似的问题,在一个

视频程序里,显示每一帧时都会莫名其妙的产生状态切

换,而这个动作占用了总执行时间的25%。通过简单地

添加一条测试语句(测试状态是否已经被设置),我把

相应的那个函数从分析得到的 5 0 个最昂贵的函数列表

中剔除了。

看上去在大多数情况下,使用分析器可以很容易达到目

的,但事实上并非如此。你必须找到程序中的关键路径。所

谓关键路径就是程序大部分运行时间都在执行的路径。对关

键路径进行优化可以显著的提高运行效率,你的用户也会因

此而高兴。

另一种情况是,也许你发现在某个函数中,时间开支

最大的步骤是装载一个特定的文件,但是你知道这种情况

只会在应用程序启动时发生一次。对这个函数进行优化也

许可以让程序的总运行时间减少几秒钟,但不会提升正常

Page 93: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

93

技 术

使用时的效率。事实上,这表明你没有进行足够的代码分

析,因为在正常使用时,这个函数所占用的时间百分比将

会越来越低,而你的关键路径所占用的时间百分比将会一

路飙升。

我想以上这些内容能够使你对这些工具有了一些了解。

代码分析工具实在是太好了,记得一定要用!如果你还没有

代码分析器,你可以试试 Intel的VTune profiler。你可以免

费试用它一个月。在下面这个网址下载它http://developer.

intel.com/vtune/analyzer/。

在本文的下一部分,我将告诉你如何让你的 C/C++编

译器做你想让它做的事。

二、Inlining,inline 关键字

什么是 inlining?我会通过描述inline关键字来回答这个

问题。

inline关键字告诉编译器“在适当的地方展开函数”,它

工作起来很像是 C 和 C++ 中的宏(#define),但是有一点

不同:inline 函数是类型安全的,其主要作用是帮助编译器

进行代码优化。有了它,你就可以同时具有宏的速度(没有

函数调用的额外开销)和函数的类型安全性,以及一大堆其

它好处。

还有什么好处呢?大多数编译器在同一时间内只能优化

一个模块中的代码。通常就是一个.h/.cpp 文件对。使用

inline函数,就使得编译器对在不同的模块中的函数也可以

进行代码优化,比如消除返回值拷贝,消除多余的临时变

量,等等。如果你想要了解更多关于编译器优化的内容,请

参考其他的参考文献,尤其是那本 Effective C++。

可怕的 inline 关键字。我不得不这样说,因为关于它

的误解实在太多了。Inline关键字并不强迫编译器inline特

定的函数,而只是建议编译器这样做。以下内容引自

MSDN:

“The inline keyword tells the compiler that inline

expansion is preferred. However, the compiler can create a

separate instance of the function (instantiate) and create

standard calling linkages instead of inserting the code inline.”

(inline 关键字告诉编译器最好进行 inline扩展。但是,编译

器可能会创建一个独立的函数实例和一个标准的调用连接,

而不是将代码内联的插入。)

某些情况下编译器会忽略你的 inline请求,这些情况包

括:在 inline 函数中使用了循环;在 inline函数中调用其它

inline函数;递归。

上面引用的那段话还隐含着其它一些内容:一个声明为

inline的函数,必须进行内部连接。这就是说,如果你的inline

函数在另一个object文件中实现,你的连接器在连接这个函

数时就会卡壳。ANSI 标准倒是提供了一种方法解决这个问

题,可惜的是目前为止 Visual C++(6.0)尚不支持这种解

决办法。

“那么,”你要问了,“到底应该怎么办呢?”答案很简

单:总是在同一个模块中实现 inline函数。这个方法做起来

很简单,只要将整个函数实现写到.h文件中,并且在所有用

到这个函数的模块中包含这个.h文件。也许这并不想你想象

中的那么美好,不过它的确可以正常工作。

事实上,考虑到隐藏实现的问题(我是个面向对象

偏执狂),我并不喜欢这个方法。但是最近我的确使用这

个方法编写了很多类。有一个好处是,我不需要输入

inl ine 这个关键字——如果你把整个函数定义放进类定

义中,编译器会自动把它看成 inline 函数。如果一个类

的所有函数都应该是 inline 的,那么我就把整个类定义

及实现都写进头文件中。我建议你只在真正迫切需要提

高运行速度时才这样做,当然,你也不在意太多的人

share 你的代码。

三、搭乘类高速列车

设计执行速度快的类是C++程序设计的关键。我用一个

3 d 向量类来说明这个问题(这在我的工作中是很常见的

类)。事实上,就在前几个星期,我刚刚完成了一个向量类。

在编写这个类的一个月里,我犯下了太多错误。

一个向量类是必须的,因为工作中有大量的向量数学运

算,显然每次都要反复书写相同的内容。如果你想提高编码

效率,同时又不想牺牲代码运行速度,那么就要编写一个向

量类,我的这一个叫作 CVector3f(3f的意思是三个 float 数

据)。为了提高代码的可读性和可维护性,我希望利用 C++

伟大的特性之一——运算符重载(operator overloading)实

现一些运算符函数(+,-,*)。

在最初的设计中,我很快地实现了一个构造函数、一个

拷贝构造函数、一个析构函数以及上面提到的那三个运算

符。设计过程中,我没有特别考虑效率的问题,也没有使用

inline函数,只是简单的把函数声明放入头文件,把函数实

现放入.cpp 文件中。

下一步是让它跑得更快。我做的第一件事是在头文件中

将所有成员函数声明为 inline函数。如果编译器真的将它们

处理成 inline函数,那么我们就可以节省下函数调用的额外

开销。对于我的向量类中的那些小函数来说,执行速度有了

显著的提升,不过对于那些较大的函数来说,这样做可能不

会有明显的效果。

我想到的第二件事是:我们真的需要析构函数吗?正常

情况下编译器会为我们生成一个空的析构函数,通常它会比

我们写的析构函数效率更高。在我们的向量类中,并没有什

Page 94: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

94

么东西需要析构,那么为什么还要浪费时间?

运算符也可以跑得更快。先前的运算符函数大致如下:

CVector3f operator+( CVector3f v ){

CVector3f returnVector; returnVector.m_x = m_x + v.m_x; returnVector.m_y = m_y + v.m_y;

returnVector.m_z = m_z + v.m_z;

return returnVector;

}

这段代码隐藏着众多的多余代码,着实令人烦恼。我

们来仔细看看这段代码,代码的第一行声明并构造了一个

临时变量。这就是说,这个对象的默认构造函数被调用,

但是我们并不需要初始化它,因为我们将要给它赋一个全

新的值。

代 码 结 尾 处 的 r e t u r n 语 句 也 是 一 样 — —

re t u r nV e c t o r 是一个局部变量,所以不能被马上用于

return。此时,拷贝构造函数将被调用,这将会占用相当

多的处理器时间,尤其对于这样的小函数更是如此。另一

个隐藏的更深的家伙是传递到函数的参数。这个参数同

样是一个实参的拷贝,于是更多的内存被占用,更多的拷

贝构造函数被调用。

如果我们编写一个新的构造函数——它接受x、y、z三

个参数,并且这样使用它:

CVector3f operator+( const CVector3f &v ) const{return CVector3f( m_x + v.m_x, m_y + v.m_y, m_z + v.m_z );

}

那又会怎么样呢?

这样做将会去掉两个拷贝构造函数的调用,进步很大,

不是吗?注意我为这个函数加上了 const 关键字。这样做不

是出于速度方面的考虑,而是为了增加代码的安全性。另一

点要指出的是(涉及到编译器的内部实现),我所编写的这

个函数允许编译器更容易的进行它自己的代码优化。这个函

数中几乎所有内容都是很清楚的,不需要什么前提条件,因

而它是一个很好的 inline候选函数,编译器还可能会对它进

行一些其它的优化,如“返回值优化”(参见本文结尾处的

参考文献)。

本节我想要说明的主要问题是,在C++代码中可能存在

着很多“隐藏的”开销。构造函数/析构函数、继承以及聚

合类型(数组等)的使用都可能产生一个看似简单、私下却

执行着复杂的初始化工作的函数。了解这种情况何时会发

生,了解如何避免或降低它的消耗,这些都无疑是C++学习

过程中一个重要的组成部分。

熟悉你的语言,这是唯一可以真正帮助你自己的。

四、刨根问底

于是现在你有了一个漂亮的、运行速度飞快的类,但是

你仍然不开心。时间过得太快了。

1、循环优化

解除循环曾经是一件“大事情”。这是什么意思呢?就

是说,一些循环可以简单地去掉。比如:for( int i = 0; i < 3; i++ ) array[i] = i;

在逻辑上这就等同于array[0] = 0; array[1] = 1, array[2] = 2;

而第二个版本会稍微快一点,因为不需要建立一个循环

—— i的初始化和自加会消耗一点时间。大部分编译器已经

可以完成这样的工作,所以在大多数情况下,你可能不会得

到太多的好处,同时代码却会大大的膨胀。我的建议是,如

果你再也找不出其它增加速度的方法,那么就试试这个,但

是不要对它希望太大。

2、移位(bit shifting)

移位只对整数运算起作用。通过移位进行2的整数次幂

的乘除法要比直接进行乘法运算快很多(当然比除法运算更

快),这是一个基本常识。

为了理解它的用法,考虑下面这几个公式:x << y = x * 2 y

x >> y = x / 2 y

Andr LaMothe在他的《Tricks of the Game Program-

ming Gurus》一书中大量地阐述了这方面的内容。在某些情

况下,这种方法可以带来巨大的回报。考虑下面的这段简化

了的代码:i* = 256;

与之对应的i = i << 8;

在逻辑上它们完全相同。对于这个简单的例子,编译器

很可能会自动将第一条语句转换为第二句,但是当你进行更

复杂的计算时(比如i = i << 8 + i << 4等于i *= 272),

编译器可能就无能为力了。

3、指针解引用(dereference)操作的地狱

你的代码中有类似下面的内容吗?

for( int i = 0; i < numPixels; i++ ){

rendering_context->back_buffer->surface->bits[i] =some_value;

}

这也许有些夸张,但是可以说明问题。这是一个很

长的循环,而且所有指针的解引用操作耗费了大量的

时间。

你可能会认为这是一个不实际的例子,但是我曾经在许

多网上发布的代码中见过与之类似的内容。

为什么不这样做?

C++

Page 95: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

95

技 术

unsigned char *back_surface_bits = rendering_context->back_buffer->surface->bits;

for( int i = 0; i < numPixels; i++ ){ back_surface_bits[i] = some_value;

}

这样你就避免了大量的解引用操作,这会大大的提高运

行速度,何乐而不为。

在 Gamedev.net 上,Goltrpoat 给出了一个更快的方法,

这个方法非常有效,强烈推荐:

unsigned char *back_surface_bits = rendering_context->back_buffer->surface->bits;

for( int i = 0; i < numPixels; i++,back_surface_bits++ )

{ *back_surface_bits = some_value;}

上面的内容只是下面内容的一个特例(虽然经常出

现):

4、循环中进行不必要的运算

考虑下面这个循环的代码:

for( int i = 0; i < numPixels; i++ )

{ float brighten_value = view_direction*light_brightness*

( 1 / view_distance );

back_surface_bits[i] *= brighten_value;}

计算brighten_value不仅代价昂贵,而且也是完全不必

要的。这个计算完全不受循环的影响,所以可以简单地移

动到循环外,而在循环中只需反复使用 brighten_value 的

值即可。

这个问题也可能以另一种形式出现——在被循环或者

对象构造函数反复调用的函数中进行不必要的初始化。谨

慎的对待你的代码,要不断的问自己“我真的需要做这些

事吗?”。

5、内嵌汇编代码

最后一种方法,如果你真的、真的明白自己在做什么,

并且知道为什么它会变得更快,你可以使用内嵌汇编代

码,或者甚至是使用 C风格链接方式的纯汇编代码(这样

它可以被你的 C/C++程序调用)。不管怎样,如果你使用

内嵌汇编,那么要不然使用条件编译(测试你编写的汇编

代码是否被你的编译平台支持),要不然放弃代码兼容性。

对于普通的 80x86汇编,你可能不用考虑太多,但是如果

你使用 MMX、SSE或3DNow!指令,就限制了代码的平台

兼容性。

当你不得不进行这项工作时,一个反编译工具可

能会有用。你可以让大多数编译器生成汇编代码,然

后你就可以仔细地检查它,看看是否能手动提高代码

的效率。

再说一次,这里又涉及到第一节中讲到的问题。在Visual

Studio 中,你可以使用/FA和/Fa编译器开关来生成汇编代

码文件。

五、数学优化

1、使用简单的数学表达式以提高效率

下面列举的只是你所能做到的事情中的一部分,显而易

见但决不可忽视的问题。

·a*b + a*c = a*(b+c);在不改变表达式含义的情况

下,等号左边的表达式比右边的少一次乘法运算;

·b/a + c/a = (1/a)*(b+c);这是上一种形式的一种

变形,不过这一次是以一个乘法和一个除法运算取代两个

除法运算。在我所知道的硬件平台上,除法运算都要比乘法

运算慢;

·((a || b ) && c) = c && ( a || b );这也许不是那

么明显,不过 C++ 标准采用惰性比较。对于等号左边的情

况,不管 c 是否为真,( a || b )表达式都会被计算,而等号

右边的表达式则在 c为假时不再计算( a || b ),因为此时整

个表达式的值已不可能为真。

以上是一些极简单也极常见的例子,但远不是完全的,

所以如果你知道一些其它的情况,不要忘记告诉我一声。

2、扬长避短

假设你有一个非常好的 PentiumIII 平台用于你的程

序设计,那么你就拥有了32位的寄存器,但是你却要执

行16位数据的操作。在某些情况下——依赖于操作的类

型和溢出的可能性——你可以一次在一个寄存器中执行

两次操作。

这就是所谓的 SIMD(Single Instruction,Multiple Data

——单指令,多数据)——比如 SSE,3DNOW!以及 MMX

——产生的原因。

在这方面我还算不上一个专家,所以我无法给出详细

的例子,但是我想最好告诉你这一点,因为它可能对你有

帮助。

六、结论:调动你的大脑

有些时候,现存的所有代码优化的方法都不能让你的程

序跑得更快。有些时候,优化的主攻方向是错误的。那么退

一步,重新想想你的算法,试试以另外的方式完成它。谁知

道你会遇到什么事!

作为一个例子,想想排序。不管是怎样的程序设计技

术,冒泡排序总是要慢于快速排序,即使它们完成的是相同

的功能。

Page 96: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

96

Delphi

Delphi中不同常规的接口 撰文 /Malcolm Groves 编译 / 江磊晶

关键词 Delphi 接口 生命周期管理 垃圾收集 对象快照

自从在 Delphi 3中引入了接口这个概念以来,已经有不少文章是在专门讨论这个话题的。那么,我还会在有关接口的话

题中增加一些什么新的内容呢?好,我现在开始准备着眼于关于接口的一些特性,其中的一些特性就是我们经常所关注的接

口的“副作用”,也就是生命周期管理(lifecycle management)。我将准备介绍给你一些有关接口的用法,这些用法可能是你在以

前所没有看到过的。并且在文章中,你也会看到我提供了不少有助于编程的工具类。

简介

为什么在当我谈到生命周期管理(lifecycle management)

的时候,有时候我会把它看作是一个副作用呢?通常我们经

常所介绍的接口是被用来整理对象层次的一个工具。好了,

现在让我解释一下其中的含义。

在接口出现以前,你可能已经开发出了对象的层

次。这你可以通过使用具体的类来从它们的每个父类来

获取特性(feature)。当然类的上溯直到TObject。通常,

这些特性被定义于类对象层次中的特定的类中,这样就

允许只有正确的子类才能继承它们所需要的特性。如果

你将类中特性的定位太接近于整个对象层次的顶层的

话,那么这些特性将会被你太多的子类所继承到;而如果

你将其定位于太靠近整个对象层次的底部的话,那也就

意味你有很多类将继承不到这些特性。所以,合理设计你

的对象层次结构就变得好象是在整个结构中定位不同类

特性的平衡游戏,不过这样做能使得你所定义的类能正

确的获得你所需要的特性。

不过,通常在你定义的对象层次中要找到一个完美的

地点来引入特性也很有可能不是一件很容易的事。经常,你

会有一些互有冲突的需求,这意味着你需要将所引入特性

的模型定位于层次的底部,这会为你以后的使用带来了很

多限制。事实上你可以将那些特性定位于你模型中的两个

完全不同的类中(不在一个继承链),并使其变得可用,而它

们共同享有的基类很可能位于离对象层次顶部很近的地

方。将特性加入到这个基类中意味着所有它的子类都会继

承到这些特性,而很有可能你不需要以这种方式来实现。一

个可供选择的方法就是将这些特性分别定义于两个不同的

类中,但是这样做我们也就丧失了在两个类中实现多态特

性的能力,记得有人好像说过这样一句话“设计就是妥协,

设计就是折衷”。

不过,接口可以让你定义跨越对象层次的特性,而不是

将这个特性定义于对象层次的底部或顶部。所以,这样我们

就可以在两个不同的类中添加入相同的特性了。因而,它可

以限制这些特性只可以被相关的子类访问到,不过它仍然保

持了在类之间实现多态特性的能力。我们可以在这两个不同

的类中实现这些特性,这两个类也共享这些特性的定义,这

对我们来说是很重要的。

生命周期管理(Lifecycle Management)

好了,这就是我们通常所认为的接口的作用。我认为其

中有很多重要的特性从Delphi 1开始就已经被加入到Delphi

中了。不过正像我在以后所提到的那样,许多聪明的程序员

已经知道了很多与接口方面问题相关的详细细节。但是,如

果你现在还不知道,你最好将你所收集的Delphi CD拿出来

作一些仔细的阅读。

我现在所要探索的就是与接口相关的类的一些特性,它

们通常是被分离作为类的实现细节的。这里我所要讨论的一

个事实就是当你创建了一个类的实例并将其存放入一个接口

变量的时候,Delphi 将会编译一些额外的代码以调用类的

_AddRef方法,这样就会将引用这个实例的计数器增加。每

当实例存放到了其他接口变量中时,这个_AddRef方法都会

被调用。每当实例被覆盖或不在它的范围之内,_Release方

法会被调用,并且引用计数器会递减。当这个引用计数器为

零时,好啦,这时就意味着没有引用可以被保存到对象中,

因而这个对象可以被安全地销毁。我们将它变得更简单一

些:如果有个类实现了你的继承于 TInterfacedObject 的接

口,在这种情况下的所有代码在文后都已经写给你了。但

是,如果你希望实现在其他类层次下的接口,你只需要再实

现几个方法就可以了。

让我们从听起来很糟糕的自动垃圾收集(Automatic

garbage collection)开始吧。如果你读过我以前所写的一

些文章,你就会知道我经常用 Java 和 Delphi 进行编程工

Page 97: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

97

技 术

作。所以我经常鼓励自己努力让每种编程语言等同于其他

的一些编程语言。所以在这篇文章中我将展示给你如何利

用这些接口的“副作用”以使我们获得几种防止内存泄漏

问题出现的方法。同时,我也将探索接口的其他两种用

法,这些用法是不与垃圾收集特性相关的。我很希望通过

这篇文章能为大家 Delphi编程的工具箱内多增加一些有用

的工具。

廉价的垃圾收集特性

好了,到目前为止,我们已经知道了一些在Delphi中接

口引用计数器的工作特性了。现在我们可以研究一下:如何

利用这些特性来获得 Delphi中自动垃圾收集的好处。

在 Delphi 中,一个很明显的获得垃圾收集特性的方法

是:访问你所有通过实现了某个接口而得到的类。不过这可

能带来极其槽糕的现象:要为你所有定制的对象构建接口,

同时也要创建子类以显露所有 VCL 对象和第三方控件中接

口的能力。

另外一个可供选择的方法是为分离对象到其他对象中的

这个对象委派职责。它们的生命周期是由接口来管理的。为

了使用这个方法,我们可以创建一个实现了接口的普通对

象,并且我们要给予这个对象到一个我们所希望管理生命周

期对象的引用。当我们这个普通对象被销毁的时候(因为这

时接口的引用计数器的值已经为零了),它同时也销毁了我

们所要求管理的那个对象。这不仅给予了我们可以针对任何

对象的一般解决方案,而且我们为了实现这个方法也需要做

不少的工作。这最后一点对我来说已经足够了。好了,现在

让我们来看看列表 1 中的代码。

列表1

unit mtReaper;interface

type ImtReaper = interface ['{1B321324-975F-4026-B742-E7B3AD486BB5}']

end; TmtReaper = class(TInterfacedObject, ImtReaper) private

FObject : TObject; public constructor Create(AObject : TObject);

destructor Destroy; override; end;implementation

uses SysUtils;constructor TmtReaper.Create(AObject: TObject);begin

FObject := AObject;end;destructor TmtReaper.Destroy;

begin FreeAndNil(FObject); inherited;

end;end.

在这里我们定义了一个名为ImtReaper的接口,其中

没有任何方法。这种类型的接口我们通常称为标签(Tag)

或标记(Marker)接口,它经常被用于标记一些内含有语

义属性的类。这种接口的存在表明了类能以一种特定的

方式被使用。在这里,我们只是想让这个接口利用引用计

数的特性。

接下来,我们定义了一个名为TmtReaper的类,它继

承于 TInterfacedObject 并且实现了 ImtReaper 这个接口。

我们继承了一个名为 TInterfacedObject的类,它是一个轻

量类,其中已经为你实现了在 IInterface(在 Delphi 5 或早

期版本中是 I U n k n o w n ) 中的一些方法,也就是

QueryInterface、_AddRef 和 _Release。正因为我们在

ImtReaper接口中没有定义任何方法,所以没有添加任何东

西到我们的 TmtReaper类中,这是为了能让TmtReaper类

能正确地实现这个接口。

最后,我们添加了一个构造子、一个析构子和一个类型

为TObject的私有变量FObject。在构造子中,我们将一个对

象的引用作为它的参数,并且在这个对象引用中我们存放了

FObject。FObject随后在析构子中被销毁。这是一个相当简

单和普通的实例。

为了使用它,我们可以使用在列表2中展示给大家的代

码。TN o i s y D e a t h 对象覆盖了它的析构子,使其调用

ShowMessage 方法,因此我们可以知道它被销毁的准确时

间。WaitAwhile 方法负责计数直到 5000,并且在窗体的

Caption属性(也就是在标题栏上)显示其中每个值。这样我

们可以停留足够长的时间,能看到当对象被销毁时候的一

些情形。事实上我们会在 Button3Click 方法中使用以上所

提 到 的 对 象 。 在 这 里 , 我 们 以 常 规 方 法 创 建 了 一 个

TNoisyDeath类型对象的一个实例。然后,我们创建了一个

TmtReaper类型对象的一个实例并且将其存放入TmtReaper

类型的变量中,这时会传递一个引用到 TNoisyDeath 类中

的构造子中。一旦我们的方法结束,ImtReaper类型变量的

范围也将会结束,并且引用计数将会递减。当引用计数的值

被减到零时,我们的 TmtReaper 对象就会被销毁,同时,

TNoisyDeath对象也将被销毁。在这段代码中,没有 try..

finally块,没有内存泄漏,代码输入量也比较少。如果你仍

然渴望学习更多的代码,请各位继续研究在本文稍后会展

示给大家的代码。

列表2

unit fReaperTestMain;interface

uses

Page 98: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

98

Delphi

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;type

TNoisyDeath = class public destructor Destroy; override;

end; TForm1 = class(TForm) Button1: TButton;

Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject);

private procedure WaitAwhile; public

end;var Form1: TForm1;

implementationuses mtReaper;{$R *.dfm}

destructor TNoisyDeath.Destroy;begin ShowMessage('Aaaaaargh!');

inherited;end;procedure TForm1.WaitAwhile;

var intIndex : Integer;begin for intIndex := 0 to 5000 do

begin Caption := IntToStr(intIndex); end;

end;procedure TForm1.Button1Click(Sender: TObject);var NoisyDeath : TNoisyDeath;

begin NoisyDeath := TNoisyDeath.Create; try

WaitAwhile; finally NoisyDeath.Free;

end;end;procedure TForm1.Button2Click(Sender: TObject);

var NoisyDeath : TNoisyDeath; Grim : ImtReaper;begin

NoisyDeath := TNoisyDeath.Create; Grim := TmtReaper.Create(NoisyDeath); WaitAwhile;

end;end.

你如果需要让你的对象保存的时间比方法调用更长的

话,只要将 ImtReaper变量保存在最终能让这个变量持久存

放的地方即可(在很多类中你需要将这个变量声明为private

的)。在这种情况下,当这些类被销毁的时候,对象也将被

销毁。你还有另一种选择:可以通过在早些时候将这个

ImtReaper 变量赋值为 nil 来销毁这个对象。

对象快照

对于我们来说,在对象被销毁的时候,我们就可以使用

在接口中引用计数这个工具了。你有没有经常以列表3的格

式来写你的代码呢?这段代码中的方法其实不仅仅只应用

于指针操作。事实上在方法执行的时候你可能所希望作出

的一些改变就是:当方法结束运行的时候,将对象恢复到以

前的状态。指针操作实际上是很简单的。让我们试想一下你

在 Paint 方法运行的时候,其中你可能会需要操作 Font、

Brush 或 Canvas 的不同属性,你还会在方法结束运行的时

候,将它们的这些属性恢复。这时,你就不需要在本地变量

中保存很多这样的属性了,而应该在 finally 块中将它们这

些属性恢复。

列表3

var OldCursor : TCursor;

begin OldCursor := Screen.Cursor; try

Screen.Cursor := crHourglass; // do your stuff finally

Screen.Cursor := OldCursor; end;end

更简单的解决方案应该是:获得当前对象状态的一个快

照,然后明确指示这个对象的快照恢复对象的状态;你也可

以简单地让这个方法结束,这样就可以把这个对象的状态自

动恢复了。让我们看看这是怎么工作的。

在列表 4 的代码片段中包含了我们所写的一个名为

ImtSnapshot的接口,其中内含了一个方法,这个方法其

实是一个名为Restore 的过程。列表 5 的代码片段中包含

了这个接口的一个简单实现,这是以TmtCursorSnapshot

对象的形式出现的。在构造子中,我们仅仅是在一个私有

变量中保存了一个Screen.Cursor当前值的一个副本。在

析构子中我们调用了 Restore 方法,它将 Screen.Cursor

中的值设置为我们先前保存在私有变量中的值。然后我们

就可以在列表6的代码中这样使用了:先简单创建了一个

TmtCursorSnapshot对象的实例,并且将这个实例存放到

一个类型为ImtSnapshot的变量中。然后每当我们想要改

变屏幕光标的时候,就可以相应的作一些改变了。当我们

的方法结束或不在ImtSnapshot变量范围的时候,或者当

显式调用Restore方法时,这个时候在我们获得对象快照

时,屏幕光标的值就会被恢复成原先保存在私有变量中的

值了。

列表4

ImtSnapshot = interface

['{D0C5F65A-70B4-4994-97F9-479EE86D20B9}']

Page 99: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

99

技 术

procedure Restore;end;

列表5

unit mtControlsSnapshot;interface

uses mtSnapshot, Controls;type

TmtCursorSnapshot = class(TInterfacedObject, ImtSnapshot) private FOriginalCursor : TCursor;

public procedure Restore; constructor Create;

destructor Destroy; override; end;implementation

uses Forms;constructor TmtCursorSnapshot.Create;

begin FOriginalCursor := Screen.cursor;end;

destructor TmtCursorSnapshot.Destroy;begin

Restore; inherited;end;

procedure TmtCursorSnapshot.Restore;begin

Screen.Cursor := FOriginalCursor;end;end.

列表6

var

CursorSnapshot : ImtSnapshot;begin CursorSnapshot := TmtCursorSnapShot.Create;

Screen.Cursor := crHourglass; // 在这里你可以写任何用于这个光标的代码

end;

指针操作是很简单的,只要在任何时候将指针的值保存

起来就可以了。现在让我们看一个稍微复杂一点的实例,它

操作了 TFont 对象。列表 7 中包含了我们 ImtSnapshot 接口

的另一个实现。不过,这段代码看起来似乎有点棘手。在构

造子中我们获得了一个TFont,它是一个我们想要获得快照

的字体对象。然后我们在私有变量FOriginalFont中创建了一

个新的 TFont实例。注意我们同时使用了一个 ImtReaper以

确保当我们的 TmtFontSnapshot对象被销毁的时候,我们内

在的FOriginalFont对象也被销毁。我们也在字体对象中保存

了一个引用,所以我们可以在以后恢复对象以前的状态。最

后,我们为内在的FOriginalFont对象赋予了一个字体对象的

当前状态。

列表7

unit mtGraphicsSnapshot;interface

uses mtSnapshot, Graphics, mtReaper;type

TmtFontSnapshot = class(TInterfacedObject, ImtSnapshot) private FOriginalFont : TFont;

FOriginalFontReaper : ImtReaper; FTargetFont : TFont; public

constructor Create(TargetFont : TFont); destructor Destroy; override; procedure Restore;

end;implementationconstructor TmtFontSnapshot.Create(TargetFont: TFont);

begin FOriginalFont := TFont.Create; FOriginalFontReaper := TmtReaper.Create(FOriginalFont);

FTargetFont := TargetFont; FOriginalFont.Assign(TargetFont);end;

destructor TmtFontSnapshot.Destroy;begin Restore;

inherited;end;procedure TmtFontSnapshot.Restore;

begin if FTargetFont <> nil then FTargetFont.Assign(FOriginalFont);

end;end.

在以前,构造子中只是简单地调用了一下Restore方法,

不过现在这个 Restore 方法内部已经有点不同了。在构造子

中我们将值分配保存到 FOr i g i n a l F on t 对象中,然后是

FTargetFont 对象。通过这个分配,你可以随意修改 TFont

对象的属性,它们可以通过调用 Restore 方法而顺利地恢复

到以前的状态。也就是说,在列表 8中第 1个方法的代码就

会变得和第 2 个方法一样简单了。

列表8

procedure TForm1.Button5Click(Sender: TObject);begin

Button5.Font.Name := 'Comic Sans MS'; Button5.Font.Style := [fsBold, fsItalic, fsStrikeOut]; try

Button5.Update; // 在这里你可以加入任何你所想要的代码

finally

Button5.Font.Name := Font.Name; Button5.Font.Style := Font.Style; end;

end;procedure TForm1.Button6Click(Sender: TObject);var FontSnapshot : ImtSnapshot;

Page 100: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

100

begin FontSnapshot := TmtFontSnapShot.Create(Button6.Font); Button6.Font.Name := 'Comic Sans MS';

Button6.Font.Style := [fsBold, fsItalic, fsStrikeOut]; Button6.Update; // 在这里,你可以加入任何你所想要的代码

end;

同样的原理可以运用到任何你所想要将对象状态恢复到

以前状态的对象上。

太多的代码!再加一个 Factory

不过,列表8中的代码仍然不能令我真正开心。现在我

不得不对我需要构建的 ImtSnapshot 接口实现的正确与否作

一些考虑,并且与之相关的其他一些事我也需要作一些考

虑。为了能使代码能被更容易地使用,我打算创建一个简单

的Snapshot Factory类,它可以针对我所要快照的对象的类

型返回正确的 ImtSnapshot 接口实现。

列表9

... TmtSnapShotFactory = class public

class function CursorSnapshot : ImtSnapshot; class function Snapshot(Target : TObject) :

ImtSnapshot;

end;implementationuses

mtControlsSnapshot, mtGraphicsSnapshot, Graphics,mtReaper,

mtDBTablesSnapshot, DBTables;

class function TmtSnapShotFactory.CursorSnapshot :ImtSnapshot;

begin

Result := TmtCursorSnapshot.Create;end;class function TmtSnapShotFactory.Snapshot(Target:

TObject): ImtSnapshot;begin if Target is TFont then

Result := TmtFontSnapshot.Create(TFont(Target)) else raise Exception.Create('No Snapshot implemented for

objects of type ' + Target.ClassName);end;

列表9的代码片段中包含了一个名为TmtSnapshotFactory

的类,在这个类中我已经添加了内含有我的 ImtSnapshot 接

口定义的部件。在这段代码中,首先我定义了两个类函数:

一个名为 CursorSnapshot,用于构造 TmtCursorSnapshot 对

象;另外一个名为Snapshot,它根据Target对象的类型来构

造正确的 ImtSnapshot 接口的实现方式。如果我们需要的

话,我们完全可以扩展这个Snapshot方法以构造更多的实现

方式。如果我们需要附加参数的话,我们也可以重载这个方

法,并以 TmtSnapshotFactory.Snapshot(Button6.Font)这样

一句调用替代在列表8代码片段中的TmtFontSnapshot.Cre-

ate这句代码。

代码还是太多

在以前,我曾在 ADUG(Austral ian Delphi User

Group)上向大家展示过我的快照对象。几天后,其中有一

位成员向我发了一封电子邮件,他在信中告诉我有一个小窍

门可以进一步简化这个代码。后来这个方法就成为我很偏爱

的Delphi窍门之一了。首先我将展示给你这个方法,然后我

们讨论这段代码为什么能正确工作。

在我前面所有的例子中,我们都声明了一个类型为

ImtSnapshot的本地变量,或者直接构造我们的对象,或者像

现在这样调用TmtSnapshotFactory的Snapshot函数来构造对

象。所有这些都能正确工作,但是在代码中不得不声明一个

本地变量却是一件痛苦的事。

“什么,你疯了吗,Malcolm?我知道让这个在ADUG中

的成员为杂志写文章是一件不智的事,因为激情使他疯

狂!”不,事实上我没有疯,至少这些代码来源于哪里不是

我们所真正关心的。让我们用列表10中的代码来替换在列表

8中的代码,并且你尝试将这个代码运行一下。注意在这里

我们移除了本地变量,而是调用了我们的Snapshot函数,但

没有保存返回值。

列表10

procedure TForm1.Button6Click(Sender: TObject);begin

TmtSnapShotFactory.Snapshot(Button6.Font); Button6.Font.Name := 'Comic Sans MS'; Button6.Font.Style := [fsBold, fsItalic, fsStrikeOut];

Button6.Update; // 在这里加入你自己的代码

end;

好了,现在你可能会合上杂志,开启运行 Delphi 编译

器,开始尝试运行以上的代码。为什么列表8和列表10的代

码都展示了相同的行为呢?我最好将这个解释也一并给予

你。在很久以前我就看到,当编译器注意到我们调用了一个

函数并且没有提供一个变量以存放返回值的时候,编译器会

为我们在堆栈上存放这个返回值。当我们方法结束的时候,

在堆栈上的这个返回值就会被弹出。编译器会为我们自动生

成存放返回值的这个变量,它的引用计数器将会递减。但是

当我们显式定义了一个本地变量的话,这个编译器生成的变

量就会被销毁。这样,如果我们使用列表10代码的话,我们

就会在这个过程中节约不少代码输入量。

这个和接口的引用计数器联系在一起的特性,可能

是一个非常有用的工具。如果你有 R a i z e 公司出品的

Delphi

Page 101: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

101

技 术

CodeSite编辑器,我将会在文章的稍后为你介绍关于这

个工具的一个很酷的用法。当然,它也能被用于我们的

ImtReaper 对象,而且我们还会连同在这篇文章中的代

码展示给你具体应该怎样来做。很显然,只有对于那些

“仅在该方法被调用时生存,并且不需要被其他方法引

用”的对象,这个技巧才有效。不过,这种情况出现的

机会非常之高。

一个普遍适用的快照对象?

到 了 现 在 , 你 也 许 会 想 : 是 不 是 可 以 编 写 一 个

ImtSnapshot接口的实现版本,让它适用于所有的对象?我猜

这是可行的,所以我匆匆写下了列表11中的这段代码,并认

为它可以适用于TPersistent的子类。让我们先来看看列表11

中的这段代码,然后解释为什么它只在某些时候有效。

列表11

...type

TmtPersistentSnapshot = class(TInterfacedObject,ImtSnapshot)

private

FOriginal : TPersistent; FOriginalReaper : ImtReaper; FTarget : TPersistent;

public constructor Create(Target : TPersistent); destructor Destroy; override;

procedure Restore; end;...

constructor TmtPersistentSnapshot.Create(Target:TPersistent);

begin

FOriginal := TPersistent(Target.ClassType.Create); FOriginalReaper := Reaper(FOriginal); FTarget := Target;

FOriginal.Assign(Target);end;destructor TmtPersistentSnapshot.Destroy;

begin Restore; inherited;

end;procedure TmtPersistentSnapshot.Restore;begin

if FTarget <> nil then FTarget.Assign(FOriginal);end;

这段代码中内含了一个 TPersistentSnapshot对象,它的

工作方式基本上类似于我们的 TFontSnapshot对象。其中最

大的不同就是在构造子中:当需要创建与目标对象相同类型

的内部实例时,我们调用了Target.ClassType.Create,然后

将其作为参数置入TPersistent方法中,并且将其结果存入私

有变量中。很显然,对于类来说,它不可以拥有带参数的构

造子,这样我们只好想其他一些方法了。但我想这应该可以

应用于一些能应付这个标准的类。

不过,有时候我们确实可以很好地对 TFont、TPen 和

TBrush对象使用这个实现方式。不过如果当我们尝试将这个

应用于任何依赖 TPersistent对象 Assign实现方式的类的话,

那么我们将会遇到麻烦。就像其他一些类一样,TFont对象

覆盖了Assign方法,在这种情况下我们就可以使用通常的快

照对象了。可是,如果我们使用一个对象调用Assign方法导

致TPersistent对象的Assign方法也运行的话,我们就会得到

一个错误信息。

但是,我们的快照对象怎么没有覆盖 Assign 方法呢?

好,现在我们需要使用我们以前使用过的技巧以代替将代码

放入到我们的Snapshot对象中。列表12中的代码包含了一个

构造子和来自于为TTable对象实现ImtSnapshot的一个Restore

方法。你可以看到,我们手工保存了一些需要在构造子中恢

复的属性,然后在析构子中手工恢复了这些属性。在功能上,

这不如 Assign的解决方案完美。但是,这样我们至少只需要

写一遍代码就可以了。利用这个对象我们可以获得TTable对

象的快照,然后就可以随意操作过滤器(F i l t e r)和索引

(Index)了。当方法结束运行时,这些属性会被自动恢复。

列表12

constructor TmtTableSnapshot.Create(TargetTable: TTable);begin

FTargetTable := TargetTable; FIndexName := TargetTable.IndexName; FFilterStatus := TargetTable.Filtered;

FFilter := TargetTable.Filter;end;procedure TmtTableSnapshot.Restore;

begin FTargetTable.Filtered := FFilterStatus; FTargetTable.Filter := FFilter;

FTargetTable.IndexName := FIndexName;end;

你可能没有注意到的是:我们也能嵌套这些快照对

象。所以我们可以获得快照对象、改变过滤器、执行针对

表格的一些操作;然后,在对表格进行另外一些操作之

前,我们可以获得另一个快照对象,并且可以改变索引。

我们可以使用Restore方法来显式恢复快照对象,或者我们

如果让方法结束,这些快照对象将会以我们创建它相反的

次序被恢复。

CodeSite 的用户请注意

在早先时候,我曾答应过展示给你在 CodeSite 编辑

器中另外一种接口的用法和一个有关“没有本地变量”的

小窍门。好了,现在我是一个 CodeSite 编辑器的忠实用

户了,但是我对以下形式在我所有的方法中包装代码感

Page 102: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

102

到厌倦了:

begin CodeSite.EnterMethod('MyMethod');

try finally CodeSite.ExitMethod('MyMethod');

end;end;

我通常会从前面的一些方法中将代码复制和拷贝过来。

但是,然后我会忘记在CodeSite调用其中一个或多个方法的

时候,改变这些方法的名字。

为了解决这个问题,在列表13中我们定义了另一个标

志性接口,这个接口名为 ImtCodeSiteHelper。还有一个名

为EnterMethod的函数,这个函数它返回了这些接口之一。

更进一步,我们定义了另外一个 TInterfacedObject 的子

类,它实现了我们的ImtCodeSiteHelper接口。其中也添加

了一个构造子,这个构造子它获得了一个 MethodName字

串作为它的参数,并且将参数的值存入一个私有变量中。然

后传递 M e t h o d N a m e 作为参数,调用了 C o d e S i t e .

EnterMethod语句。析构子除了将保存着的MethodName作

为参数以调用 CodeSite. ExitMethod语句外,没有做其他

任何事情。列表14中的代码展示给你如何以我们前面同样

的方法来使用接口。同时图 1 展示给你在 CodeSite 编辑器

中的最终输出。

列表13

unit mtCodeSiteHelper;interface

type ImtCodeSiteHelper = interface ['{DD29F1B7-B954-45D1-A016-8C185EAB7947}']

end; function EnterMethod(const MethodName : String) : ImtCodeSiteHelper;

implementationuses CSIntf;type

TmtCodeSiteHelper = class(TInterfacedObject, ImtCodeSiteHelper) private

FMethodName : String; public constructor Create(const MethodName : String);

destructor Destroy; override; end;function EnterMethod(const MethodName : String) :

ImtCodeSiteHelper;begin Result := TmtCodeSiteHelper.Create(MethodName);

end;constructor TmtCodeSiteHelper.Create( const MethodName : String);

begin FMethodName := MethodName; CodeSite.EnterMethod(FMethodName);

end;destructor TmtCodeSiteHelper.Destroy;begin

CodeSite.ExitMethod(FMethodName); inherited;end;

end.

列表14procedure TForm1.Button1Click( Sender: TObject);begin

EnterMethod('Button1Click'); Foo;end;

procedure TForm1.Foo;begin EnterMethod('Foo');

Bar;end;procedure TForm1.Bar;

begin EnterMethod('Bar'); // 在这里加入你自己的代码

end;

我们将 5行代码缩减为了 1行,对于每个方法我们都要

这样做。以我的观点来看,花5分钟时间以列表13那样来写

一下代码是相当值得的。

我们已完成的一些事

上面的这 3 个实例当然不仅仅能被用于你可以设置特

性的接口。我确定,如果你以不同的视角来观察这些代码

的话,你会提出更多的问题。这些利益可能你在以前没有

得到过,直到你在设计使用接口的时候,这些利益才会被

你获取。你可以很容易地在你的Delphi应用程序中使用以

上的这些技巧。如果有一种技术可以很容易地实现、很容

易地使用,并且可以节约我不少的代码输入量,还会防止

我犯错误,那么我就已经受益匪浅了。非常感谢诸位阅读

了这篇文章。

图 1

Delphi

Page 103: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

103

技 术

使用AutoDump+处理挂起和崩溃 撰文 / 郑昀

关键词 IIS COM+ 调试 AutoDump+

AutoDump+(AD+)是微软提供的一个方便实用的服务器调试工具。本文将说明 AutoDump+ 的适用场合与使用方法。

概述

经常会在新闻组中看到“Help Me!My IIS hang!”这

样醒目的标题,您也许也有过这样的辛酸经历:

“Sometimes (every 30 minutes or every 15 minutes),

without an excplicit reason, some sites hang. In this newsgroup

other people have the same problem on Windows 2000, NT

4 SP5, NT 4 SP6 but unfortunately we are not a lot of

people so Microsoft ignore us and so we have no solutions/

patch for this incident.”

用过Microsoft IIS、COM+的开发人员都知道,如果你使

用得不恰当的话,它们会毫无预兆地嘎然而止。当那一刻来

临的时候,你除了哭泣之外,唯一能做的可能只有重启IIS服

务,并祈祷下一次不要来得太快。更加不幸的是,如果组件

服务(COM+)出现了意外,比如说内存异常,你只有重启计

算机了,这对于支撑着关键性业务的站点可不是一个好消息。

当看着进度条停滞不前的浏览器,当看着永无休止调用着

的COM+组件,我们无从知晓它们在那里做什么,在等待什么。

为什么不试试AutoDump+(简称AD+或ADPlus)呢?如果

没有AD+,我们也许永远也不知道Hang、Crash她们为什么存在。

u ADPlus.vbs

u 版本 - 5.03

u 发行日期 - 2002 年 1 月 31 日

u 作者 - Robert Hensing, Solution Integration Engineering

u 目的 -自动地生成内存映像或日志文件,其中包含

用于 N 层 WinDNA/.NET 应用程序排错的脚本输出。

u 估计下载大小 - 247KB(ADPlus.vbs)

u 下载:包含在Microsoft Debugging Tools for Win-

dows 工具包中。

系统要求

下面是对运行该组件的计算机的要求:

u Windows NT 4.0 Service Pack 4 或更新的版本。

u Windows Scripting Host version 2.0或更新的版本。

u 至少 10MB 的可用磁盘空间或网络共享空间,用于

放置输出文件。

u 已经安装了Microsoft Debugging Tools for Windows。

安装 Windows2000 符号

为了更好地使用这一工具,我提请您安装Microsoft Win-

dows 2000 Customer Support Diagnostics 软件,它包括:

u符号。符号是软件开发人员分配给各个不同代码段的

名称。软件包中包含用于 Windows 2000 的符号。

u调试工具软件包。软件包中包括调试程序(K D 、

Windbg、CDB)、扩展的调试工具(Windows NT 4.0 和

Windows 2000 零售和调试版)及其他相关的调试工具软件包。

有了调试符号,AutoDump+输出调试信息才能被充分解读。

u Microsoft Windows 2000 Customer Support Diagnostics

u 估计下载大小 - 104MB

u 下载地址: http://www.microsoft.com/Win-

dows2000/downloads/tools/symbols/download.asp

更多信息

AD+ 有三种操作模式:

u 挂起模式(Hang)

u 崩溃模式(Crash)

u 快速模式(Quick)

你应该在下列情况下使用 AD+:

u 进程挂起

u 单处理器计算机上进程占用100%CPU,双处理器计

算机上进程占用 50%CPU

u 进程意外崩溃或者关闭

继续阅读之前,我们假设您熟悉以下知识:

u Windows Debugger (Windbg)

u Windows 调试技术

u Windows编程体系结构

没有安装调试符号的情况

在没有安装调试符号的情况下,我们运行 ADPlus.vbs,

Page 104: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

104

开发工具

看看有什么结果。

运行下列命令,它将导致对Inetinfo.exe和其他Out-Of-

Process(OOP)应用程序的 Full-Memory Dumps:cscript.exe adplus.vbs -hang -iis

CScript 将会输出:Microsoft (R) Windows Script Host Version 5.6Copyright (C) Microsoft Corporation 1996-2001. All rights

reserved.

以及一个对话框提示你安装调试符号:

对话框上建议您安装好调试符号后,新建一个环境变量

“_NT_SYMBOL_PATH”保存调试符号的安装路径。

然后,让我们继续:

The '-hang' switch was used, Autdoump is running in 'hang' mode.

Dumping process info for IIS 5.x and all COM+ server applicationsexcept for the System application.

--------------------------------------------------------Dumping process: IIS (INETINFO.EXE)

(Process ID: 1140)

最后有一个对话框报告日志存放地点:

这时候,默认在 Debugging Tools For Windows安装路径下新

建了一个目录,它的名字类似于这样“Normal_Hang_Mode__Date_10-

28-2002__Time_21-25-1919”。下面是 Dump的内容:

u MemoryDump 文件:PID-792__INETINFO.

EXE__full_2002-10-28_21-25-25-750_0318.dmp,

u 相应的说明:PID-792__INETINFO.EXE__Date_10-

28-2002__Time_21-25-1919.log。

说明文件中会依次给出以下信息:

u Autodump+ 启动的时间

u 操作系统信息

u 运行计算机名

u 堆栈信息

u 句柄信息

u 线程堆栈遍历信息

u 加载的模块信息

u DLL 信息

u 关键段信息

u 线程 CPU使用情况信息

这是肉眼看得懂的。至于多达十几 MB 的 DMP 文件,

只有让 WinDBG 来解释了。

启动 Windbg,按 Ctrl+D,将出现选择“Open crash

Dump”文件对话框,选中上面的 PID-792__INETINFO.

EXE__full_2002-10-28_21-25-25-750_0318.dmp。当出现对

话框询问是否保存Workspace Information时,选择Yes。之

后Windbg将会出现两个窗口“Disassembly”和“Command”。

这里我们不需要“Disassembly”窗口,关掉它。

“Command”窗口将会告诉你,当Dump进行时,Active

Thread 在做什么。

由于我们没有安装调试符号,所以只能遗憾地看到:

Symbol search path is: *** Invalid *** : Verify_NT_SYMBOL_PATH setting

Executable search path is:..................................................................Wake debugger - code 80000007 (!!! second chance !!!)

eax=00000001 ebx=00000000 ecx=0027aa80 edx=00000000esi=00000000 edi=00000068

eip=77f8fb68 esp=0006f8a0 ebp=0006f910 iopl=0 nv

up ei pl zr na po nccs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000

efl=00000246

*** ERROR: Symbol file could not be found. Defaulted toexport symbols for ntdll.dll -

ntdll!NtReadFile+b:

77f8fb68 c22400 ret 0x24*** ERROR: Symbol file could not be found. Defaulted to

export symbols for ADVAPI32.dll

这时,输入命令“kb”,这是召唤线程堆栈信息的符咒。你可

以查看Windbg帮助,那里写得很明白:KB(Display Stack Backtrace),

B就是显示传入每一个函数的前三个参数。输出如下所示:

0:000> kb

ChildEBP RetAddr Args to ChildWARNING: Stack unwind information not available. Follow-

ing frames may be wrong.

0006f910 77da86d3 00000068 0006f9d8 00000216 ntdll!NtReadFile+0xb

0006f93c 77da9431 00000068 0006f9d8 00000216 ADVAPI32!

SetSecurityDescriptorSacl+0x4c0006f9b8 77d929f7 00000068 0006f9d8 00000216 ADVAPI32!

StartServiceCtrlDispatcherW+0x34d

0006fbf4 01002884 00279390 010040c8 00000000 ADVAPI32!StartServiceCtrlDispatcherA+0x72

0 0 0 6 f d 3 0 0 1 0 0 1 e 9 4 0 0 2 d 7 f 9 8 0 0 0 0 0 0 2 4 0 0 0 6 f f c 0

inetinfo+0x28847 7 d f d b e e 2 4 7 4 f f 5 0 2 4 7 4 f f 0 c f b 9 3 e 8 0 c 5 5 c 3 f f f f

inetinfo+0x1e94

0c24448d 00000000 00000000 00000000 00000000 0x2474ff50

上面的输出可以大致分为三列内容:

u ChildEBP(列 1)

Page 105: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

105

技 术

u RetAddr(列 2)

u Args to Child(列 3~5)

其他可以尽情练习的命令有:

u ~*kb: ~* 代表对于所有的线程应用后面的 kb 动作

u !locks:显示所有关键段,这样你就可以看到挂起时

哪一个线程在等待了。

u lm:列出所有加载的模块。

u dc:导出地址。

这几个命令都是重点推荐的,不可不试!

安装调试符号的情况

以上介绍的是没有安装调试符号的情况,我们来看一看

安装了之后有什么不同。

首先,安装调试符号。其次,最好新建一个环境变量,

如下图所示:

然后,重新打开 Windbg。这次 Windbg 加载了 Dmp文

件之后,就会报告找到了调试符号:

Symbol search path is: D:\NTSymbolsExecutable search path is:

............................................Wake debugger - code 80000007 (!!! second chance !!!)eax=00000004 ebx=00000000 ecx=00000000 edx=00000000

esi=00000000 edi=0000006ceip=77f82861 esp=0006f8a0 ebp=0006f910 iopl=0 nv

up ei pl zr na po nc

cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000efl=00000246

*** WARNING: symbols timestamp is wrong 0x3af357eb

0x38175b30 for ntdll.dllntdll!_output+567:77f82861 c22400 ret 0x24

*** WARNING: symbols timestamp is wrong 0x3c1aab1e0x385134fe for ADVAPI32.dll

我们来比较一下“~*kb”命令在两种情况下的区别:

没有安装调试符号情况下:

4 id: 420.484 Suspend: 1 Teb 7ffda000 Unfrozen*** ERROR: Symbol file could not be found. Defaulted to

export symbols for RPCRT4.DLL -ChildEBP RetAddr Args to ChildWARNING: Stack unwind information not available. Follow-

ing frames may be wrong.0119ff74 77d375ec 77d37a82 00280068 00270000 ntdll!

ZwReplyWaitReceivePortEx+0xb

0119ffa8 77d379a0 0027b898 0119ffec 77e76523 RPCRT4!RpcBindingSetOption+0x6e9

0119ffb4 77e76523 0028f788 00270000 50000161 RPCRT4!

RpcBindingSetOption+0xa9d0119ffec 00000000 77d37988 0028f788 00000000 KERNEL32!

TlsSetValue+0x115

安装了调试符号:

4 id: 420.484 Suspend: 1 Teb 7ffda000 Unfrozen

ChildEBP RetAddr Args to Child0119ff74 77d375ec 77d37a82 00280068 00270000 ntdll!

memmove+0x14

0119ffa8 77d379a0 0027b898 0119ffec 77e76523 RPCRT4!LOADABLE_TRANSPORT::LOADABLE_TRANSPORT+0x97

0119ffb4 77e76523 0028f788 00270000 50000161 RPCRT4!

ReadSeed+0x110119ffec 00000000 77d37988 0028f788 00000000 KERNEL32!

CreateThread+0x13

也就是说,微软警告你没有调试符号的情况下,可能会

被一个错误的堆栈误导。很明显,两种情况下堆栈展开也不

一样。所以我们强烈建议您安装调试符号。

处理崩溃的触发模式

能够自动处理Crash情况,也是AD+让人激赏的功能之一。

你可以配置ADPlus,让它一直运行,当发生异常时,选择几种

方式处理:用信使服务通知管理员,或者自动 Dump memory。

它能监视的异常有:

u Invalid Handle(非法句柄)

u Illegal Instruction(非法指令)

u Integer Divide by Zero(整数除 0 操作)

u Floating Point Divide by Zero(浮点数除 0操作)

u Integer Overflow(整数溢出)

u Invalid Lock Sequence(非法锁序列)

u Access Violation(访问违例)

u Stack Overflow(栈溢出)

u C++ EH Exception(C++ EH 异常)

u Unknown Exception(未知异常)

这也是一个管理员早就希望拥有的功能了吧。

总结

总结一下,AD+ 大致的使用步骤是:

u 安装 Windows2000 的调试符号,

u 安装 Windows2000 SP1 的调试符号,

u 安装 Windows2000 SP2 的调试符号,

u 新建环境变量“_NT_SYMBOL_PATH”,

u 安装Microsoft Debugging Tools for Windows最新版本,

u 当 IIS Hang时,运行脚本cscript.exe adplus.vbs -hang -iis,

u 用 Windbg加载 dmp文件,运用各种命令组合观察。

值得注意的是,AD+已经被扩展,而不仅仅限于拯救IIS

和 COM+,一般的应用和服务也完全支持。

Page 106: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

106

问题描述

见 2002 年第 11 期杂志。

问题分析

构造数学模型

我们把 ID和真实姓名看作图中的顶点,如果某个 ID可

以对应于某个真实姓名,则在这两个点之间连接一条边,这

样构造了一个二部图。此二部图 G 共有 2n个顶点,设 X表

示ID对应的顶点集合,Y表示真实姓名对应的顶点集合。初

始的时候X中的每个点到Y中的每个点都有边相连,因为开

始的时候每个 ID 可能对应任何一个姓名。我们用集合 H记

录当前在房间内人的姓名,初始时H为空。接下来我们依次

处理输入中的每一条记录,有以下几种情况:

u 遇到命令 E,表示某个人进入房间,将其姓名加入

到集合 H 中;

u 遇到命令 L,表示某个人离开房间,将其姓名从集

合 H 中删除;

u 遇到命令M,表示当前房间内的某个人用某个ID发

出一条消息。这时房间内的所有人都可能对应于该ID,换句

话说房间外面的所有人都不可能对应该ID。因此我们扫描所

有人的名字,如果该名字不在集合H中,则删除当前发送消

息的 ID 和该名字之间连接的边(如果该边存在的话)。

经过上述处理,我们得到一个二部图G,如果某个ID可

能对应某个名字,则在它们之间连接一条边。请注意上述处

理过程的第三条规则。对于 ID x 和姓名 y,“x 发出信息时

y在房间内”只是“x对应于 y”的必要条件而非充分条件,

所以我们不能仅仅根据“x 发出信息时 y在房间内”就判断

x可能对应y,而只能根据“x发出信息时y不在房间内”判

断出 x 不对应 y。下面的一个输入说明了上述情况:

2a bE A

M aE BM a

M bQ

在处理到第二个 Ma命令的时候,A,B都在房间内,但

是我们不能认为a对应于B,因为在遇到第一个Ma命令的时

候房间内只有 A,而 B不在房间内,所以我们已经知道了 a

不可能对应 B。

算法设计

得到这样一个二部图G以后,我们就可以找出所有确定

的 ID 和姓名对应关系。一种自然的想法是,找出图中某个

度是1的点,那么就得到了一个确定的

对应关系,将这两个互相匹配的点从图

中删除,对剩下的点重复上述过程,直

到无法找到度为1的点为止。例如对于

前面的例子,构造的二部图如图所示。

首先我们找到度为 1 的顶点 a,得到唯一确定的对应关

系 a-A,然后将 a和 A 都从图中删除,结果只剩下对应 b-

B,所以最后得到 a-A 和 b-B两个对应关系。

但事实上这种算法是错误的。例如对于下述输入:

51 2 3 4 5E a

E bM 1E c

M 2E dL a

L bM 3L c

E eM 4M 5

Q

其对应的二部图如

右所示:

对于该输入,如果

按照上述算法计算则不

存在任何确定的对应关系。但事实上我们可以发现3一定对

应于 c。因为如果 3 不对应 c,则 3 一定要对应 d,4 一定对

搜寻恐怖分子题解

主持人 / 胡海星

——2002年第11期题解

编程擂台

Page 107: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

107

技 术

应 e,但是 5 就没有可对应的真实姓名了,这和题目的已知

条件“每个名字一定会对应唯一的一个 ID”矛盾。

从上面的例子可以看出,判断某一条边是否是确定的充

要条件是:删除该边以后剩下的图中不存在一个完美匹配

(所谓完美匹配,是指每个 X中的点都有一个 Y中的点与其

对应的匹配)。

进一步,我们可以知道,所有确定的匹配关系一定属于

图 G的某个完美匹配,例如上面例子中的边3-c,一定属于

G 的一个完美匹配。所以我们可以得到以下算法:

1. 根据输入数据构造一个二部图 G

2. 求 G 的一个完美匹配 M,根据题意这样的完美匹配

一定存在

3. for M中的每一条边 e

4. do 将 e 从 G 中删除,得到二部图 G'

5. if G' 中不存在完美匹配

6. then e是一条可以完全确定的对应关系

7. 输出所有确定的对应关系

算法的关键部分在于求二部图的完美匹配,这可以通过

经典的匈牙利算法实现。限于篇幅,我们只简单地介绍一下

匈牙利算法。

匈牙利算法求最大匹配

术语说明:

1) 二部图:如果图 G=(V,E)的顶点集合 V 可分为两个

集合X,Y,且满足X∪Y=V,X∩Y=Φ,则G称为二部图;

图 G 的边集用 E(G)表示,点集用 V(G)表示。

2) 匹配:设 M是 E(G)的一个子集,如果 M中任意两条

边在 G 中均不邻接,则称 M 是 G 的一个匹配。

3) 饱和与非饱和:若匹配M的某条边与顶点v关联,则称

M饱和顶点v,并且称v是 M饱和的,否则称v是 M不饱和的。

4) 交互道:若 M 是二分图 G=(V,E)的一个匹配。设从

图G中的一个顶点到另一个顶点存在一条道路,这条道路是

由属于 M 的边和不属于 M 的边交替出现组成的,则称这条

道路为交互道。

5) 可增广道路:若一交互道的两端点为关于 M 非饱和

顶点时,则称这条交互道是可增广道路。显然,一条边的两

端点非饱和,则这条边也是可增广道路。

6) 最大匹配:如果M是一匹配,而不存在其它匹配M',使

得 |M|<|M'|,则称M是最大匹配。其中|M|表示匹配M中的边数。

7 ) 完美匹配:如果 M 是 G=(V,E ) 的最大匹配,且

|M|=|X|=|Y|,则称 M 是 G 的完美匹配。

8) 对称差:A,B是两个集合,定义 A⊕ B=(A∪B)\(A

∩ B),则 A ⊕ B 称为 A 和 B 的对称差。

定理:M为G的最大匹配的充要条件是G中不存在可增

广道路。 (证略)

Hall定理:对于二部图 G,存在一个匹配 M,使得 X的

所有顶点关于M饱和的充要条件是:对于X的任意一个子集

A,和 A 邻接的点集为 T(A),恒有:|T(A)| ≥ |A|。(证略)

匈牙利算法是基于Hall定理中充分性证明的思想,其基

本步骤为:

1. 任给初始匹配M;

2. 若 X已饱和则结束,否则进行第 3步;

3. 在X中找到一个非饱和顶点x0,作

V1←{x0},V

2←Φ

4. 若T(V1)=V

2则因为无法匹配而停止,否则任选一点

y∈ T(V1)\V

2;

5. 若 y已饱和则转 6,否则做一条从 x0→ y的可增广

道路 p,M ← M ⊕ E(p),转 2;

6. 由于y已饱和,所以M中有一条边(y,z),作V1←V

1

∪{z}, V2← V

2∪{y}, 转 4;

另一种常见的求最大匹配的算法是通过构造流网络然后

求最大流来实现,感兴趣的读者可以参考 Introduction to

Algorithm(Second Edition)。

本期获胜者

陈淳([email protected]

2003 年第 1 期题目 蓄水池

到了下个世纪,地球上的很多地区将会产生水源危机。

科威特政府为了缓减水源危机,在城市内修建了很多蓄水

池。所有的蓄水池都通过管道相连通,但不同的蓄水池具有

不同的高度和容积。因为所有的蓄水池都是相连通的,所以

只需要一个水源就

可以灌满所有的蓄

水池。但在缺水时

期,有些蓄水池可

以灌满水,有些可

以有一部分水,有

些 则 得 不 到 任 何

水,如图所示。

你的任务是写

一个程序,计算对

于给定体积的水,将其灌入整个蓄水池系统,水平面所能达到

的最大高度。为了简化问题,连接蓄水池的管道容积忽略不计。

输入:

输入文件为 cistern.in。第一行是一个整数 k,1≤ k≤

30,表示输入文件中有k组不同的数据。每组数据的第一行

是一个整数 n,1 ≤ n≤ 50000,表示一共有 n个(下转 91页)

Page 108: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

108

专家门诊

问题描述:发帖人提供了一份《电力调度主站监控系统

概要设计》。该系统是电力调度、水电站、变电站后台监控通

用系统网络版。文档中包含脚本、实时数据库、历史数据库、

EXCEL 报表系统、WEB发布和 IE客户、远程诊断、电力设

备信息管理等方面的概要设计,共 17 章,近 200 页的内容。

㈦主题:走出MFC子类化的迷宫[VC/MFC]

作者:mahongxi 帖号:1228284

问题描述:许多Windows程序员都是跳过SDK直接进行

RAD 开发工具的学习,有些人可能对子类化机制比较陌生。

我们先看看什么是 Windows的子类化。Windows给我们或是

说给它自己定义了许多丰富的通用控件,如:Edit、ComboBox

、ListBox⋯⋯等。这些控件功能丰富,能为我们开发工作带

来极大方便。但是,在实际开发中还是有些情况这些标准控

件也无能为力,我们就可以采用子类化很好地解决这类问题。

㈧主题:var now = new Date(); 这个now 到底是变量还

是新的对象[JavaScript]

作者:ATCG 帖号:1240386

问题描述:这是一个“错误的”问题:“变量”和“对

象”两者并非矛盾关系。实际上,它们常常是对同一个事物

的不同指称:对象总是要保存在某个变量中,才能被其他的

程序所引用的。而且,now这个变量有其特殊性:它是window

对象的一个属性。所以,如果去掉语句中的 var,代码仍然

可以通过编译,但语意则发生了变化。

㈨主题:如何利用winsock控件进行跨网段通讯[Visual Basic]

作者:pasl 帖号:1224478

问题描述:如何在不同的局域网内的两台计算机间通

讯?常见的办法有两种:端口反弹(让局域网内机器主动连

接外部机器)和 http 隧道(把数据加上 http 头,伪装后与

网外机器通信)。

㈩主题:你真的知道++i与 i++的区别么[C++ Builder]

作者:KennyYuan 帖号:1230279

问题描述:C++正本清源系列之二。对于重载了 ++运

算符的类型来说,前置的 ++ 和后置的 ++ 调用的是两个不

同的函数(出于代码统一的目的,通常会让后置的 ++运算

符直接调用前置的)。并且,一般情况下,后置++需要进行

一次对象拷贝操作,而前置 ++ 则没有这笔开销。

㈠主题:Oracle、SQL Server、MySQL与DB2的比较[Oracle]

作者:redblues 帖号:1244308

问题描述:Oracle技术成熟,是大型企业解决方案的首

选,但价格和硬件要求都较高;SQL Server 简单易用,是

Windows 平台下的好选择;MySQL 是免费数据库,没有事

务、子查询、视图等特性,但速度快、概念简洁;DB2是IBM

的大型数据库,特长在于信息的存取。

㈡主题:我对高展先生的软件工程方法的几点看法[软件

工程]

作者:gwvd 帖号:1262210

问题描述:也许高展先生的方法更适合中国的国情,但

是软件工程需要一个统一的标准,需要与主流接轨,好比建

筑工程的蓝图有个统一的标准一样。所以,我认为高展先生

的软件工程方法将被 UML 取代。

㈢主题:关于重构的一些想法[软件工程]

作者:cajon 帖号:1273046

问题描述:重构必须建立在良好的版本控制基础上;重

构在多数情况下应该是个体工作;重构的时候必须保证测试

的完全进行;重构需要良好的设备的配合;重构的过程应该

有勇气放弃或者继续,而不能停在原地;重构不是万能的,

但是不重构是危险的。

㈣主题:Javascript的内存释放实验[JavaScript]

作者:lanbor 帖号:1247117

问题描述:我以前也看过关于javascript的内存释放的文

章,但从来也没仔细看过。今天我做了一个小小的实验,证

实内存释放还是有用的:将不再使用的变量设为 null,系统

的性能的确有提高。

㈤主题:有关查询表中已删除的ID号的问题[SQL Server]

作者:happydreamer 帖号:1275557

问题描述:数据库中的每一行都有一个连续的ID。当表

中某些行被删掉以后,ID变得不再连续。能否想一个简单的

办法把被删掉的 ID 列出来?可以用游标或者临时创建的自

增列解决,但仍然比较麻烦。

㈥主题:做项目经理难吗[项目管理]

作者: ljf1107 帖号:1230499

CSDN 12月论坛TOP 10浏览相关帖子:http://expert.csdn.net/Expert/TopicView1.asp?id=贴号

Page 109: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

109

技 术

h t t p : // e x p e r t . c s d n . n e t就 诊 到 此

专家门诊是CSDN最活跃和最受欢迎的频道,软件开发涉及到的领域实在太过广泛,即使是最优秀的程序员,

也不能解决所有的问题。专家门诊论坛提供了很好的技术交流的平台。杂志的这个栏目可以说是CSDN论坛的精

华集。

为了更好地为广大读者服务,编辑部专门组织了专家小组,挑选开发中常遇到和论坛中常见的疑难和经典问题,

予以解答。我们希望读者能把开发中感到疑惑和难以解决的问题向本栏目提供,我们将对提问入选的读者予以奖励。

来信请寄[email protected],您也可以登陆CSDN杂志频道参与本栏目。

Programmer Q&A

目录“代码诊断”征稿

你 是 否 在 工 作 经 常 遇 到 类 似 问

题:编写代码时,经常为一个小地方头

疼不已,不知道该如何写出一段合理的

代码来解决它,或是即使写出代码也觉

得很难看,很难扩展。殊不知,正当你

为这些小问题绞尽脑汁时,也许其他的

程序员已经解决了这个问题,或是总结

了一段漂亮、规范的代码。

《程序员》杂志计划新增的一个小

栏目“代码诊断”,就是为了解决上述

问题。我们希望你把工作中遇到的问题

或是感觉写得别扭的代码加以提炼,然

后发给我们。经过我们筛选后刊登在杂

志上,让全国的程序员们来帮助你解决

问题。同时,如果你觉得对某个问题的

解决颇有心得,也欢迎你将解决方案寄

给我们。

来稿一经采用,稿费从优。

来稿请寄:[email protected]

C/C++专家门诊

如何知道模板的参数类型是否派生自特定的某个类

Visual C++专家门诊

如何能够看到动态库接口函数的参数列表

如何实现任意长和宽的图象旋转

数据库专家门诊

如何从 Oracle 数据库中获取结果集并返回给 Java程序

Visual Basic 专家门诊

在 MSChart 中如何显示数据点的标签

如何打印 MSChart 图表

Delphi 专家门诊

如何针对字符串对程序进行优化

Page 110: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

110

专家门诊

C/C++专家门诊

问题:我有下面这样的一个模板函数:

template<class T> void f (std::vector< T > &v)

由于某些原因,我需要知道类型T是否派生自特定的某

个类。请问要如何做到?

解答:

你可以使用boost::is_base_and_derived<T,U>,这个模

板能够满足你的要求。如果你希望只用STL来解决这个问题

的话,Herb Sutter 在他的《More Exceptional C++》的第

3条款中讨论了这个问题。下面列出一些专家对这个问题的

解决方案:

1. Alexandrescu(《Modern C++ Design》的作者)的

方法:

template<typename D, typename B>class IsDerivedFrom1{

class No {}; class Yes { No no[2]; }; static Yes Test( B* );

static No Test(...);

public:

enum { Is = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) };

};

如果D是从B派生出来的,那么IsDerivedFrom<D, B>::

Is 的值为 true,否则就为 false。

2. Stroustrup(C++设计者,《The C++ Programming

Language》的作者)的方法:

template<typename D, typename B>

class IsDerivedFrom2{ static void Constraints(D* p)

{ B* pb = p; pb = p; // suppress warnings about unused variables

}

protected:

IsDerivedFrom2() { void(*p)(D*) = Constraints; }};

// 如果 B 的类型是 void,则强制该模板函数编译失败

template<typename D>class IsDerivedFrom2<D, void>

{ IsDerivedFrom2() { char* p = (int*)0; /* error */ }};

如果 D 是从 B派生出来的,则 IsDerivedFrom2<D, B>

可以通过编译,否则无法通过编译。

上面两种方法中,第一种(Alexandrescu的方法)比较

复杂,但它提供了一个可以在编译器检查的枚举值,使得你

可以根据这个值的结果选择各种不同的行为。Sutter的书中

还提到了第三种方法,它结合了这里的两种方法,详情请看

他的书。

另外,在Loki库中也提供了解决这个问题的办法。你可

以像下面这样:bool b = Loki::Conversion<A*, B*>::exists;

可以在 SourceForge.net 下载 Loki库。

Visual C++专家门诊

问题:使用 Microsoft Visual Studio的工具DEPENDS.EXE

可以查看动态库的接口函数,但如何能够看到这个动态库接

口函数的参数呢?

解答:

可以通过反汇编来知道接口函数的参数,建议使用

W32DSM来分析,也可以直接使用VC来分析,就是麻烦一点。

现在使用 W32DSM 来具体说明:

1、先打开需要分析的DLL,然后通过菜单“功能->出

口”来找到需要分析的函数,双击就可以了。它可以直接定

位到该函数。

2、看准该函数的入口。一般函数是以以下代码作为入口

点的:push ebpmov ebp, esp

...

3、然后往下找到该函数的出口,一般函数出口有以下语句:...

ret xxxx;// 其中 xxxx 就是函数参数的所有的字节数,为 4的倍数,xxxx

除以 4得到的结果就是参数的个数。

其中参数存放的地方:

ebp+08 // 第一个参数

ebp+0C // 第二个参数

ebp+10 // 第三个参数

ebp+14 // 第四个参数

ebp+18 // 第五个参数

ebp+1C // 第六个参数

?­?­

另外需要注意,还有一种经常看到的调用方式:

sub esp,xxxx // 开头部分

//函数的内容

⋯⋯

//函数的内容

add esp,xxxx

ret // 结尾部分

其中 xxxx/4 的结果也是参数的个数。

还有一种调用方式常用于比较简单的函数,没有参数的

Page 111: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

111

技 术

压栈过程,里面的esp+04就是第一个参数、esp+08就是第二

个参数,以此类推。

到现在为止,你应该能很清楚地看到了传递的参数的个

数。至于传递的是些什么内容,还需要进一步的分析。最方

便的办法就是先找到是什么软件在调用此函数,然后通过调

试的技术,找到该函数被调用的地方。一般都是通过PUSH

指令来实现参数的传递的。这时可以看一下具体是什么东西

被压入堆栈了,一般来说,如果参数是整数,一看就可以知

道了。如果是字符串的话也比较简单,只要到那个地址上面

去看一下就可以了。如果传递的是结构体,就没有很方便的

办法解决,只能读懂汇编代码了。

以上的分析只是抛砖引玉,希望对大家有点用处。

问题:如何实现任意长和宽的图象旋转?

解答:

图像旋转是指把定义的图像绕某一点以逆时针或顺时针

方向旋转一定的角度,通常是指绕图像的中心以逆时针方向

旋转。

假设图像的左上角为(le f t , t op),右下角为(r i gh t ,

bottom),则图像上任意点(x0,y0)绕其中心(xcenter,

ycenter)逆时针旋转angle角度后,新的坐标位置(x′, y

′)的计算公式为:

xcenter = (right - left + 1) / 2 + left;

ycenter = (bottom - top + 1) / 2 + top;

x′ = (x0 - xcenter) cosθ - (y0 - ycenter) sin θ +

xcenter;

y′ = (x0 - xcenter) sin θ + (y0 - ycenter) cosθ +

ycenter;

与图像的镜像变换相类似,也采用按行逐点变换的方式

实现图像的旋转,其步骤如下:

(1) 用getimage()把图像保存到内存缓冲区,并擦除

原图像。

(2) 计算图像高度 height,宽度 width,及保存一行图

像信息占用的字节数linebytes,计算公式与镜像变换的计算

公式相同。

(3) 对图像逐行进行旋转变换。

(4) 释放内存图像缓冲区。

值得指出的是,这种处理方法速度不够快。为此可以

采用另一种方法:先在图像变换缓冲区中处理完毕后,再

将变换后的图像一次显示在屏幕上。这样可以取得较好的

显示效果。

数据库专家门诊

问题:因为工作需要,需要从 O r a c l e 中获取结果集

(ResultSet)并返回到 Java 程序,请问如何实现?

解答:

因为 Oracle 不能像 Sybase 或 SQL Server那样直接从存

储过程中返回结果集,因此我们将需要用到游标(Cursor)

和游标变量。下面我将举个例子来详细阐述这个过程。

一、编写Oracle的存储过程。

有过 SQL Server 编程经验的人可能会认为从 Oracle 中

返回结果集也很简单,直接在存储过程中这么写:se l e c t

xxx, yyy, zzz from TableName 。很可惜,在 Oracle 中,

我们不能这么方便地返回结果集。

在 Oracle PL/SQL存储过程中,我们需要使用游标变

量(REF CURSOR)作为存储过程的输出参数,并通过游

标变量来返回结果集。在使用游标变量之前,我们还必须

先定义一个游标变量类型。有两种类型的REF CURSOR类

型:强类型和弱类型。强 REF CURSOR 类型跟指定的字

段关联起来,指向的游标中只能返回这些字段。弱 REF

CURSOR可以指向任何游标,使用起来会方便很多,但是

牺 牲 了 安 全 检 查 。 在 我 们 的 例 子 中 将 使 用 弱 R E F

CURSOR。

1、创建测试用表:

CREATE TABLE "MY_PRODUCT"(

"PRODUCT_ID" VARCHAR2(20) NOT NULL,"NAME" VARCHAR2(20) NOT NULL,"DESCRIPTION" VARCHAR2(50) NOT NULL,

"PRICE" NUMBER(10) NOT NULL)

在 SQL*Plus 中运行上述代码,成功后,会有“Table

created.”显示。(请注意你登录的用户有相关的权限。)

2、插入测试数据:

INSERT INTO MY_PRODUCT (PRODUCT_ID ,NAME ,DESCRIPTION ,PRICE ) VALUES ('001' ,'CPU' ,'the heart of the computer' ,999

) ;INSERT INTO MY_PRODUCT (PRODUCT_ID ,NAME ,DESCRIPTION ,

PRICE ) VALUES ('002' ,'VGA card' ,'drive the display to show'

,988 ) ;INSERT INTO MY_PRODUCT (PRODUCT_ID ,NAME ,DESCRIPTION ,

PRICE ) VALUES ('003' ,'display' ,'has a screen for you to

see' ,2500 ) ;Commit ;

在 SQL*Plus 中运行上述代码,成功后,会有“1 row

created.1 row created.1 row created.Commit complete.”

显示。

3、定义游标变量类型:

CREATE OR REPLACE PACKAGE typesAS

TYPE ref_cursor IS REF CURSOR;

Page 112: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

112

专家门诊

END;

在 SQL*Plus中运行上述代码,成功后,会有“Package

created.”显示。现在,我们可以开始使用ref_cursor来声明

REF CURSOR 类型变量了。

4、编写存储过程:

CREATE OR REPLACE PROCEDURE "GET_PRODINFO"(v_cursor OUT types.ref_cursor) ASBEGIN

OPEN v_cursor FORSELECT PRODUCT_ID,NAME,DESCRIPTION,PRICE FROM MY_PRODUCT

WHERE PRICE<1000;

END;

在SQL*Plus中运行上述代码,成功后,会有“Procedure

created.”显示。

5、在 SQL*Plus 中测试你的存储过程:

SET SERVEROUTPUT ON;declaremy_cursor types.ref_cursor;

prod_id varchar(20);name varchar(20);prod_descrip varchar(50);

prod_price number;begin GET_PRODINFO(my_cursor);

<<label>> fetch my_cursor into prod_id,name,prod_descrip,

prod_price;

if my_cursor%FOUND THEN DBMS_OUTPUT.PUT(prod_id); DBMS_OUTPUT.PUT(name);

DBMS_OUTPUT.PUT(prod_descrip); DBMS_OUTPUT.PUT_LINE(prod_price); goto label;

end if; close my_cursor;END;

在SQL*Plus中运行上述代码,成功后,会有下列显示:

002VGA carddrive the display to show988

001CPUthe heart of the computer999PL/SQL procedure successfully completed.

上述测试通过以后,表明你的存储过程已经没有错误,

可以运行了,我们将转向用 JDBC 来调用存储过程。

二、在Java中用JDBC调用此存储过程。

在用 JDBC 调用此存储过程时需要注意以下几点:

1、用 JDBC的 CallableStatement类时,要注意调用的query

string 的书写格式:{call GET_PRODINFO(?)}L»

? 代表存储过程中的参数,{}必须有。

2、要注册返回参数的类型:registerOutParameter(1,OracleTypes.CURSOR);

OracleTypes.CURSOR 为 Oracle JDBC 所自带的类型,

所以使用前先要 import oracle.jdbc.driver.OracleTypes;

3、用 getObject(int i)得到返回参数:L¨ResultSet)cs.getObject(1);

getObject(int i)返回存储过程的第i个参数,返回结果为

Object,要造型到 ResultSet。

完整的源代码如下:

import java.sql.*;

import oracle.jdbc.driver.*;public class Test{

public static void main(String args[]){String url ="jdbc:oracle:thin:@127.0.0.1:1521:Orcl";

try{C l a s s . f o r N a m e ( " o r a c l e . j d b c . d r i v e r .

OracleDriver"); Connection con=DriverManager.getConnection(url,

"user","password");

CallableStatement cs = con.prepareCall("{callGET_PRODINFO(?)}");

cs.registerOutParameter(1,OracleTypes.CURSOR);

cs.executeQuery();ResultSet ors = (ResultSet)cs.getObject(1);while(ors.next())

{System.out.println(ors.getString(1));

System.out.println(ors.getString(2));

System.out.println(ors.getString(3));System.out.println(ors.getString(4));System.out.println("-----------------------");

}}catch(Exception e)

{System.out.println(e.getMessage());e.printStackTrace();

}}

}

大家在运行上述代码时要注意将数据库的URL改为你当

前数据库的,同时将用户名和密码改为你的用户名和密码。

至此,你已经成功地用 Java通过 JDBC从 Oracle中返回

结果集了。希望这篇文章能给大家带来帮助。

Visual Basic 专家门诊

问题:在 MSChart 中如何显示数据点的标签?

解答:

1、显示数据点的标签 With MSChart1.Plot For i = 1 To MSChart1.ColumnCount ' 遍历图表数据网格中的与

列关联的标签文本

' 确定数据点标签的位置

.SeriesCollection(i).DataPoints.Item(-1) _

.DataPointLabel.LocationType = VtChLocationTypeTop ' 设置标签类型为数据点数值

Page 113: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

113

技 术

.SeriesCollection(i).DataPoints.Item(-1) _ .DataPointLabel.Component = VtChLabelComponentValue ' 设置数值的格式

.SeriesCollection(i).DataPoints.Item(-1) _ .DataPointLabel.ValueFormat = "?.0" ' 显示一位小数

Next i

End With

其中:Component 属性可选 VtChLabelComponentValue

(数据点数值)、VtChLabelComponentPercent (数据点数值

显 示 为 该 数 据 点 数 值 占 系 列 总 数 值 的 比 例 )、

VtChLabelComponentSeriesName(用系列名称来标注数据

点)、VtChLabelComponentPointName(用数据点名称来标注

数据点)。

2、隐藏数据点的标签

With MSChart1.Plot For i = 1 To MSChart1.ColumnCount .SeriesCollection(i).DataPoints.Item(-1) _

.DataPointLabel.Component = 0 Next i End With

问题:如何打印 MSChart 图表?

解答:

在 VB4的 Graph 控件里,设置 DaraMode属性为 4、5、

6则分别可以将图表拷贝到剪贴板、直接打印和写入文件。

VB5、VB6 的 MSChart 控件的 DaraMode 属性已无此参数,

我们可以使用 EditCopy方法将图表拷贝到剪贴板:

Clipboard.Clear ' 清除剪贴板中的内容

MSChart1.EditCopy ' 将当前图表的图片复制到剪贴板

Printer.PaintPicture Clipboard.GetData(), 0, 0 '从剪贴板取回图形,发送

给打印机

Printer.EndDoc ' 完成打印。

Delphi 专家门诊

问题:如何针对字符串对程序进行优化?

解答:

D e l p h i 有三种字串类型:短字符串(S t r i n g [ n ] ,

n=1..255)存储区为静态分配,大小在编译时确定,这

是继承于 BP for Dos 的类型;字符数组(PChar)主

要是为了兼容各类 AP I,在 BP 7 中已经出现,如今在

Delphi中更加应用广泛,其存储区可以用字符数组静态

分 配 , 也 可 用 G e t M e m 手 动 分 配 ; 而 长 字 符 串

(AnsiString)是Delphi独有的,其存储区在运行时动态

分配,最灵活也最易被滥用。

对于字符串,主要的优化策略有以下几点:

1、不重复初始化

Delphi默认字符串类型 AnsiString 会自动初始化为空。

如下代码:var s:stringL»begins:='';

?­?­end;

s:='';语句就属多此一举。但是值得注意的是这对函数

返回值 Result无效。而一般说来,用 var 实参传递比返回字

符串值要更快一些。

2、使用 SetLength 预分配长字符串(AnsiString)

动态分配内存是 AnsiString的一大长项,但容易弄巧成

拙。一个典型的例子如下:s2:=' ';

for i:=2 to length(s1) do s2:=s2+s1[i];

且不说可用Delete取代之,主要问题在于上例的循环中

s2的内存区域被不停地重复分配,相当费时。一个简单有效

的办法如下:SetLength(s2,length(s1)-1);for i:=2 to length(s1) do s2[i-1]:=s1[i];

这样 s2 内存只会重新分配一次。

3、字符串与动态数组的线程安全(Thread Safety)

在Delphi 5以前动态数组与长字符串的操作这些非线程

安全调用是由引用计数来处理其临界问题的,而自 Delphi5

起就改为直接在一些临界指令前加lock指令前缀来避免这个

问题。但是在 PentiumⅡ处理器中 lock指令相当费时,因而

整体效率至少下降一半。

解决这个问题的办法只有一个,那就是修改Delphi RTL

核心代码。在备份原文件后,将source\rtl\sys\system.pas中

所有的 lock 替换为{lock},当然必须是整字替换。

如此还未完全优化。下一步是将 Delphi4 运行库中也有

的 xchg 指令去掉,因为该指令有隐含的 lock前缀,所以必

须将system.pas内_LstrAsg和_StrLAsg两个过程中的 XCHG

EDX,[EAX] 替换为如下代码:mov ecx,[eax]mov [eax],edxmov edx,ecx

大功告成,编译一下,覆盖system.dcu即可。如此其执

行效率将比 Delphi5 提高 6 倍,比 Delphi4 提高 2 倍。

4、避免使用短字符串

由于很多字符串操作会先把短字符串转换为长字符串,

从而减慢了执行速度,因此还是少使用短字符串为妙。

5、避免使用 Copy 函数

6、总是使用长字符串,必要时转换为 Pchar

很多人认为 AnsiString是天生低效的。其实这在很大程

度上是由代码编写不良、内存管理乱用和缺乏支持的函数所

致。如上所述,一旦被动态分配了一块内存,长字符串就成

了一个线性的字节序列,并无所谓的效率问题。当然,若有

更多有效的函数支持那就更好了。

Page 114: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

114

一枝看上去很美的花 撰文 / 剃刀

躺在我面前的,是阎宏博士编著、电子工业出版社出

版、厚达1024页的《Java与模式》。如果按照我的朋友Jacques

Lebrun 在他《The Mythical Man-Month:一次豆豆式的书

评尝试》中的说法,这本书是归于“体态狼伉”的“庞然巨

物”,而且恰好 1K 的页码数的确也很能“让人产生心理障

碍”。不过,由于本人研究模式理论有日,也知道在设计模式

方面很难写出一本能“作掌上舞”的小书,所以,还是暂且

抛开“豆豆式”的套路,回到严谨的批评上来。在《Java与

模式》的序 1中,朱天华博士将此书盛赞为“散发着奇妙香

气的曼妙花朵”,而我的评价则是“一枝看上去很美的花”。

直到最近,中文的模式理论类书籍只有《设计模式》一

本。《设计模式》的经典地位毋庸置疑,但是其中使用的范

例太老旧,很难让读者获得直观的感性认识,兼之理论阐述

太过抽象,导致阅读体验极差。《设计模式》的读者普遍反

映“看不懂”,实是情理之中。而《Java 与模式》是迄今为

止第一本模式理论的中文入门教材,自然,也是最好的一

本,因为并没有第二本。

对于《Java 与模式》,作者阎宏博士的期望并不仅仅是

普及模式理论和设计模式技术,而且还希望探索模式理论的

哲学根源,将设计模式技术与中国传统文化结合起来。这种

技术的形而上学探索,便是朱博士所赞的“奇妙香气”,也

正是笔者说它“看上去很美”的原因。本文中,笔者将对这

两方面分别评论,以期给此书一个全面而不失偏颇的评价。

迄今为止最好的模式教材

一本模式教材应该是什么样子的?或者说,如何评价一

本模式教材的优劣。

首先,它应该对C.Alexander的模式理论做必要的介绍。

毕竟大多数读者接触“模式”这个词是从“设计模式”开始,

他们学习模式的目的是提高软件开发的能力。模式教材的作

者不可能也不应该假设他的读者都读过《建筑的永恒之道》。

由于Alexander的模式理论对软件模式的形式、理论有着极其

深远的影响,因此模式教材的作者应该帮读者补上这一课。

其次,这本书应该对面向对象的一些指导性原则作必要

的介绍。像开放-封闭原则(OCP)、Liskov替换原则(LSP)、

依赖倒转原则(ISP)等原则,是面向对象程序设计努力的方

向。同样,一个负责的作者也不应该假设他的读者都读过

Robert Martin 的 Designing Object Oriented C++ Applica-

tions Using The Booch Method,他也应该帮那些面向对象

的初学者补上这一课。

第三,这本书应该对《设计模式》书中提到的大部分模

式做足够详尽的介绍。由于《设计模式》的经典地位和它的

枯燥晦涩,一本模式教材最应该做的就是不厌其详地介绍

《设计模式》中一些重要模式的来龙去脉,说明它们为什么

必要,说明它们的适用范围以及(更重要的)不适用的范围,

说明它们如何解决问题以及遗留下的问题。如果不能解答

《设计模式》给读者留下的这些疑问,一本模式教材将无法

引起读者的共鸣,也无法给读者立竿见影的效用。

最后,书中的内容应该“与时俱进”。范例应该来自应

用更为广泛的软件项目,语言最好是Java或者C#(由于C++

内在的局限性,并不适合用于描述设计模式),图例应该用

UML⋯⋯尽管我很推崇Robert Martin那本书,但如果看到

一本今天的模式教材还在用Booch方法,我一定先把它扔到

一边去。

在我所见到的模式书籍中,Alan Shalloway 的 Design

Patterns Explained和阎博士这本《Java与模式》都符合上述

四点要求,所以都可以被评为“好的模式教材”;而Pattern

Hatching、The Pattern Almanac 等书则不符合上述要求,

因此不是“好的模式教材”(请注意:不是好的模式教材,

并不妨碍它们成为好的模式专著。但那不在我的评判范围之

内。对适用范畴之外的事物保持恰当的沉默,是严谨的科学

态度所必须的)。现在 Design Patterns Explained尚未出版

中译本,因此不论它是否优于《Java 与模式》,后者都是迄

今为止最好的中文模式教材。

除“Alexander 的模式理论”和“面向对象设计原则”

这两项要素之外,《Java与模式》一

书有两个值得注意的特点:第一,

它采用了大量从金融业务系统中抽

取出来的Java代码示例;第二,书

中穿插着大量源自中国古典名著的

哲学小品。关于第二个特点,我将

在后面评论。至少,阎博士扎实的

基础和长期在华尔街开发软件项目

的经验使得这些范例不至于像《设

书 评

Page 115: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

115

服 务 & 广 告

计模式》中的范例那样生涩,也不至于像The Joy of Pat-

terns 中的范例那样流于简单。

但是,这并不意味着阎博士对模式的阐述并无缺陷。对

于模式来说,除了“解决方案(solution)”之外,最重要的元

素还包括“问题(problem)”、“约束(force)”和“效果

(consequence)”。读者每学习一个模式,除了学到模式所代表

的解决方案之外,还必须相应地学到该模式所适用的范围(由

“问题”给出)、不适用的范围(由“约束”给出)和该模式造

成的正反两面的效果。否则,读者仅学到一个解决方案,却

不知道应该在何时使用(以及,在何时不使用),则只能靠自

己在不断的试错过程中摸索该模式的约束条件。摸索模式的

约束条件,与摸索问题的解决方案,两者的工作量往往相差

无几——也就是说,模式将没有任何帮助。阎博士对此显然是

很清楚的,在《Java 与模式》的 1.6 节中,阎博士明确无误

地列举出了模式的“十大要素”(包含了一些并非最重要的要

素,如“范例[example]”、“已知应用[known uses]”等)。

可是,在讲解具体模式的时候,阎博士却忽略了“约束”

这个重要的约束——至少是没有明确地在每个模式中指出。

这种行文安排,或许是由于阎博士的哲学态度(关于这一

点,我将在后面讨论)。但是,这种疏漏的后果可能是很严

重的:缺乏(至少是不明确标明)约束条件的模式介绍,加

上似是而非、放之四海皆准的哲学小品,可能很容易让读者

对模式抱一种过分的乐观态度,并将对模式的误用归咎于没

有获得那种无影无形的“道”,从而失去加深对模式认识的

机会。甚至于,对于一个不太负责的读者,对约束条件的疏

漏可能让全书的努力付诸东流。对于一本 1K页的技术书籍

来说,让读者冒着放弃科学意味的风险而追求哲学意味,成

本恐怕太高了一点。努力地(也可能是一厢情愿地)为这种

疏漏做一些补救,也是我写这篇书评的目的之一。

上面就是我对《Java与模式》这本书技术层面的评价,

一言以蔽之:白璧微瑕。在 Design Patterns Explained 的中

译本出版之前,这本书毫无疑问是最好的中文模式教材;即

使 Design Patterns Explained 出版,由于中西文化上的差异

和翻译水平的限制,它与读者的亲合度也未必强于《Java与

模式》,因此后者也可能继续成为最好的中文模式教材。

前面已经提到,作者阎宏博士非常注重模式理论与中国

传统思想的结合,全书也处处穿插着源自中国经典的哲学小

品,并试图向读者传达一种中国道家思想的哲学意味。下

面,我将对这种哲学意味进行评价。作为软件开发者,面对

一本阐述软件开发技术的书籍,我将尽力把评论收敛在科学

的范畴之内,并对科学所不能及的范畴保持恰当的沉默。

重要而非必要的哲学意味

综观全书,阎宏博士的论点大致可以总结为:

软件模式理论起源于C.Alexander的建筑模式理论,后者又

起源于中国的道家思想。并且,模式理论与包括孙子、墨子等在内

的中国传统思想多有暗合之处。因此,朝着中国传统思想的方向为

模式理论做形而上学的探索,有助于发展“软件工程哲学”。

对于阎博士赋予这本书的哲学意味(以及它占据的篇

幅),我的评价是:重要,但非必要。下面,我将详细阐述我

的观点。

软件模式起源于C.Alexander的建筑模式理论,这是毫

无疑问的。不论是 GoF 的《设计模式》、Alan Shalloway的

Design Patterns Explained,还是 Linda Rising 的 The Pat-

tern Almanac,都强调Alexander的著作《建筑的永恒之道》

和《建筑模式语言》对软件模式思想的影响。但是,Alexander

的建筑模式理论(以及从它衍生而来的软件模式理论)是否

起源于中国的道家思想,就有可商榷之处了。

中国的道家思想似乎是适用范围最为广泛的一种思想。

天地运转、人情世故,似乎无不蕴涵在一句“道可道非常道”

之中。这种极其广泛的适用性就给我们一个暗示:它很可能

是一个并无意义的理论。

为了演示如何从矛盾式推出自己想要的理论,我的朋友

Snowfalcon做了一个更有趣的实验。下面是Snowfalcon即兴

写下的一段文字:

软件的永恒之禅

模式的哲学可以追溯到佛教的起源。可以将佛教的理

论“投射”到软件设计中。

⋯⋯

首先,“无名特质”(QWAN)意味着一个软件的内在属

性不仅仅存在于这个软件之中,而且存在于这个软件与其

它软件的相互作用之中,存在于这个软件与计算机外部的

世界,特别是用户的相互作用之中。

“无名特质”其实在佛教中早已有详细的阐述。佛教中

讲“空”就是这个概念。佛教中的“空”分为“人空”和

“法空”。其中“法空,则谓一切事物之存在皆由因缘而产

生,故亦无实体存在”。也就是说,所有的事物都由内在的

本质“因缘”所定,当它反映到人脑中则形成了“色法”。

所谓“色法”,泛指有质碍之物,即占有一定之空间、具有

自他互相障碍及会变坏之性质者。无名特质对应了空,它

是一切事物的产生和内在的本质。但是由于人的观察,形

成了色法。色法阻碍了人对事物即空的本质觉悟,从而说

它是自他互相障碍。

因此软件中这个最重要的属性之所以是“无名”的,是

因为它是映射到人脑之前的属性。在这个属性被映射到人

脑中之后,就已经经历了用各种方式局限到各种不同范围

之内的过程;这种过程导致这个无名的质变成一大堆“有

名”的“色法”。在本书后面读者会读到,这些“有名”的

Page 116: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

116

听起来是不是也很有道理?为什么“模式的哲学”既可

以起源于道家思想,也可以起源于佛教?甚而至于,你可以

感觉到:只要有足够的逻辑能力和语言能力,你还可以让“模

式的哲学”起源于其他的很多思想。原因就在于:和道家思

想一样,佛教的理论也用足够含混的语言包容了矛盾,因此

能够从中推导出任何其他理论。

对于阎博士的这本书,情况正是这样:不论把模式理论

归结于道家思想或者佛教思想或者别的什么思想,都不能给

模式理论以更多的内涵。

除了提纲挈领的哲学思想之外,阎博士在书中处处采用

的“文化小品”也有着同样的问题:它们对于理解单个的模

式并无更多的帮助。这类来自中国古典名著的小故事,在点

缀着读者的阅读体验的同时,也混淆着读者的头脑。

其实这种介绍模式的手法并不新鲜。就在 CSDN 网站

上,我也不止一次地看到有人用比喻的手法来介绍设计模式

(以及其他比较难理解的东西,例如面向对象),并美其名曰

“深入浅出”之类。对于这种降低难度的办法,我一向不感

冒。对于模式,最重要的元素莫过于“约束”——“问题”

和“解决方案”都是一目了然的,但真正重要的是要知道“何

时不使用模式”,也就是“约束”所讨论的东西。什么样的

比喻能够描述出软件问题的复杂度?即使有这样的比喻,它

又怎能不像软件问题一样难以理解?所以,我反对在讨论模

式的时候滥用比喻——毕竟我们讨论的是软件问题的解决方

案,而不是那些看似轻松的比喻。

即使是阎博士精心挑选的比喻,也同样无法令人满意。

我随手捡一个例子:

这就是典型的过分简化的比喻:既然可以说这种“身外

身”的手段是Prototype模式,那么我可不可以说它是Factory

Method 模式呢?又可不可以说它是 Singleton模式呢?又可

不可以说它是Flyweight模式呢?这样一个“案例”,既不能

说明模式的“问题”,也不能说明模式的“约束”——如果

讨论设计模式而不说明“问题”和“约束”,那讨论就没有

任何意义。这样的比喻,简单则简单矣,恐怕读者看完了例

子还是如坠云中,不知道自己的软件中是不是适合用这种

“身外身的手段”。

到此,读者可以看到我的观点:阎博士给这本《Java与

模式》赋予的哲学意味、文化意味,不论是提纲挈领的哲学

渊源,还是贯穿全书的文化小品,对于模式的知识都并无助

益。因此,我认为它并非必要。这对于那些想要通过这本书

“得道”而掌握模式精髓的读者,无疑是一个打击,不过也

应了最近又开始流行的一句老话:No silver bullet。

尽管并非必要,但这些内容的确有其价值——不是科学

范畴的,而是心理学范畴的。书中的哲学意味尽管不能给读

者更多的模式理论,但的确能够吸引中国读者的眼球,让他

们有兴趣捧起这本书来。同样,这些比喻也可以让读者更快

地对模式有一个大致的了解,虽然他们会在需要使用模式的

时候花掉更多的时间去摸索模式的适用范围。

写在最后

如果可以的话,我宁可不要对《Java与模式》的哲学意

味做任何评价,因为这种纠缠了哲学、逻辑和科学的文字写

来实在费劲。只是,作者阎宏博士对他书中的哲学意味如此

看重,我如果评价他的书而不评价这一部分,对他是大不

恭,故此勉力为之。现在,我终于又可以回到轻松的书评路

线上来了。

借用 Jacques Lebrun的那句“法国人的俏皮话”来评

价阎宏博士:他没有时间往短里写。这本《Java与模式》

会有1K页这么厚,阎博士的“贪心”是肇因——想在一本

书里放下模式理论起源、模式基础和模式实例,而且还不

愿厚此薄彼,最后的成果变成“狼伉巨物”也就是情理之

中了。不过,这种“贪心”的风格把这本书置于一个略显

尴尬的境地:它太大,无法成为公共汽车上的阅读物;如

果要当参考手册来看,又显得太稀——在查阅一个急需的

模式时,恐怕是不会有太多人有心情顺便欣赏“孙大圣大

战黄风怪”的。

所以,这本《Java与模式》的确是一本好书,但前提是

你有足够的耐性来读。总之,除掉其中那些有助于消化的、

带有淡淡甜味的辅料之外,它能给读者的养分与它 1K的页

码并不相称。对此,我也只能希望阎博士能够有时间把这本

书“往短里写”了。

“色法”就包括软件性能要求的可变性、软件的可用性;以

及系统的可扩展性、灵活性和可插入性等等。这些质就是色

法,如果我们用色法去观察世界,那么就不能理解无名特质

的本身的空。

正如我所暗示的,还可以把这样的逻辑游戏继续玩下

去。你甚至可以声称模式的哲学起源于柏拉图的思想:模

式是“理型的解决方案”,是某一类具体解决方案的“灵

魂”;具体解决方案是模式在真实世界的“投影”;当人们

看到这些“投影”时,他们灵魂中的理性受到震荡,并因

此而探寻完美的“理型”——也就是模式。当然,也可以

声称模式的哲学起源于赫拉克利特的思想,只要你愿意。

孙悟空在与黄风怪的战斗中,“使一个身外身的手段:

把毫毛揪下一把,用口嚼得粉碎,望上一喷,叫声‘变!’

便有百十个行者,都是一样打扮,各执一根铁棒,把那怪

围在空中。”换言之,孙悟空可以根据自己的形像,复制出

很多“身外之身”来。

老孙的这种身外身的手段在面向对象的设计领域里,

叫做原始模型(Prototype)模式。

书 评

Page 117: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

117

服 务 & 广 告

读《C++沉思录》 撰文 / 山人

随着海外图书的大量引进,现在

关于C++的图书可谓前所未有的丰富。如果说我以前初学C++

时烦恼的是无书可看,那么,现在的程序员恐怕要为选择一本

适合自己的图书而大伤脑筋了。

但是,我注意到,市面上的C++经典图书莫不把重点放

在学习和理解C++语言的本身上,如《C++ Primer》、《The

C++ Programming Language》、《Essential C++》、《Effective

C++》等等,虽然书中举出了大量的精彩例子,作者解释得

非常细致、透彻,但是我们很难知道,这些例子究竟是怎么

来的,不知道作者完成这些例子的过程。也就是说,我们知

道了作者思考的过程和结果,但是很难知道他们思考的源头。

所以,我看了《C++沉思录》后,心里便感觉到,这是

一本异样的书,是关于C++图书中的一个另类。正如作者开

篇所说,“这是一本关于 C++ 程序设计的书,具体来说,它

首先是一本关于程序设计的书,其次才是一本关于 C++ 的

书。从这个意义上讲,这本书与其他大多数C++的书籍都不

同,那些书所关注的是C++语言本书,而不是怎样用好它。”

《程序员》的读者对于该书的作者Andrew Koenig和Bar-

bara Moo应该不会陌生,因为在《程序员》的“名人堂”栏

目中曾介绍了这对C++界的神仙伴侣。这夫妻两人都是C++

创始人Bjarne Stroustrup的朋友,也是著名的C++大师Stanley

Lippman 的至交。曾经有人戏言,如果以家庭为单位,那么

Andrew Koening 和 Barbara Moo 组成的无疑是 C++ 技术含

金量最高的家庭。

作为ANSI C++标准委员会的项目编辑,Andrew Koenig

在 C++发展历史上具有毋庸置疑的权威地位。从1988年起,

受 Bjarne Stroustrup的邀请,Andrew Koenig一直担任JOOP

杂志的C++专栏作家,后来,他又应邀在C++ Journal和C++

Report 这两本杂志上开辟了专栏。《C++ 沉思录》即是由

Andrew Koenig 从这些专栏文章中挑选、增补和重写完成。

因为我作过编辑,所以我知道,与专门写书相比,这种写书

的方式有很大的不同。

对于杂志而言,要求每篇文章都独立成篇。其文章主要

是解决一个现实的问题,或者阐明一个与现实相关的概念或

观点,内容的重点放在可用性、总结性、前瞻性上。而且由

于杂志必须合理分配各个栏目的内容,所以具体到某篇文章

的页码都有严格的限制,因此要求文章的语言必须尽量的精

简,用最少的文字表达最多的信息。所以,把杂志上的文章

集结成的书,应用性很强,满书充满了技术点。当然,对于

读者而言,必须具有一定的基础,要懂得文章中提到的概

念,以能够跟上作者的思路。

而书稿则不同,它主要要求文章的连续性、系统性。这

样,为了上下文的衔接,有时不可避免地包含了大量随处可

见的、基础的、而又并没有多少价值的内容。

所以,阅读《C++沉思录》这本书是一件愉快而又痛苦

的经历,说愉快,便是因为该书的结构非常紧凑,篇幅适量,

阅读时,不知不觉,书已经翻到了尽头。全书近 400页,算

比较厚的,但是,我只花了不到2天就看完了。说痛苦,是

因为书中每个章节都充满了技术点,应用性极强,是跟着作

者不停的思考。

阅读本书时还有一个很明显的感觉:亲切,作者并没有

居高临下的态势,而是像在展示自己一步步领悟C++、直至

彻悟C++的过程。跟随作者清晰的思路,读者理解的不再仅

仅是问题的答案和解答过程,而更多的收获是懂得了怎样使

用 C++、怎样用 C++ 风格的思考。作者把书名定为《C++

沉思录》,的确是深有用意。

为什么要用 C++

这是作者给我们的第一个思考,这也是每一个学习、使

用 C++ 的人所应考虑的第一个问题。我想很多程序员都想

过这个问题,因为在 CSDN 论坛上关于 C++ 与 Java、C++

与 Delphi的大论战出现过好几次。

按照作者的看法,这也是使用C++编程时的动机,没有

好动机,怎么有好的结果呢?但是作者并没有简单地用

“C++ 是最优秀的语言”来回答这个问题。

当我们要驾驶一辆汽车时,如果我们要关心汽车的每样

东西是如何运行的:如发动机、传动装置、方向盘和车轮的

连接等,那么我们永远无法开动这辆车。

作者认为,编程也是同样的道理,我们只能关注问题中

那些在当前情况下最为重要的部分、忽略那些我们此刻并不

重视的因素,这种有选择的忽略即是抽象。不以各种方式进

行抽象,就不能编写任何有价值的计算机程序。而C++使我

们更容易把程序看作抽象的集合,使我们可以用一种更抽象

的风格来编程。

Page 118: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

118

作者还认为,C++是为那些信奉实用主义的用户群准备

的,这种实用主义在某种程度上体现了 C++ 语言及其使用

者的灵活性。C++程序员总是为了特定的目的编写不完整的

抽象,他们会为了解决特定问题设计一个很小的类,而不在

乎这个类是否提供所有用户希望的功能。如果这个类够用

了,那么他们可以对那些不尽人意的地方视而不见。

有关抽象和务实的基本思想贯穿了全书,更重要的是,

这些思想渗透了 C++ 设计原则和应用策略。

内容介绍

全书分为六篇:

第一篇对主题的扩展介绍,这些主题遍布全书的各个部分。

第二篇和第三篇是全书的重点,占据了全书一半的篇幅。

第二篇着眼于继承和面向对象编程,这些是 C++ 中最

重要的思想。在这里,作者对于面向对象提出了自己的理

解,他认为,面向对象即指使用继承和动态绑定的编程方

式。当然,在继承之前,必须先用C++建立抽象,即先建立

一个有效的类。

类是 C++ 的核心概念,怎样设计一个良好的类呢?作

者提出了以下富有价值的建议:

l 需要一个构造函数吗?

l 数据成员是私有的吗?

l 是不是每个构造函数初始化所有的数据成员?

l 需要析构函数吗?

l 需要虚析构函数吗?

l 需要复制构造函数吗?

l 需要一个赋值操作符吗?

l 赋值操作符能正确地把对象赋给对象本身吗?

l 需要定义关系操作符吗?

l 删除数组时记住用 delete[]吗?

l 记得在复制构造函数和赋值操作符的参数类型中加上

const 吗?

l 如果函数有引用参数,它们应该是 const 引用吗?

l 记得适当地声明成员函数为 const 吗?

对于怎样设计类和使用继承,作者给出了设计surrogate

(代理)类和handle(句柄)类一步步从简到繁的详细思路,

读者将从中懂得继承的重要性何在,它能做什么,读者还会

知道为什么将继承对于用户隐藏起来是有益的,以及什么时

候要避免继承。

第三篇探索模版技术。直观上讲,模版就像是一种复杂

的宏,但是,作者认为模版才是C++里最重要的思想,因为

模版提供了一些特别强大的抽象机制,它们不仅可以构造对

所包含的对象类型一无所知的容器,还可以建立远远超出类

型范畴的泛型抽象。

对于继承和模版之所以重要的另一个原因是,它们可以

能够扩展C++而不必等待人去开发新的语言和编译器。进行

扩展的方法之一是通过类库,第四篇讲述库的设计和使用。

第五篇讲述一些特殊的编程技术。第一个例子是设计一

个追踪类,用来记录和输出其他类的创建和销毁、赋值和赋

值的情况。通过这个例子,读者对于类的构造、析够、拷贝

和赋值函数将有深刻的认识。第二个例子是建立一个集中管

理成批分配的内存的类。第三个是关于应用器、操作器和函

数对象的技术。通过这几个例子,读者可以知道如何把类紧

密地组合在一起,或者它们尽可能减少。

第六篇,作者对全书的内容进行了回顾。

客观上讲,C++ 的确非常复杂,即使是具有多年 C++

编程经验的程序员,也很难说能够懂得C++的全部特性。对

此,本书作者认为,如果掌握了C++的核心思想,纵使没有

通晓 C++的全部特性,也并不妨碍我们成为优秀的 C++程

序员。所以,作者并没有在本书中包含那些纷繁的技术细

节,而是毫不含糊地把 C++ 的核心观念展现在读者面前。

在全书的最后,作者还对于读者怎样提高 C++ 水平提

出了 2 个经典建议:

1、做理解的事情;理解要做的事情

2、逐步加深理解扩展理解

下面是附件的建议:

1、做练习时要把握分寸,欲速而不达

2、依据操作思考:思考时重点放在数据的行为、而不是

程序的结构上

3、早些考虑测试

4、思考:也许,这是最重要的

另外,在编程序时要注意的三个最重要的建议:

1、避免使用指针;2、提倡使用程序库;3、使用类来

表示概念。

最后

在书中可以看到感谢Stanley Lippman、Bjarne Stroustruo

的话,而在C++ Primer中也可以看到感谢Andrew Koening、

Barbara Moo的话,所谓物以类聚、人以群分,大师们的相互

支持和帮助也促使了他们本身的提高。反思国内的情况,与

这些既有坚实的理论知识、又有丰富的实践经验的团体相

比,的的确确找不出能与之相提并论的角色。

我想,这与国内计算机发展起步较晚有关,没有足够的

时间(国外的大师都有20余年了吧)和实践(国外的大师莫

不是大型项目的佼佼者)的积累,谈“高手”肯定是“纸上

谈兵”。不过,国内目前有许多C++的爱好者和使用者,相

信将来的一段时间内,同时具有爱好者和使用者的人群之中

将会出现真正的“C++ 高手”,甚至于大师。

书 评

Page 119: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

119

服 务 & 广 告

2002年软件开发类图书市场热点回顾

撰文 / 流云

.NET强势出击 Java 魅力不减

在国内知名的计算机图书网站China-pub上,有一篇书

评这样写到:

“说到虔诚,有谁能够和程序员们相比!一个程序员在

选定了某种技术之后,孜孜不倦、悬梁刺股的精神能让多少

善男信女自叹弗如:开放源代码程序员即便衣衫褴褛也仍然

在勒着裤腰带写让 Linux更完美的代码、Java的信徒们口中

喃喃地念着‘Write once,run anywhere’的咒语、CB 用

户们为 Imprise被 Corel收购而痛心疾首捶胸顿足......程

序员永远都不会像The Mummy中的那个流氓一样在木乃伊面

前换着不同的护身符——这就是程序教众的虔诚。”

��

其实对于程序员来说,如何选择一种开发技术或工具并

享用终生,的确是一个令人头痛的问题。可以说,IT行业的

技术之争从来没有像今天这样激烈过。新技术的竞争同时也

带来了计算机图书市场的繁荣。

2002 年对于微软是重要的一年,其.NET战略的重要组

成部分—— Visual Studio.NET的中文版已于 2002 年 3月 22

日正式在中国推出。其实早在 Visual Studio.NET 正式版还

未推出之前,市面上就已经出现了一些.NET图书,2002 年

再次成为微软图书的天下是因为微软出版社(MS Press)在

其中推波助澜。微软出版社为了配合 Visual Studio.NET 的

推广以及.NET技术的普及,特地邀请Visual Studio.NET项

目开发组的核心开发人员和计算机图书专业作家精心编写了

“微软.NET程序员系列”丛书,该丛书自面市以来,在美国

图书销量排行榜上一直高居前列,成为程序开发人员和网络

开发人员了解.NET技术的权威工具书。清华大学出版社出

版了“微软.NET 程序员系列”丛书的简体中文版。同时,

加上国内外众多出版社自己组织的.NET图书,使得.NET图

书几乎涵盖了.NET技术及其应用的各个方面,占尽了计算

机图书市场的风光。

面对微软的挑战,Java阵营涛声依旧。Java图书市场近

几年不断看好,特别是 2002 年 Java好书不断,魅力无穷。

从一月份开门红的《JAVA技术手册》(原书名《Java in a

Nutshell,Third Edition》荣获 2000年 Java Developer's

Journal 读者票选总冠军)到年底上市的《Java与模式》(阎

宏著),加上机械工业出版社推出的侯捷翻译的经典巨著

《Java 编程思想(第 2 版)》,足以让 Java 信徒们大开眼界。

看来.NET 和 Java 之争目前还难分胜负,.NET 和 Java图书

将继续在计算机图书市场各领风骚。

C++经典热卖 Delphi 推陈出新

C 语言的书一直占据着计算机语言类的市场,经年不

衰。今年C++经典名著成为畅销热点,特别是机械工业出版

社引进的C++之父Bjarne Stroustrup博士的鸿篇巨构《C++

程序设计语言》(特别版)和《C++语言的设计和演化》在

国内面世以来,得到了读者的强烈反响。加上 Bruce Eckel

的《C++ 编程思想(第 2 版)》、Andrew Koenig、Barbara

Moo 夫妇合著的《C++ 沉思录》以及潘爱民翻译的《C++

Primer(第三版)》等多部 C++的重要作品引发了读者们对

C++语言、程序设计乃至计算机科学的广泛讨论。10月底,

机械工业出版社华章公司举办的“C++之父中国行”将2002

年的 C++ 经典图书热推向高潮,许多 C++ 程序员得以和

Bjarne Stroustrup 博士进行现场的技术交流。

Delphi作为优秀的 RAD 开发工具,在国内有着众多的

开发使用人员,并一直受到程序员的追捧。2002年Delphi图

书市场随着新版本 Delphi6 的推出再次推陈出新,在读者中

影响较大的丛书有机械工业出版社的“Borland / Inprise核

心技术丛书”,其中影响较大的作品有国外 Paul Kimmel 的

《Delphi6 应用开发指南》、李维的《Delphi6/Kylix2 SOAP

/ Web Service 程序设计篇》。令人遗憾的是巨著《Delphi6

开发人员指南》由于种种原因,直到 2002年底才迟迟上市。

随着 Delphi7 的推出,2003 年将会是 Delphi7 的图书天下,

同时读者将更加关注知名 Delphi作者的新作。

软件工程领潮流 高端选题成热门

2002年可以说是计算机图书市场的软件工程图书年。从

数量上讲,各大出版社引进的外版软件工程图书累计达数百

个品种,其中仅机械工业出版社和中信出版社就引进了70多

Page 120: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

120

种。从质量上讲,引进了一大批在国外畅销的软件工程佳

作,如:流行 20 年经久不衰的软件工程经典——《软件工

程:实践者的研究方法(第5版)》,被开发人员视为宝典的

《设计模式》、《分析模式》、《人月神话》,实践性极强的《编

写有效用例》、《UML 和模式应用:面向对象分析与设计导

论》、UML开山鼻祖亲撰的《统一软件开发过程》和《UML

参考手册》等。

由于历史的原因,国内软件开发过程的方法和管理一直

是一个薄弱点,又由于国外的软件开发过程方法和管理不断

出新,使得我们在这方面的差距更加拉大。因此,软件工程

方面的图书既是国内计算机图书需求的一个潜在热点,也是

高端计算机图书市场争夺的制高点。2002年许多出版社纷纷

看好这一领域,推出了不少好书,其中大手笔有机械工业出

版社的“软件工程技术丛书”、电子工业出版社的“软件项

目管理系列丛书”、人民邮电出版社的“软件工程系列教

材”、中信出版社的“软件工程丛书/需求与设计系列”、清

华大学出版社的SEI软件工程系列丛书(中、英文版)等等,

都受到了读者的好评。

以前计算机图书市场竞争的范围主要集中在低、中端

图书上,出版社总在有意无意中回避着高风险的高端图书市

场。随着国内软件业的繁荣和发展,计算机高端图书市场正

在一天天成长起来。因为低、中端图书市场竞争的激烈,高

端图书市场已经在2002年成为强势出版社的竞争热点。软件

能力成熟度CMM、统一开发过程RUP、极限编程XP、个体

软件过程 PSP、敏捷建模 AM、面向对象开发 OOA/OOD/

OOP,目前市面上的高端图书选题也越来越广泛,几乎与国

外的新技术同步。高端技术图书的升温将有利于广大的软件

工程技术人员接触到更多的软件工程思想和新技术,全面提

高 IT 行业的整体素质。

西风台风外版风 正版盗版影印版

2002年的计算机图书市场火红的依然是外版图书,受读

者欢迎的还是微软、IBM、西蒙-舒斯特、Addison Wesley、

O'Reilly、麦克米兰等公司的外版书。另外以侯捷、李维为

代表的“台风”也已经成功登陆,占领了大陆计算机图书市

场的不小份额。一些中小出版社纷纷靠外版书杀进竞争激烈

的计算机出版界竟也风骚一时。优秀外版书的引进固然给国

内读者带来了世界一流的名家名作,但也对国内的计算机图

书出版产生了一些负面影响。

首先,外版书大大增加了计算机图书的成本,成为读者

购书最大的心痛。一方面,几乎所有出版社都把外版书作为

出版争夺的重点,不少出版社不顾中国计算机图书的市场情

况,盲目抬高版权交易费用,使版权争购已经到了白热化的

地步。另一方面,版权费用加上译者的稿酬使得外版书定价

高居不下。

其次,一些出版社在外版书的翻译上把关不严,给许多

优秀的作品带来了“硬伤”,成为读者购书最大的遗憾。特

别严重的甚至误人子弟,不知所云。

另外,国内出版社过分依赖外版书本身就是一种急功近

利的行为,不利于自身的发展。而且经过近几年大规模的引

进,外版书的资源正面临枯竭。如果不加紧培养国内自己的

优秀作者,不扶持本版书的出版,那么,计算机图书的可持

续性出版将成为一句空话。可以预见,未来强势出版社争夺

的将不再是外版书的版权,而是国内新兴的优秀作者。

2002年的盗版情况比以前几年略有好转,但盗版的威胁

主要来自于网站上的盗版电子书。正版计算机图书的昂贵定

价在某种程度上使得盗版者有机可乘,但是有识之士不断提

醒大家“为了保护你的眼睛和作者的积极性请购买正版”,

实际上绝大多数计算机图书都是打折销售的。

2002年可喜的一方面是影印版图书大量上市,给喜欢原

汁原味的读者带来了福音,不少读者宁可降低阅读速度看原

文,也不愿意看错误百出的中译本。目前影印版图书的面还

比较窄,主要限于国外教材和经典名著。

四强争霸欲比高 后来居上黑马多

1999年,以机械工业出版社、清华大学出版社、人民邮

电出版社、电子工业出版社联合举办的“中国计算机图书四

强联合展销会”为契机,国内计算机图书业进入了洗牌阶

段,适者生存,弱者出局。由此,计算机图书业进入理性发

展阶段。

伴随着 2000年中国互联网业的思考,从 2001 年开始,

计算机图书市场亦进入缓慢增长期。一些较小的出版社悄无

声息退出竞赛,而一些大出版社纷纷调整出版策略和产品结

构,寻找新的“经济增长点”。2002 年虽然中国计算机图书

四强争霸的局面没有改变,但在出版策略和产品结构上已经

调整完毕。

2002年,机械工业出版社的“软件工程技术丛书”、“编

程思想第二代”、“Borland / Inprise核心技术丛书”亮点不

断,再次成为专业读者心中一面旗帜。

另一家大社——清华大学出版社则凭借多年计算机教

材的出版经验,形成了层次完整、种类齐全的图书品种结

构。2002 年,清华大学出版社的“世界著名大学计算机教

材精选”和“微软.NET 程序员系列”丛书再次吸引了读

者的眼球。

人民邮电出版社和电子工业出版社原来一直在中、低端

市场拼搏,2002年也开始争夺高端市场和教材市场。人民邮

电今年出版的“极限编程系列丛书”、“C/C++ 实务精选系

列丛书”、《个体软件开发》等图书受到了读者的广泛认可,

好书推荐

Page 121: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

121

服 务 & 广 告

一举奠定了自己的高端地位。

电子工业出版社在2002年中推出的“软件项目管理系列

丛书”收入了 Larry Constaintine 的《超越混沌》、Steve

McConnell的《快速软件开发》、Ed Yourdon的《死亡之旅》

等经典著作,成为今年软件工程类图书的一大亮点。

面对强势出版社,许多中小出版社也不示弱,在计算机出

版市场切得属于自己的一块蛋糕。2002年是黑马倍出的一年。

黑马之一:中国电力出版社是 1998 年秋季才正式进军

计算机图书市场,随着与 O'Reilly、Addison- Wisley等世

界著名出版社进行版权合作之后,他们推出了一系列国外计

算机图书的经典之作,使广大读者眼前一亮,让计算机图书

出版界为之一震。

黑马之二:华中科技大学出版社初入计算机图书市场,

即与名家合作,他们推出的侯捷译作系列一炮打响,2002年

翻译出版了《STL源码剖析》、《Essential C++中文版》等书

赢得了一大批读者。

黑马之三:中国水利水电出版社在计算机图书市场慧眼

独具,他们与众不同的选题常常能给读者带来惊喜,比如:

2002年的畅销书《Delphi 第三方控件使用大全(II)》和《程

序调试思想与实践》就是这样的选题。中国水利水电出版社

在多年的经营中树立了自己特有的专业品牌。

居安思危谋发展 与时俱进促繁荣

近几年的计算机图书出版市场已经由“兵荒马乱”的战

国时代,进入了四强鼎立,市场细分的整合阶段。虽然目前

计算机图书出版依旧鱼龙混杂,但市场的格局已趋明朗化,

一些主力出版社也逐渐理清了发展思路,树立了自己的品

牌。然而居安思危,计算机图书出版市场仍然问题不少,这

主要集中在以下几个方面:

一、跟风出版、选题重复现象十分严重,读者定位不够

明确。

国外的出版公司一般都有自己明确的市场定位,而国内

的出版社喜欢模仿和跟风。一但某种(类)图书畅销,大家

就模仿书名跟风出版,导致了市场的趋同性。以“百例”为

例,目前市场上以“百例”冠名的图书不下10种,但最终得

到读者认可的只有中国水利水电社万水公司的“效果百

例”、机械工业出版社的“时尚百例”等少数几种。大量的

同类同名图书泛滥导致了作者资源、出版社资源、图书资源

的浪费。

其实计算机图书选题重复并不可怕,可怕的是没有自己

的创意和定位,大量的模仿和抄袭造成图书质量参差不齐,

良莠杂陈。

计算机图书通常分为高、中、低端读者三个层次,涉

及到普及入门、实用技术、研发提高不同服务方向。现在

市场上的计算机图书在这三个层次的定位上存在着严重的

模糊不清,造成了高级开发并不高级,轻松入门难以轻松

的尴尬局面,还有从入门到精通的,更是既不利于入门也

不利于精通。于是读者常常在琳琅满目的计算机书架上找

不到合适的书。

二、工作室难成气候,专业作者队伍尚未形成。

随着计算机图书事业的繁荣,曾经有一批专门从事计算

机图书写作和编辑的“工作室”应运而生,为出版社和书商

提供服务。但是这些工作室大都势单力薄,缺少有经验的译

作者,还有一些工作室急功近利,廉价雇佣学生攒书,已经

使自己名声狼藉。加上这些工作室大都游离于出版社和计算

机公司之外,得不到足够的资源,发展空间有限。

尽管国内也出了一些小有名气的计算机图书作译者,但

目前还没有真正形成一支熟悉电脑图书写作规律、掌握读者

阅读心理、密切跟踪技术发展、具有丰富实践经验的专业计

算机图书写作队伍,这显然与计算机图书市场巨大的经济效

益不相适应。

三、引进版过热,中国特色不强。

虽然大量引进的外版书弥补了计算机图书市场的不足,

但这种繁荣带来的将是阴虚火旺,后劲不足。目前在高端图

书中几乎没有针对我国软件开发人员的特点而编写的专著,

更缺乏这一类实践经验丰富的专业作者。我国软件工程技术

人员与国外同类人员相比,天生就 “营养不足”,最大的问

题就是缺乏大型项目的开发经验,缺乏新技术的使用经验。

如何能编写出适合我国软件开发人员特点的软件开发图书,

将是各社策划编辑要解决的一个重要课题。

其实不少有远见的出版社已经意识到以上问题,正在积

极调整自己的出版策略。有的出版社走精品路线,广泛联系

有实力的作译者,在图书出版上以质取胜,宁精勿滥。有的

出版社大量引进计算机专业人才,聘请有经验的专家顾问,

加强策划、编辑队伍建设,凸现专业出版风格。还有的出版

社积极培养专业写作队伍,大力扶持潜力作者。

总之,面对计算机图书市场的持续低迷,读者眼光的不

断提高,前几年那种赶潮出版、急功近利、粗制滥造、盲目

上马的做法再也行不通了。可以预见2003年计算机图书市场

的竞争将更加激烈,好书将更加丰富,选题将更加广泛,热

点将更加精彩。

毫无疑问,计算机图书出版界只有与时俱进,积极进

取,才能更创辉煌!

Page 122: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

122

架上书新

编写信息安全策略

定价:28 元

确 定 组 织 的

安全策略是一个

组织实现安全管

理和技术措施的

前提,否则所有的

安全措施都将无

的放矢。本书以通俗的语言描述了什

么是安全策略、怎样编写安全策略以

及策略的维护周期,并给出了许多安

全策略的样板。

本书对于企业安全策略的编写人

员来说是一本难得的参考书。适用于各

组织的高层技术人员和管理人员,特别

是从事网络安全领域的研究人员、IT技

术服务领域的技术和管理人员。

UML数据库设计应用

定价:30元

本 书 结 合 实

际案例,详细介绍

了 UML 在数据库

设计过程的应用。

全书共 9 章。

从需求分析、业务

建模、需求定义、分析和初步设计到

构建模型、物理实现,内容由浅入

深,循序渐进。最后,作者对案例研

究工程中的 UML 的应用进行了分析

和回顾。附录部分是一个完整案例。

该书适合 UML 的应用研究人员

和数据库系统设计人员阅读,对于学

习数据库设计与实现的学生,也有很

好的参考价值。

数据库系统基础(第三版)

定价:70元

本书包括 6部分内容和 4个附录。

分别介绍了数据库设计技术两极的基本

概念、基本原理、关系数据模型和关系

数据库管理系统、对象数据库和对象-

关系系统、与数据库相关的主题、实现

数据库管理系统的技术以及有影响的新

数据库模型、新兴技术和应用。附录A

为显示 ER或EER模式的图形记号,附

录 B为磁盘的重要

物理参数,附录 C

和 D介绍基于网状

数据库模型和层次

数据库模型的传统

数据库系统。

本书可作为一

个学期的课本,同时也是从事数据库

技术研究和应用开发人员的一本系统

而全面的参考书。

Visual C++实现MPEG/JPEG编解码

技术(1CD)

定价:68 元

本书以 Visual

C + + 作为开发工

具,从实用角度出

发,向读者介绍了

数字音、视频编解

码技术的基础理

论、实现方法和实

用技巧,并给出了具体的工程案例——

数字录像监控系统。

全书主要内容包括:压缩技术基

础、JPEG压缩编解码技术、JPEG 2000

编解码技术、MPEG- 1 压缩编码技

术、MPEG-2 标准、MPEG-4 压缩编

码标准、MPEG-4 实用源代码分析以

及数字录像监控系统。

本书内容丰富、叙述详细、实用性

强,可供广大从事数字编码、多媒体开

发的技术人员阅读参考。

Java 与 UML面向对象程序设计

定价:28 元

本书共分 1 2

章。前4章介绍了

面向对象的精髓。

接下来让您对面向

对象编程的实践有

较好的了解。第 7

章介绍了设计模

式。第8章介绍软件重构。第9章简要

介绍针对大规模和小规模的面向对象

软件项目以及当前的一些主要开发方

法。第 10章介绍当前面向对象软件开

发的工具软件。第11章给出了作者为

开发更好的软件而提出的一些指导意

见。最后,第12章提供了关于面向对

象软件和 Java方面更多的学习资源。

本书强调理论和设计相结合,可

作为高等院校的教科书,也可作为相

关人员在学习面向对象程序设计时的

参考书。

现代软件工程技术与 CMM 的融合

定价:44 元

全书共分 1 1

章,前 3章介绍了

软件工程和 CMM

的基本概念、原理

和体系结构;4-6

章给出了软件工程

和 CMM 融合的框

架结构、重点关键过程域实施方案分

析和软件项目管理的方法步骤;7-10

章分别介绍了美国SIAC公司、联想公

司、摩托罗拉公司实施CMM的案例分

析和针对软件项目监理的案例分析;

第 11章给出了“基于高新技术的我国

软件产业发展规划构想的研究”。

书中列举大量实例,为软件企业

提供系统可行的参考方案。该书适合

从事软件行业的工程技术人员、过程

管理人员和软件经理人阅读,也可做

为高校相关专业学生的参考用书。

——人民邮电

新书上架

Page 123: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

123

服 务 & 广 告

架上书新 ——电子工业出版社

软件需求(34元)

多数IT系统的

失败都与需求不明

确有关。本书以面

向实践、案例教学

的方式,介绍了当

前需求工程的各项

技术。面向软件供

应链牵涉到的所有

人员都将从中学习到新技术,

作者Soren lauesen是丹麦哥本哈根

IT大学、哥本哈根商学院教授,长期从

事人机交互、计算机需求规范、面向对

象的设计、系统开发等研究工作。

译者麦中凡系北京航天航空大学

教授、博士生导师,长期从事软件开发

工程与教学工作。主要研究方向为软

件工程和现代信息技术。

CMM 实践应用-Infosys 公司的软

件项目执行过程(29元)

本书以印度著名软件服务供应商

Infosys公司所采用过程为例,通过讲

解一个典型软件项目开发生命周期所

采用的管理技术和管理过程,用实例

教学法生动地讲述了枯燥、繁杂理论

的实现过程。书中简要回顾了CMM的

基础理论,介绍了 Infosys的背景及其

大致的开发过程,

按照项目的生命周

期介绍了 CMM 在

软件开发过程中的

应用。附录说明了

一个通过 ISO认证

的机构如何过渡到

CMM,并给出了

Infosys 实现 CMM的一些经验。

高速网与互联网络——性能与服

务质量(第二版)

高速网络现在

不仅是局域网而且

也是广域网市场的

主角。本书对高速

网络和 TCP/IP 互

联网络的设计技术

进行了全面、系统

和及时的综述。本书的特点在于对高

速网络与互联网络的讨论围绕性能和

服务质量这两个关键设计问题。本书

共分七个部分,包括协议与网络基

础、各种高速网络、性能建模和估计、

拥塞与通信量管理、互联网络的路由

选择、IP 网络的服务质量以及压缩。

本书作者 William Stallings 从麻省

理工学院计算机科学博士毕业,是国际

上颇有影响的计算机网络教授。先后出

版了 17 种不同内容的图书,有的已再

版多次。曾4次获得教科书与院校作者

协会最佳计算机科学图书年度奖。

信号与系统(第二版)英文版

定价:59 元

本书是美国麻

省理工学院电气工

程与计算机科学系

经 典 教 材 ,作 者

Oppenheim是该领

域的权威,在国际

上享有盛名。

书中讨论了信号与系统分析的基

本理论、基本分析方法及其应用。全书

共分 11章,主要讲述了线性系统的基

本理论、信号与系统的基本概念、线性

时不变系统、连续与离散信号的傅里

叶表示、傅里叶变换以及时域和频域

系统的分析方法等。

本书可作为通信与电子系统类、

自动化类以及电类专业信号与系统课

程的双语教材,也可以供从事信息处

理工作的科技工作者参考。

C++ 大学教程(第二版,含光盘)

定价:78 元

是一本 C++编

程 方 面 的 优 秀 教

程,全面介绍了过

程式编程与面向对

象 编 程 的 原 理 方

法,分析了各种性

能问题、移植性问

题和可能出错的地方,介绍了如何提

高软件工程质量,详细讨论了新的

ANSI C++标准和标准板库,并提供

丰富的自测练习和习题。书中所配的

“多媒体教室”光盘更具有特色,通过

交互环境,使学习过程更加有趣。

作者Harvey M.Deitel在计算机学

术和工业领域有 3 8 年的工作经历。

Deitel博士编写和合著了数十本专著和

多媒体教学软件包。其著作被翻译成

多国文字,享有很高的国际声誉。

密码编码学与网络安全:原理与

实践(第二版)

定价:48 元

密码编码学与网络安全是当今通

信与计算机界的热门课。本书内容新

颖丰富,讲述基本的数据加密原理和

数论的概念,各种加密算法和常用的

协议以及它们在网络中的应用。书中

各章都提供许多习

题和参考读物,并

列出推荐网站。本

书适用于通信计算

机专业的本科生或

研究生,也可作为

通信或计算机领域

的研究人员和专业

技术人员的参考书。

本书作者为William Stallings(《高

速网与互联网络——性能与服务质量》

(第二版)的作者)。

Page 124: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

124

编读往来

新年到了!

现在,北京的雪依然下得很大!在大雪纷飞中感觉到了

前行道路的泥泞,甚至上班也有些迟了,可接到了公司的通

知,圣诞节之夜有精彩安排,另外还集体看《英雄》⋯⋯

生活如此,平凡普通中总有新鲜之处,有困难所在,更

有惊喜和期盼,这是实实在在的,虽然理想和希望非常远

大,非常宏观,但于每一天中,更多的则是平实中细细品味

缓慢前行中的种种酸甜苦辣。

程序员杂志一路走来,2000 年试刊和 2001 年创刊时

的那份喜悦和感动,早已幻化成日日相伴的滴滴温情。刚

刚过去的2002 年,编辑部共出刊了12期杂志、2本专刊以

及最近正在热销的一套《程序员 2002 增值合订本》,这是

一年的果实,特别是加班加点制作完成的合订本,虽然称

不上硕果累累,但确实凝结了众位编辑的极大心血,希望

读者喜欢。

一年中,每期杂志面市,编辑部总会收到很多的读者来

信,这些热情的反馈,无论是解决问题,还是提出建议,都

是对我们的肯定和鼓励。我们深知,年轻的《程序员》还有

很多不足,但是有这么多忠实的读者,有这么多始终支持我

们成长的网友,还有许许多多或在技术领域颇有专长、或对

企业及软件项目管理很有心得的杂志专栏作家、作者,这些

人所汇成的合力,才是杂志真正得以发展壮大的根本动力和

支柱。

作为一本技术刊物,2003年的《程序员》将继续在以技

术为核心的体系下,重视管理,关注人才,围绕软件开发人

员的工作、学习与成长,进行全方位的扫描、报道和探讨。

报道将更加前沿,更加有深度。

技术和管理将继续得到深化。以技术前沿作引导,以技

术的深度应用和实践心得、解决方案为特色。挖掘新颖的角

度,作更深的技术剖析和前景探讨。

服务版块在现有书评的基础上,加大认证、培训、就业

等方面的指导,真正为开发人员工作之外的提升和就业提供

帮助。

保持品牌栏目,加强明星作家的培养,加强专题和特别

策划,这是新的更进一步的目标。

编辑部的活动会更多,更丰富。程序员和CSDN共同举

办的技术沙龙,已经在北京地区享有一定的知名度,2003

年编辑部将在此基础上,举行更多、更有针对性的作者读

者见面会以及主题沙龙,让大家网上、网下的交流更加畅

通便捷。

辞旧迎新,这是我们在新的 2003 年的第一个开篇。

瑞雪兆丰年!又是一个新的起点,新的开始,让崭新的

《程序员》与您同行!

程序员论坛

jeny :怎样评测程序员的工作质量?

chenyanzhen:要量化考核。

(1) 必须对程序员负责模块所需工时有个精确估计,如

根据代码量和难度进行区分,然后结果和实际进行对比等。

(2) 测试时记录程序员负责模块的bug数(当然也是平

均 bug数与实际 bug数对比,这和代码量有关)。

Mty:分三点:工作量、工作时间和工作质量(难易程度,

运行稳定性等)

其实最主要的要在规定的时间内完成工作,并在此时间

内对其完成的任务进行充分测试。

Pyzfl:看工作日记,从工作日记可以看出以下内容:

(1) 工作计划

(2) 工作记录

(3) 对潜在问题的分析

(4) 对已知问题的解决思路

(5) 对总体进度的把握

(6) 对工作的建议⋯⋯

只要主管不是太外行,肯定可以看出该人的工作量与能

力。然后合理安排:

聪明而懒惰的人——作主管

聪明而勤快的人——作分析员

愚蠢而勤快的人——作程序员

愚蠢而懒惰的人——赶快辞退!

Nizvoo:文档 + 代码 + 态度。

Swordlqm:根据这几年的经验,我总结如下:

(1)工作态度

(2)工作量化(包括工作的安排)

(3)工作日志

(4)代码质量

如果一个人的工作态度不好,不仅影响个人、团队,更

会影响公司的发展。其次看工作日志与实际完成的工作(首

先要保证模块能用)、工作进度是否合理,最后看代码的质

量。如果整个安排的工作模块根本没有成形,代码质量再高

都没有用。

工作量化程度与项目经理、项目主管的分析与管理能力

Page 125: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

125

服 务 & 广 告

读者调查问卷统计结果

上期你最喜欢的文章: 热心读者:

 

本期奖品为程序员杂志社出品的《程序员2 0 0 2 增值合订本》一套。

2002.12

满意率:94.1%

名人堂:认识C++之父

从程序员到文档工程师

对话:中国软件核心技术

程序员的君子五德

技术专题:数据仓库

江 西 黄炉山 

西 安 詹 磊 

重 庆 王国华

上 海 孙家靖

杭 州 沈效东  

成正比。

总之,要看公司对程序员的工作范围是如何规定的,国

内还没有几家公司能够说程序员就是写代码,很多公司的程

序员其实做的事情都是比较杂的。

网友 zhirenShao:

编辑您好,我是《程序员》的忠实读者,从试刊开始就

一期都没少过。还试图参加编程擂台,后来我的程序被告知

测试没通过,好像犯了个低级错误,不过没关系,自娱自乐

嘛。今年 4 月,签证下来,奉父命,只身赴澳大利亚。

来到澳洲,我一踏进书店,哇,经典大作赫然架上:The

C++ programming Language、Thinking in Java、还有

Tanenbaum 老师的 OS 和 networks的经典教材。DDJ、CUJ

在琳琅满目的杂志栏里一下子就吸引了我的视线。全是从美

国来的。

但价格一个比一个吓人,标价40美元的书,换算成澳元

也就 70多澳元嘛,可这里售价却在100澳元。唉,美国版税

+太平洋运费+澳洲关税,上帝啊。我现在正在阅读Think-

ing in Java 2ed,是在国内买的影印版,买外版实在吃不消。

我买过几次DDJ,但发现内容离我目前还有差距,都在讲什

么 Distribute Computing,没什么兴趣看,而且价格也比较

贵,还是《程序员》好啊,内容精彩,价格也不贵,我最喜

欢了。但从出国的那天起,就读不到《程序员》了,因为这

里没有。刚刚打电话给老爸,叫他买这期的杂志和今年的合

订本给我寄过来。

想到这里,就给你们写了这份邮件,谢谢你们的工作,

祝新年快乐!

Amone:这份信从另外一个侧面告诉我们,国内的读者

很有一些福气——看影印版图书的福气。这里,我还想告诉

国内的读者一个更大的福气:编辑部正在努力和国外的一些

著名开发类期刊协商合作事宜,如果能够合作成功,那国内

的读者们可就更有福气了。

读者李华:一年过去了,我收集和珍藏了每一期的《程

序员》杂志,每当工作中出现问题或者希望阅读哪一类专题

时,总是苦于找不到对应的文章,希望能够提供全年的文章

目录索引。

Amone:2002年的合订本(下),我们安排了全年的目录

索引。另外,在CSDN网站的杂志频道,我们还会公开全年

文章的索引,届时你可以直接浏览或下载。

读者唐家峰:请问通过何种渠道投稿?是直接寄到编辑

部还是寄给编辑个人?

Amone:编辑部设有专门的投稿信箱 [email protected]

您可以直接投稿,当然,您也可以投稿给相应版块的编辑:

管理版块:[email protected]

技术版块:[email protected]

服务版块:[email protected]

另外,还有一位叫詹磊的读者给编辑部寄来了自己的一

份投稿——《车站调度:一个启发性的算法》一文,对此,

技术编辑回复如下:

詹磊同学对“火车进站问题”给出的二叉树解法,由于

之前已有这一类的解答,因此抱歉这篇投稿不能被采用。

但从这篇文章中,我们看到詹磊同学已经具备对于大学

生来说最为重要的独立思考能力和钻研精神,我们为你感到

高兴。尽管这篇投稿未被采用,但相信你一定会有更大的成

就,也希望以后能收到你更多的投稿。

(注:为了提高编辑的工作效率,也为了让投稿作者能

够得到及时回复,希望大家尽量采用电子邮件投稿。)

编读往来

Page 126: PROGRAMMER - pudn.comread.pudn.com/downloads8/ebook/26475/2003.01.pdf · 侯捷。两年过去,这家出版社的图书质量我发现依然没有什么变化,更不要谈 出现大陆侯捷了。

PROGRAMMER

126

l 日前,微软公司软件产品

Windows Movie Maker 2.0测试版

在互联网上向用户开放下载。因其

卓越的性能和全新的用户体验,近

期,该产品获得美国权威IT资讯网

站 About.com所授予的“桌面视频

软件类”五星级产品评价。此次

Windows Movie Maker 2.0版本所

具有的数项创新性功能,使其在智

能化和实用性方面得到了极大的提

升。由微软亚洲研究院研究并直接

参与开发的此项技术,重新定义了

Windows Movie Maker在桌面视频

编辑软件领域中的地位,更让用户

感受到了前沿技术转化所带来的全

新用户体验。(Charlie)

l 近日,金蝶公司面向中小企

业推出定价为 2 6 8 0 元的“金蝶

2000XP迷你版”,主要对象为需要对

财务记账实现电算化的私营企业、

乡镇企业、小型事业单位等,产品主

要涵盖了小型企业的总账、报表、往

来和出纳等业务,声势浩大地拉开

了“金蝶财务软件旋风行动”在北

京、上海、成都等地大规模推广的大

幕。(里)

l 日前,神州数码(中国)有

限公司与中国电信集团旗下最大的

卫星通信公司——南方卫星联手,

正式推出“软件工程师远程培训”课

程,面向全国企业及个人用户提供

实时的、高质量的远程培训服务。神

州数码“软件工程师培训” 课程自

推出以来深受广大学员欢迎,已经

成为中国软件培训市场的领导品

牌。此次采用远程教学的方式,不但

打破了地域和时间的制约,更有效

地解决了师资及价格等关键问题,

从而为更多的软件学员提供了便

捷、经济、随时随地享受业界一流水

准的培训机会。(辉)

l 微创 BMS XP SP1 是 BMS XP 的升级版,它结合了微软优秀的软件开发管

理流程和经验,旨在管理软件开发流程中任何能够对产品最终功能、性能、稳定

性和易用性造成伤害的各种缺陷,并通过量化手段使管理团队随时随地了解项目状

态,以便决策,从而规范软件开发管理流程,提高软件质量。(孟)

l 11月25日,北京—全球图形技术市场的领头羊NVIDIA公司(纳斯达克上

市代码:NVDA)今日推出 NVIDIA GeForce FX 图形芯片(GPU)。这款新式图

形芯片基于全新的 NVIDIA CineFX架构,能够实时实现影院级图形及特殊效果。

(里)

l 11 月 27 日,SAP 公司于同一天签约两家中国大型钢铁企业——马鞍山钢

铁股份有限公司和杭州钢铁集团公司,为他们提供ERP系统,这两个项目近日已

正式启动。这标志着我国钢铁企业信息化建设的进一步深入。两个项目的实施服

务均由全球五大钢铁集团之一的德国蒂森克虏伯集团下的汉思公司提供。(里)

l 11 月 28 日,Borland 公司宣布推出 Borland Delphi 7 Studio。该产品全面

支持 Web 技术,具有集成模型驱动开发特性,并且还能够预览 Microsoft .NET

Framework。利用 Delphi 7 Studio,Delphi开发人员将能够着手开发.NET技巧并

为.NET准备应用程序,同时又不必放弃Windows平台上的工作和技巧。(Charlie)

l 12月3日至7日,酝酿及筹备已久的联想技术创新大会(Legend World 2002)

在北京联想大厦隆重召开。本次大会以“创新科技 畅想未来”为主题,来自全球

知名 IT企业、国内科研院所、大学院校的顶级技术专家以及国内50多名院士、教

授及各行业学者共同站在了联想的讲台上,一起畅谈科技未来。为期一周、包含近

百场论坛的大会为专业人士及爱好者们提供了一席精妙绝伦的技术盛宴。(琦)

l 12 月 5日,“微软亚洲信息与协作技术应用大会 MEC 2002”在北京召开。

围绕“Energize Your Enterprise ——为企业注入活力”这一主题,大会通过两天

共四十余场主题讲座,全面阐释了基于微软.NET架构的Microsoft Exchange 2000

Server 如何以较低的总拥有成本构建面向企业级的协作应用。(节)

l 8 月 19 日,点击科技发布了“竞开协同,点击未来”战略之后。12 月 10

日,“竞开协同应用平台”的首款应用软件——“竞开协同之星”(简称GK-Star)

开始试用活动,同时点击科技还携“竞开协同之星”参加“2002 中国电子政务技

术与应用展览会”,备受瞩目。(辉)

l12 月 17 日,AMD宣布推出多套专为 AMD Alchemy 解决方案系列处理

器而设计的 AMD Alchemy 解决方案DBAu1000、DBAu1500 及 DBAu1100 开发

电路板套件工具。AMD 的客户可利用这几套全新的开发电路板套件工具开发新产

品,迅速将产品推向市场。(辉)

l 12 月 17日,微软(中国)有限公司推出“护航计划”,这是该公司有史以

来最大规模的企业级服务网络构建举措。通过该计划,微软公司将在投资 5 亿人

民币在全国范围内发展150家高水平技术服务合作伙伴,构建起一个覆盖全国100

个城市的专业企业服务网络。第二天,微软公司又在北京与全球同步启动

Microsoft.NET connected Logo 认证计划,向国内符合条件的独立软件开发商

(ISV)提供.NET connected Logo 认证服务。此举标志着 Microsoft.NET 开发平

台的基本体系已经走向成熟和稳定,软件开发商可以在这一平台上率先进入 Web

应用时代。(琦、辉)

(更多新闻请见www.CSDN.net。欢迎提供信息:[email protected]

厂商直击