英文原文地址:
https://source.android.com/source/code-style.html#java-language-rules
刚入门在进行软件开发的时候,可能在写code的时候有的亲是随心所欲想怎么写就怎么写,只要功能OK就行,而有的亲呢可能会想我的变量名要怎么见名知意, 我的
code要怎么写才健壮等等,当然我更看好后一种想法的小伙伴了,因为有一个好的代码风格,你写出来的code不光便于你自己理解,同时在团队合作的时候更便于别人
的理解与维护,并且能够体现自身的素养与功底,那么我们就根据android对想要向他们提交patch的代码风格要求来学习一下,同时规范自己的每一行代码吧。
好了,划线进入正题~~
===============================================================================================================================
如下这些规则不是指南也不是建议,而是严格的规定(口气还是满严肃的^V^).。如果想要向android贡献代码,不遵循如下的规则提交将不会被接受。
虽然Android中并非所有存在的代码都遵循了如下这些规则,但是我们要求未来添加的任何新代码必须要遵循这些规则。
先简单列一下本文的目录
Java语言规则
-不要忽略异常
-不要捕获通用异常
-不要使用Finalizer
-高校imports
Java库规则
Java风格规则
-使用javadoc标准注释
-写短方法
-在标准的位置定义属性(字段)
-控制变量的有效域
-将import语句排序
-用空格进行缩进(即不要用tab)
-遵循字段命名约定
-使用标准的Brace风格(条件语句除非在一行否则都需要加上花括号{})
-控制单行的长度
-使用标准的java注解
-把缩写当作一个单词
-使用TODO注解
-少加log
-保持一致性
Java测试代码风格规则
-遵循测试方法命名约定
正文内容如下,如果看到上面的目录就能明白,可以不用看详细解说,如果看过详细解释那么目录就可以作为条目温习了
Java语言规则
android遵循标准的java编码规则,另外添加了如下几条规则:
不要忽略异常
有时人们很容易写出像如下这种完全忽略异常的代码
void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { } }
忽略异常的方式会在你的代码里创建一座矿山留给其他人去征服在未来的某一天(相当于给别人埋坑)。你必须按照某种原则处理
你代码里面的所有异常。特定的处理方式根据场景的不同做不同的处理。
每当有人在写一个空的catch语句是都应该有一种毛骨悚然的感觉。虽然肯定有时候这样写确实是对的,但是你至少应该对这样写
有所思考。在java里你无法逃避这种毛骨悚然的感觉。----James Gosling
可以接受的备选方案如下(按照优先级排序):
-将异常抛给你的调用者
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."); } }
/** Set port. If value is not a valid number, 80 is substituted. */ void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { serverPort = 80; // default port for server } }
/** Set port. If value is not a valid number, die. */ void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { throw new RuntimeException("port " + value " is invalid, ", e); } }
那么你将需要省略掉这个源头异常,也就是这个运行时异常的真正原因。
-最后一招:如果你确定以及肯定你忽略掉这个异常是正确的选择,那么至少你应该在catch语句块上写下为什么要这么做,
或者打印log提醒曾经出现过异常
/** If value is not a valid number, original port number is used. */ void setServerPort(String value) { try { serverPort = Integer.parseInt(value); } catch (NumberFormatException e) { // Method is documented to just ignore invalid user input. // serverPort will just be unchanged. } }
有时候为了偷懒人们更愿意用如下方式捕获异常
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! }
错误的异常,也就是Error.java,因为Error也继承自Throwable,这样如果直接捕获Throwable,那么真正的Error也被捕获了。这样
做是相当危险的,这就意味着你永远不期望错误的出现,及时是RuntimeException例如ClassCastException,甚至或不捕获了应用
层级的错误处理。它隐藏了你代码的错误处理性能。这就意味着如果在你调用的代码里面某人添加了新的异常类型,编译器并不
会提醒你有新的异常类型需要做特殊处理。毕竟在大多数情况下并非所有的异常都可以用同样一种方式进行处理。
当然万事无绝对,对于此规则也有一些例外的情况:某些测试代码和你想要捕获所有错误的顶层代码(阻止这些错误显示到UI界面上,
或者为了保证接下来批量的任务可以继续执行下去)。在这种场景下你可以捕获通用异常(Exception)或者Throwable并且恰当的处理。
在做这样的决定前你需要做相当仔细的考虑,虽然你会在catch语句块中添加注解说明在这里做这样的处理是安全的。
捕获通用异常的替换处理方式如下:
-捕获可能会在对应的try语句块中所有异常,每个异常对应一个catch语句块,虽然这样写会略显尴尬,但是还是建议捕获所有的异常,
注意在catch语句中不要出现太多重复的代码
-重构你的代码使用多个try语句块去实现细粒度更高的错误处理,例如从解析中将IO处理分离出来,在每个场景中将错误分开处理。
-重新抛出这个异常,许多时候有些异常不需要在你这一层来进行处理,那就索性将它继续抛出去
记住:异常是你的朋友!当编译器想你抱怨你没有捕获某一个异常的时候,不要皱眉,要微笑,编译器只是想在编译的时候就告诉你,
你有需要处理的潜在错误,不用到运行时候发现再处理
不要使用Finalizer
Finalizer是一种能够在对象在被垃圾回收机制回收时执行一段代码的方法。
优点:可以方便做清理工作,尤其是外部资源
缺点:Finalizer什么时候会被调用是没有保证的,甚至它是否会被调用也是不确定的。
抉择:我们不使用Finalizer。在大多数场景下,你可以将需要在Finalizer中所做的处理用一个好的异常处理来取代。如果你确实需要使用Finalizer,
你可以定义一个closer方法(或者类似的其它方法)并且在注解中写清楚该方法什么时候需要被调用。具体例子可以参考InputStream类。在这种情
况下在Finalizer中打印提示的log信息是比较恰当的方式但并不是必须的,只要这里打印log不会太频繁以至于淹没整理日志。
高效Imports
当你想用用foo包中的Bar类时,你有如下两种方式可以将它import进来
1.import foo.*;
优点:潜在的减少了import语句的数量
2.import foo.Bar;
优点:比较清晰的能够知道具体哪个类是需要用到的,对于维护者来说增加了代码的可读性。
抉择:android代码中所有的import都使用后一种方式,但是java标准库(java.util.*, java.io.*, etc)和单元测试代码(junit.framework.*)例外。
java类库规则
在使用android中的java库和工具是有约定的。在某些情况下,在一些重要的途径中约定会有所改变,还有一些旧代码中会使用一些被废弃的模式以及
类库。当在处理这类代码时,允许跟随已存在的代码风格。但当在创建新的组件的时候不要再使用被废弃的库。
Java风格规则
使用javadoc标准注解
每一个文件的顶部都应该有版权信息语句,然后跟上包命语句,以及一系列的import语句,每一类语句块之间用一个空白行隔开。在这之后就是类或者
接口的声明。在javadoc注解中描述清楚这个类或者接口具体是用来做什么的。参考如下:
/* * Copyright (C) 2013 The Android Open Source Project * * 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; /** * Does X and Y and provides an abstraction for Z. */ public class Foo { ... }
以第三人称的口吻来描述。
/** Returns the correctly rounded positive square root of a double value. */ static double sqrt(double a) { ... }
/** * Constructs a new String by converting the specified array of * bytes using the platform's default character encoding. */ public String(byte[] bytes) { ... }
如果你的方法做了一些很复杂的事情(例如强制约束或者有比较重要的副作用),这样的方法你就必须要加上注解了。也就是如果不是类似属性“Foo”这种
很明显一眼就能看出其表达的意思,那么你就应该要写出具体的注解。
这样每一个你所写的方法,公开或者其它的,都会从javadoc中收益,如果javadoc注解写得很清楚的话,更利于后期自己或其他人的维护。公开方法属于
API的的一部分因此需要javadoc注解的支持。
Android目前并没有指定javadoc注解的风格。但是你应该遵循How to Write Doc Comments for the Javadoc Tool这个链接中的说明来添加。
写短小的方法
在某种程度上这是可行的,方法应该尽量短小并且集中。然而在有些场景下长方法更合适,因此在方法长度上没有硬性的规定。那么如果方法的长度超过了
40行,就因该考虑一下是否能够在不影响方法的架构的基础上将方法分成几个小的方法
将属性字段定义放在标准的位置
属性字段应该定义在文件的顶部,即类的顶部,或者在即将使用这个字段的方法的上面。
限制变量的作用域
本地变量的作用域应该尽量控制在最小的范围内。这样做能够尽量增加你代码的可读性以及可维护性,减少出现错误的可能性。变量的声明尽量在某一范围的
语句块内,这个语句块范围能够尽量小,最好是刚好覆盖所有使用这个变量的语句。
本地变量应该在第一次使用前进行声明,在每个本地变量被声明的附近应该有对其进行初始化的语句。如果在声明时没有足够的信息来对其进行合理的初始化,
那么你应该尽量推迟该变量的初始化指导你能够进行初始化为止。
这个规则的例外是关于try-catch语句块的。如过变量的初始化是依靠需要进行try-catch进行异常检查的方法调用返回的值,那么该变量必须在try语句块中
进行初始化。如果该变量必须会在try语句块外被用到,那么你必须将该变量的声明提出到try语句块外,并给初始化一个不算是很合理的值。
// Instantiate class cl, which represents some sort of 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"); } // Exercise the set s.addAll(Arrays.asList(args));
但是即使在这种情况下,也可以将try-catch封装在方法中来避免,如下所示
Set createSet(Class cl) { // Instantiate class cl, which represents some sort of Set try { return (Set) cl.newInstance(); } catch(IllegalAccessException e) { throw new IllegalArgumentException(cl + " not accessible"); } catch(InstantiationException e) { throw new IllegalArgumentException(cl + " not instantiable"); } } ... // Exercise the set Set s = createSet(cl); s.addAll(Arrays.asList(args));
循环变量应该被声明在for语句内,除非因此会有编译错误除外:
for语句
for (int i = 0; i < n; i++) { doSomething(i); }
for (Iterator i = c.iterator(); i.hasNext(); ) { doSomethingElse(i.next()); }
import语句排序
import语句的顺序应该按照如下分组进行排列:
1.Android类的import语句
2.第三方类库import语句(com,junit,net,org)
3.java和javax
要完全匹配IDE的设置,import顺序应该是:
-每组import按照英文字母排序,并且大写字母排在小写字母的前面(例如 Z应该排在a的前面)
-在每组import语句之间应该有一个空白行来隔开(android, com, junit, net, org, java, javax)
import的排序原本上是没有风格要求的。这就意味着要么IDE自动导入的时候总是在更改import的顺序,或者
用IDE研发的coder关闭了IDE的自动import管理功能而自己手动进行import的书写。这样做被认为是很糟糕
的。当IDE中java风格被设定的时候,首选风格会影响所有方面。它几乎满足我们“选择一个排序,并保持一致”
的需求。因此我们可以选择一种风格,更新风格指南,使得所选风格遵循import排序的这种规则。我们期望所有
用IDE进行编码的工程师能够在没有其它工程师的努力下遵循这种import排序的模式。
这种风格的选择需要满足如下条件:
-做imports的人想要查看第一个import应该在android的顶部
-做imports的人想要查看最后一个import应该在java的底部
-人类能够很轻松的遵循这种风格
-集成开发环境能够遵循这种风格
static import的使用以及放置的位置稍有争议。有的人认为静态导入应该穿插在其它各组imports中,而有的人认为
应该单独给静态导入分一个组。此外,我们还没确定一种方法使得所有IDE都遵循一种排序方式。
当大多数人都认为import排序是一种很小的事情时,请作出你自己的裁决并保持一致。
用空格进行缩进
我们使用4个空格作为一个缩进块,从来不使用tab进行缩进。当有疑问时,请保持和周围code一致。
我们使用8个空格作为换行的缩进,包括方法调用和变量的赋值,例如:
正确的方式
Instrument i = someLongExpression(that, wouldNotFit, on, one, line);
Instrument i = someLongExpression(that, wouldNotFit, on, one, line);
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; }
class MyClass { int func() { if (something) { // ... } else if (somethingElse) { // ... } else { // ... } } }
if (condition) { body(); }
if (condition) body();
if (condition) body(); // bad!
限制单行的长度
在你的代码中每一行应该最多有100字符的长度。
这个规则有很多的争议,不过最后的裁决是100字符的长度已经是长度的最大值了,不能超过这个长度了。
例外1:如果在注释中有命令的例子或者url链接长度超过100个字符,为了命令以及url链接的方便复制粘贴
长度可以超过100个字符
例外2:import语句可以不受这个规则的限制,因为毕竟人很少关注这个,而且这样也简化了工具(IDEs)的开发。
使用标准的Java注解
注解应该优先于其它修改针对统一语言元素来讲。简单的修饰注解(例如@Override)应该同其所修饰的语言元素
处于同一行[关于这一条我搜了下android的代码,有的确实遵循了,但是有没有,个人认为还是单起一行比较美
观]。如果需要好几个注解,或者参数注解,那么每个注解应该单独占一行。
有三个在java中预定义的注解在Android中的标准做法如下:
-@Deprecated:此注解必须用在被注解的元素是即将被弃用的,不鼓励再继续使用。当你使用这个注解时同时需
要在该元素上加载@Deprecated的javadoc标签并写明转换的实现方法。另外,需要注意的是即使别标注为
@Deprecated的方法还是应该能够工作的。
如果你在旧的代码中看到有方法被加上了@Deprecated的标签请给它添加上@Deprecated的注解
-@Oevrride:此注解必须被用在子类重写在父类中声明或实现的方法上。
例如,如果你使用了@inheritdocs javadoc标签,并且来自于某一个类(注意是类不是接口),那么与此同时你
就必须给重写的父类方法添加上@Overrride注解
-@SuppressWarnings:此注解应该仅被用在无法消除警告的情况下。如果一个警告通过了“不可能消除”的测试,那么
此注解就必须被使用了,为了确保所有的警告都准确反应出了代码中的问题。
当SuppressWarnings注解是必须的时候,它的前面必须加上一个TODO注释,解释“不能被消除”的情况。这通常是被
鉴定出一个违规类有不太合适的接口。例如:
// TODO: The third-party class com.third.useful.Utility.rotate() needs generics @SuppressWarnings("generic-cast") List<String> blix = Utility.rotate(blax);
// TODO: Remove this code after the UrlTable2 has been checked in.
// TODO: Change this to use a flag instead of a constant.
For example: testMethod_specificCase1 testMethod_specificCase2 void testIsDistinguishable_protanopia() { ColorMatcher colorMatcher = new ColorMatcher(PROTANOPIA) assertFalse(colorMatcher.isDistinguishable(Color.RED, Color.BLACK)) assertTrue(colorMatcher.isDistinguishable(Color.X, Color.Y)) }