一个项目从开始开发到后期的迭代版本,是多个人共同开发的结果,所以当团队人多起来的时候,编译的规范就显得很重要.这里整理了普遍使用的一些编译规范,希望大家都遵守.
简单说明
Android下的应用程序大部分是基于java语言编写的,所以规范都是按照java的来
Java 样式规则
使用Javadoc 标准注释
每个文件应该在顶部,有版权的声明接着包和导入语句 (由一个空行分隔每个块),最后是类或接口声明.在 Javadoc 注释中,描述类或接口做些什么.
/*
* Copyright (C) 2016 Globalegrow E-Commerce
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.foo;
import android.os.Blah;
import android.view.Yada;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 做 X 和 Y 还有为 Z 提供一种抽象
*
* @author: zhengwu
* @date: 2016-12-29
*/
public class Foo {
...
}
每个类和公共方法,你写必须与至少一个句子描述的类或方法并包含的 Javadoc 注释.这句话应该开始用第三人称描述性动词.
例子:
单行注释
/** 返回一个双精度值的正确圆正平方根. */
static double sqrt(double a) { ...}
or
多行注释
/**
* 构造一个新的字符串,通过转换指定的字节数组的
* 使用平台的默认字符编码.
*/
public String(byte[] bytes) { ...}
你不需要为简单的 get 和 set 方法写 Javadoc ,如 setFoo() .如果该方法做更复杂的事物 (如强制约束或有一个重要的副作用),这个时候你必须记录它.
写简短的方法
在可行的情况下,保持方法小而且比较集中.我们认识到,很长的方法有时候是适当的, 所以没有硬性限制放在方法长度.如果一种方法超过 40 行左右,想想是否它可以被分解而不伤害程序的结构。
在标准的地方定义字段
在顶部的文件或紧接使用它们的方法之前定义字段,这里建议在顶部的文件中定义,这样方便查找.
限制变量范围
将局部变量的范围降到最低。通过这样做,您增加可读性和可维护性代码并减少出错的可能性.每个变量应该在包含变量所有使用的最内层块中声明.
局部变量应该在它们首次使用的点上声明。 几乎每个局部变量声明都应该包含一个初始化器。 如果你还没有足够的信息来明智地初始化变量,推迟声明直到你这样做.
异常是try-catch语句. 如果一个变量用一个抛出被检查异常的方法的返回值初始化,它必须在try块中初始化.如果值必须在try块之外使用,那么它必须在try块之前声明,在那里它还不能明智地初始化:
// 初始化类cl, 这个类用来表示某种Set
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");
}
...
// 使用这个 set
s.addAll(Arrays.asList(args));
下面的这种写法是推荐的
Set createSet(Class cl) {
// 初始化类cl, 这个类用来表示某种Set
try {
return (Set) cl.newInstance();
} catch(IllegalAccessException e) {
throw new IllegalArgumentException(cl + " not accessible");
} catch(InstantiationException e) {
throw new IllegalArgumentException(cl + " not instantiable");
}
}
...
// 使用这个 set
Set s = createSet(cl);
s.addAll(Arrays.asList(args));
循环变量应该在for语句本身中声明,除非有强制的理由不这样做:
for (int i = 0; i < n; i++) {
doSomething(i);
}
and
for (Iterator i = c.iterator(); i.hasNext(); ) {
doSomethingElse(i.next());
}
导入语句的顺序
import语句的排序是:
- Android imports
- Imports from third parties (com, junit, net, org)
- java and javax
还有
- 每个分组中按字母顺序排列,大写字母前加小写字母(e.g. Z before a)
- 在每个主要分组(android,com,junit,net,org,java,javax)之间用空行分隔
使用缩进空格
使用4个空格缩进块,而不是制表符
我们使用8个空格缩进进行换行,包括函数调用和赋值,例如,这是正确的:
Instrument i =
someLongExpression(that, wouldNotFit, on, one, line);
下面这个是不正确的
Instrument i =
someLongExpression(that, wouldNotFit, on, one, line);
换行
没有一个精确的公式解释如何换行,并且经常不同的解决方案是有效的.然而,有一些规则可以应用于常见的情况, 这里列举了常见的几种.
int longName = anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne
+ theFinalOne;
int longName =
anotherVeryLongVariable + anEvenLongerOne - thisRidiculousLongOne + theFinalOne;
Picasso.with(context)
.load("http://ribot.co.uk/images/sexyjoe.jpg")
.into(imageView);
loadPicture(context,
"http://ribot.co.uk/images/sexyjoe.jpg",
mImageViewProfilePicture,
clickListener,
"Title of the picture");
命名约定
变量的命名
- 非公共,非静态字段名以m开头
- 静态字段名称以s开头
- 其他字段以小写字母开头
- 公共静态最终字段(常量)所以大写字母并以下画线连接
例子
public class MyClass {
public static final int SOME_CONSTANT = 42;
public int publicField;
private static MyClass sSingleton;
int mPackagePrivate;
private int mPrivate;
protected int mProtected;
}
Android 的许多元素(如SharedPreferences,Bundle或Intent)都使用键值对方法,因此对这个也规范如下:
所有的变量前面都加static final
| 组件 | 命名前缀 |
| ------------- |: -----:|
|SharedPreferences |PREF_
|Bundle |BUNDLE_
|Fragment Arguments |ARGUMENT_
|Intent Extra | EXTRA_
|Intent Action |ACTION_
|BroadCast Action|ACTION_
请注意,Fragment - Fragment.getArguments()的参数也是一个Bundle。 然而,因为这是一个很常见的使用Bundles,我们为它们定义一个不同的前缀。
例子:
// Note the value of the field is the same as the name to avoid duplication issues
static final String PREF_EMAIL = "PREF_EMAIL";
static final String BUNDLE_AGE = "BUNDLE_AGE";
static final String ARGUMENT_USER_ID = "ARGUMENT_USER_ID";
// Intent-related items use full package name as value
static final String EXTRA_SURNAME = "com.myapp.extras.EXTRA_SURNAME";
static final String ACTION_OPEN_USER = "com.myapp.action.ACTION_OPEN_USER";
如果是启动Activity
public static Intent getStartIntent(Context context, User user) {
Intent intent = new Intent(context, ThisActivity.class);
intent.putParcelableExtra(EXTRA_USER, user);
return intent;
}
如果是启动Fragment
public static UserFragment newInstance(User user) {
UserFragment fragment = new UserFragment;
Bundle args = new Bundle();
args.putParcelable(ARGUMENT_USER, user);
fragment.setArguments(args)
return fragment;
}
注意1:这些方法应该在onCreate()之前的类的顶部
注意2:如果我们提供上面描述的方法,extras和参数的键应该是私有的,因为它们不需要暴露在类外部。
类的命名
类名使用大写骆驼拼写法来命名,如果类是继承了相应的组件,则要以组件名字结尾来命名,如:SignInActivity, SignInFragment, ImageUploaderService,ChangePasswordDialog
| 类 | 命名格式 | 示例 |
| ------------- |: -------------:|: -----:|
| Activity | 描述+Activity |HomeActivity,MainActivity |
|Fragment |描述+Fragment | 如购物车,CartFragment|
| Service | 描述+Service | PushMessageService |
| BroadcastReceiver | 描述+Receiver | OnlineReceiver |
|ContentProvider | 描述+Receiver| 如联系人的内容提供者,ContactsProvider|
|Dialog|描述+Dialog|如普通的选择提示对话框,ChoiceDialog|
|Adapter|描述+Adapter|如联系人列表,ContactsListAdapter|
|基础功能类|Base+父类名| 如BaseActivity,BaseFragment|
|工具类|描述+Utils|如处理字符串的工具类,StringUtils|
|管理类|描述+Manager|如管理联系人的类,ContactsManager|
方法的命名
| 命名风格 | 含义 |
| ------------- |: -----:|
|initXX()|初始化,如初始化所有控件initView()|
|isXX()|是否满足某种要求,如是否为注册用户isRegister()|
|displayXX()|显示提示信息,如displayXXDialog,displayToast,displayXXPopupWindow|
|saveXX()| 保存XX数据|
|resetXX()| 重置XX数据|
资源的命名
资源的名字是用小写的、下划线连接
图片资源的命名,没有在下面列出来的图片可以参考: 图片的用途_用到的地方 来命名,如bg_sign_in, start_splash
用到的一些图标的命名
Layout 下资源的命名
| 组件 | 类名字 | 布局文件名 |
| ------------- |: -------------:|: -----:|
|Activity| UserProfileActivity| activity_user_profile.xml|
|Fragment| SignUpFragment | fragment_sign_up.xml|
|Dialog| ChangePasswordDialog| dialog_change_password.xml|
|AdapterView Item| -- | item_person.xml|
|抽取出来复用的xml布局(include)| --|include_bottom_tabs|
当XML元素没有任何内容时,您必须使用自动关闭标记。
正确的
<TextView
android:id="@+id/text_view_profile"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
不对的
<TextView
android:id="@+id/text_view_profile"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</TextView>
Menu 下资源的命名
与布局文件类似,菜单文件应与组件的名称匹配。 例如,如果我们定义将要在UserActivity中使用的菜单文件,则文件的名称应为activity_user.xml.
注意这里不要加menu,因为已经在menu目录下了
Values 下的文件命名
values文件夹中的资源文件应为复数,如:strings.xml,styles.xml,colors.xml,dimens.xml,attrs.xml
colors.xml: 定义颜色值, 如果是通用的颜色,用具体的颜色名称来表示.如果是只有单独的界面用到的或者有 代表性的,则用如goods_price,progressbar_bg,main_window_background
themes.xml: 定义主题,所有的主题名字以Theme结尾
styles.xml: 定义使用的样式,所有的样式名字以Style结尾
strings.xml: 定义所有使用的字符串, 字符串名称以标识其所属性的前缀开头,看下面表格:
| 前缀 | 描述 |
| ------------- |: -----:|
|error_ |An error message
|msg_ |A regular information message
|title_ |A title, i.e. a dialog title|
|action_ |An action such as "Save" or "Create"|
id 的命名
都是用小写字母、下划线链接
| 组件 | 描述 |
| ------------- |: -----:|
|TextView|_text|
|ImageView|_image|
|Button|_button|
|CheckBox|_check|
|ProgressBar|_bar或者_view|
|ScrollView|_view|
|自定义view|_view|
|LinearLayout|_layout或者_container|
|RelativeLayout|_layout或者_container|
|Fragment|fragment|
|Menu|menu|
<ImageView
android:id="@+id/profile_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<menu>
<item
android:id="@+id/menu_done"
android:title="Done" />
</menu>
使用标准括号样式
大括号不自己另起一行; 他们和他们之前的代码在同一行:
class MyClass {
int func() {
if (something) {
// ...
} else if (somethingElse) {
// ...
} else {
// ...
}
}
}
我们需要在条件语句周围添加括号. 特例:如果整个条件(条件和正文)符合一行,您可以(但不是必须)将它全部放在一行上,例如,这是可以接受的:
if (condition) {
body();
}
下面这样也可以
if (condition) body();
但是这样就不可以
if (condition)
body(); // 不好的!容易造成歧义
限制代码每一行的长度
代码中的每行文字长度应试最多为100个字符,也有特例:
- 如果注释行包含示例命令或长度超过100个字符的文字URL,那么该行可能长于100个字符,以便于剪切和粘贴.
- import行可以超过限制,因为人们很少看到它们(这也简化了工具写入)
正确使用首字母缩略词
将缩写词作为命名变量,方法和类中的单词,以使名称更易读:
使用TODO注释
对临时代码使用TODO注释,短期解决方案,或者足够好但不完美的代码。 TODO应在所有大写字母中包含字符串TODO,后跟冒号:
Java 语言规则
不要忽略异常的处理
可能很容易编写完全忽略异常的代码,例如:
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 {}块中替换一个适当的值
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) {
serverPort = 80; // default port for server
}
}
不捕获泛型异常
try {
someComplicatedIOFunction(); // may throw IOException
someComplicatedParsingFunction(); // may throw ParsingException
someComplicatedSecurityFunction(); // may throw SecurityException
// phew, made it all the way
} catch (Exception e) { // I'll just catch all exceptions
handleError(); // with one generic handler!
}
导入具体的包路径
当你想使用包foo中的类Bar时,有两种可能的方法来导入它:
- import foo.*;
- import foo.Bar; //使用这个方式,代码可读性更强.
其他还需要注意的地方
- 缩放保证不失真的,图片可以做成点9图
- strings.xml中使用%1$s实现字符串的通配
- 代码中不出现中文,最多注释中可以出现中文