单元测试
一个简单的测试框架
Junit
断言语法 (这个以后用到再好学习)
既然我们可以使用其它的编程结构来仿真断言的效用,这表明将断言添加到 Java 的关键之
处在于它们很容易编写。断言语句有两种常用的形式:
assert boolean-expression;
assert boolean-expression: information-expression;
用 Ant构建
自动化所有事物
用 CVS进行版本控制
执行每日构建(daily build)
记录日志级别
日志记录
LogRecord是一个信使(Messenger)对象 ,它的任务仅仅是将信息从一个地方传送到
另一个地方。LogRecord中的所有方法都是获取器(getter)和设置器(setter),下
面这个例子使用获取器方法卸出了所有存储在LogRecord中的信息。
多重处理器
过滤器
当我们要编写代码将日志记录消息发送给 Logger 对象时,那么就在编写代码的时候,我
们通常就要决定日志记录消息应该是什么级别(日志记录 API 当然允许我们设计更复杂的
系统,在其中消息级别可以动态决定,但是在实际中并不普遍)。Logger 对象有一个可以
设置的级别,以便能够决定它可以接收什么级别的消息;而其它所有消息都将被忽略掉。这
可以被看作是一种基本的过滤功能,但是通常这也就是我们所需要的全部了。
格式器(Formatter)
Formatter 是一种向 Handler 的处理步骤中插入格式化操作的方式。如果我们一个
Handler 注册了一个 Formatter 对象。那么在通过 Handler 发布 LogRecord 之前,它
首先会被送到 Formatter。在被格式化之后,LogRecord 被返回给 Handler,接着把它
发布出去。
通过名字空间控制记录日志级别
尽管不是强制的,但是将要使用日志记录器的类名传递给记录器是明智的。这使得我们可以
操纵在相同的包层次中(按包目录结构的粒度)的日志记录器组的记录日志级别。例如,我
们可以修改在 com 中的所有包的所有记录日志级别,或者仅在 com.bruceeckel 中,更
或者仅在 com.bruceeckel.util 中的所有包的所有记录日志级别,如随后的例子所示:
大型工程的记录日志实践
乍一看,Java 记录日志 API 对大多数编程问题来说,可能都显得过于工程化了。直到我们
开始创建更大的工程时,这些额外的特性和性能才能派上用场。在这一节我们将会着眼于这
些特性以及推荐的使用它们的方法。如果我们仅在较小的工程上使用日志记录,那么我们可
能不需要用到这些特性。
配置文件
调试
尽管恰当使用System.out语句或者日志记录信息可以产生对程序行为非常有价值的洞察
16
,但对于困难问题这种方法就变得笨拙而费时了。另外,我们可能需要比允许打印语句要
更深入地洞察程序。正因为如此,我们需要调试器。
除了可以更快更容易地显示用打印语句产生的信息,调试器还可以设置断点
(breakpoint),然后当程序到达这些断点时会停止运行。调试器还可以显示任何时刻的
程序状态,查看我们感兴趣的变量值,逐行地运行程序,连接在远程运行的程序,以及其它
更多的功能。尤其当我们开始创建较大的系统时(在这里 bug 可能更容易被隐藏起来),去
熟悉调试器是很值得的。
使用 JDB 调试
Java 调试器(JDB, Java Debugger)是一个装载在 JDK 中的命令行调试器。根据 JDB
的调试指令以及它的命令行界面,JDB 至少从概念上讲是 Gnu 调试器(Gnu Debug,GDB,
是从最初的 Unix DB 中得到启发的)的后代。JDB 对于学习如何进行调试和执行较简单的
调试任务来说很有用,而且有助于你去了解无论 JDK 安装在哪里 JDB 总是可用的。不过,
对于较大的工程,我们可能希望能够使用一个图形化调试器,这在稍后会讲述。
形化调试器
使用像 JDB 这样的命令行调试器很不方便。我们必须要使用显式命令进行查看变量值的状
态(局部变量,转存变量),列出源代码执行位置(列表),找出系统中的线程(线程),设
置断点(断入方法内部,断在方法外部),等等诸如此类的操作。图形化的调试器使得我们
不需要显式命令,通过使用几下点击就能完成这些事情,而且还可以查看正在被调试的程序
细节。
因此,尽管我们可能想尝试使用 JDB 开始进行调试,不过我们会发现学习使用图形化调试
器来快速跟踪到 bug 显得更有效率。在本书此版本的编写过程中,我们开始使用 IBM 的
Eclipse 编辑器和开发环境,它包含一个很好的 Java 图形化调试器。Eclipse 的设计与
实现都很优良,而且我们可以从 www.Eclipse.org 免费下载它(这是一个免费的工具,
不是实验版或共享软件——感谢 IBM 投入资金、时间和努力使它可以供每个人使用)。
其他的免费开发工具也有图形化的调试器,例如 Sun 的 Netbeans 和 Borland 的
JBuilder 免费版。
追踪内存消费
下面是剖析器为说明内存使用目的而能够展示的数据类型:
为某一特定类型分配的对象个数。 对象分配发生的地方。 在该类实例的分配中涉及到的方法。 闲散对象:已分配的,但是没有被使用过的,也没有被垃圾回收的对象。这些对象
会持续增加 JVM 堆的大小而且会表现为内存泄漏,这可能会引起内存溢出的错误
或者在垃圾回收器方面过多的开销。 过度分配的临时对象,它们增加了垃圾收集器的工作量,因此降低了应用的性能。 在释放那些添加到垃圾收集器但是没有被移除掉的实例时所导致的失败(这是闲散
对象的特殊情况)。
追踪 CPU 的使用
剖析器还能追踪到 CPU 在代码的不同部分花费了多少时间。它们能告诉我们:
方法被调用的次数。 每个方法占用 CPU 时间的百分比。如果某个方法调用了其他的方法,那么剖析器
可以告诉我们花费到这些其他方法上的时间总量。 每个方法花费的绝对时间,包括它等待 I/O 的时间,加锁时间等。这些时间依赖
于系统的可用资源。
这样我们就能够确定代码的那些部分需要优化了。
覆盖测试
覆盖测试(coverage testing)可以显示在测试期间没有被执行的代码行。这样就会吸
引我们注意那些没有被使用到的代码,因此它们应该成为被移除或重构的候选对象。
JVM剖析接口
剖析代理(profiler agent)对那些它感兴趣的事件与 JVM 进行通信。Java 虚拟机的
剖析接口支持以下事件:
进入和退出方法 分配、移动和释放一个对象 创建和删除一个堆区域 开始和结束一次垃圾收集循环 分配和释放一个 JNI 全局引用 分配和释放一个 JNI 弱全局引用 载入和卸载某个编译过的方法 开始和结束某个线程 为设备准备的类文件数据 载入和卸载某个类文件 处于竞争状态的 Java 监视器的事件:等待进入,进入及退出 处于竞争状态的 raw 监视器的事件:等待进入,进入及退出 未处于竞争状态的 Java 监视器的事件:等待和已完成等待 丢弃监视器 丢弃监视器 丢弃堆 丢弃对象 请求导空或复位剖析数据 JVM 初始化和关闭
在进行剖析时,Java 虚拟机将这些事件发送到剖析代理,接着剖析代理将期望的信息传送
给剖析器前端,如果需要的话,剖析器前端可以是在另一台机器上的运行进程。
最优化指南 避免为性能而牺牲代码的可读性。 不能孤立地考虑性能。要权衡所需付出的努力与能够得到的利益之间的关系。 性能是大型工程要关心的问题,但是它通常不是小型工程要考虑的问题。 使程序可以运转应该比钻研程序的性能有更高的优先权。一旦我们拥有了可运转的
程序,我们就可以使用剖析器来使其更为有效。仅当性能被确定为是一个关键因素
的时候,在初始设计/开发过程期间才应该予以考虑。 不要假设瓶颈在什么地方。而是应该运行剖析器来获得数据。 在任何可能的情况下,尽量通过将对象设置为 null 从而显式地将其销毁。有时这
可能是对垃圾回收器的一种很有帮助的提示。 程序大小的问题。仅当程序是大型的、运行时间长而且速度也是一个问题时,性能
优化才有价值。 static final 变量可以通过 Java 虚拟机进行优化以提高程序的速度。因此程
序常量也应该被声明为 static 和 final。
Doclets
虽然为文档支持开发一种工具,作为帮助我们在程序中追踪问题的想法有一点不可思议,但是 doclets 就具备这种令人吃惊的用处。因为 doclet 探入了 Javadoc 解析器的内部,
它能够得到 javadoc 解析器可以获得的信息。有了它,我们就可以编程检验代码中的类名、
域名和方法签名,并且标记潜在的问题。
从 Java 源文件中产生 JDK 文档的过程涉及到使用标准的 doclet 进行解析源文件和格式
化这个被解析文件的操作。我们可以编写定制的 doclet 来定制我们的 javadoc 注释的格
式。但是,doclet 允许我们做的比对注释进行格式化要多得多,因为 doclet 可以获取被
解析的源文件的很多信息。
我们可以提取类中所有的成员信息:域、构造器、方法以及与每个成员变量相关的注释(唉,
方法体的代码无法获得)。关于成员变量的细节被封装到了特殊对象的内部,它包含关于成
员变量的属性的信息(private、static 和 final 等)。这些信息对于探测编写得很拙劣
的代码会有所帮助,这些代码包括应该是私有的但却是公有的成员变量,没有注释的方法参
数以及没有遵守命名规则的标志符等等。