统一规范标准将有助于提高行业编码规范化水平,帮助行业人员提高开发质量和效率、大大降低代码维护成本
2017年年初,首次公开了《阿里巴巴Java开发手册》,自从第一个版本起,倍受业界关注。为了让开发者更加方便、快速的将规范推动并实行起来,阿里巴巴基于手册内容,研发了一套自动化的IDE检测插件(IDEA、Eclipse), 该插件在扫描代码后,将不符合《手册》的代码按Blocker/Critical/Major三个等级显示在下方,甚至在IDEA上,还基于Inspection机制提供了实时检测功能,编写代码的同时也能快速发现问题所在。对于历史代码,部分规则实现了批量一键修复的功能,提升代码质量,提高团队研发效能。
接下来我们先来学习学习阿里编程规范再基于Intellij来学习阿里插件的安装及使用:
代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。 反例:_name / name / n a m e / n a m e / n a m e name / name_ / name name/name/name / name
代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。 说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,纯拼音命名方式更要避免采用。比如:
正例:renminbi / alibaba / taobao / youku / hangzhou 等国际通用的名称,可视同英文。
反例:DaZhePromotion [打折] / getPingfenByName() [评分] / int 某变量 = 3在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数字 1 混淆,造成误解。
杜绝完全不规范的缩写,避免望文不知义。 反例:AbstractClass“缩写”命名成 AbsClass;condition“缩写”命名成 condi,此类随意缩写严重降低了代码的可阅读性。
为了达到代码自解释的目标,任何自定义编程元素在命名时,使用尽量完整的单词组合来表达其意。比如:
正例:在 JDK 中,表达原子更新的类名为:AtomicReferenceFieldUpdater。
反例:int a 的随意命名方式。常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
不允许硬编码,即不允许任何魔法值( 即未经定义的常量 ) 直接出现在代码中。比如:
正例:PAGE_SIZE / THREAD_POOL_SIZE
反例: String key =” Id # taobao _”+ tradeId;在常量与变量的命名时,表示类型的名词放在词尾,以提升辨识度。比如:
正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT
反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。 说明:大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解和维护。 正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下。
如果变量值仅在一个固定范围内变化用 enum 类型来定义。
类型与中括号紧挨相连来表示数组。 正例:定义整形数组 int[] arrayDemo; 反例:在 main 参数中,使用 String args[]来定义。
方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase 风格,必须遵从驼峰形式。 正例: localValue / getHttpMessage() / inputUserId
类名使用UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外: ( 领域模型的相关命名 )DO / BO / DTO / VO 等。比如:
正例: MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例: macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。 说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计理念。比如:
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。 正例:应用工具类包名为 com.alibaba.ai.util、类名为 MessageUtils(此规则参考 spring 的框架结构)
抽象类命名使用 Abstract 或 Base 开头 ;
异常类命名使用 Exception 结尾 ;
测试类命名以它要测试的类的名称开始,以 Test 结尾;
枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可读性降低。 说明:子类、父类成员变量名相同,即使是 public 类型的变量也是能够通过编译,而局部变量在同一方法内的不同代码块中同名也是合法的,但是要避免使用。对于非 setter/getter 的参数名称也要避免与成员变量名称相同。
POJO 类中布尔类型的变量,都不要加 is ,否则部分框架解析会引起序列化错误。
Service / DAO 层方法命名规约 1)获取单个对象的方法用 get 做前缀。 2)获取多个对象的方法用 list 做前缀(习惯:getXXXList)。 3)获取统计值的方法用 count 做前缀。 4)插入的方法用 save( 推荐 ) 或 insert 做前缀。 5)删除的方法用 remove( 推荐 ) 或 delete 做前缀。 6)修改的方法用 update 做前缀(或modify)。
领域模型命名规约 1)数据对象: xxxDO , xxx 即为数据表名。 2)数据传输对象: xxxDTO , xxx 为业务领域相关的名称。 3)展示对象: xxxVO , xxx 一般为网页名称。 4)POJO 是 DO / DTO / BO / VO 的统称,禁止命名成 xxxPOJO 。
方法名的命名,需要使用“动宾结构短语”或“是动词+表语结构短语”,如果宾语是一个对象集合,还是最好使用复数。比如:
增加:createXxx(Xxx xxx);
修改:modifyXxx(Xxx xxx);
查询:
获取单个对象: getXxx(int id);
不分条件列举: listXxxs();
对有条件查询: searchXxxs(条件);
删除:deleteXxxs()避免通过类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
所有的覆写方法,必须加@ Override 注解。
对外暴露的接口签名,原则上不允许修改方法签名,避免对接口调用方产生影响。
接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。
在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。 不要在方法体内定义:
Pattern pattern = Pattern.compile(规则);获取当前毫秒数 System.currentTimeMillis(); 而不是 new Date().getTime(); 说明:如果想获取更加精确的纳秒级时间值,用 System.nanoTime()。在 JDK8 中,针对统计时间等场景,推荐使用Instant 类。
对于“明确停止使用的代码和配置”,如方法、变量、类、配置文件、动态配置属性等要坚决从程序中清理出去,避免造成过多垃圾。
Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。比如:
正例: ”test”.equals(object);
反例: object.equals( ”test” );所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。(注意空指针) 说明:对于 Integer var = ?在-128 至 127 之间的赋值, Integer 对象是在IntegerCache . cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。
关于基本数据类型与包装数据类型的使用标准如下: 1)所有的 POJO 类属性必须使用包装数据类型。 2)RPC 方法的返回值和参数必须使用包装数据类型。 3)所有的局部变量【推荐】使用基本数据类型。
序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败 ; 如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。
构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
使用索引访问用 String 的 split 方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛 IndexOutOfBoundsException 的风险。说明:
String str = “a,b,c,,”;
String[] ary = str.split(“,”);
//预期大于 3,结果是 3
System.out.println(ary.length);当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读。
类内方法定义顺序依次是:公有方法或保护方法 > 私有方法 > getter / setter方法。
final 可提高程序响应效率,声明成 final 的情况: 1)不需要重新赋值的变量,包括类属性、局部变量。 2)对象参数前加 final ,表示不允许修改引用的指向。 3)类方法确定不允许被重写。
关于 hashCode 和 equals 的处理,遵循如下规则: 1)只要重写 equals ,就必须重写 hashCode 。 2)因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。 3)如果自定义对象做为 Map 的键,那么必须重写 hashCode 和 equals 。
不要在 foreach 循环里进行元素的 remove / add 操作。 remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。 反例:
List<String> a = new ArrayList<String>();
a.add(“1”);
a.add(“2”);
for (String temp : a) {
if(“1”.equals(temp)){
a.remove(temp); //不要这么做
}
}说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗? 正例:
Iterator<String> it = a.iterator();
while(it.hasNext()){
String temp = it.next();
if(删除元素的条件){
it.remove();
}
}集合初始化时,尽量指定集合初始值大小。 说明: ArrayList 尽量使用 ArrayList(int initialCapacity) 初始化。
使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。 说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的 value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效 率更高。如果是 JDK8,使用 Map.foreach 方法。
Map<String, String> map = new HashMap<String, String>();
map.put(“1”, “@@”);
map.put(“2”, “#”);
/** * JDK8推荐使用 */
map.forEach((K, V) -> {
System.out.println(“Key : ” + K + “ Value : ” + V);
});
/** * foreach推荐使用 */
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(“Key : ” + entry.getKey());
System.out.println(“Value : ” + entry.getValue());
}
/** * 不推荐使用 */
for (String key : map.keySet()) {
System.out.println(“Key : ” + key);
System.out.println(“Value : ” + map.get(key));
}Map 类集合 K/V 能不能存储 null 值的情况,如下表格:
集合类 | Key | Value | Super | 说明 |
|---|---|---|---|---|
Hashtable | 不允许为 null | 不允许为 null | Dictionary | 线程安全 |
ConcurrentHashMap | 不允许为 null | 不允许为 null | AbstractMap | 分段锁技术 |
TreeMap | 不允许为 null | 允许为 null | AbstractMap | 线程不安全 |
HashMap | 允许为 null | 允许为 null | AbstractMap | 线程不安全 |
在每一个switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在每一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。
在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码。
尽量少用 else, if-else 的方式可以改写成:
if(condition){
…
return obj;
}
// 接着写 else 的业务逻辑代码;说明:如果非得使用if()…else if()…else…方式表达逻辑,【强制】请勿超过3层,超过请使用状态设计模式或者卫语句。 卫语句示例:
public void today() {
if (isBusy()) {
System.out.println(“change time.”);
return;
}
if (isFree()) {
System.out.println(“go to travel.”);
return;
}
System.out.println(“stay at home to learn Alibaba Java Coding Guidelines.”);
return;
} 除常用方法(如 getXxx/isXxx)外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。 正例:
boolean existed = (file.open(fileName, “w”) != null) && (…) || (…);
if (existed) {
…
}反例:
if ((file.open(fileName, “w”) != null) && (…) || (…)) {
…
}方法中需要进行参数校验的场景: 1)调用频次低的方法。 2)执行时间开销很大的方法,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。 3)需要极高稳定性和可用性的方法。 4)对外提供的开放接口,不管是RPC/API/HTTP接口。 5)敏感权限入口。
方法中不需要参数校验的场景: 1)极有可能被循环调用的方法,不建议对参数进行校验。但在方法说明里必须注明外部参数检查。 2)底层的方法调用频度都比较高,一般不校验。一般 DAO 层与 Service 层都在同一个应用中,部署在同一 台服务器中,所以 DAO 的参数校验,可以省略。 3)被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。
应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。
应用中的扩展日志(如打点、临时监控、访问日志等)命名方式: appName_logType_logName.log。其中: logType:日志类型,推荐分类有 stats/desc/monitor/visit 等; logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用、什么类型、什么目的、也有利于归类查找。 正例:mppserver 应用中单独监控时区转换异常,如: mppserver_monitor_timeZoneConvert.log 说明:推荐对日志进行分类,错误日志和业务日志尽量分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。
对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。 说明:logger.debug(“Processing trade with id: ” + id + ” symbol: ” + symbol); 如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象, 会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。 正例:(条件)
if (logger.isDebugEnabled()) {
logger.debug(“Processing trade with id: ” + id + ” symbol: ” + symbol);
}正例:(占位符)
logger.debug(“Processing trade with id: {
} symbol : {
} “, id, symbol);避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。 正例:
可以使用warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。注意日志输出的级别,error 级别只记录系统逻辑出错、异常等重要的错误信息。如非必要,请不要在此场景打出 error 级别。
谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使 用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。 说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?

图中默认上层依赖于下层,箭头关系表示可直接依赖,如:开放接口层可以依赖于Web 层,也可以直接依赖于 Service 层,依此类推。
“勿以善小而不为,勿以恶小而为之”、“细节决定成败”,有太多的名言告诉我们,要注重细节。一个优秀的程序员,必须要有坚实的基础,而对于命名规则这样容易掌握的基础更是应该如此。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/220321.html原文链接:https://javaforall.cn