统一的代码风格在多人协作开发中的作用是不言而喻的,通过参考一些比较优秀的实践,这里大量参考了阿里巴巴 JAVA 开发手册,再结合了个人的思考,制定了这么一套规范,由于个人的认识是非常有限的,本规范也肯定存在很多不合理和需要补充的东西,在这里恳请大家根据自己的实践和工作中,提出一些中肯的建议和修改意见。
意义
- 高度有秩序的代码具有天然的美感,写代码的人心情会好很多。
- 减低开发人员流动带来的风险。
- 对于新人能更好地理解和参与到开发中。
- 更好的可维护性。
- 随之带来更好的产品质量和开发效率提升。
命名规约
- 【强制】所有编程相关命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
- 【强制】所有编程相关的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。
- 【强制】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。
类的命名
- 【强制】抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。
- 【强制】相关组件类应该以组件名作为后缀以便识别。
组件 | 命名规则 | 命名举例 |
---|---|---|
Activity | ×××Activity | MainActivity |
Fragment | ×××Fragment | HomeFragment |
Dialog | ×××Dialog | AlertDialog |
Service | ×××Service | DownloadService |
BroadcastReceiver | ×××Receiver | LoginReceiver |
ContentProvider | ×××Provider | UserProvider |
Adapter | 名字+类型+Adapter | ArticleListAdapter, ImageGridAdapter |
AsyncTask | ×××Task | LoginTask |
Handler | 名字+所在线程+Handler | HomeUIHandler, CompressWorkHandler |
ViewHolder | VH + 名字 | VHArticle |
- 【推荐】实用工具类命名成
**Utils
、**Helper
。 - 【推荐】EventBus 发布的事件名命名成以 Event 结尾,比如
LoginEvent
。 - 【推荐】如果使用到了设计模式,建议在类名中体现出具体模式。
- 【推荐】接口一般以大写
I
开头,回调的接口一般为Listener
或者Callback
结尾。
方法命名
- 【强制】POJO 类中的任何布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。
==反例==:定义为基本数据类型 boolean isSuccess; 的属性,它的方法也是 isSuccess() ,部分Json 框架在反向解析的时候,“以为”对应的属性名称是 success,导致属性获取不到,进而抛出异常。 - 【强制】EventBus 回调的方法必须和相关 Event 类一致,比如LoginEvent类的方法签名为:
public final void onLoginEvent(LoginEvent e);
- 【推荐】客户端逻辑大部分是基于事件驱动的,应该以
on
来开头,比如登录按钮的点击可以是:onLoginClick()
。
变量命名
- 【强制】常量命名全部大写,单词间用下划线隔开。
- 【强制】局部变量和一般类变量以小写字母开头。
- 【推荐】static 类变量名称以
s
开头,final类变量用f
开头。 - 【推荐】组件相关的可以用【全部首字母+名字】的命名,比如 id 是 R.id.tv_login 的控件名称是:
tvLogin
。
资源文件命名
除了 attr 和 style 资源遵循驼峰命名之外,其他资源的命名统一用小写字母+下划线的风格。
- 【推荐】布局文件命名:
布局类型 | 命名规则 | 例子 |
---|---|---|
Activity | activity_××× | HomeActivity 对应 activity_home |
Dialog | dlg_××× | LoginDialog 对应 dlg_login |
Fragment | frag_××× | HomeFragment 对应 frag_home |
页面标题 | title_××× | title_main |
列表Footer | footer_××× | footer_article |
列表Header | header_××× | header_article |
列表的Item | item_××× | item_article |
可重用嵌入布局 | include_ ××× | include_navagator |
分割线 | line_+颜色+大小 | line_gray_1px |
- 【推荐】id命名:截取相关组件首个字母作为开头,然后以下划线作为分割。比如【TextView:tv_name】。
- 【推荐】图片命名:【类别+名称+状态(如果有)】,根据图片的用途可以分类为图标「ic」,背景「bg」,前景「fg」(比较少用到),图片「img」(比如引导图,开机页面),如果有多个状态用同一张图,那么首选声明为 【××_selected】 。
状态 | 示例 |
---|---|
正常状态 | ic_login_primary |
checked | ic_login_checked |
pressed | ic_login_pressed |
selected | ic_login_selected |
disable | ic_login_disable |
- 【推荐】drawable命名:
- xml定义图形:参考图片命名规则,在以上基础上添加辨识,比如: bg_login_shape, bg_login_vector, bg_login_shape_primary。
- 多个状态drawable: 命名为 【××_selector】,比如 bg_login_selector。
- 【推荐】颜色命名:
- 单色:命名规则为【类型+名字+状态(如果有)】,我把用到颜色的元素类型做了个归类,分别是字体「text」,背景「bg」,线「line」,还是一些常规色(比如白色,透明等),正例:text_black_primary, bg_black_selected, line_orange。
- 字体selector颜色:【名字 + text_selector】 ,比如 tab_item_text_selector。
- 【推荐】尺寸命名:通用性比较强和需要适配的在 dimens.xml 中设置,一般的可以在代码中直接写。
- 字体大小: 【font_××】,××和设计稿保持一致的换算。
- 间距:组件外用【××_margin】,组件内用【××_padding】, 例子: app_left_margin。
- 控件大小:【宽度:××_width】,【高度:××_height】,其他【××_size】,例子: login_button_height。
- 【推荐】其他:文字不得直接写到 java 代码和 layout 代码里面,应该抽离到相关的 strings 资源文件中;menu 和动画资源命名不需要加前缀或者后缀,能表达意图即可,因为 R.menu 或者 R.anim 已经携带了相关的信息。
代码风格
- 【强制】大括号的使用约定。如果是大括号内为空,则简洁地写成{}即可,不需要换行;如果
是非空代码块则:
- 左大括号前不换行。
- 左大括号后换行。
- 右大括号前换行。
- 右大括号后还有 else 等代码则不换行;表示终止右大括号后必须换行。
- 【强制】 左括号和后一个字符之间不出现空格;同样,右括号和前一个字符之间也不出现空格; 代码块缩进 4 个空格,如果使用 tab 缩进,请设置成 1 个 tab 为 4 个空格。
1 | 正例: |
- 【强制】任何运算符左右必须加一个空格。
说明:运算符包括赋值运算符 =、逻辑运算符 &&、加减乘除符号、三目运行符等。
- 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。
- 【强制】单行字符数限制不超过 120 个,超出需要换行,换行时,遵循如下原则:
- 换行时相对上一行缩进 4 个空格。
- 运算符与下文一起换行。
- 方法调用的点符号与下文一起换行。
- 在多个参数超长,逗号后进行换行。
- 在括号前不要换行,见反例。
1 | 正例: |
- 【强制】方法参数在定义和传入时,多个参数逗号后边必须加空格。
正例:下例中实参的 “a” ,后边必须要有一个空格。
method("a", "b", "c")
;
【强制】在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码,避免使用下面的形式:
if (condition) statements
;【推荐】推荐尽量少用 else, if-else 的方式可以改写成:
1 | if(condition){ |
- 【推荐】循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量,进行不必要的
try-catch
操作(这个 try-catch 是否可以移至循环体外),遍历长度的提前获取;避免使用foreach
,迭代器,尽量采用下标的形式
1 | 正例: |
编码规约
OOP 规约
- 【强制】过时的类或者方法不要使用,覆盖的方法要用
Override
声明。这样当写错覆盖方法时可以得到编译错误提示。 - 【强制】Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
- 【强制】单例或者实用类构造方法应该声明为 private。
- 【强制】如果用到 FastJson 来解析数据,必须在相关 getter 和 setter 方法上添加注解。
- 【推荐】声明为 public 的方法要对参数进行校验,private 的可以不用。
- 【推荐】当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读。
- 【推荐】 类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。
- 【推荐】循环体内,字符串的联接方式,使用 StringBuilder 的 append 方法进行扩展。
- 【推荐】类成员与方法访问控制从严:
- 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
- 工具类不允许有 public 或 default 构造方法。
- 类非 static 成员变量并且与子类共享,必须是 protected。
- 类非 static 成员变量并且仅在本类使用,必须是 private。
- 类 static 成员变量如果仅在本类使用,必须是 private。
- 若是 static 成员变量,必须考虑是否为 final。
- 类成员方法只供类内部调用,必须是 private。
- 类成员方法只对继承类公开,那么限制为 protected。
常量定义
- 【强制】不允许出现任何魔法值(即未经定义的常量)直接出现在代码中。
- 【强制】long 或者 Long 初始赋值时,必须使用大写的 L,不能是小写的 l ,小写容易跟数字 1 混淆,造成误解。
- 【推荐】不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。如:缓存
相关的常量放在类:CacheConsts 下;系统配置相关的常量放在类:ConfigConsts 下。 - 【推荐】避免使用枚举类型,用
@IntDef
进行替代。
异常处理
【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
1
2
3
4
5
6反例:
void setServerPort(String value) {
try {
serverPort = Integer.parseInt(value);
} catch (NumberFormatException e) { }
}【强制】异常不要用来做流程控制,条件控制,因为异常的处理效率比条件分支低。
- 【强制】finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
- 【强制】不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。
- 【强制】捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。==说明==:如果预期抛的是绣球,实际接到的是铅球,就会产生意外情况。
【推荐】不要偷懒直接捕获顶级异常,这样会把Runtime的异常也囊括进来,要明确每种异常出现的场景被给出相应的处理。
1
2
3
4
5
6
7
8
9反例:
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!
}【推荐】除非有充分的理由,不要捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类,如:IndexOutOfBoundsException / NullPointerException,这类异常由程序员预检查来规避,保证程序健壮性。
代码注释
- 【强制】类、类属性、类方法的注释必须使用 javadoc 规范,使用/*内容/格式,不得使用//xxx 方式。
- 【强制】所有的抽象方法(包括接口中的方法)必须要用 javadoc 注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
- 【强制】所有的类都必须添加创建者信息。
【强制】对那些临时性的、短期的、够棒但不完美的代码,请使用TODO注释。
1
// TODO: Change this to use a flag instead of a constant.
【强制】方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/ /注释,注意与代码对齐。
1
2
3
4
5
6
7
8
9
10void method(){
// 打个招呼
sayHello();
/*
这里保持移动,
并不断对步数加1
*/
keepMove();
}【参考】好的命名、代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一
个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释是相当大的负担。
Android 实践
- 【强制】避免对同个 View 进行多次查找,应该缓存起来,比如列表中使用的 ViewHolder 模式。
1 | 反例: |
- 【强制】不要使用 System.out.println() ,printf() 打印日志。
- 【强制】调试日志要有个开关,在正式发布的时候不能出现应用日志打印。
- 【强制】读文件,数据库,网络请求等一些耗时操作必须在异步线程中执行。
- 【强制】不能粗暴的把 Activity, Service 对象或者一些比较重的对象直接声明为 static 变量,如果必须这么做,请注意它们的生命周期变化。
- 【强制】每引入一个第三方库,要检查是否加入相关的混淆规则。
- 【强制】自定义属性动画 (ObjectAnimator) 的 getter 和 setter 方法时要注意防止混淆。
- 【强制】在 (Activity,Fragment) 组件退出时要保证相关资源得到释放,比如 EventBus 订阅的事件,注册的广播,轮播任务等。
- 【强制】对于Api Level大于等于23的设备要进行运行时权限检查。
- 【推荐】在使用 Thread,Handler,AsyncTask,Timer,耗时的匿名回调类,内部类时,考虑声明成静态的内部类,并且弱引用或者软引用来处理相关的大对象,并且要注意生命周期的变化,避免出现内存泄漏问题。
- 【推荐】利用 TextView 的
drawableLeft
等来减少 ImageView 的使用。 - 【推荐】通过使用 RelativeLayout 或者 ConstraintLayout 来减少布局的层次。
- 【推荐】在
findViewById
尽可能选取一个比较近的父节点来减少查找时间。 - 【推荐】Bundle 中的常量 Key 值统一在一个文件中,而不是直接在 Activity 或者 Fragment 中,比如 BundleKeys 文件中。
- 【推荐】可滑动列表复用的 ViewHolder 对象中,对于一些事件对象应该尽可能地复用。
- 【推荐】对隐式 Intent 的运行时调用
resolveActivity
进行检查保护。 - 【推荐】使用 NotificationCompat 兼容包来处理消息通知。
- 【推荐】延迟创建没有用到的对象,比如 Adapter 的列表对象,应该在请求数据取到之后再去创建,而不是提前创建好。
- 【推荐】使用 ArrayMap,Sparse×× 系列对象来减少内存消耗和避免AutoBoxing。
- 【推荐】更新列表局部数据避免粗暴地调用 Adapter.notifyDataSetChanged,应该只更新变化的部分。
- 【推荐】用 @Nullable, @NonNull, @IdRes, @MainThread 等Support Annotations注解来增强代码的编译期检查和可读性。
- 【参考】如果不用
getColor(int id, Theme theme)
等相关方法,至少使用 ContextCompat 来获取相关 Color 或者图片资源。 - 【参考】尽可能通过 Fragment 来实现视图层,Activity 只是负责嵌入和管理。
- 【参考】通过 Picasso,Glide 等开源组件去加载和缓存你的图片。