强制:编程时必须遵守的规定,含有强制字样或字体用加粗式样标注。
推荐:编程时推荐遵守的规定,字体用普通式样标注。
避免超过 2000 行的源文件。
每个 Java 源文件都包含一个单一的公共类或接口。若私有类和接口与一个公共类相关联,可以将它们和公共类放入同一个源文件。公共类必须是这个文件中的第一个类或接口。
Java 源文件还遵循以下规则:
所有的源文件都应该在开头有一个的注释,其中列出版本信息、日期和版权声明。
/*
* Copyright (c) 2014 SIMCOM, Inc.
* All Rights Reserved.
* SIMCOM Proprietary and Confidential.
*/
在多数 Java 源文件中,第一个非注释行是包语句,在它之后可以跟引入语句。
package com.android.sim; import java.io.IOException;
在导入包时当完全限制代码所使用的类的名字,尽量少用通配符的方式,但导入一些通用包,或用到一个包下大部分类时,则可是使用通配符方式。同一包中的类在导入时应声明在一起,无效的未使用到的引用要即时删除。
下表(强制)描述了类和接口声明的各个部分以及它们出现的先后次序。
类/接口声明的各部分 | 说明 |
---|---|
类/接口文档注释(/**……*/) | 类和接口应该有标准 Javadoc 注释。 |
类或接口的声明 | 类名和接口的第一个字符大写,每个单词的首字母大写,中间可以有下划线。 |
类/接口实现的长度限制 | 限制一个匿名内部类的长度不要超过 80 行。 |
类的(静态)变量 | 首先是类的公共变量,随后是保护变量,再后是包一级别的变量(没有访问修饰符,access modifier),最后是私有变量。静态 final 变量应全部大写,中间可以有下划线。 |
实例变量 | |
构造器 | 构造器的代码长度(不计空行),不应超过 200 行。 |
方法 | 方法名应为动词或动宾短语,首字母小写,其后每个单词首字母大写,方法的参数不要超过 8个,参数名字必须和变量的命名规范一致,public 的方法之前应该有 Javadoc 注释,方法之后的大括号位于行尾。方法应该保持简短和重点突出,对方法的代码长度并没有硬性的限制。如果方法代码超过了 40 行,就该考虑是否可以在不损害程序结构的前提下进行分拆。 |
4 个空格作为缩进排版的一个单位,不使用制表符 tab。
8 个空格作为换行后的缩进,包括函数调用和赋值。
Instrument i = someLongexpression_r(that, NotFit, on, one, line); // 推荐 Instrument i = someLongexpression_r(that, NotFit, on, one, line); // 避免
尽量避免一行的长度超过 100 个字符。
例外:如果注释行包含了超过 100 个字符的命令示例或者 url 文字,为了便于剪切和复制,其长度可以超过 100 个字符。
例外:import 行可以超过限制,因为很少有人会去阅读它。这也简化了编程工具的写入操作。
大括号不单独占用一行,应紧接着上一行书写。
class MyClass { int func() { if (something) { // ... } else if (somethingElse) { // ... } else { // ... } } }
我们需要用大括号来包裹条件语句块。不过也有例外,如果整个条件语句块(条件和语句本身)都能容纳在一行内,也可以(但不是必须)把它们放入同一行中。也就是说,这是合法的:
if (condition) { body(); } // 推荐 if (condition) body(); // 避免 if (condition) body(); // 错误
当一个表达式无法容纳在一行内时,可以依据如下一般规则断开:
以下是断开方法调用的一些例子:
someMethod(longExpression1, longExpression2, longExpression3, longExpression4, longExpression5); var = someMethod1(longExpression1, someMethod2(longExpression2, longExpression3));
以下是两个断开算术表达式的例子。前者更好,因为断开处位于括号表达式的外边,这是个较高级别的断开。
long1 = long2 * (long3 + long4 - long5) + 4 * longname6; // 推荐 long1 = long2 * (long3 + long4 - long5) + 4 * long6; // 避免
以下是两个缩进方法声明的例子。前者是常规情形。后者若使用常规的缩进方式将会使第二行和第三行移得很靠右,所以代之以缩进 8 个空格。
// 常规缩进 someMethod(int anArg, Object anotherArg, String yetAnotherArg, Object andStillAnother) { ... } // 为避免太靠右,用8个空格缩进 private static synchronized horkingLongMethodName(int anArg, Object anotherArg, String yetAnotherArg, Object andStillAnother) { ... }
if 语句的换行通常使用 8 个空格的规则,因为常规缩进(4 个空格)会使语句体看起来比较费劲。比如:
// 请不要使用这种缩进 if ((condition1 && condition2) || (condition3 && condition4) ||!(condition5 && condition6)) { // 不好的缩进 doSomethingAboutIt(); // 该行和if条件处于同一级 } // 避免 // 使用这种缩进 if ((condition1 && condition2) || (condition3 && condition4) ||!(condition5 && condition6)) { doSomethingAboutIt(); } // 推荐 // 或这种 if ((condition1 && condition2) || (condition3 && condition4) ||!(condition5 && condition6)) { doSomethingAboutIt(); } // 推荐
这里有三种可行的方法用于处理三元运算表达式:
alpha = (aLongBooleanExpression) ? beta : gamma; alpha = (aLongBooleanExpression) ? beta : gamma; alpha = (aLongBooleanExpression) ? beta : gamma;
Java 程序有两类注释:实现注释(使用/*…*/和//界定的注释)和文档注释(由/**…*/界定,可通过 javadoc 工具转换成 html 文件)。
实现注释用以注释代码或者实现细节。文档注释从实现自由的角度描述代码的规范。它可以被那些手头没有源码的开发人员读懂。
注释应被用来给出代码的总括,并提供代码自身没有提供的附加信息。注释应该仅包含与阅读和理解程序有关的信息。例如,相应的包如何被建立或位于哪个目录下之类的信息不应包括在注释中。
在注释里,对设计决策中重要的或者不是显而易见的地方进行说明是可以的,但应避免提供代码中己清晰表达出来的重复信息。
注释不应写在用星号或其他字符画出来的大框里。注释不应包括诸如制表符和回退符之类的特殊字符。
使用 Javadoc 标准注释,每个文件的开头都应该有一句版权说明。然后下面应该是package 包语句和 import 语句,每个语句块之间用空行分隔。然后是类或接口的定义。在Javadoc 注释中,应描述类或接口的用途。
/* * Copyright (c) 2014 SIMCOM, Inc. * All Rights Reserved. * SIMCOM Proprietary and Confidential. */ package com.android.internal.foo; import android.os.Blah; import android.view.Yada; import java.sql.ResultSet; import java.sql.SQLException; /** * 一句话功能描述 * 功能详细描述 * @see 相关类/方法 * @deprecated */ public class Foo { ... }
每个类和自建的 public 方法必须包含 Javadoc 注释,注释至少要包含描述该类或方法用途的语句。并且该语句应该用第三人称的动词形式来开头。
/** Returns the correctly rounded positive square root of a double value. */ static double sqrt(double a) { ... } or /** * Constructs a new String by converting the specified array of * bytes using the platform's default character encoding. */ public String(byte[] bytes) { ... }
如果所有的 Javadoc 都会写成“sets Foo”,对于那些无关紧要的类似 setFoo()的 get和 set 语句是不必撰写 Javadoc 的。如果方法执行了比较复杂的操作(比如执行强制约束或者产生很重要的副作用),那就必须进行注释。如果“Foo”属性的意义不容易理解,也应该进行注释。
无论是 public 的还是其它类型的,所有自建的方法都将受益于 Javadoc。public 的方法是 API 的组成部分,因此更需要 Javadoc。
程序可以有 4 种实现注释的风格:块、单行、尾端和行末。
块注释通常用于提供对文件,方法,数据结构和算法的描述。块注释被置于每个文件的开始处以及每个方法之前。它们也可以用于其他地方,比如方法内部。在功能和方法内部的块注释应该和它们所描述的代码具有一样的缩进格式。
块注释之首应该有一个空行,用于块注释和代码分割开来,比如:
/*
* Here is a block comment.
*/
块注释可以以/*-开头,这样indent(1)就可以将之识别为一个代码块的开始,而不是重排它。
/*-
* Here is a block comment with some very special
* formatting that I want indent(1) to ignore.
*
* one
* two
* three
*/
短注释可以显示在一行内,并与其后的代码具有一样的缩进层级。如果一个注释不能在一行内写完,就该采用块注释(参见“块注释”)。单行注释之前应该有一个空行。以下是一个Java代码中单行注释的例子:
if (condition) { /* Handle the condition. */ … }
极短的注释可以与它们所要描述的代码位于同一行,但是应该有足够的空白来分开代码和注释。若有多个短注释出现于大量代码中,它们应该具有相同的缩进。
以下是一个Java代码中尾端注释的例子:
if (a == 2) { return true; /* special case */ } else { return isPrime(a); /* works only for odd a */ }
注释界定符“//”,可以注释掉整行或者一行中的一部分。它一般不用于连续多行的注释文本;然而,它可以用来注释掉连续多行的代码段。以下是所有三种风格的例子:
if (foo > 1) { // Do a double-flip. ... } else { return false; // Explain why here. } //if (bar > 1) { // // // Do a triple-flip. // ... //} else { // return false; //}
文档注释描述 Java 的类、接口、构造器,方法,以及字段。每个文档注释都会被置于注释定界符/**…*/之中,一个注释对应一个类、接口或成员。
该注释应位于声明之前:
/** * The Example class providers ... */ public class Example { ... }
注意顶层的类和接口是不缩进的,而其成员是缩进的。描述类和接口的文档注释的第一行(/**)不需缩进,随后的文档注释每行都缩进 1 格(使星号纵向对齐)。成员,包括构造函数在内,其文档注释的第一行缩进 4 格,随后每行都缩进 5 格。
若你想给出有关类、接口、变量或方法的信息,而这些信息又不适合写在文档中,则可使用实现块注释或紧跟在声明后面的单行注释。例如,有关一个类实现的细节,应放入紧跟在类声明后面的实现块注释中,而不是放在文档注释中。
文档注释不能放在一个方法或构造器的定义块中,因为 Java 会将位于文档注释之后的第一个声明与其相关联。
在类、接口定义之前当对其进行注释,包括类、接口的目的、作用、功能、继承于何种父类,实现的接口、实现的算法、使用方法、示例程序等。
/**
* 一句话功能描述
* 功能详细描述
* @see 相关类/方法
* @deprecated
*/
据标准Javadoc规范对方法进行注释,以明确该方法功能、作用、各参数含义以及返回值等。复杂的算法用/**/在方法内注解出。
/**
* 一句话方法描述
* 方法详细描述
* @param 参数名 参数描述
* @param 参数名2 参数描述
* @return 返回值类型说明
* @throws Exception 异常说明
* @see 类/方法/成员
*/
成员变量和常量需要使用javadoc形式的注释,以说明当前变量或常量的含义。
/** * 成员变量描述 */ private String test; /** 成员变量描述 */ private int hello;
推荐一行一个声明,因为这样以利于写注释。
int level; // indentation level int size; // size of table
注意:上面的例子中,在类型和标识符之间放了一个空格,另一种被允许的替代方式是使用制表符。
int level; // indentation level int size; // size of table char username; // username
尽量在声明局部变量的同时初始化。唯一不这么做的理由是变量的初始值依赖于某些先前发生的计算。
只在代码块的开始处声明变量(一个块是指任何被包含在大括号”{“和”}“中间的代码)。不要在首次用到该变量时才声明之。这会把注意力不集中的程序员搞糊涂,同时会妨碍代码在该作用域内的可移植性。
void myMethod() { int int1 = 0; // beginning of method block if (condition) { int int2 = 0; // beginning of “if” block ... } }
避免声明的局部变量覆盖上一级声明的变量。例如,不要在内部代码块中声明相同的变量名:
int count; ... myMethod() { if (condition) { int count = 0; // 避免 ... } ... }
当编写类和接口是,应该(强制)遵守以下格式规则:
class Sample extends Object { int ivar1; int ivar2; Sample(int i, int j) { ivar1 = i; ivar2 = j; } int emptyMethod() {} ... }
每行之多包含一条语句,例如:
argv++; // 推荐 argc--; // 推荐 argv++; argc--; // 避免
复合语句是包含在大括号中的语句序列,形如”{ 语句 }“。例如下面各段。
一个带返回值的return语句不使用小括号”()“,除非它们以某种方式使返回值更为显见。例如:
return; return myDisk.size(); // 避免 return (size ? size : defaultSize); // 避免
if-else语句应该具有如下格式:
if (condition) { statements; } if (condition) { statements; } else { statements; } if (condition) { statements; } else if (condition) { statements; } else{ statements; }
注意:if语句总是用”{“和”}“括起来,避免使用如下容易引起错误的格式:
if (condition) // 避免 statement;
一个for语句应该具有如下格式:
for (initialization; condition; update) { statements; }
当在for语句的初始化或更新子句中使用逗号时,避免因使用三个以上变量,而导致复杂度提高。若需要,可以在for循环之前(为初始化子句)或for循环末尾(为更新子句)使用单独的语句。
一个while语句应该具有如下格式:
while (condition) { statements; }
do { statements; } while (condition);
一个switch语句应该具有如下格式:
switch (condition) { case ABC: statements; /* falls through */ case DEF: statements; break; case XYZ: statements; break; default: statements; break; }
每当一个case顺着往下执行时(因为没有break语句),通常应在break语句的位置添加注释。上面的示例代码中就包含注释/* falls through */。
一个try-catch语句应该具有如下格式:
try { statements; } catch (ExceptionClass e) { statements; } try { statements; } catch (ExceptionClass e) { statements; } finally { statements; }
空行将逻辑相关的代码段分隔开,以提高可读性。下列情况应该总是使用空行:
下列情况应该使用空格:
while (true) { ... }
a += c + d; a = (a + b) / (c * d); while (d++ = s++) { n++; }
for (expr1; expr2; expr3)
myMethod((byte) aNum, (Object) x); myMethod((int) (cp + 5), ((int) (i + 3)) + 1);
命名规范使程序更易读,从而更易于理解。它们也可以提供一些有关标识符功能的信息,以助于理解代码。
包名由全部小写字母组成,包名的前缀以com开头,包名后续部分的格式为:
[域名反转].[项目名].[模块名].[子模块名]…
例如:com.android.sim.message.sms
类名是个一名词,采用大小写混合的方式,每个单词的首字母大写。尽量使你的类名简洁而富于描述。使用完整单词,或约定成俗并且使用广泛的缩写词,如url,html,接口和类名规则一至但要使用I前缀。
继承自系统组件类的命名,后缀必须明确表示出系统组件的类别,Activity类后缀使用Activity,Service类后缀使用Service,BroadcaseReceiver类后缀使用Receiver,ContentProvider使用Provider。
方法名是一个动词或者动名词结构,采用大小写混合的方式,第一个单词的首字母小写,其后单词的首字母大写,即驼峰命名规则。
以它做什么来命名,而不是以它怎样做命名。如doUpdate(),isNumber()。
第一个单词的首字母小写,其后单词的首字母大写。变量名不应以下划线或美元符号开头,尽管这在语法上是允许的。变量名的选用应该易于记忆,即,能够指出其用途。尽量避免单个字符的变量名,除非是一次性的临时变量。临时变量通常被取名为 i,j,k,m 和 n,它们一般用于整型;c,d,e,它们一般用于字符型。
其中系统控件中在后缀中体现控件类型,如下所示:
组件名称 | 简写 | 组件名称 | 简写 |
---|---|---|---|
Button | Btn | RadioButton | Rbtn |
ImageButton | Ibtn | TextView | Tv |
ImageView | Iv | ListView | Lv |
ProgressBar | Pbar | EditText | Et |
ScrollView | Sv | CheckBox | Cb |
RelativeLayout | Rly | LinearLayout | Lly |
TableLayout | Tly | LinearLayout | Aly |
FrameLayout | Fly |
非 public 的、非 static 的字段名称以 m 开头。
static 字段名称以 s 开头。
其它字段以小写字母开头。
public class MyClass { public int publicField; private static MyClass sSingleton; int mPackagePrivate; private int mPrivate; protected int mProtected; }
类常量的声明,应该全部大写,单词间用下划线隔开。
static final int MIN_WIDTH = 4; static final int MAX_WIDTH = 999; static final int GET_THE_CPU = 1;
自定义异常的命名必须以Exception为结尾,已明确标示为一个异常。
异常实例一般使用e、ex等,在多个异常时使用该异常名或简写加E,Ex等组成,如:SQLEx,ActionEx。
命名必须以全部单词小写,单词间以下划线分割,并且尽可能的使用名词或名词组,即使用 模块名_功能名称 来命名。
addressbook_list.xml // 推荐 list_addressbook.xml // 避免
layout中所使用的id命名必须以全部单词小写,单词间以下划线分割,并且尽可能的使用名词或名词组,并且要求能够通过id直接理解当前组件要实现的功能。
EditText名 @+id/book_name_edit // 推荐 EditText名 @+id/textbookname // 避免
采用大小写混合模式,第一个单词首字母小写,其余单词首字母大写最后一个单词为该View 类型的缩写,格式如下:
逻辑名+View 类型缩写(View 缩写参照 8.4 组件名称缩写表)。
Button homeBtn
命名必须以全部单词小写,单词间以下划线分割,并且尽可能的使用名词或名词组,格式如下:
逻辑名称多个单词用下划线连接,同时使用activity名称注释。
main_menu_about main_title common_exit common_app_name
layout中使用的所有资源(如drawable,style等)命名必须以全部单词小写,单词间以下划线分割,并且尽可能的使用名词或名词组,即使用模块名_用途来命名。如果为公共资源,如分割线等,则直接用用途来命名。如:menu_icon_navigate.png
在使用单位时,如果没有特殊情况,一律使用 sp 作为文字大小的单位,将 dip 作为其他元素的单位。因为这两个单位是与设备分辨率无关的,能够解决在不同分辨率的设备上显示效果不同的问题。另外,在编码中定义控件的 margin 或 padding 属性时,SDK 里面并没有提供 dip 单位的 api 设置接口,而是提供了默认的 px 设置。
Button btn = new Button(context); LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT); lp.setMargins(0, 0, 0, 0); btn.setTextSize(12); btn.setPadding(0, 0, 0, 0);
这个时候,一般在设置 margin 和 padding 时,应该对要设置的 dip 值转换为 px 单位,而字体的大小设置中,系统默认给出了 sp 的单位,所以可以不用进行转换。转换的方法参考下面的代码:
/** * 把dip单位转成px单位 * @param context context对象 * @param dip dip数值 * @return dip对应的px值 */ public static int formatDipToPx(Context context, int dip) { DisplayMetrics dm = new DisplayMetrics(); ((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(dm); int dip = (int) Math.ceil(dip * dm.density); return dip; }
避免用一个对象访问一个类的静态变量和方法。应该用类名替代。
classMethod(); // 推荐 AClass.classMethod(); // 推荐 anObject.classMethod(); // 避免
位于 for 循环中作为计数器值的数字常量,除了-1,0 和 1 之外,不应被直接写入代码。
避免在一个语句中给多个变量赋相同的值,它很难读懂。
如果类只是用来作为信息传递的中间变量,则应该声明为信令类,即所有的全局变量都是 final 类型,在初始化时赋值。
private final String name; public Foo(String str) { name = str; } public Foo(String str ) { this.str = str; // 避免在构造函数中出现this引用 }
有时,完全忽略异常是非常诱人的。
void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { } // 错误 }
绝对不要这么做。也许你会认为:你的代码永远不会碰到这种出错的情况,或者处理异常并不重要,可类似上述忽略异常的代码将会在代码中埋下一颗地雷,说不定哪天它就会炸到某个人了。你必须在代码中以某种规矩来处理所有的异常。根据情况的不同,处理的方式也会不一样。可接受的替代方案包括(按照推荐顺序):向方法的调用者抛出异常。
void setServerPort(String value) throws NumberFormatException { serverPort = Integer.parseInt(value); }
根据抽象级别抛出新的异常。
void setServerPort(String value) throws ConfigurationException { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { throw new ConfigurationException("Port " + value + " is not valid."); } }
默默地处理错误并在 catch {} 语句块中替换为合适的值。
/** 设置端口。假如值不是数字则用80代替 */ void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { serverPort = 80; // 服务默认端口 } }
捕获异常并抛出一个新的 RuntimeException。这种做法比较危险:只有确信发生该错误时最合适的做法就是崩溃,才会这么做。
/** 设置端口,假如值不是数字则程序终止。 */ void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { throw new RuntimeException("port " + value " is invalid, ", e); } }
请记住,最初的异常是传递给构造方法的 RuntimeException。如果代码必须在 Java 1.3 版本下编译,需要忽略该异常。最后一招:如果确信忽略异常比较合适,那就忽略吧,但必须把理想的原因注释出来。
/** 假如值不是数字则使用原来的端口号。 */ void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { // 方法记录:无视无效的用户输入。 // 服务端口不会被改变。 } }
有时在捕获 Exception 时偷懒也是很吸引人的,类似如下的处理方式:
try { someComplicatedIOFunction(); // 可能抛出IOException someComplicatedParsingFunction(); // 可能抛出ParsingException someComplicatedSecurityFunction(); // 可能抛出SecurityException // 其他可以抛出Exception的代码 } catch (Exception e) { // 一次性捕获所有exceptions handleError(); // 只有一个通用的处理方法! }
不要这么做。绝大部分情况下,捕获顶级的 Exception 或 Throwable 都是不合适的,Throwable 更不合适,因为它还包含了 Error 异常。这种捕获非常危险。这意味着本来不必考虑的 Exception(包括类似 ClassCastException 的 RuntimeException)被卷入到应用程序级的错误处理中来。这会让代码运行的错误变得模糊不清。这意味着,假如别人在你调用的代码中加入了新的异常,编译器将无法帮助你识别出各种不同的错误类型。绝大部分情况下,无论如何你都不应该用同一种方式来处理各种不同类型的异常。
本规则也有极少数例外情况:期望捕获所有类型错误的特定的测试代码和顶层代码(为了阻止这些错误在用户界面上显示出来,或者保持批量工作的运行)。这种情况下可以捕获顶级的 Exception(或 Throwable)并进行相应的错误处理。在开始之前,你应该非常仔细地考虑一下,并在注释中解释清楚为什么这么做是安全的。
比捕获顶级 Exception 更好的方案:
请记住:异常是你的朋友!当编译器指出你没有捕获某个异常时,请不要皱眉头。而应该微笑:编译器帮助你找到了代码中的运行时(runtime)问题。
Finalizer 提供了一个机会,可以让对象被垃圾回收器回收时执行一些代码。
优点:便于执行清理工作,特别是针对外部资源。
缺点:调用 finalizer 的时机并不确定,甚至根本就不会调用。
结论:我们不要使用 finalizers。大多数情况下,可以用优秀的异常处理代码来执行那些要放入 finalizer 的工作。如果确实是需要使用 finalizer,那就定义一个 close() 方法(或类似的方法),并且在文档中准确地记录下需要调用该方法的时机。相关例程可以参见InputStream。这种情况下还是适合使用 finalizer 的,但不需要在 finalizer 中输出日志信息,因为日志不能因为这个而被撑爆。
当需要使用 foo 包中的 Bar 类时,存在两种可能的 import 方式:
1. import foo.*;
优点:可能会减少 import 语句。
2. import foo.Bar;
优点:实际用到的类一清二楚。代码的可读性更好,便于维护。
结论: 用后一种写法来 import 所有的 Android 代码。不过导入 java 标准库 (java.util.*、java.io.*等) 和单元测试代码 (junit.framework.*) 时可以例外。
import 语句的次序应该如下:
1. Android imports
2. 第三方库(com、junit、net、org)
3. java 和 javax
为了精确匹配 IDE 的配置,import 顺序应该是:
1. 在每组内部按字母排序,大写字母排在小写字母的前面。
2. 每个大组之间应该空一行(android、com、junit、net、org、java、javax)。
3. 原先次序是不作为规范性要求的。这意味着要么允许 IDE 改变顺序,要么使用 IDE的开发者不得不禁用 import 自动管理功能并且人工维护 import。这看起来比较糟糕。每当说起 java 规范,推荐的规范到处都是。符合我们要求的差不多就是“选择一个次序并坚持下去。”于是,我们就选择一个规范,更新规范手册,并让 IDE去遵守它。我们期望:不必耗费更多的精力,用 IDE 编码的用户就按照这种规则去 import 所有的 package。
基于以下原因,选定了本项规则:
静态 import 的使用和位置已经成为略带争议的话题。有些人愿意让静态 import 和其它 import 混在一起,另一些人则期望让它们位于其它 import 之上或者之下。另外,我们还未提到让所有 IDE 都遵守同一个次序的方法。
因为大多数人都认为这部分内容并不要紧,只要遵守你的决定并坚持下去即可。
局部变量的作用范围应该是限制为最小的(Effective Java 第 29 条)。使用局部变量,可以增加代码的可读性和可维护性,并且降低发生错误的可能性。每个变量都应该在最小范围的代码块中进行声明,该代码块的大小只要能够包含所有对该变量的使用即可。
应该在第一次用到局部变量的地方对其进行声明。几乎所有局部变量声明都应该进行初始化。如果还缺少足够的信息来正确地初始化变量,那就应该推迟声明,直至可以初始化为止。
本规则存在一个例外,就是涉及 try-catch 语句的情况。如果变量是用方法的返回值来初始化的,而该方法可能会抛出一个 checked 异常,那么必须在 try 块中进行变量声明。如果需在 try 块之外使用该变量,那它就必须在 try 块之前就进行声明了,这时它是不可能进行正确的初始化的。
// 实例化类cl,表示有序集合 Set s = null; try { s = (Set) cl.newInstance(); } catch(IllegalAccessException e) { throw new IllegalArgumentException(cl + " not accessible"); } catch(InstantiationException e) { throw new IllegalArgumentException(cl + " not instantiable"); } // 集合练习 s.addAll(Arrays.asList(args));
但即便是这种情况也是可以避免的,把try-catch 块封装在一个方法内即可。
Set createSet(Class cl) { // 实例化类cl,表示有序集合 try { return (Set) cl.newInstance(); } catch(IllegalAccessException e) { throw new IllegalArgumentException(cl + " not accessible"); } catch(InstantiationException e) { throw new IllegalArgumentException(cl + " not instantiable"); } } ... // 集合练习 Set s = createSet(cl); s.addAll(Arrays.asList(args));
除非理由十分充分,否则循环变量都应该在for语句内进行声明。
for (int i = 0; i n; i++) { doSomething(i); } for (Iterator i = c.iterator(); i.hasNext(); ) { doSomethingElse(i.next()); }
Annotation 应该位于 Java 语言元素的其它修饰符之前。 简单的 marker annotation(@Override 等)可以和语言元素放在同一行。 如果存在多个 annotation,或者annotation 是参数化的,则应按字母顺序各占一行来列出。
对于 Java 内建的三种 annotation,Android 标准的实现如下:
当需要使用@SuppressWarnings annotation时,必须在前面加上TODO注释行,用于解释“不可能消除”警告的条件。通常是标明某个令人讨厌的类用到了某个拙劣的接口。
// TODO: 第三方类 com.third.useful.Utility.rotate() 必须采用泛型 @SuppressWarnings("generic-cast") List<String> blix = Utility.rotate(blax);
如果需要使用@SuppressWarnings annotation,应该重新组织一下代码,把需要应用 annotation 的语言元素独立出来。
简称和缩写都视为变量名、方法名和类名。以下名称可读性更强:
好 | 差 |
---|---|
XmlHttpRequest | XMLHTTPRequest |
getCustomerId | getCustomerID |
class Html | class HTML |
String url | String URL |
long id | long ID |
如何对待简称,JDK 和 Android 底层代码存在很大的差异。因此,你几乎不大可能与其它代码取得一致。别无选择,把简称当作完整的单词看待吧。
关于本条规则的进一步解释,请参阅 Effective Java 第 38 条和 Java Puzzlers 第 68条。
对那些临时性的、短期的、够棒但不完美的代码,请使用 TODO 注释。
TODO 注释应该包含全部大写的 TODO,后跟一个冒号:
// TODO: Remove this code after the UrlTable2 has been checked in. // TODO: Change this to use a flag instead of a constant.
如果 TODO 注释是“将来要做某事”的格式,则请确保包含一个很明确的日期(“在2013 年 11 月会修正”),或是一个很明确的事件(“在所有代码整合人员理解了 V7 协议之后删除本段代码”)。
记录日志会对性能产生显著的负面影响。如果日志内容不够简炼的话,很快会丧失可用性。日志功能支持五种不同的级别。以下列出了各个级别及其使用场合和方式。
注意:
日志的黄金法则是:你的日志记录不会导致其它日志的缓冲区溢出,正如其他人的日志也不会让你的溢出一样。
在eclipse的preferences中,选择java → code style → formatter中选择Import,选择工程根目录下的development/ide/eclipse/目录下的android-formatting.xml。
在eclipse的preferences中,选择java → code style → Organize Imports中选择Import,选择工程根目录下的development/ide/eclipse/目录下的android.importorder。
说明:导入这两个文件,是为了与源码中的Android程序保持一致的编码规范。android-formatting.xml用来配置eclipse编译器的代码风格;android.importorder用来配置eclipse的import的顺序和结构。