Tag Archive for '翻译'

规划你的WebAPI – 安全


译自“Professional Web APIs with PHP: eBay,
Google, PayPal, Amazon, FedEx, Plus Web Feeds” (chapter 12)


你一定听过这样一句话“失败的计划是必定失败(Fail to plan, and you plan to fail.”对WebAPI来说,在开始一切工作之前的规划尤为重要,因为他不仅会影响到实现难度,同时会让那些使用API的开发者痛苦不堪。

Continue reading '规划你的WebAPI – 安全'

ChromeOS是什么[翻译]

原文:http://www.tech-no-media.com/2009/07/what-chromeos-is-not.html

很多人都对ChromeOS兴奋不已,很明显有些博客言过其实。也许是该总结一下到底google的新OS是(不是)什么了。

ChromeOS
1.是linux:如果你认为Moblin(REF)是linux内核,clutter window manager也是linux的话。那么ChromeOS以及他还没有命名的轻量级窗口管理器也是linux。如同其他linux版本一样,ChromeOS也会开源,我们可能看到其他基于ChromeOS的版本 - 正如Ubuntu基于Debian一样。

2.是为上网本设计的操作系统:有一件事可以确定,ChromeOS会根据上网本的使用模式而专门优化,这和第一台EEE PC上的Xandros OS或 gOS类似:本地预装一部分应用程序,其他应用则基于云上。最主要的不同在于:google Gears使得云应用变得更易用,用户体验更好。如果你看一下google ChromeOS的合作伙伴,你会注意到他们大多在上网本市场有着很好的表现。

3.是基于ARM的:再看一眼合作伙伴,其中有Qualcomm,Freescale和德州电器,没有Intel!这三家是ARM芯片制造商同时对ChromeOS可以很好的运行在基于ARM的芯片上有很大的兴趣。对x86的兼容性会帮助ChromeOS占有更多的市场,但是大多数预装ChromeOS的机器会是基于ARM的。

4.会在货架上出售:对,ChromeOS的目标就是帮助出售便宜的上网电脑(用户只有上网机会为google创造价值)。在摆放时应避免和PC放到一起,造成消费者的混淆。

5.对linux来说是件好事:linux最大的问题是他的市场份额。因为linux只能代表2%的市场,很多软件,游戏和硬件商会忽略它,因为市场实在是太小了,不愿意将产品或者驱动移植到linux上。作为平衡,linux社区起到很大的作用,很多驱动,应用软件是由程序员自己开发的。但有一个问题,很多流行的游戏是由游戏工作室设计的,驱动应该是在硬件发布之前而不是之后开发的。如果基于linux的平台 - ChromeOS能够得到更多的市场份额,对整个linux社区都是一件好事 - 可以获得更好的商用软件供应商和硬件厂商的支持。

ChromeOS不是
1.并不完全依靠“云”:即便google没有使用Gnome或者KDE,她仍然可能会移植大多数的linux应用程序到ChromeOS,那么最终将有一整套完整的应用生态环境出现。尽管可能需要等上几年。因为Google gears的支持,类似google doc这样的web应用可以在离线状态下运行,所以本地应用程序可能是不需要的。

2.不是微软杀手:尽管为ChromeOS编写本地应用程序是可能的,但是ChromeOS最主要的目标是web接入。这意味着一开始可能只有很少的商用软件支持。因此,大多数人还是想要Windows作为他们的PC平台。ChromeOS可能只能在上网本制造商方面威胁到微软,但微软并没有在这方面赚钱,所以也就不大可能会引起大的对抗。

3.还没有准备好:google预期第一台装备ChromeOS的设备在2010年下半年诞生,尽管第一个beta版已经available。当ChromeOS上市的时候,Windows7和其他诸如Moblin和Jolicloud的linux操作系统将和它同台竞争。

REST API vs. SOAP API

译自“Professional Web APIs with PHP: eBay,
Google, PayPal, Amazon, FedEx, Plus Web Feeds”

REST API
当处理REST请求时,因为信息是通过GET,所以,信息在传输过程中会进行URL编码(URL-encoded);当需要进一步处理时,首先要进行解码(唯一例外是用户名和密码)。不同的请求类型应该使用不同的endpoints(URLs);如果要以单独的脚本程序来处理所有的请求,你可以让所有的请求都指向同一个endpoint,或者配置web服务器来映射许多endpoints到同一个脚本。我建议用后一种方式;它符合规范同时允许你以后在不需要影响外部接口的情况下做修改。

允许程序员使用web接口来请求API - 在调试程序时将变得非常有用;程序员可以快速的判断问题源于请求本身还是代码。你可以提供给程序员的调试工具越多,你的网站也就越容易开发。

SOAP APIs
当处理SOAP请求时,首先要检查请求是否符合WSDL指定的格式。如果你使用诸如NuSOAP的工具,他可以帮你做到这一点。事实上,大多数SOAP API使用一个框架来处理许多低级的工作。 SOAP API使用单一endpoint接收所有请求(作为一个通用规则,一些大的API会根据功能来拆分到不同的endpoint),因此,或者是你有一个很大的脚本文件,或者是在每个功能点都调用很多required()方法。

允许程序员在使用web接口时可以粘贴整个请求文档到一个表单,然后发送到你的服务器。从直接的经验来看,有这样一个工具对程序员调试程序时是非常有用的。提供脚本或者函数从而让程序员可以手动创建请求对那些没有使用SOAP框架的程序员是很有帮助的。

apache-derby连接jdbc连接url的解析

来自http://db.apache.org/derby/docs/10.4/devguide/devguide-single.htm。我这里只是翻译一下,其实最关键要搞清楚的就是系统文件夹(通常是C:/databases)和类路径(从类路径开始或者从jar文件中开始查找)的区别。

jdbc:derby:db1
•打开系统文件夹中的db1

jdbc:derby:london/sales
•打开数据库lodon/sales,london位于系统文件夹下,sales是london的子目录

jdbc:derby:/reference/phrases/french

•打开数据库 /reference/phrases/french。 unix下,就是从根目录开始的路径。windows下则是C:\reference\phrases\french(如果当前驱动器是C.如果一个包含数据库的jar文件在用户的类路径下,则这个路径是jar文件内的路径。

jdbc:derby:a:/demo/sample
•打开驱动器A中的数据库,路径是\demo\sample

jdbc:derby:c:/databases/salesdb jdbc:derby:salesdb
•这两个连接到相同的数据库 - salesdb.在windows下derby默认的系统路径是C:\databases.

jdbc:derby:support/bugsdb;create=true
•在系统路径下创建一个新的数据库 - support/bugsdb。如果不存在,则自动产生相应的文件夹。

jdbc:derby:sample;shutdown=true
•关闭sample数据库。(如果没有启用验证,则不需要提供用户密码)

jdbc:derby:/myDB
•以只读方式连接myDB(位于类路径下)

jdbc:derby:classpath:/myDB
•同样以只读方式连接myDB数据库。使用classpath的原因是,路径下可能存在和数据库同名的文件夹。

jdbc:derby:jar:(C:/dbs.jar)products/boiledfood
•访问只读数据库boiledfood, 位于C:dbs.jar中的products文件夹。

jdbc:derby:directory:myDB
•访问myDB(位于系统文件夹)。使用directory是类路径下可能存在同名文件夹(myDB)

OO设计原则

抖胆翻译一下scea study guide里的第一章的OO设计原则.纪录一下.

开闭原则
类应该被扩展,而不是被修改

Liskov替换原则
子类可以替换父类

依赖注入原则
依赖于抽象,而不是实现

接口分离原则
接口应该分开,避免单一通用的接口

合成重用原则
尽量用多态聚合代替继承

最少知识原则
操作知道尽可能少的当前类中的对象(自身,参数,类中的其他实例对象)

发布重用等效原则
细粒度的重用相当于细粒度的版本发布

包依赖:

共同关闭原则
一同变更的类应放在一起

公用重用原则
如果类不会一起被重用,就不应该放到一起.

非循环依赖原则
包之间不可以有循环依赖

依赖不变原则
依赖不应该经常变化

抽象不变原则
抽象包不应该经常变化

Javablog » How MIDlet Signing is Killing J2ME

Javablog » How MIDlet Signing is Killing J2ME

TODO: 翻译

实践的一小步-代码质量的一大步

实践的一小步-代码质量的一大步
作者:Steven Hale December 14, 2007
翻译:ZhangV 2008-2-18
原文出自:DevX.com
原文链接:http://www.devx.com/Java/Article/36231/0/page/1

假设持续整合是你开发过程中很重要的一部,你也很想把代码覆盖检查并入你的自动构建过程.但是如何设定覆盖率目标呢?经验丰富的代码覆盖支持者会建议你75%,85%甚至100%.

通过对项目中的代码分析,我意识到需要设定比上述数据低得多的目标.我不想成为团队中的Cathcart上校(Josef Heller的小说<<第22条军规>>中的人物,因为每当部下已经达到当前的飞行任务次数时,他就会设定更高的任务次数,虽然Yossarian不断地完成飞行任务,但却似乎永远都无法完成这个总在变化的次数,小说中最后Yossarian开小差逃到瑞典-译者).

我选择增量改进的策略而不是专制的设定更高的目标.为了成功的执行这个策略,每次构建都必须达到比之前构建更高的覆盖率.通过每次一小步的提升,来最终提升代码质量.

以下介绍如何使用Cobertura和Apache Ant来实现这个增量改进.

单元测试,代码覆盖,持续整合
这三个概念已经被广泛的接受为最佳实践.事实上,大多数程序员知道他们必须要单元测试.如果你还不知道,让我来引用Google研发总监Peter Norvig的名言:

"如果你觉得不需要写单元测试,把所有原因写下来到一张纸上.仔细的揣摩这张纸.然后,扔掉这张纸开始写单元测试."

但是谁来测试测试人员呢? 也就是说,你怎么来确认你写了足够的测试呢?这些是非常宝贵的信息,因为那些没有被测试覆盖到的代码正是最耗费你经历的地方.一个解决办法是使用代码覆盖工具,它们会告诉你有被测试代码的百分比,然后加入一个代码覆盖检查到你的持续构建过程.如果你的覆盖检查不通过,那么构建也该被认定为失败.

在我的增量改进策略中,我选择Cobertura是因为她4个定义完整而简单的ant任务扩展.其中之一 cobertura-check,当代码覆盖率没有达到要求是会认定构建失败.例如下面这个例子,当覆盖率小于80%时,Ant会认为构建是失败的:

<target name="coverage_check">
<cobertura-check totallinerate="80"/>
</target name="coverage_check">

但是,你可以使用之前的覆盖率来作为当前检查的标准.通过几个Cobertura的任务以及两个核心的ant任务,你就可以达到这个目标.而不需要担心是否应该考量 代码行率(line rate),分支率(branch rate)或者其他的覆盖数据.你的目标是改进,而不是一个绝对的硬性指标.

创建一个XML格式的代码覆盖报表
创建好ant任务后,你可以加入增量覆盖率检查到你的构建脚本中,第一步是用cobertura-report任务创建一个覆盖率报表:

<cobertura-report format="xml"/>

下面是一个生成的报表:

<?xml version="1.0"?>
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-02.dtd">

<coverage line-rate="0.43612334801762115" branch-rate="0.48344370860927155" version="1.8" timestamp="1181043899853">
<sources>
<source>./src/java</source>
</sources>
<packages>
...
</packages>
</coverage>

确保你把这个文件保存安全的地方(或者放到你的版本控制系统),因为之后你会用到他们.

从报表中得到覆盖率数据
一开始你可能会用Ant的XmlProperty任务来直接获取代码行率(line rate).但是这种做法有几个问题:
1.Cobertura的行率(line-rate)是十进制小鼠,但是cobertura-check是用整数形式的百分率
2.在一个实际的项目里,coverage.xml可能会很大,使用XmlProperty可能会造成内存不足的错误

我建议使用Ant的XSLT任务来获取你所需要的数据

<xslt in="coverage.xml" out="build/coverage.properties" style="src/xsl/coverage.xsl" />

这个简单的xsl模板生成仅仅包含你所需要的数据的属性文件:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  version="1.0">
<xsl:output method="text" omit-xml-declaration="yes"/>
<xsl:template match="coverage">
total.line-rate=
<xsl:value-of select="floor(@line-rate*100)"/>
</xsl:template>
</xsl:stylesheet>

注意方法:floor(@line-rate*100), 会把行率转成一个整数,生成的coverage.properties文件只有一行:

total.line-rate=44

这个属性文件仅仅是一个临时的文件,构建之后你可以把它清理掉.

使用Ant的property任务读入这个属性文件中唯一的值total.line-rate:

<property file="build/coverage.properties" />

然后你可以把之前的"80"替换为新的Ant属性值:

<cobertura-check totallinerate="${total.line-rate}"/>

完整的例子
最终的build.xml应该是这样(其他的任务省略掉):

<target name="coverage_check" depends="check_against_previous_rate">
<antcall target="coverage_report"/>
</target>

<target name="coverage_report">
<cobertura-report format="xml" destdir="." />
</target>

<target name="check_against_previous_rate" depends="coverage_xml_to_properties">
<property file="build/coverage.properties" />
<cobertura-check totallinerate="${coverage.line-rate}" />
</target>

<target name="coverage_xml_to_properties">
<xslt in="coverage.xml" out="build/coverage.properties" style="src/xsl/coverage.xsl" />
</target>

注意:新的覆盖报表只有在覆盖检查通过时才生成.(覆盖率必须要高于前一次的构建)

记得要先执行coverage_report,然后执行coverage_check.实际开发中,你应该加入另一个covertura-report任务来生成HTML的报表.

跟踪改进率
一个附加的好处是你可以通过记录覆盖率数据到一个文件来跟踪改进率.通过echo人物:

<target name="time">
<tstamp>
<format property="date.time" pattern="yyyy-MM-dd HH:mm"/>
</tstamp>
</target>

<target name="log" depends="time">
<echo file="${history.txt}" append="true">
${date.time};total.line-rate;${total.line-rate}
</echo>
</target>

可量化的结果,看得见的改进
在使用了上述的措施后的一周后,我们的代码覆盖率提升了30%.可喜的是,那些之前不情愿写测试的程序员现在也很自豪地看到项目的覆盖率提升.

敏捷开发的民主精神在于,每一个团队成员都可以为整个团队提出目标 - 通过写单元测试. 没有专制的目标,没有cathcart上校.

你也可以更进一步.把这种增量式改进策略用到其他的地方.

作者Steven Hale,工作与瑞典的Omegapoint AB咨询公司.有超过20年的系统开发经验.

看板在行动(Kanban in Action)

作者:DavidAnderson 原文
翻译:ZhangV 2007-11-26

之前,在讨论我在corbis所使用的软件工程管理方法时 ,我曾提到引入看板系统来维持项目运行。因为它的引入,我们现在每个月平均发布两个新的版本。然而,这些并没有使用传统的敏捷开发方法如两周迭代(two week iteration)或快速迭代(sprint)。而是用看板系统来管理变更请求(CR-Change Request)。当CR被完成时,它被设在准备发布状态,并在每周三被发布。

尽管我们让没有用Visual Studio的开发人员使用了outlook的teamlook客户端联入Team Foundation Server跟踪所有CR,但基本上的日常工作,我们都是使用白板作为看板来跟踪所有CR,把Post-It当作看板卡。

我们的支持过程是由一组常见的软件工程资源驱动的,没有一个专职的支持维护团队。通过设置看板,我们可以向管理层保证 - 我们正在履行我们的承诺,我们使用了一定的资源来进行支持活动,而不需要特别指出是谁在负责。

每天早会上所有团队成员会围绕看板来进行当天的工作安排,讨论进度和当前存在的问题。达伦戴维斯,作为项目经理,会主持会议,并分析每张卡片并与其他成员讨论。每张卡上写着CR的标题以及ID,及负责人的名字。每个负责人有责任根据当前CR的进行阶段移动相应CR的卡片。达伦利用每天的早会来保证Team Foundation Server上的数据和看板是同步的。该看板系统基本上是自我组织(self organizing),但整个团队每天都会验证它的有效性。

一些关键概念。红星 - 这个任务已经严重超时的,超过28个工作日服务水平协议( SLA),通过该系统。这使"迟到"的项目能够自我加速,不需直接管理干预。粉红色卡表示这个CR在Team Foundation Server里还是open的。这些粉红色卡包含了描述信息和在Team Foundation Server里的ID 。还有一些其他类型的卡片:黄色-客户提出(customer-valued)的CR;蓝色-客户提出(和要求)的BUG;绿色-IT维护项,如:的sql 2005的升级;橙色-新增(或额外)的BUG;粉红-issue。

看板限制卡片最多只能有20张,分为不同阶段,系统的分析,开发,测试,构建/合并,部署。此外,我们也允许有3个bug可以放在任何地方。这样可以有效地利用闲散资源。当人手不够的时候,我们会减少这个数量,或者干脆不要。此外,还有两个维护项,这让我们能解决固定分配一些资源到大大系统维护和升级,例如API版本升级,与平台的升级。 如net2.0或sql server 2005 。

看板赋予我们三个成功要素:

  • 减少“进行中(work-in-progress)”, (讽刺的是,这些事情本身会限制工作的完成)
  • 量入而出(如果有新的CR,首先看看板上有没有足够的空间)
  • 确定优先顺序

我们每周和公司的技术总监开会讨论,决定任务的优先等级。他们从后台日志中拿出新的CR并考虑怎么把他们配置到看板上。这迫使他们思考哪些是最重要的事情。看板强迫他们给出优先等级。

看板还把我们从定时迭代的压迫(constraints of time-boxed iterations)中解放出来。尽管我们现在每两个星期发布一个新版本,大的任务可以在系统中最多可以存在60天。那些对于“一(两)周迭代”来说太大的任务同样可以放到看板里,而不需要特殊对待。

最后,看板解放了我们,我们不再把每个迭代都当作一个小型的项目而投入过多的管理成本它基本上是自行组织和管理,除非有特殊情况出现,如需要加快请求或因环境或系统的维修问题变更发布时间。

technorati的标签:敏捷,朱+安德森,精益生产,看板,软件+工程

顺便问一下customer-valued怎么翻译?

最后修改:2009-8-12

写商业计划书之前要做的10件事

翻译-写商业计划书之前要做的10件事
原文

撰写计划书之前最好研究一下计划书应该包含哪些部分.

1.研究所有相关章节
开始写之前,确定你已经理解必要的章节,不同章节的目的和计划书的目标. Business Plan Pro(r)
2005之类的软件会帮助你确保不会漏掉任何章节.但如果你选择自己写,可以参考这两个网站:www.bplans.co.uk和www.businessplanhelp.co.uk,他们会帮助你全面了解每个必要的组成部分,写出令人信服的商业计划.

2.决定法律结构
尽管大多数的企业关注于"创意"或者"想法" -- 这是可以理解的,但是日常执行和后勤也是不可忽视的.例如,在做贸易的时候,你要决定是单一供货商,合伙制还是有限公司.决定之前,列出当地的会计师和业务关系或者通过网站(www.startups.co.uk)研究不同选择的特点.类似的,诸如了解你的增值税义务(原文:VAT obligation),注册商标或公司名,以及起草员工合同都需要被考虑到.

3.掌握数据
无论你是否喜欢统计数据,深入地了解会影响到你业务的数据是你成功至关重要的因素,尤其是在计划阶段.开始阶段了解以下这些非常重要:
.你的启动成本
.你的盈亏平衡点
.你的资金需求
.你的之后几个月的现金流预测
免费的计算器可以帮助你计算这些数据.强烈建议你通过一个基本会计打包(原文:basic account package)建立你的业务.QuickBooks(r)
(www.intuit.co.uk) 或 Sage(r) (www.sage.co.uk)会提供这样的服务.

4.获取行业分析数据
尽管你拥有独一无二的地方,但是仍然有很多与你相似的公司存在,作一些市场调研会帮助你更好的了解你的目标市场.
所有的公司被英国政府使用标准工业划分系统(SIC)分为不同的类别;先找到你所在的类别并找到你的SIC编码.这会帮你搜索到你竞争对手和其他业界参与者的有关资料.然后通过类似Cobweb
(www.cobwebinfo.com)的数据源寻找更多的数据资料.Cobweb提供大量的商业档案资料.这会让你获得外部的视角来分析业界其他类似公司.

5.研究市场
当准备投放广告时,不如先使用www.overture.com的关键字助手看看哪些与你的业务或服务有关的关键字被搜索的最多.这会帮助你有效的投放广告或确定一个URL或者使用搜索引擎优化来优化你的网站.你也可以通过输入这些关键字到搜索引擎来找到你的竞争对手.

6.慎重评估需求水平
与估计成本不同,最难预测的事情是你的产品或提供的服务的需求水平.通常的规则是保守地估计可能的需求和使用相似取代方法.
即便是一个新的独一无二的创意也应该用相似取代来估计,而不是凭空编造数据或说自己没有可以比较的数据.一个经典例子,最近伦敦旅游发展项目的昙花一现.一个最主要的问题就是游客的数量远远低于预先估计的数量,因此入不敷出.如果负责人事先研究过英国最主要的吸引游客的地方,他们就会找到潜在游客的上限数量.由于估计的游客数量高过这个上限,这个疯狂的优化假设造成最终的失败 -- 成本和收益远不成比例.

7.进入市场策略
由于竞争的激烈,企业家要慎重选择如何进入市场或如果打动客户的芳心.大多数新的公司会考虑为他们的新品牌采用多渠道进入的策略,但是这样做不仅仅成本巨大,而且比单一渠道昂贵许多.互联网营销是一个很吸引人的策略,因为营销成本容易跟踪.此外,找到当前相似市场的供应商所处的位置也可以告诉我们哪些市场活动是最有效的.当然,这些是建立在正确的数据上的.

8.雇用合适的人
除了财务方面的估计,执行力强并可以信赖的人是潜在投资者要研究的.无论企业家和创建人的技能水平如何,他常常是需要协助的.尽管很多非核心的活动是可以外包的,某些部门,例如销售,还是需要随时关注的.你应该列出所有的必要技能,把他们放到模型里定价,发现缺口并合适的候选人.

9.定义并明确客户获得的好处
许多企业家无法清楚的说出他们的所做的事能够带来的好处.因此,"电梯行销"这个词被引入到现代词汇来解决这个问题.--所谓的电梯行销是,你的创意,以及支持他的商业模型,公司方案,市场策略和竞争手段需要你在一段电梯升降的过程中表达出来.
这个简单的方法目的是让企业家用心地思考在描述他的产品或服务时如何使用语言的艺术.同时也是提醒他们要以客户为中心并确保他们集中精力描述这些"好处".

10.找一个导师
许多的创业者会妄想他们的创意会被别人偷走,并在开始前表现的很不理性.通常,创意都是严密的保护并且只在知己讨论.但是这些"知己"(一般是家人或朋友) 通常很难提出足够具有挑战性的问题因为一来他们不愿意冒犯你,二来他们缺少相关的经验或足够的判断力去严格地分析这个新的风险.正因如此,有重大缺陷的创意可以在还没有“关键时候掉链子”的时候被及时纠正,从而可以正常地发展。强烈建议创业者在早期找一个独立的导师帮你审视你的创意。这些人可以帮你在向投资人或者银行展示之前“敲打”你的创意。最后,形成鲜明对比的是,一些创业者觉得投入越多越好,他们参加每一个商业计划竞赛来获得独立的反馈而不是获奖。

AOP与OOP

(最初发布在JavaResearch.org,现在整理好放到自己的地方.)

作者:Narayanan A.R. June 15, 2005
翻译:ZhangV 2005-06-28
原文出自:DevX.com
原文链接:http://www.devx.com/Java/Article/28422/0/page/1

首先你要明确的一点,AOP和OOP是两种不同的认识事物的角度,并不是说有了AOP就不要用OOP.AOP所关注的是传统OOP不能优雅解决的问 题.(程序员通常都是完美主义者,当解决某个问题不优雅的时候,那就意味着不完美.)下面将就一个简单的例子来说明他们到底如何的不同.

作为一个使用OOP多年的人来说,当我听说AOP可以解决一些OOP一直都不能优雅地解决的问题时,我觉得应该去探个究竟了.对两种技术的比较最能给我们实际应用提供见解.这里我设计了一个例子:一个OOP应用,其中某些方面适合使用AOP.

本文展示了一个简单的例子.一开始介绍了问题域,然后分别给出传统OOP的解决方法与结合了AOP解决方法.后者使用了 JDK5.0,JUnit,和 AspectWerkz.最后我会说明如何编写代码.读完本文后,我希望你能知道AOP到底是什么,用来解决什么样的问题.

问题域描述
一个软件公司雇佣一个程序员,指定给他一个业务部门并要求他随时向经理报告.当团队成员完成他们的目标时,经理会给他们相应的奖金.公司所需要的方案必须能够增加一个新的雇员并给当前的员工增加奖金.为了方便,我们用CSV文件存储数据.


图1 解决方案模型

类Manager(经理)继承自类Employee,包含一个额外的属性,Managing Project.一个部门可能包含很多员工.多个部门构成了公司.暂不考虑公司这样的一个类,因为它在问题域之外.

解决方案设计
以下流程图描述了解决方案设计.


图2 对象之间的交互(增加一个新的员工,指派给他一个部门和经理)

出于简单的考虑,本文只关注必需的细节.你也可以深入代码得到你想要的其他信息.
下载
EmployeeServiceTestCase, 一个JUnit测试用例,模拟一个最终用户,创建新员工记录,指派部门和经理.它获取所有可用的部门和经理数据并显示在图形界面上.为了实例化域对象 BusinessUnit和Manager,获得的记录将传递给工厂类.之后,通过给EmployeeService传递一个引用来创建一个 Employee对象.这个服务类使用EmployeeFactory创建对象,并把这个对象传给EmployeeRepository 来进行持久化操作.

应用程序中需要面向哪些"Aspect"
到目前为止,对模型和设计的讨论还限于一个较抽象的层面.现在,我转向这个应用的其他方面 - 这对理解AOP的价值至关重要.

操作所需的资源

<pre lang="java">

public static Set findAllBusinessUnits() throws RepositoryException {
Set businessUnits = new HashSet();
try {
FileReader businessUnitFile = null;
BufferedReader bufferedBusinessUnitFile = null;
try {
businessUnitFile = new FileReader(FILE_NAME);
bufferedBusinessUnitFile = new BufferedReader(businessUnitFile);
String businessUnitRecord;
while((businessUnitRecord = bufferedBusinessUnitFile.readLine()) != null) {
BusinessUnit businessUnit = BusinessUnitFactory.createBusinessUnit(businessUnitRecord);
businessUnits.add(businessUnit);
}
} finally {
if(bufferedBusinessUnitFile != null) {
bufferedBusinessUnitFile.close();
}
if(businessUnitFile != null) {
businessUnitFile.close();
}
}
} catch(IOException ioe) {
String message = "IOError. Unable to find Business Unit records";
logger.log(SEVERE, message, ioe);
throw new RepositoryException(message, ioe);
}

logger.log(INFO, "Manager Records returned:" + businessUnits.size());
return businessUnits;
}
</pre>

上面的代码通过FileReader和BufferedReader来读取CSV文件中的业务数据.
应用程序重复地从资源文件中取得数据然后在操作完成后释放. 我们会发现:去掉程序的这两个"Aspect"将提高代码的可读性并达到一个更好的设计,因为去掉这些"多余"的东西,剩下的代码才是这个方法真正的精 髓.这个方法的作用是读取业务单位数据.所以不应该也不需要去知道"如何获取和释放资源以及这个过程中出现的异常"这个"切面".同样地,使用AOP处理 异常也变得不同.(后面将详细介绍)

持久层
传统的OOP使用持久化类(repository classes)来持久应用程序的持久层.即:

<coolcode lang="java" linenum="off">
public class EmployeeRepository {

public static void createEmployee(Employee employee) throws RepositoryException {
//使用print writer把数据放入csv文件
}

public static String findEmployeeRecordById(String id) throws RepositoryException {
//使用file reader来获得指定id的员工数据
}

public static Employee findEmployeeById(String id) throws RepositoryException {
//使用该方法获取员工数据,Employee对象由工厂类创建
}

public static void updateEmployee(Employee employee) {
//更新员工数据
}
}
</coolcode>

类EmployeeService 使用一个EmployeeRepository给应用中相关雇员提供服务,在一个企业应用中,从域模型 (domain model)中去掉持久层代码是一种设计上的改进.模型设计者和程序员就可以关注各自的业务逻辑和持久层处理.后面你将会看到如何通过 AOP来达到这样的效果.

日志
删除用于调试的日志代码将会极大地改进代码的可读性.考虑下面的代码片断:

1.
2. public Employee createEmployee(String name,
3. String contactNumber,
4. BusinessUnit businessUnit,
5. Manager manager)
6. throws EmployeeServiceException {
7. String id = createNewEmployeeId();
8. Employee employee =
9. EmployeeFactory.createEmployee(id, name, contactNumber, businessUnit, manager);
10. try {
11. EmployeeRepository.createEmployee(employee);
12. } catch(RepositoryException re) {
13. String message = "Created employee successfully:" + employee;
14. logger.log(SEVERE, message);
15. throw new EmployeeServiceException(message, re);
16. }
17. logger.log(INFO, "Created employee successfully:" + employee);
18. return employee;
19. }

上面的代码里包含了一个致命错误和一个成功信息.输出日志这一"Aspect"同样可以移到业务模型外独立实现.

异常处理
异常处理的例子我这里不再赘述,但这节已经通过上面的代码讨论了潜在的问题.当你调用EmployeeRepository 对象的 createEmployee 方法时,你可能会得到一个RepositoryException异常.传统的解决方法是,在这个类中处理.另一种方法是,当 RepositoryException 异常被抛出时createEmployee 方法返回null,catch块中的其他逻辑可以在类外处理这一错误.
错误处理在不同的情况中也会不同.但是,通过AOP可以区分开每种情况.

图3

图3中描述了AOP方法的设计以及在一个更抽象的层次上类间的交互.你可以通过对比图1和图3来更好地理解AOP.
程序的目的是通过BusinessUnit对象读取CSV文件中的记录然后填入类BusinessUnitService 中的map.使用AOP来填充这个map有点类似后门(backdoor)方法 -- 控制被委派给BusinessUnit 来读取存储介质中的记录.

AOP就是定义一些切入点(pointcut)和处理方法(advice).一个"切入点"是源代码中一个执行点.前面的例子定义了一个"切入点" -- 类 BusinessUnitService中的findBusinessUnits方法.一个"处理方法"顾名思义就是当执行到某个"切入点"时的一块代 码.类BusinessUnitPersistentAspect 包括advice方法findAllBusinessUnits,该方法从存储介质中载入数据,然后使用工厂类创建BusinessUnit 对象.然后这个对象被加入map,map对象的引用通过 BusinessUnitService 对象获得."切入点"和"处理方法"组成了所谓的"aspect".

为了读取存储介质中的数据,OOP方法通过一个DAO类来做.而AOP中,你只要定义一个"切入点"和相应的"处理方法"来读取数据.AOP框架会以advice的形式注入代码,既可以在执行期也可以在编译期.

总而言之,当类BusinessUnitService 中的findAllBusinessUnits 方法被调用时,AOP框架会在"切入点"处注入处理方法,通过BusinessUnit 对象预先读取数据来填充map对象.这样,持久层方面的代码就可以移到业务代码之外了.

新方法里的"Aspect"

本节讨论如何用AOP为应用程序的各个"切面"建模

操作资源

类BusinessUnitPersistenceAspect 的持久方法使用了一个buffered reader.你甚至可以定义"切面"的"切面",但为了简单,这里只关注类的查找方法.

1.
2. @Aspect("perJVM")
3. public class BufferedFileReaderAspect {
4.
5. @Expression("execution(* org.javatechnocrats.aop.withaop.aspects.BusinessUnitPersistenceAspect.find*(..))")
6. Pointcut businessUnitPersistenceAspect;
7.
8. // 其他"切入点"定义
9.
10. @Expression("businessUnitPersistenceAspect ||
11. employeePersistenceAspect ||
12. managerPersistenceAspect")
13. Pointcut allPersistencePointcuts;
14.
15. private Map<Class, String> fileNames;
16.
17. public BufferedFileReaderAspect() {
18. System.out.println("BufferedFileReaderAspect created");
19. fileNames = new HashMap<Class, String>();
20. fillFileNames();
21. }
22.
23. @Before("allPersistencePointcuts")
24. public void assignReader(JoinPoint joinPoint) throws Throwable {
25. System.out.println("assignReader advice called");
26. Object callee = joinPoint.getCallee();
27. IBufferedFileReaderConsumable bufReaderConsumable = (IBufferedFileReaderConsumable)callee;
28. Class persistenceClass = callee.getClass();
29. String fileName = fileNames.get(persistenceClass);
30. FileReader fileReader = new FileReader(fileName);
31. BufferedReader bufferedReader = new BufferedReader(fileReader);
32. bufReaderConsumable.setBufferedReader(bufferedReader);
33. }
34.
35. @AfterFinally("allPersistencePointcuts")
36. public void releaseReader(JoinPoint joinPoint) throws Throwable {
37. //释放buffered reader等资源
38. }
39. //其他方法
40. }

上面的代码试图为每一个方法创建"切入点"-- 所有以find开头的方法.无论何时这些方法被调用,assignReader方法都会被提前执行.这里它获取被调用的类实例然后设置新建的BufferedReader.

同样地,在releaseReader 方法里,代码会预先关闭buffered reader集合.本节只解释@before和@
AfterFinally 这两个"切入点".(以J2SE 5.0的标记定义).另外,你也可以在方面定义的xml文件中声明他们.你可以查看例程源代码中的aop.xml文件.

下载

持久化

前面提到,OOP方法使用BusinessUnit 来为应用的持久层填充Map.在下面的高亮代码中(@before一行,以及while循环代码 - 译者注),当BusinessUnitService 中的方法findAllBusinessUnits 被调用时"处理方法 "findAllBusinessUnits 也将被调用.

1.
2. @Aspect("perJVM")
3. public class BusinessUnitPersistenceAspect implements IBufferedFileReaderConsumable {
4.
5. private BufferedReader buffFileReader;
6.
7. @Before("execution(Collection org.javatechnocrats.aop.withaop.BusinessUnitService.findAllBusinessUnits())")
8. public void findAllBusinessUnits(JoinPoint joinPoint) throws Throwable {
9. System.out.println("findAllBusinessUnits advice called");
10. Map<String, BusinessUnit> businessUnits =
11. ((BusinessUnitService)joinPoint.getThis()).getBusinessUnits();
12. String businessUnitRecord;
13. while((businessUnitRecord = buffFileReader.readLine()) != null) {
14. BusinessUnit businessUnit = BusinessUnitFactory.createBusinessUnit(businessUnitRecord);
15. businessUnits.put(businessUnit.getId(), businessUnit);
16. }
17. }
18.
19. public void setBufferedReader(BufferedReader buffFileReader) {
20. System.out.println("BusinessUnitPersistenceAspect.setBufferedReader called");
21. this.buffFileReader = buffFileReader;
22. }
23.
24. public BufferedReader getBufferedReader() {
25. System.out.println("BusinessUnitPersistenceAspect.getBufferedReader called");
26. return this.buffFileReader;
27. }
28. }

"处理方法"从数据存储中读取记录,使用工厂类创建一个BusinessUnit实例.然后这个实例被加入到Map.该Map掌管程序的所有持久化"aspect".

日志
本文中的例子没有包含一个完整的日志AOP解决方案.但是,它为java.lang.Object类的toString方法定义了一个"切入点"来获取类 的调试信息.因此,域中的类不需要实现toString方法.通常情况下你可能需要为每一个类都要实现toString方法.

1.
2. @Aspect("perJVM")
3. public class LoggingAspect {
4.
5. @Around("execution(String org.javatechnocrats.aop.withaop..*.toString())")
6. public Object toStringAdvice(JoinPoint joinPoint) throws Throwable {
7. System.out.println("toStringAdvice called");
8. String toString = (String)joinPoint.proceed();
9. Object target = joinPoint.getThis();
10. Field fields[] = target.getClass().getDeclaredFields();
11. List members = new ArrayList(fields.length + 1);
12. members.add(toString);
13. for(Field field : fields) {
14. field.setAccessible(true);
15. Object member = field.get(target);
16. members.add(field.getName() + "=" + member);
17. }
18. return members.toString();
19. }

你也可以用这个样例代码完成错误处理"切面".

深入源代码

为了理解样例需求的OOP设计,请参看源代码并思考以下几个问题:
下载

* 首先分析oldway包中EmployeeServiceTestCase 类中的代码
* 查看testEmployeeCredit 方法
* 搞懂业务类Employee和BusinessUnit
* 学习 service,repository和factory这些概念.这些是业务驱动设计的主要概念.
* 更深入地理解oldway包中的service,repository和factory类

而AOP地理解则应该是:
* 分析newway包中EmployeeServiceTestCase 类
* 查看service,repository和factory类,基本和前一种差不多.只是你要让"处理方法"截获程序的执行流程.
* 研究aspect类学习"切入点"的定义

要执行程序,你需要做的工作:
* 下载AspectWerkz 2.0 http://aspectwerkz.codehaus.org/
* 设置以下的环境变量:
set JAVA_HOME=c:\Program Files\Java\jdk1.5.0
set ASPECTWERKZ_HOME=C:\aw_2_0_2
set PATH=%PATH%;%ASPECTWERKZ_HOME%\bin
set CLASSPATH=
C:\aw_2_0_2\lib\aspectwerkz-2.0.RC2.jar;C:\aw_2_0_2\lib\aspectwerkz-jdk5-2.0.RC2.jar; classes;C:\ junit\3.8.1\resources\lib\junit.jar
* 解压缩源代码和其他文件
* 编译Java文件,但不要编译测试用例否则你调试时会遇到一个错误.
* 进行离线调试.假设你把文件解压缩到c:\aop ,类文件解压到c:\aop\classes,在c:\aop目录下执行以下命令:
% ASPECTWERKZ_HOME%\bin\aspectwerkz -offline etc/aop.xml -cp classes classes
* AOP框架会修改类来注入必要的字节码
* 编译测试用例,使用JUnit运行它.

后记
当你完成了上面的这些工作,你应该有以下的领悟:
* 程序中的交叉关联
* 关于AOP中"切面"的含义
* 如何用AOP来把程序业务层中的交叉关联分离出来,使用"切入点"和"处理方法"
* OOP和AOP时在程序控制流上的不同

从本文你应该也得到一种看待实际开发的新视角.你应该有信心使用AOP来改进项目中的设计,建模,提高代码的重用性.至少,你可以开始使用AOP来处理日志,错误和持久化.

个人觉得,AOP的学习曲线相对较陡,尤其在理解定义"切入点"的句法时.理想的情况是,使用OOP来设计业务模型,使用AOP把业务模型中的交叉关联移出,从而使代码简洁并提高可读性.

AOP的一个缺点是会使调试变得困难,因为不同于OOP,程序流变的复杂了,交互是由编译期或执行期决定.我准备将来做一些自动化工具来解决这个问题.