Plusaber's Blog

  • Home

  • Tags

  • Categories

  • Archives

JUnit_单元测试之道

Posted on 2015-03-05 | In java | Comments:

About testing

单元测试

单元测试(unit testing)就是指对软件中的最小可测试单元进行检查和验证,根据语言和具体情况的不同,可以是,类、方法等。

单元测试可以由两种方式完成。

人工测试 自动测试
通过main方法(或其他)执行需要测试的单元,然后人为的观察输出确定是否正确称为人工测试。 借助工具支持并且利用自动工具执行用例被称为自动测试。
消耗时间并单调:由于测试用例是由人力资源执行,所以非常缓慢并乏味。 速度快,人力资源需求少
可信度较低:人工测试可信度较低是可能由于人工错误导致测试运行时不够精确。 可信度更高:自动化测试每次运行时精确地执行相同的操作。
非程式化:编写复杂并可以获取隐藏的信息的测试的 话,这样的程序无法编写。 程式化:试验员可以编写复杂的测试来显示隐藏信息。

Cases

一个基本的开发测试过程包括:编写function code,编写测试case code,编写运行测试code的程序。

在java中,通常需要测试的最小单元是function。

case1

如果我们需要测试下面的plus functoon

1
2
3
4
5
6
package cc.openhome;
public class Calculator {
public int plus(int op1, int op2) {
return op1 + op2;
}
}

我们可以编写下面的测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package test.cc.openhome;
import cc.openhome.Calculator;
public class CalculatorTest {
public static void testPlus() {
Calculator calculator = new Calculator();
int expected = 3;
int result = calculator.plus(1, 2);
if(expected == result) {
System.out.println("成功!");
}
else {
System.out.printf(
"失敗,預期為 %d,但是傳回 %d!%n", expected, result);
}
}
}

然后执行测试:

1
2
3
4
5
6
7
package test.cc.openhome;

public class TestRunner {
public static void main(String[] args) {
CalculatorTest.testPlus();
}
}

大功告成!然而这是开始。

case2

当然一个类中一般包含多个方法,需要测试的方法也会有多个。
假设我们增加了方法:

1
2
3
4
5
6
7
8
9
10
package cc.openhome;
public class Calculator {
public int plus(int op1, int op2) {
return op1 + op2;
}

public int minus(int op1, int op2) {
return op1 - op2;
}
}

最直接的,我们可以增加一个测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package test.cc.openhome;
import cc.openhome.Calculator;
public class CalculatorTest {
...

public static void testMinus() {
Calculator calculator = new Calculator();
int expected = 1;
int result = calculator.minus(3, 2);
if(expected == result) {
System.out.println("正確!");
}
else {
System.out.printf(
"錯誤,傳回 %d,但應該是 %d!%n", result, expected);
}
}
}

我们可以执行测试如下:

1
2
3
4
5
6
7
8
package test.cc.openhome;

public class TestRunner {
public static void main(String[] args) {
CalculatorTest.testPlus();
CalculatorTest.testMinus();
}
}

可以注意到,这两个测试方法中的判别结果正确与否的逻辑是一样的,我们可以重构将其分离出来:

