Code Style Guidelines for Contributors
版本:Android 4.0 r1
英文原文:http://source.android.com/source/code-style.html
以下规则并非指导或推荐的性质,而是必须遵守的规定。如果不遵守这些规定,Android通常不会接受投稿。
已有的代码未必全部遵守了这些规定,但是新的代码全部都应该遵守。
在本文中
我们遵循标准的Java编码规范,并加入了新的规则:
有时,完全忽略异常是非常诱人的,比如:
void setServerPort(String value) {
}
绝对不要这么做。也许你会认为:你的代码永远不会碰到这种出错的情况,或者处理异常并不重要,可类似上述忽略异常的代码将会在代码中埋下一颗地雷,说不定哪天它就会炸到某个人了。你必须在代码中以某种规矩来处理所有的异常。根据情况的不同,处理的方式也会不一样。
无论何时,空的catch语句都会让人感到不寒而栗。虽然很多情况下确实是一切正常,但至少你不得不去忧虑它。在Java中你无法逃离这种恐惧感。
-James Gosling
可接受的替代方案包括(按照推荐顺序):
·
向方法的调用者抛出异常。
void setServerPort(String value) throws NumberFormatException {
}
·
根据抽象级别抛出新的异常。
void setServerPort(String value) throws ConfigurationException {
}
·
默默地处理错误并在catch {}语句块中替换为合适的值。
void setServerPort(String value) {
}
·
捕获异常并抛出一个新的RuntimeException。这种做法比较危险:只有确信发生该错误时最合适的做法就是崩溃,才会这么做。
void setServerPort(String value) {
}
请记住,最初的异常是传递给构造方法的RuntimeException。如果代码必须在Java 1.3版本下编译,需要忽略该异常。
·
最后一招:如果确信忽略异常比较合适,那就忽略吧,但必须把理想的原因注释出来:
void setServerPort(String value) {
}
有时在捕获Exception时偷懒也是很吸引人的,类似如下的处理方式:
try {
} catch (Exception e) {
}
不要这么做。绝大部分情况下,捕获顶级的Exception或Throwable都是不合适的,Throwable更不合适,因为它还包含了Error异常。这种捕获非常危险。这意味着本来不必考虑的Exception(包括类似ClassCastException的RuntimeException)被卷入到应用程序级的错误处理中来。这会让代码运行的错误变得模糊不清。这意味着,假如别人在你调用的代码中加入了新的异常,编译器将无法帮助你识别出各种不同的错误类型。绝大部分情况下,无论如何你都不应该用同一种方式来处理各种不同类型的异常。
本规则也有极少数例外情况:期望捕获所有类型错误的特定的测试代码和顶层代码(为了阻止这些错误在用户界面上显示出来,或者保持批量工作的运行)。这种情况下可以捕获顶级的Exception(或Throwable)并进行相应的错误处理。在开始之前,你应该非常仔细地考虑一下,并在注释中解释清楚为什么这么做是安全的。
比捕获顶级Exception更好的方案:
·
分开捕获每一种异常,在一条try语句后面跟随多个catch
语句块。这样可能会有点别扭,但总比捕获所有Exception要好些。请小心别在catch语句块中重复执行大量的代码。
·
重新组织一下代码,使用多个try块,使错误处理的粒度更细一些。把IO从解析内容的代码中分离出来,根据各自的情况进行单独的错误处理。
·
再次抛出异常。很多时候在你这个级别根本就没必要捕获这个异常,只要让方法抛出该异常即可。
请记住:异常是你的朋友!当编译器指出你没有捕获某个异常时,请不要皱眉头。而应该微笑:编译器帮助你找到了代码中的运行时(runtime)问题。
Finalizer提供了一个机会,可以让对象被垃圾回收器回收时执行一些代码。
优点:便于执行清理工作,特别是针对外部资源。
缺点:调用finalizer的时机并不确定,甚至根本就不会调用。
结论:我们不要使用finalizers。大多数情况下,可以用优秀的异常处理代码来执行那些要放入finalizer的工作。如果确实是需要使用finalizer,那就定义一个close()方法(或类似的方法),并且在文档中准确地记录下需要调用该方法的时机。相关例程可以参见InputStream。这种情况下还是适合使用finalizer的,但不需要在finalizer中输出日志信息,因为日志不能因为这个而被撑爆。
当需要使用foo包中的Bar类时,存在两种可能的import方式:
1.
import foo.*;
优点:可能会减少import语句。
1.
import foo.Bar;
优点:实际用到的类一清二楚。代码的可读性更好,便于维护。
结论:用后一种写法来import所有的Android代码。不过导入java标准库(java.util.*、java.io.*等)
和单元测试代码
(junit.framework.*)时可以例外。
使用Android Java类库和工具存在一些惯例。有时这些惯例会作出重大变化,可之前的代码也许会用到过时的模板或类库。如果用到这部分过时的代码,沿用已有的风格就是了(参阅Consistency)。创建新的组件时就不要再使用过时的类库了。
每个文件的开头都应该有一句版权说明。然后下面应该是package包语句和import语句,每个语句块之间用空行分隔。然后是类或接口的定义。在Javadoc注释中,应描述类或接口的用途。
package com.android.internal.foo;
import android.os.Blah;
import android.view.Yada;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Foo {
}
每个类和自建的public方法必须包含Javadoc注释,注释至少要包含描述该类或方法用途的语句。并且该语句应该用第三人称的动词形式来开头。
例如:
static double sqrt(double a) {
}
或
public String(byte[] bytes) {
}
如果所有的Javadoc都会写成“sets Foo”,对于那些无关紧要的类似setFoo()的get和set语句是不必撰写Javadoc的。如果方法执行了比较复杂的操作(比如执行强制约束或者产生很重要的副作用),那就必须进行注释。如果“Foo”属性的意义不容易理解,也应该进行注释。
无论是public的还是其它类型的,所有自建的方法都将受益于Javadoc。public的方法是API的组成部分,因此更需要Javadoc。
Android目前还没有规定自己的Javadoc注释撰写规范,但是应该遵守Sun
Javadoc约定。
为了把规模控制在合理范围内,方法应该保持简短和重点突出。不过,有时较长的方法也是合适的,所以对方法的代码长度并没有硬性的限制。如果方法代码超过了40行,就该考虑是否可以在不损害程序结构的前提下进行分拆。
字段应该定义在文件开头,或者紧挨着使用这些字段的方法之前。
局部变量的作用范围应该是限制为最小的(Effective Java第29条)。使用局部变量,可以增加代码的可读性和可维护性,并且降低发生错误的可能性。每个变量都应该在最小范围的代码块中进行声明,该代码块的大小只要能够包含所有对该变量的使用即可。
应该在第一次用到局部变量的地方对其进行声明。几乎所有局部变量声明都应该进行初始化。如果还缺少足够的信息来正确地初始化变量,那就应该推迟声明,直至可以初始化为止。
本规则存在一个例外,就是涉及try-catch语句的情况。如果变量是用方法的返回值来初始化的,而该方法可能会抛出一个checked异常,那么必须在try块中进行变量声明。如果需在try块之外使用该变量,那它就必须在try块之前就进行声明了,这时它是不可能进行正确的初始化的。
// Instantiate class cl, which represents some sort of Set
Set s = null;
try {
} catch(IllegalAccessException e) {
} catch(InstantiationException e) {
}
// Exercise the set
s.addAll(Arrays.asList(args));
但即便是这种情况也是可以避免的,把try-catch
块封装在一个方法内即可:
Set createSet(Class cl) {
}
...
// Exercise the set
Set s = createSet(cl);
s.addAll(Arrays.asList(args));
除非理由十分充分,否则循环变量都应该在for语句内进行声明,:
for (int i = 0; i n; i++) {
}
和
for (Iterator i = c.iterator(); i.hasNext(); ) {
}
import语句的次序应该如下:
1.
2.
第三方库(com、junit、net、org)
3.
java和javax
为了精确匹配