阿⾥巴巴开发规范
⼀:编程规范
(⼀):命名规范
1.【强制】类名使⽤UpperCamelCase风格,但以下情形例外:DO / BO / DTO / VO / AO / PO等。
正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
2.【强制】⽅法名、参数名、成员变量、局部变量都统⼀使⽤lowerCamelCase风格,必须遵从驼峰形式。
正例: localValue / getHttpMessage() / inputUserId
3.【强制】常量命名全部⼤写,单词间⽤下划线隔开,⼒求语义表达完整清楚,不要嫌名字长。
正例:MAX_STOCK_COUNT
反例:MAX_COUNT
4.【强制】抽象类命名使⽤Abstract或Base开头;异常类命名使⽤Exception结尾;测试类命名以它要测试的类名开始,以Test结尾。
5.【强制】包名统⼀使⽤⼩写,点分隔符之间有且仅有⼀个⾃然语义的英语单词。包名统⼀使⽤单数形式,但是类名如果有复数含义,类名可以使⽤复数形式。
正例:应⽤⼯具类包名为com.alibaba.ai.util、类名为MessageUtils(此规则参考spring的框架结构)
6.【推荐】接⼝类中的⽅法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的Javadoc注释。尽量不要在接⼝⾥定义变量,如果⼀定要定义变量,肯定是与接⼝⽅法相关,并且是整个应⽤的基础常量。
正例:接⼝⽅法签名void f(); 接⼝基础常量String COMPANY = “alibaba”;
反例:接⼝⽅法定义public abstract void f();
说明:JDK8中接⼝允许有默认实现,那么这个default⽅法,是对所有实现类都有价值的默认实现。
7.【参考】枚举类名建议带上Enum后缀,枚举成员名称需要全⼤写,单词间⽤下划线隔开。
说明:枚举其实就是特殊的常量类,且构造⽅法被默认强制是私有。
正例:枚举名字为ProcessStatusEnum的成员名称:SUCCESS / UNKNOWN_REASON。
8.【参考】各层命名规约:
A) Service/DAO层⽅法命名规约
1) 获取单个对象的⽅法⽤get作前缀。
2) 获取多个对象的⽅法⽤list作前缀。
3) 获取统计值的⽅法⽤count作前缀。
4) 插⼊的⽅法⽤save/insert作前缀。
5) 删除的⽅法⽤remove/delete作前缀。
6) 修改的⽅法⽤update作前缀。
B) 领域模型命名规约
1) 数据对象:xxxDO,xxx即为数据表名。
2) 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
3) 展⽰对象:xxxVO,xxx⼀般为⽹页名称。
4) POJO是DO/DTO/BO/VO的统称,禁⽌命名成xxxPOJO。
(⼆):常量定义
1.【强制】long或者Long初始赋值时,使⽤⼤写的L,不能是⼩写的l,⼩写容易跟数字1混淆,造成误解。
说明:
Long a = 2l;
写的是数字的21,还是Long型的2?
2.【推荐】不要使⽤⼀个常量类维护所有常量,按常量功能进⾏归类,分开维护。
说明:⼤⽽全的常量类,⾮得使⽤查功能才能定位到修改的常量,不利于理解和维护。
正例:缓存相关常量放在类CacheConsts下;系统配置相关常量放在类ConfigConsts下。
(三):代码格式
1.【强制】⼤括号的使⽤约定。如果是⼤括号内为空,则简洁地写成{}即可,不需要换⾏;如果是⾮空代码块则:
1) 左⼤括号前不换⾏。
2) 左⼤括号后换⾏。
3) 右⼤括号前换⾏。
4) 右⼤括号后还有else等代码则不换⾏;表⽰终⽌的右⼤括号后必须换⾏。
2.【强制】 左⼩括号和字符之间不出现空格;同样,右⼩括号和字符之间也不出现空格。详见第5条下⽅正例提⽰
反例:
复制代码if (空格a == b空格)
3.【强制】 if/for/while/switch/do等保留字与括号之间都必须加空格
4.【强制】注释的双斜线与注释内容之间有且仅有⼀个空格。
正例:
复制代码// 这是⽰例注释,请注意在双斜线之后有⼀个空格 String ygb = new String();
(四):OOP规约
1.【强制】Object的equals⽅法容易抛空指针异常,应使⽤常量或确定有值的对象来调⽤equals。
正例:”test”.equals(object);
反例:object.equals(“test”);
2.关于基本数据类型与包装数据类型的使⽤标准如下:
1) 【强制】所有的POJO类属性必须使⽤包装数据类型。
2) 【强制】RPC⽅法的返回值和参数必须使⽤包装数据类型。
3) 【推荐】所有的局部变量使⽤基本数据类型。
说明:POJO类属性没有初值是提醒使⽤者在需要使⽤时,必须⾃⼰显式地进⾏赋值,任何NPE问题,或者⼊库检查,都由使⽤者来保证。
正例:数据库的查询结果可能是null,因为⾃动拆箱,⽤基本数据类型接收有NPE风险。
反例:⽐如显⽰成交总额涨跌情况,即正负x%,x为基本数据类型,调⽤的RPC服务,调⽤不成功时,返回的是默认值,页⾯显⽰为0%,这是不合理的,应该显⽰成中划线。所以包装数据类型的null值,能够表⽰额外的信息,如:远程调⽤失败,异常退出。
2.【强制】构造⽅法⾥⾯禁⽌加⼊任何业务逻辑,如果有初始化逻辑,请放在init⽅法中。
3.【推荐】使⽤索引访问⽤String的split⽅法得到的数组时,需做最后⼀个分隔符后有⽆内容的检查,否则会有抛IndexOutOfBoundsException的风险。
说明:
String str ="a,b,c,,";
String[] ary = str.split(",");
// 预期⼤于3,结果是3 System.out.println(ary.length);
4.【推荐】 类内⽅法定义的顺序依次是:公有⽅法或保护⽅法 > 私有⽅法 > getter/setter⽅法。
说明:公有⽅法是类的调⽤者和维护者最关⼼的⽅法,⾸屏展⽰最好;保护⽅法虽然只是⼦类关⼼,也可能是“模板设计模式”下的核⼼⽅法;⽽私有⽅法外部⼀般不需要特别关⼼,是⼀个⿊盒实现;因为承载的信息价值较低,所有Service和DAO的getter/setter⽅法放在类体最后。
5.【推荐】循环体内,字符串的连接⽅式,使⽤StringBuilder的append⽅法进⾏扩展。
说明:反编译出的字节码⽂件显⽰每次循环都会new出⼀个StringBuilder对象,然后进⾏append操作,最后通过toString⽅法返回String 对象,造成内存资源浪费。
反例:
复制代码String str = "start";for (int i = 0; i < 100; i++) { str = str + "hello"; }
6.【推荐】final可以声明类、成员变量、⽅法、以及本地变量,下列情况使⽤final关键字:
1) 不允许被继承的类,如:String类。
2) 不允许修改引⽤的域对象,如:POJO类的域变量。
3) 不允许被重写的⽅法,如:POJO类的setter⽅法。
4) 不允许运⾏过程中重新赋值的局部变量。
5) 避免上下⽂重复使⽤⼀个变量,使⽤final描述可以强制重新定义⼀个变量,⽅便更好地进⾏重构。
7.【推荐】慎⽤Object的clone⽅法来拷贝对象。
说明:对象的clone⽅法默认是浅拷贝,若想实现深拷贝需要重写clone⽅法实现属性对象的拷贝。
8.【推荐】类成员与⽅法访问控制从严:
1) 如果不允许外部直接通过new来创建对象,那么构造⽅法必须是private。
2) ⼯具类不允许有public或default构造⽅法。
3) 类⾮static成员变量并且与⼦类共享,必须是protected。
4) 类⾮static成员变量并且仅在本类使⽤,必须是private。
5) 类static成员变量如果仅在本类使⽤,必须是private。
6) 若是static成员变量,必须考虑是否为final。
7) 类成员⽅法只供类内部调⽤,必须是private。
8) 类成员⽅法只对继承类公开,那么限制为protected。
说明:任何类、⽅法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。思考:如果是⼀个private的⽅法,想删除就删除,可是⼀个public的service成员⽅法或成员变量,删除⼀下,不得⼿⼼冒点汗吗?变量像⾃⼰的⼩孩,尽量在⾃⼰的视线内,变量作⽤域太⼤,⽆限制的到处跑,那么你会担⼼的。
(五):集合处理
1.【强制】关于hashCode和equals的处理,遵循如下规则:
1) 只要重写equals,就必须重写hashCode。
2) 因为Set存储的是不重复的对象,依据hashCode和equals进⾏判断,所以Set存储的对象必须重写这两个⽅法。
3) 如果⾃定义对象作为Map的键,那么必须重写hashCode和equals。
说明:String重写了hashCode和equals⽅法,所以我们可以⾮常愉快地使⽤String对象作为key来使⽤
2.【强制】泛型通配符来接收返回的数据,此写法的泛型集合不能使⽤add⽅法,⽽不能使⽤get⽅法,作为接⼝调⽤赋值时易出错。
说明:扩展说⼀下PECS(Producer Extends Consumer Super)原则:第⼀、频繁往外读取内容的,适合⽤<? extends T>。第⼆、经常往⾥插⼊的,适合⽤``。
3.【强制】不要在foreach循环⾥进⾏元素的remove/add操作。remove元素请使⽤Iterator⽅式,如果并发操作,需要对Iterator对象加锁。
说明:扩展说⼀下PECS(Producer Extends Consumer Super)原则:第⼀、频繁往外读取内容的,适合⽤<? extends T>。第⼆、经常往⾥插⼊的,适合⽤。
正例:
Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = ();
if (删除元素的条件) {
}
}
反例:抽象类的使用
List<String> list = new ArrayList<String>(); list.add("1"); list.add("2"); for (String item : list) { if ("1".equals(item)) { ve (item); } }
说明:以上代码的执⾏结果肯定会出乎⼤家的意料,那么试⼀下把“1”换成“2”,会是同样的结果吗?
4.【推荐】集合初始化时,指定集合初始值⼤⼩。
说明:HashMap使⽤HashMap(int initialCapacity) 初始化,
正例:initialCapacity = (需要存储的元素个数 / 负载因⼦) + 1。注意负载因⼦(即loader factor)默认为0.75,如果暂时⽆法确定初始值⼤⼩,请设置为16(即默认值)。
反例:HashMap需要放置1024个元素,由于没有设置容量初始⼤⼩,随着元素不断增加,容量7次被
迫扩⼤,resize需要重建hash表,严重影响性能。
5.【推荐】使⽤entrySet遍历Map类集合KV,⽽不是keySet⽅式进⾏遍历。
说明:keySet其实是遍历了2次,⼀次是转为Iterator对象,另⼀次是从hashMap中取出key所对应的value。⽽entrySet只是遍历了⼀次就把key和value都放到了entry中,效率更⾼。如果是JDK8,使⽤Map.foreach⽅法。
正例:values()返回的是V值集合,是⼀个list集合对象;keySet()返回的是K值集合,是⼀个Set集合对象;entrySet()返回的是K-V值组合集合。
6.【推荐】⾼度注意Map类集合K/V能不能存储null值的情况,如下表格:
|集合类| Key | Value | Super |说明|
|-------------------|--------------|--------------|-------------|------------------------|
| Hashtable |不允许为null|不允许为null| Dictionary |线程安全|
| ConcurrentHashMap |不允许为null|不允许为null| AbstractMap |锁分段技术(JDK8:CAS)|
| TreeMap |不允许为null|允许为null| AbstractMap |线程不安全|
| HashMap |允许为null|允许为null| AbstractMap |线程不安全|
反例: 由于HashMap的⼲扰,很多⼈认为ConcurrentHashMap是可以置⼊null值,⽽事实上,存储null值时会抛出NPE异常(六) 并发处理
1.【强制】线程资源必须通过线程池提供,不允许在应⽤中⾃⾏显式创建线程。
说明:使⽤线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不⾜的问题。如果不使⽤线程池,有可能造成系统创建⼤量同类线程⽽导致消耗完内存或者“过度切换”的问题。
2.【强制】线程池不允许使⽤Executors去创建,⽽是通过ThreadPoolExecutor的⽅式,这样的处理⽅式让写的同学更加明确线程池的运⾏规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool: 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积⼤量的请求,从⽽导致OOM。
2)CachedThreadPool和ScheduledThreadPool: 允许的创建线程数量为Integer.MAX_VALUE,可能会创建⼤量的线程,从⽽导致OOM。
3.【强制】SimpleDateFormat 是线程不安全的类,⼀般不要定义为static变量,如果定义为static,必须加锁,或者使⽤DateUtils⼯具类。
private static final ThreadLocal<DateFormat> df =new ThreadLocal<DateFormat>(){
Override
protected DateFormat initialValue(){
return new SimpleDateFormat("yyyy-MM-dd");
}
};
说明:如果是JDK8的应⽤,可以使⽤Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat,官⽅给出的解释:
simple beautiful strong immutable thread-safe。
4.【强制】⾼并发时,同步调⽤应该去考量锁的性能损耗。能⽤⽆锁数据结构,就不要⽤锁;能锁区块,就不要锁整个⽅法体;能⽤对象锁,就不要⽤类锁。
说明:尽可能使加锁的代码块⼯作量尽可能的⼩,避免在锁代码块中调⽤RPC⽅法。
5.【强制】并发修改同⼀记录时,避免更新丢失,需要加锁。要么在应⽤层加锁,要么在缓存加锁,要么在数据库层使⽤乐观锁,使⽤version作为更新依据。
说明:如果每次访问冲突概率⼩于20%,推荐使⽤乐观锁,否则使⽤悲观锁。乐观锁的重试次数不得⼩于3次。
6.【强制】多线程并⾏处理定时任务时,Timer运⾏多个TimeTask时,只要其中之⼀没有捕获抛出的异常,其它任务便会⾃动终⽌运⾏,使⽤ScheduledExecutorService则没有这个问题。
7.【推荐】使⽤CountDownLatch进⾏异步转同步操作,每个线程退出前必须调⽤countDown⽅法,线程执⾏代码注意catch异常,确保countDown⽅法被执⾏到,避免主线程⽆法执⾏⾄await⽅法,直到超时才返回结果。
说明:注意,⼦线程抛出异常堆栈,不能在主线程try-catch到。
(七) 控制语句
1.【强制】在⼀个switch块内,每个case要么通过break/return等来终⽌,要么注释说明程序将继续执⾏到哪⼀个case为⽌;在⼀个switch块内,都必须包含⼀个default语句并且放在最后,即使空代码。
2.【强制】在⾼并发场景中,避免使⽤”等于”判断作为中断或退出的条件。
说明:如果并发控制没有处理好,容易产⽣等值判断被“击穿”的情况,使⽤⼤于或⼩于的区间判断条件来代替。
反例:判断剩余奖品数量等于0时,终⽌发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,
3.【推荐】循环体中的语句要考量性能,以下操作尽量移⾄循环体外处理,如定义对象、变量、获取数据库连接,进⾏不必要的try-catch 操作(这个try-catch是否可以移⾄循环体外)。
(⼋) 注释规约
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论