1
2
3
4
5
6
7
8
9
10
11
12
package test.cc.openhome;
public class Assert {
public static void assertEquals(int expected, int result) {
if(expected == result) {
System.out.println("正確!");
}
else {
System.out.printf(
"失敗,預期為 %d,但是傳回 %d!%n", expected, result);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package test.cc.openhome;
import cc.openhome.Calculator;
public class CalculatorTest {
public static void testPlus() {
Calculator calculator = new Calculator();
int expected = 3;
int result = calculator.plus(1, 2);
Assert.assertEquals(expected, result);
}
public static void testMinus() {
Calculator calculator = new Calculator();
int expected = 1;
int result = calculator.minus(3, 2);
Assert.assertEquals(expected, result);
}
}

然而,当前的处理方法是,每次function code增加一个方法,就需要对于的增加一个测试方法,同时还要对应的在测试执行程序(TestRunner)中增加调用测试方法。

case3

为了避免每次都要修改测试执行程序(TestRunner),我们可以将测试抽象为接口,然后将test case改为类(之前是方法级别),实现测试接口:

1
2
3
4
package test.cc.openhome;
public interface Test {
void run();
}

可以修改测试执行程序(TestRunner)如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package test.cc.openhome;
import java.util.*;
public class TestRunner {
private List<Test> tests;
public TestRunner() {
tests = new ArrayList<Test>();
}
public void add(Test test) {
tests.add(test);
}
public void run() {
for(Test test : tests) {
test.run();
}
}
}

之后每次需要测试新增的方法,我们不需要再修改TestRunner的代码,只需要添加一个新的testcase的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package test.cc.openhome;
import cc.openhome.Calculator;
public class CalculatorPlusTest implements Test {
@Override
public void run() {
Calculator calculator = new Calculator();
int expected = 3;
int result = calculator.plus(1, 2);
Assert.assertEquals(expected, result);
}
}

package test.cc.openhome;
import cc.openhome.Calculator;
public class CalculatorMinusTest implements Test {
@Override
public void run() {
Calculator calculator = new Calculator();
int expected = 1;
int result = calculator.minus(3, 2);
Assert.assertEquals(expected, result);
}
}

执行测试:

1
2
3
4
5
6
7
8
9
package test.cc.openhome;
public class CalculatorTest {
public static void main(String[] args) {
TestRunner runner = new TestRunner();
runner.add(new CalculatorPlusTest());
runner.add(new CalculatorMinusTest());
runner.run();
}
}

case4

还没结束。。前面的测试只能对单个单个的方法进行测试,不能对test进行组合,设计TestSuite用于组合多个test为一个test。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package test.cc.openhome;
import java.util.*;
public class TestSuite implements Test {
private List<Test> tests;
public TestSuite() {
tests = new ArrayList<Test>();
}
@Override
public void run() {
for (Test test : tests) {
test.run();
}
}
public void add(Test test) {
tests.add(test);
}
}

这样就可以将多个test组合为一个test,然后按照一个test进行执行。

case5

还有问题。。目前每次需要测试一个方法,都需要创建一个类实现test接口。当方法很多时,这是一个很大的问题,费时费力。

最开始的设计时,test是方法级别,由于需要保持TestRunner不总是被修改,我们设计了test接口并将每个test改为了class级别。有没有方法可以保持TestRunner的稳定,并且使每个test回到方法级别?

答案当然是有的,我们可以将所有的test以方法级别都放在一个类中,但是每次具体选择哪个测试方法由参数决定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package test.cc.openhome;
import java.lang.reflect.*;
public class TestCase extends Assert implements Test {
private String fName;

public TestCase() {}
public TestCase(String name) {
fName = name;
}

protected void setUp() {}
protected void tearDown() {}

@Override
public void run() {
setUp();
runTest();
tearDown();
}

public void runTest() {
Method runMethod= null;
try {
runMethod= getClass().getMethod(fName, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (!Modifier.isPublic(runMethod.getModifiers())) {
throw new RuntimeException("方法 \"" + fName + "\" 必須是 public");
}
try {
runMethod.invoke(this, new Class[0]);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public String getName() {
return fName;
}

public void setName(String name) {
fName = name;
}
}

有好一些吗?好像是有,至少不用分别建立Test的实作类别了,然而你还可以让测试人员更方便一些。你想在执行测试时,用每个testXXX()方法名称 来建构TestCase的子类别实例,而后运用反射(Reflection)来执行那些testXXX()方法, 让测试人员不用自己去作一些建立物件的动作。为此,你要重构TestSuite:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package test.cc.openhome;
import java.lang.reflect.*;
import java.util.*;
public class TestSuite implements Test {
private List<Test> tests = new ArrayList<Test>();

public TestSuite() {}
public TestSuite(Class clz) {
// 找出每個test開頭的方法
Method[] methods = clz.getDeclaredMethods();
for (Method method : methods) {
if (Modifier.isPublic(method.getModifiers())
&& method.getName().startsWith("test")) {
Constructor constructor = null;
try {
constructor = clz.getConstructor();
// 建立TestCase實例
TestCase testCase = (TestCase) constructor.newInstance();
// 設定要呼叫的testXXX()方法
testCase.setName(method.getName());
// 加入測試
add(testCase);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}

@Override
public void run() {
for (Test test : tests) {
test.run();
}
}

public void add(Test test) {
tests.add(test);
}
public void add(Class clz) {
tests.add(new TestSuite(clz));
}
}
1
2
3
4
5
6
7
8
9
package test.cc.openhome;
public class TestRunner {
public static void run(Test test) {
test.run();
}
public static void run(Class clz) {
run(new TestSuite(clz));
}
}

现在,如果需要写测试,只需要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package test.cc.openhome;
import cc.openhome.Calculator;
public class CalculatorTest extends TestCase {
private Calculator calculator;

public CalculatorTest() {}
public CalculatorTest(String name) {
super(name);
}

@Override
protected void setUp() {
calculator = new Calculator();
}
@Override
protected void tearDown() {
calculator = null;
}

public void testPlus() {
int expected = 5;
int result = calculator.plus(3, 2);
assertEquals(expected, result);
}

public void testMinus() {
int expected = 1;
int result = calculator.minus(3, 2);
assertEquals(expected, result);
}

public static void main(String[] args) {
TestRunner.run(CalculatorTest.class);
}
}

我们还可以自由组合各种形式的测试,可以添加一个test方法,也可执行所有test方法,也可混合组合:

1
2
3
4
5
6
7
8
9
10
11
12
13
package test.cc.openhome;
public class CalculatorAll {
public static Test suite() {
TestSuite suite = new TestSuite();
suite.add(CalculatorPlusMinusTest.suite());
suite.add(CalculatorTest.class);
suite.add(new CalculatorTest("testPlus"));
return suite;
}
public static void main(String[] args) {
TestRunner.run(suite());
}
}

case6

在很多情况下,我们希望收集测试的结果,而只是简单的输出到控制台,而是希望进一步处理和操作,这时我们需要设计TestResult用以添加测试结果信息,这里就不在细述。

测试工具

由上面的case可以看出,设计一个强大的可扩展的test系统有很多问题需要考虑,测试工具需要做到:

易于撰写测试
只要撰写testXXX()方法,程式会自动找出并进行测试,事实上,经由设计,在JDK 5以上,还可以使用标注(Annotation)来标示测试方法,JUnit 4.x就可以如此设定。

易于组合测试
可指定测试单一testXXX()方法,可 自动发掘测试案例(Test case)中的所有testXXX()方法,可以自由组合TestSuite等。

让单元测试彼此独立
每个testXXX()方法是封装在一个TestCase的实例中运行,所以单元测试与单元测试间是彼此独立的,如果单元测试有前置状态,你也可以利用setUp()、tearDown()来使之处于前置状态。

自动收集并产生结果
经由适当的组合,你可以一次运行所指定的任意个单元测试,过程中会自动收集结果,最后可用指定的方式(Test runner)来呈现结果,事实上,藉由Ant或Maven之类的工作,你还可以进一步产生各种类型的测试报告并邮寄至相关人等。

JUnit

JUnit是个单元测试(Unit test)框架,单元测试指的是测试一个工作单元(a unit of work)的行为。举例来说,对于建筑桥墩而言,一个螺丝钉、一根钢筋、一条钢索甚至一公斤的水泥等,都可谓是一个工作单元,验证这些工作单元行为或功能(硬度、张力等)是否符合预期,方可确保最后桥墩安全无虞。

测试一个单元,基本上要与其它的单元独立,否则你会在同时测试两个单元的正确 性,或是两个单元之间的合作行为。就软体测试而言,或支援物件导向的程式而言,例如Java,「通常」单元测试指的是测试某个方法,你给予该方法某些输入,预期该方法会产生某种输出,例如传回预期的值、产生预期的档案、新增预期的资料等。

JUnit 促进了“先测试后编码”的理念,强调建立测试数据的一段代码,可以先测试,然后再应用。这个方法就好比“测试一点,编码一点,测试一点,编码一点……”,增加了程序员的产量和程序的稳定性,可以减少程序员的压力和花费在排错上的时间。

  • JUnit 是一个开放的资源框架,用于编写和运行测试。
  • 提供注释来识别测试方法。
  • 提供断言来测试预期结果。
  • 提供测试运行来运行测试。
  • JUnit 测试允许你编写代码更快,并能提高质量。
  • JUnit 优雅简洁。没那么复杂,花费时间较少。
  • JUnit 测试可以自动运行并且检查自身结果并提供即时反馈。所以也没有必要人工梳理测试结果的报告。
  • JUnit 测试可以被组织为测试套件,包含测试用例,甚至其他的测试套件。
  • JUnit 在一个条中显示进度。如果运行良好则是绿色;如果运行失败,则变成红色。

JUnit 3 vs JUnit 4

Junit也处于不断发展的过程中,虽然基本思想没有大的变化,但是具体实现和使用,从JUnit 3到JUnit 4还是有一些变化。

上面描述的各个case的发展的最后面,就是JUnit 3的主要框架和思想了,当然JUnit 3提供了更完善和强大的设计和支持。

JUnit 3与JUnit 4主要区别,参考JUnit测试框架之JUnit3和JUnit4使用区别的总结

  • 在JUnit3中需要继承TestCase类,但在JUnit4中已经不需要继承TestCase
  • 在JUnit3中需要覆盖TestCase中的setUp和tearDown方法,其中setUp方法会在测试执行前被调用以完成初始化工作,而tearDown方法则在结束测试结果时被调用,用于释放测试使用中的资源,而在JUnit4中,只需要在方法前加上@Before,就代表这个方法用于初始化操作,如上面的beforeDoTest方法,方法名是随意的
  • 在JUnit3中对某个方法进行测试时,测试方法的命令是固定的,例如对addBook这个方法进行测试,需要编写名字为tetAddBook的测试方法,而在JUnit4中没有方法命令的约束,例如对addBook这个方法进行测试,那么可以编写addBookToLibrary的方法,然后在这个方法的前面加上@Test,这就代表这个方法是测试用例中的测试方法
  • 编写JUnit4的测试用例和编写一个普通的类没有什么区别,只是需要加上Annotation指定要测试的方法,这种松偶合的设计理念相当优秀,能很好把测试分离出来.使用JUnit4的Annotation功能,需要JDK 1.5或以上版本
  • JUnit 4还提供了其他基于注解的强大的功能的支持。

后面主要介绍JUnit 4的用法,当然很多也是从JUnit 3继承下来的。

参考http://openhome.cc/Gossip/JUnit/。

Java编程风格

Posted on 2015-02-26 | In java | Comments:

import语句

  • 尽量不要使用通配符,如import java.util.*
  • 按照是否静态、顶级包等性质对import进行分组。

声明

类声明

  • 每个顶级类都在一个与它同名的源文件中。
  • 累成员顺序需要按照一定的逻辑关系组织在一起,而不是新的方法习惯性添加到类的结尾。
  • 当有多个同名重载函数时,这些函数应该按照一定的逻辑顺序连续放在一起。

变量声明

  • 每次只声明一个变量,不要使用组合声明。
  • 需要使用变量时才声明(在第一次使用时才声明),并尽快初始化。不要在一个代码快把局部变量都一次性声明。
  • 数组初始化:写成块状结构。
1
2
3
4
5
6
new int[] {0, 1, 2, 3};

new String[][] {
{"a", "b"},
{"c", "d"}
};

命名约定

约定

  • 包名全部小写,连续的单词只是简单连接起来,不使用下划线。
  • 类名以UpperCamelCase风格命名,通常是名词或者名词短语,接口名称有时可能是形容词或者形容词短语。
  • 测试类的命名以要测试的类的名称开始,以Test结束。
  • 方法名以lowerCamelCase风格命名。方法名通常是动词或者动词短语。下划线可能出现在JUnit测试方法名称中以分割名称的逻辑组件,一个典型的模式是:test<MethodUnderTest>_<state>。
  • 常量命名模式为CONSTANT_CASE,全部字母大写,用下划线分割单词。
  • 非常量字段名,按照lowerCamelCase。
  • 参数名,按照lowerCamelCase。
  • 局部变量名,按照lowerCamelCase,但是可以有更为宽松的缩写。

CamelCase

驼峰式命名法分大驼峰式命名法(UpperCamelCase)和小驼峰式命名法(lowerCamelCase)。 名字从散文形式(prose form)开始:

  • 把短语转换为纯ASCII码,并且移除任何单引号。例如:”Müller’s algorithm”将变成”Muellers algorithm”。
  • 把这个结果切分成单词,在空格或其它标点符号(通常是连字符)处分割开。
    推荐:如果某个单词已经有了常用的驼峰表示形式,按它的组成将它分割开(如”AdWords”将分割成”ad words”)。 需要注意的是”iOS”并不是一个真正的驼峰表示形式,因此该推荐对它并不适用。
  • 现在将所有字母都小写(包括缩写),然后将单词的第一个字母大写:
    • 每个单词的第一个字母都大写,来得到大驼峰式命名。
    • 除了第一个单词,每个单词的第一个字母都大写,来得到小驼峰式命名。
  • 最后将所有的单词连接起来得到一个标识符。

示例:

1
2
3
4
5
6
7
Prose form                Correct               Incorrect

"XML HTTP request" XmlHttpRequest XMLHTTPRequest
"new customer ID" newCustomerId newCustomerID
"inner stopwatch" innerStopwatch innerStopWatch
"supports IPv6 on iOS?" supportsIpv6OnIos supportsIPv6OnIOS
"YouTube importer" YouTubeImporter

格式

大括号

  • 与if, else, for, do, while一起使用,即使只有一条语句,也需要加上大括号。
  • 非空块时遵循Kernighan风格:
    • 左大括号前不换行
    • 左大括号后换行
    • 右大括号前换行
    • 如果右大括号是一个语句、函数体或类的终止,则右大括号后换行; 否则不换行。例如,如果右大括号后面是else或逗号,则不换行。
1
2
3
4
5
6
7
8
9
10
11
return new MyClass() {
@Override public void method() {
if (condition()) {
try {
something();
} catch (ProblemException e) {
recover();
}
}
}
};
  • 空块,可以直接使用单行void doNothing() {}

空白

垂直空白

  • 类内连续成员之间(可选,主要是用于逻辑分组)
  • 函数体内,语句的逻辑分组之间。

水平空白

  • 任何保留字与紧随其后的左括号(时:
  • 任何保留字与其前面的右大括号}
  • 任何左大括号前{
  • 任何二元或三元运算符的两侧
  • , : ;及右括号)后
  • 括号内的最左最右元素的空格不是必须的。
1
2
3
4
5
6
7
8
9
@Override
public String playShiritori(Set<String> candidates, String lastResponse) {
Set<String> choices = getNextChoices(candidates, lastResponse);
if (choices.isEmpty()) {
return null;
} else {
...
}
}

用小括号来指定逻辑组:推荐

自动换行

  • 如果在非赋值运算符处断开,则应该在该符号之前断开(比如+,应该和后面的语句一起放到下一行)
  • 如果在赋值运算符出断开,通常在该符合之后断开。
  • 方法名或够照函数名与左括号留在同一行。
  • 逗号(,)与前面的内容留在同一行。

注释

块注释风格
块注释与其周围的代码在同一缩进级别。它们可以是/* ... */风格,也可以是// ...风格。对于多行的/* ... */注释,后续行必须从*开始, 并且与前一行的*对齐。以下示例注释都是OK的。

1
2
3
4
/*
* This is // And so /* Or you can
* okay. // is this. * even do this. */
*/

JavaDoc

参考如何写Java文档注释(Java Doc Comments)

“文档注释”(Java Doc Comments)是专门为了用javadoc工具自动生成文档而写的注释,它是一种带有特殊功能的注释。

文档注释只负责描述类(class)、接口(interface)、方法(method)、构造器(constructor)、成员字段(field)。相应地,文档注释必须写在类、接口、方法、构造器、成员字段前面,而写在其他位置,比如函数内部,是无效的文档注释。

文档注释采用HTML语法规则书写,支持HTML标记(tag),同时也有一些额外的辅助标记。需要注意的是,这些标记不是给人看的(通常他们的可读性也不好),他们的作用是为了javadoc工具更好地生成最终文档。

文档注释与一般注释的最大区别在于起始符号是`/而不是/或//`。*

Javadoc块的基本格式如下所示:

1
2
3
4
5
6
7
一个文档注释由两部分组成:

/**
* 描述部分(description)
*
* 标记部分(block tags)
*/

描述部分自然不用多说,所谓的标记符号指的是@param, @return, @see之类的。
或者是以下单行形式:

1
/** An especially short bit of Javadoc. */

需要注意的几点:

  1. 第一行以特殊的文档定界符/** 开头
  2. 在描述段落和标记段落之间空一行,描述段落和标记段落必须分开,不能揉在一起,描述段落必须在标记段落之前
  3. 每一行注释都应该跟后面描述的类、方法等保持同样距离的缩进

描述部分(Description)

描述部分的第一行应该是一句对类、接口、方法等的简单描述,这句话最后会被javadoc工具提取并放在索引目录中。

怎么界定第一句话到哪结束了呢?答案是跟在第一个句号(英文标点)之后的tab、空行或行终结符规定了第一句的结尾。

例如下面这句注释,第一句的结尾是Prof.:

1
2
3
/**
* This is a simulation of Prof. Knuth's MIX computer.
*/

除了普通的文本之外,描述部分可以使用:

  1. HTML语法标签,例如 <b>xxx</b>
  2. javadoc规定的特殊标签,例如 {@link xxx} 。标签的语法规则是:{@标签名 标签内容}

需要注意的地方:

  1. 标签在有javadoc工具生成文档时会转化成特殊的内容,比如 {@link URL} 标签会被转化成指向URL类的超链接
  2. 如果注释包含多段内容,段与段之间需要用 <p> 分隔,空行是没用的
  3. 最后结尾行 */ 和起始行不同,这里只有一个星号
  4. 为了避免一行过长影响阅读效果,务必将每行的长度限制在80个字符以内
  5. 善用javadoc工具的复制机制避免不必要的注释:

如果一个方法覆盖了父类的方法或实现了接口种的方法,那么javadoc工具会在该注释里添加指向原始方法的链接,此外如果新方法没有注释,那么javadoc会把原始方法的注释复制一份作为其注释,但是如果新方法有注释了,就不会复制了。

注释风格:

  1. 使用 <code>关键字</code> 来强调关键字,建议强调的内容有:java关键字、包名、类名、方法名、接口名、字段名、参数名等
  2. 控制 {@link xxx} 的数量,太多的链接会使文档的可读性很差,因为读者总是跳来跳去。不要出现相同的链接,同样的链接只保留第一个;不要为java自带的内容或是常识性的内容提供链接
  3. 描述一个方法时,应当只保留方法名字,不要附带方法的参数。比如有个方法是add(Object obj),那么用add指代该方法即可,而不是add(Object obj)
  4. 英文注释可以是短语也可以是句子。如果是句子,首字母要大写,如果是短语,首字母小写。
  5. 英文注释使用第三人称,而不是第二人称。

标记部分(Tag)

1
2
3
4
5
6
7
8
9
@author(只出现在类和接口的文档中)
@version(只出现在类和接口的文档中)
@param(只出现在方法或构造器的文档中)
@return(只出现在方法中)
@exception(从java1.2之后也可以使用@thrown替代)
@see
@since
@serial(也可以使用@serialField或@serialData替代)
@deprecated

此外,如果有多个相同标记,也要注意顺序:

1
2
3
4
多个@author标记,应该按照时间顺序排列,即原作者应该排在第一个位置
多个@param标记,应该按照参数定义的顺序排列
多个@exception(或是@thrown)应该按照异常的字母顺序排列
多个@see标记,应该按照注释的逻辑顺序排列,即从最近的到最远的,从最具体的到最一般的

如果方法有参数,@param标记必须包含,而且每个对应一个参数
如果方法有返回值,@return标记必须包含

空行(即,只包含最左侧星号的行)会出现在段落之间和Javadoc标记(@XXX)之前(如果有的话)。 除了第一个段落,每个段落第一个单词前都有标签<p>,并且它和第一个单词间没有空格。

至少在每个public类及它的每个public和protected成员处使用Javadoc,除了测试类和方法。

Examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Returns an Image object that can then be painted on the screen.
* The url argument must specify an absolute {@link URL}. The name
* argument is a specifier that is relative to the url argument.
* <p>
* This method always returns immediately, whether or not the
* image exists. When this applet attempts to draw the image on
* the screen, the data will be loaded. The graphics primitives
* that draw the image will incrementally paint on the screen.
*
* @param url an absolute URL giving the base location of the image
* @param name the location of the image, relative to the url argument
* @return the image at the specified URL
* @see Image
*/
public Image getImage(URL url, String name) {
try {
return getImage(new URL(url, name));
} catch (MalformedURLException e) {
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/**
* Graphics is the abstract base class for all graphics contexts
* which allow an application to draw onto components realized on
* various devices or onto off-screen images.
* A Graphics object encapsulates the state information needed
* for the various rendering operations that Java supports. This
* state information includes:
* <ul>
* <li>The Component to draw on
* <li>A translation origin for rendering and clipping coordinates
* <li>The current clip
* <li>The current color
* <li>The current font
* <li>The current logical pixel operation function (XOR or Paint)
* <li>The current XOR alternation color
* (see <a href="#setXORMode">setXORMode</a>)
* </ul>
* <p>
* Coordinates are infinitely thin and lie between the pixels of the
* output device.
* Operations which draw the outline of a figure operate by traversing
* along the infinitely thin path with a pixel-sized pen that hangs
* down and to the right of the anchor point on the path.
* Operations which fill a figure operate by filling the interior
* of the infinitely thin path.
* Operations which render horizontal text render the ascending
* portion of the characters entirely above the baseline coordinate.
* <p>
* Some important points to consider are that drawing a figure that
* covers a given rectangle will occupy one extra row of pixels on
* the right and bottom edges compared to filling a figure that is
* bounded by that same rectangle.
* Also, drawing a horizontal line along the same y coordinate as
* the baseline of a line of text will draw the line entirely below
* the text except for any descenders.
* Both of these properties are due to the pen hanging down and to
* the right from the path that it traverses.
* <p>
* All coordinates which appear as arguments to the methods of this
* Graphics object are considered relative to the translation origin
* of this Graphics object prior to the invocation of the method.
* All rendering operations modify only pixels which lie within the
* area bounded by both the current clip of the graphics context
* and the extents of the Component used to create the Graphics object.
*
* @author Sami Shaio
* @author Arthur van Hoff
* @version %I%, %G%
* @since 1.0
*/
public abstract class Graphics {

/**
* Draws as much of the specified image as is currently available
* with its northwest corner at the specified coordinate (x, y).
* This method will return immediately in all cases, even if the
* entire image has not yet been scaled, dithered and converted
* for the current output device.
* <p>
* If the current output representation is not yet complete then
* the method will return false and the indicated
* {@link ImageObserver} object will be notified as the
* conversion process progresses.
*
* @param img the image to be drawn
* @param x the x-coordinate of the northwest corner
* of the destination rectangle in pixels
* @param y the y-coordinate of the northwest corner
* of the destination rectangle in pixels
* @param observer the image observer to be notified as more
* of the image is converted. May be
* <code>null</code>
* @return <code>true</code> if the image is completely
* loaded and was painted successfully;
* <code>false</code> otherwise.
* @see Image
* @see ImageObserver
* @since 1.0
*/
public abstract boolean drawImage(Image img, int x, int y,
ImageObserver observer);


/**
* Dispose of the system resources used by this graphics context.
* The Graphics context cannot be used after being disposed of.
* While the finalization process of the garbage collector will
* also dispose of the same system resources, due to the number
* of Graphics objects that can be created in short time frames
* it is preferable to manually free the associated resources
* using this method rather than to rely on a finalization
* process which may not happen for a long period of time.
* <p>
* Graphics objects which are provided as arguments to the paint
* and update methods of Components are automatically disposed
* by the system when those methods return. Programmers should,
* for efficiency, call the dispose method when finished using
* a Graphics object only if it was created directly from a
* Component or another Graphics object.
*
* @see #create(int, int, int, int)
* @see #finalize()
* @see Component#getGraphics()
* @see Component#paint(Graphics)
* @see Component#update(Graphics)
* @since 1.0
*/
public abstract void dispose();

/**
* Disposes of this graphics context once it is no longer
* referenced.
*
* @see #dispose()
* @since 1.0
*/
public void finalize() {
dispose();
}
}

编程实践

  • switch, default不能省略。
  • 静态成员:使用类进行调用
  • @Override:能用则用
  • Finalizers: 禁用

参考Google Java编程风格指南。

Java awt的review以及netbean打包jar包

Posted on 2015-02-09 | In java | Comments:

好久没做java界面开发,几乎全忘了。。最近帮同学开发一个简单的用于通过按键计算时间的程序,重新回顾了一下java awt和swing,这里记录遇到的两个问题。

  1. key press事件在key被push后会一直发生,被监听组件收到,而不是按下去时的一个事件。一直push的过程中,大概几毫秒到几百毫秒间会触发一个keypressed事件。
  2. 获得focus的组件才会得到事件的通知,一般为上一个有action的能获得focus的组件。可以通过binding绑定到其他组件上,这样就可以获得其他组件收到的事件通知。

另外是关于netbean打包jar包的问题,记录如下:

  1. Click on Properties
  2. Click on Compress JAR File
  3. Accept changes, click ok
  4. Click the button in the ribbon tab with the hammer/broom. (clean and build project)
  5. Go to the directory where your project is stored
  6. Look in the dist folder

参考How to create a Jar file in Netbeans.

Maven, Ant在Mac的配置

Posted on 2015-02-06 | In java | Comments:
  1. 下载Ant和Maven。
  2. 解压包到/urs/local下(任何目录都可以,选择自己需要的路径)
  3. 对于Ant,需要设置ANT_HOME环境变量指向前面解压的Ant包的bin目录,并加入系统PATH中。对于Maven,需要配置M2_HOME(Maven 2后使用的路径),MAVEN_HOME(部分旧的项目可能使用),都设置为指向前面解压的Maven包的bin目录。配置完成,可以使用which ant, which mvn查看安装的路径是否正确。
1
2
3
4
5
6
ANT_HOME=/usr/local/apache-ant-1.9.6
export PATH=$ANT_HOME/bin:$PATH

M2_HOME=/usr/local/apache-maven-3.3.9
MAVEN_HOME=/usr/local/apache-maven-3.3.9
export PATH=$M2_HOME/bin:$PATH

关于配置环境变量的方法,可以参考Linux环境变量配置.

Ant编译编码问题

Posted on 2015-02-05 | In java | Comments:

Ant编译编码问题

使用Ant编译项目遇到了编码问题,这里记录一下解决方案。

编码问题来源于代码的注释,所以应该是中文编码的问题,如果是使用eclipse,可以随便打开一个有中文注释的java文件查看properties,确认文件的编码,这次的情况是部分文件为GBK编码。

找到项目的Ant的build.xml,找到javac的配置部分,加入的encoding的指定,否则默认是使用UTF-8进行编译。

build.xml原来的编译配置:

1
2
3
4
5
6
...
<javac debug="true" debuglevel="${debuglevel}" destdir="bin" includeantruntime="false" source="${source}" target="${target}">
<src path="src"/>
<classpath refid="LogMiner.classpath"/>
</javac>
...

指定encoding为gbk:

1
2
3
4
5
6
...
<javac debug="true" debuglevel="${debuglevel}" destdir="bin" includeantruntime="false" source="${source}" encoding="gbk" target="${target}">
<src path="src"/>
<classpath refid="LogMiner.classpath"/>
</javac>
...
1…8910…17
Plusaber

Plusaber

Plusaber's Blog
82 posts
12 categories
22 tags
Links
  • LinkedIn
  • Indeed
  • Baito
  • Kaggle
© 2014 – 2019 Plusaber
Powered by Hexo v3.8.0
|
Theme – NexT.Mist v7.1.1