This document discusses using the MXML compiler (mxmlc) to compile Flex projects from the command line rather than within Flex Builder. It provides an example command to compile a FlexMXML file located in the user's Documents folder. Additional command line arguments are also demonstrated, such as specifying the output SWF file location and adding library paths. The document recommends adding the Flex SDK bin directory to the system PATH environment variable so mxmlc can be called directly from the command line without specifying the full SDK path.
1. Java API 设计指南
作者: Eamonn McManus
原文地址: http://www.artima.com/weblogs/viewpost.jsp?thread=142428
译者: 王磊
电子邮件: [email protected]
(该译文可以随便转载,但请保留前面的声明,谢谢)
前言:
市场上关于如何设计和编写优秀 Java 代码的书如此之多,可能要用汗牛充椟来形容,
但是想找到一本如何设计 API 的书,却是难之又难。这里我将把自己一些关于 API 设计的
经验与大家分享。
分享这些经验是源于最近我参加了 JavaPolis 上的一个讨论,这个讨论是由 Elliotte Rusty
Harold 发起的,是关于设计 XOM 时的一些原则性问题,讨论中的思想交流如此精采,令我
受益颇多。虽然这次讨论主题是与 XOM 有关,但是大部分的时间我们都在讨论设计 XOM
API 时的一些原则性问题,而这些内容对于 API 设计而言,则是通用的。这几年,Java 的应
用日益广泛,开源项目也是蒸蒸日上。一本能够指导开发人员设计和编写 API 的好书,可
以帮助开发人员设计和编写更好的 API。
在过去的五年中,我一直参与 JMX API 的修订及调整,在此过程中,同样受益颇多。
特别在这次讨论会,我对 Elliotte 提出的一些观点高举双手赞同。
接下来的内容是讨论会上的一些主要观点的总结, 其中包括一些个人或者是来自他人的
经验,也参考一些相关的文档,希望对大家设计 API 有所裨益。
下面是个人推荐的一些阅读和参考资料。
下面给出的网址是 Netbeans 网站上的一篇关于 API 设计的优秀文档,
http://openide.netbeans.org/tutorial/apidesign.html
Josh Bloch's 的 Effective Java 作为 Java 设计的圣经之一,从来都不会被漏下。
设计需要进化
API 的价值就在于能够帮助你完成许多功能,但请不要忘记要持续的改善它,否则它的
价值就会逐渐减少。而且要根据用户的反馈信息,来改善 API,或者说是对 API 进行演化,
另外改进 API 时要注意的就是在不同版本间要保持兼容性, 这点至关重要, 它也是一个API
是否成功的重要标识之一。
如果一个功能以 API 的方式公布出来,那么在发布以后,它的对外接口就已经固定,
无论什么情况,都不能取消,而且一定要能够按照原有的约定功能正确执行。如果 API 不
能在版本间保持兼容性(译注:这里应该指的是向下兼容) ,用户将会难以接受这样一个千
变万化的 API,最终的结果只能是让用户放弃使用你的 API。如果你的 API 被大量的软件或
者模块所使用,又或者被大型软件使用,这个兼容问题也就愈加严重。
以一个 Java 应用程序为例,如果 Module1 使用了 Banana1.0 的 API,而 Module2 则使
用了 Banana2.0的 API, 现在要同时部署这两个模块到一个Web Application 上,如果 Banana2.0
对 Banana1.0 保持兼容的话,整个部署就会非常简单,直接使用 Banana2.0 就可以了。如果
Banana2.0 和 Banana1.0 不兼容的话,就很难同时在一个程序中同时使用 Module1 和 Module2
2. (可能要自定义 ClassLoader,或者是其它方式,这对于用户未免有些为难了) 。最终的结果
可能就是你失去一个用户,这时的 API 就不能为他人提供任何价值(译注:看来它也不能
带来经济价值了) 。
分析 Java SE 平台所提供的 API,有着严格的兼容性控制。其根本目标就在于保证用户
在版本升级时,不会导致低版本的代码无法运行。这也再次说明,当一个 API 发布以后,
任何 API 公开的方法,常量等元素都不能被移出 API,否则会严重降低 API 的价值。 (译注:
所以个人一向比较怀疑 deprecated 的价值,因为既然所有的方法都不会被删除,即使那些被
deprecasted 的方法和变量也会被仍然保留, 其功能也不应该会被改变, 因此从这个角度来说,
其方法和变量的使用仍然是安全的。 )
说到兼容性,必然要谈到二进制兼容性,这是指对于已经编译完成的代码,不会因为版
本的变更,而致使编译后的代码不能正常运行。比如说你将一个 public 方法从 API 中移走,
就会无法正常运行,会抛出 NoSuchMethodException 这类错误。
但源代码的兼容性也不可忽视,在某些特殊情况下,修改 API 时,会产生源代码兼容
问题,导致源代码无法编译通过。例如添加一个重载的同名方法,其参数不同,如
getName(User)和 getName(String),当用户使用 getName(null)时,会因为存在二义性,而产
生编译错误,用户必须给出 getName((String)null),来明确标识要调用的方法。因此必须寻
找一种方式,来保持源代码的兼容性。
通常情况下,如果源代码不兼容,代码编译就无法通过,用户必须修改源代码才能保证
程 序 正 常 运 行 , 但 这 并 不 是 一 个 好 的 解 决 方 案 。 以 J2SE6 中 的
javax.management.StandardMBean 为例,这个类的构造函数已经采用泛型了。这样会使得一
些类在创建这个 Bean 的时候,会出现编译不能通过的问题,但是解决方案有时却是非常简
单, 往往只需要添加一个 cast 进行强类型转换就可以解决这样的一个编译问题了。 但是更多
的时候,需要更复杂的方案才能解决这些编译问题,例如在一个方法中调用第三方的 API
方法,如果有一天这个 API 方法的参数被修改了,在修改源代码时候,可能会发现你的方
法中不含有 API 方法需要的参数,这时就需要修改方法参数。以下是这种情况的示范代码。
public void callApi(Parameter1 p1,Parameter2 p2)
{
//do some operations
new ThirdClass().doCallThirdApi(p1);
}
现在第三方的 API 现在变成了 doCallThirdApi(Parameter1 p1,Parameter3 p3)
这时可能需要将方法改成:
public void callApi(Parameter1 p1,Parameter2 p2,Parameter3 p3)
{
//do some operations
new ThirdClass().doCallThirdApi(p1,p3);
}
这样一个 API 的改变,很可能引发一个多米诺骨牌事件。
通常情况下,你不知道用户如何使用 API 来完成工作。除非能够确认 API 的修改对用
户的代码不会造成破坏,才可以考虑修改 API。 ,反之如果 API 的改变会影响用户现在的代
码,就必须有一个足够的理由。只有意识到对 API 的修改,会严重的破坏用户现有的代码,
在修改 API 时才会谨慎地,尽量地保证 API 兼容性。修改 API 的时候要尽量避免以下几种
情况,以免对用户的代码产生破坏:
3. 1. 降低方法的可见性,如将 public 变成 package 或者 proteced,又或者将 protected 变
成 private。
2. 修改方法的参数,如删除一个参数、添加一个参数、或者是改变参数的类型,尤以
后两者更为严重。
就象业界游行的经验之谈“不要使用 3.0 版本以下的软件” (译注:所以我总想把自己
的软件直接发布为 9.9。,同样的情况对 API 也是一样的,前几个版本的 API 往往包含有大
)
量的错误,这些错误不应该被隐藏起来,因为纵然是冰山的底部,也终有浮出水面的一天。
因此在正式发布 API 1.0 以前,请不要忘记提供若干个 0.x 版本。对于使用 0.x 版本的用户,
会有一个比较明确的说明,用户会清楚的知道当前版本所公布的 API 还不稳定,有可能在
正式发布的时候有所更改,0.x 版本也不保证兼容性。 (译注:以 Visual Studio2000 beta2 为
例,与正式版的差别就非常大,所以 0.x 版本通常只是用来学习,或者进行技术预言,而不
能在产品中使用) 。但是一旦 1.0 版本正式发布,请记住,就是对 API 的兼容性就做出一个
正式的承诺。象 JCP 组织在对某一个规范推出正式版本以前,通常都会发布若干个草稿版
本(如意向草稿,公共预览草稿,最终建议版本等) 。如果方便的话,对于规范性的内容,
在发布 API 时,提供一个 API 的实现可能会更有效的推行规范(译注:因为规范性的内容
更多的是以 Interface 的方式来发布 API,大家可以参考一下 Interface 和 Abstract Class,所以
提供一个实现往往更好,象 sun 发布 J2EE 规范时,就提供了一个默认的实现。。 )
当 API 的设计和开发到了一定阶段以后,可能会发现以前的版本已经出现了一些问题,
又或者需要添加新的功能,此时设计人员完全可以重新创建新的 API,并放到新的包中,这
样就可以保证那些使用老版本的用户可以很轻松的移植到新版本上, 而不会产生问题。 牢 请
记一点:添加新的功能,请不要修改原有的内容。
API 的设计目标
设计一个 API 要达到哪位目标呢?除了兼容性以外,也从 Elliotte 的讨论中提出一些目
标。
API 的正确性必须保证:
以 XOM 为例,无论用户如何调用 API,都不应该产生错误的 XML 文档。再如 JMX,
不管是注册一个错误的 MBean 还是并发执行一些操作,又或者 MBeans 使用了一些特殊的
名称,MBean Server 都必须保持状态的一致性,不能在某个错误的 MBeans 进行了操作以后,
整个系统就无法提供服务。
API 的易用性:
API 必须易于使用。 通常易用性一向难以评价。 但是有一个办法可以有效的提高易用性,
就是编写大量范例代码,并将其很好的组织在一起,从而为用户提供API 参考。 (译注:个
人认为一个好的 FAQ 可以提供各种 API 使用的范例。 )
另外下列原则也可以用来判断 API 的易用性:
1. 是不是总是经常出现一组操作代码? (译注: 这里是指如果有多行代码重复被调用,
说明它们应该被放到一个方法中,避免用户重复编写一组代码。 )
2. 在使用 API 时,是否需要经常参考 JavaDoc 或者是源代码,才能知道应该调用哪
个方法呢?(译注:比较理想的情况就是,大部分操作只需要通过类名和方法的名
称就可以明白) 。
3. 根据名称调用一个方法,但是该方法所做的事并不是用户所想要的。 (译注:例如
4. 调用一个 command 方法,以为是执行一个操作,但是结果这个方法是做备份用。)
API 必须易学:
很大程度上,API 的易学和易用性是相似的,一般来说,易用也就易学。如果要使 API
易学,下列基本原则要遵循的:
1. API 越小就容易学习;
2. 文档应该有范例;
3. 如果方便的话,尽可能将 API 与一些常用的 API 保持一致。例如如果要做一个资
源访问的 API,尽可能与 J2SE 中的 IO 使用一致,自然很容易学习。
(译注:如果你要关闭一个资源,就象 Java 的 File,Connection 一样,使用 close,而不
是 destroy。)
API 的运行速度必须够快:
Elliotte 也是考虑了很久,才给出这一条。但是要在保证 API 简单而且正确的前提下,
再来考虑 API 的性能问题。在设计 API 时,你可能会先使用一种能够快速实现但是性能不
好的方式来实现 API,然后再根据实际情况再修改 API 的实现,以调整性能。至于如何调整
性能,绝对不要通过直觉来判断何种方式能获得高性能。只能通过正确,严格的测试以后,
再对性能瓶颈进行优化从而提高性能。 (译注:过早的优化是所有的错误根源,这已经是一
个普遍认同的观点,特别是对于 Java 程序,因为它的 JVM 越来越快,越来越聪明。 )
API 必须足够的小:
这里所说的小不仅是指编译后代码的文件比较小, 而且更重要的是运行时占用的内存 要
小。之所以提出最小化的概念,还有一个原因:就是因为很容易为 API 添加新的内容,但
是要将一个内容从 API 中移出就很困难,所以不要随便向 API 中添加内容,如果不确定一
项内容,就不要将它加入到 API 中。通过这样一个建议或者说是限制,可以提醒一个 API
设计人员更加关注 API 中最重要的功能,而非一些枝节的问题。
(译注:许多时候这个最小化原则是很难遵守的,如不变类通常比可变类更好一些, 但
是它会占用更多的内存,而可变类占用的内存会少些,但要处理线程,并发等问题,所以 更
多时候是一个权衡,大固然不好,小也必就好) 。
有一种设计 API 的方法很常见,但是结果却令人头痛,这种方法就是在设计 API 前,
会详细的考虑每一个用户的需求,并设计出相应的方法,可能在实现中还要设计一堆的
Protected 方法,这样使得用户可以通过继承来调整默认实现。为什么这种方法不好呢?
因为考虑的过于详细,功能边界也就越大,所面对的需求也就越多,因此要提供的功能
和可供用户调整的功能也就更加庞大,也就是说这种方法会使得 API 包含很多的功能,最
终就是 API 膨胀性的增长。
事实上 API 中包含的功能越多,也就更加难以学习和使用,而且其学习难度往往是以
几何级数进行增长,而不是线性增长。想像一下,理解并学习使用 10 个类,100 个方法的
API 对于一个程序员并不困难,大概一天就可以完成,但是对于一个 100 个类,1000 个方
法的 API,即使对于一个非常优秀的程序员,估计 10 天的时间是不足以完全理解。
另外在一个庞大的 API 中,如何才能尽快的找到最重要的内容,如何找到完成所需功
能的方案,一直都是 API 中设计中的一个难题。
JavaDoc 工具给用户带来了许多的方便,但一直以来它都没有解决如何学习和使用一个
庞大 API 库的方法。JavaDoc 将指定包中的所有类都放置在一起,并且将一个类中的所有方
5. 法放置在一起(译注:这里指的是 allclassesframe.html,indexall.html)
,纵然是天才,看到
成千上万的方法和类也只能抱头而泣了。现在看来,只能寄希望于JSR260 标准,希望它能
够有效地增强 JavaDoc 工具,从而可以获得 API 的完整视图,能够更加宏观地表示 API,如
果这样,即使是很庞大的 API 包,也不会显得拥挤,从而也就更加容易理解和使用。
另外 API 越大,出现的错误可能性也就越多,与前面使用的难度一样,错误的数量也
是呈几何级数增长而不是线性增长。对于小的 API,投入相同的人力进行编码和测试时,可
以获得更好的产出。
如果设计的 API 过于庞大,必然会包含了许多不必要的方法,至少有许多 public 的类
和方法,对于大部分用户是用不到,也会占用更多的内存,并降低运行效率。这违反了通 常
的一个设计原则:“不要让用户为他使用不到的功能付出代价” 。
正确的解决方案是在现在的例子上来设计 API(译注:更象原型演化的方式)。先来想
像一下:一个用户要使用 API 来解决何种问题呢,并为解决这些问题添加足够的类和方法。
然后将与之无关的内容全部移除,这样可以自己来检查这些 API 的有用性,这种方法还会
带来一个有用的附加功能,它可以更加有效测试代码。同时也可以将这些例子与同伴分享。
(译注,这个概念与测试先行是非常相似的) 。
接口的功能被夸大了:
在 Java 的世界,有一些 API 的设计原则是很通用的,如尽量使用接口的方式来表达所
有的 API(不要使用类来描述 API) 。接口自有它的价值,但是将所有的 API 都通过接口来
表示并不见得总是一个好的设计方案。在使用一个接口来描述 API 时,必须有一个足够的
理由。下面给出了一些理由:
1、接口可以被任何人所实现。假设 String 是一个接口而非类,永远都无法确认用户提
供的 String 实现能够遵循你希望的规则:字符串类是一个不变量;它的 hashCode 是按照一
定的算法规则来返回数字; length 永远都不会是一个负数等。
而 如果真的由用户来提供一个
String 的实现,可以想象代码中要加入多少异常处理代码和相关的判断语句才能保证程序的
健壮性。
实践告诉我们,如果 API 完全是由接口来定义,用户在使用这些 API 时会发现不得不
进行大量的强制转型(译注:个人认为,强制转型并不是因为 API 是通过接口来定义引起
的,而是不好的 API 定义引起的,而且强制转型从程序的设计角度几乎是无法避免的,除
非所有的子类都不添加任何新的功能,而这一点与前面的抽象类演化又是矛盾的) 。
2、接口不可能拥有构造函数或者是 static 方法。如果需要接口的实例,不可能直接实
例化接口,只能通过某种方式,可能是 new 也可能是通过参数传递的方式来获得一个接口
的具体实现对象。当然,这个对象可能是由你来实现的,也可能是由第三方供应商开发的。
如果 Integer 是一个接口而非一个类,则无法通过 new Integer(int)来构造一个 Integer 对象,
可能会通过一个 IntegerFactory.newInteger(int)来获得一个 Integer 对象实例,天啊,API 变得
更加复杂和难以理解了。
(译注:个人认为原文作者有些过于极端化了,因为 Java 不是一个纯粹的 API,它同
时是一种语言,一个平台,所以提供的 String 和 Integer,都是作为基础类型来提供。虽然同