《阿里巴巴开发手册》学习总结(一)——编程规约

一. 编程规约

1. 命名风格
  • 类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO /PO / UID 等 (如UserDO / HtmlDTO是正确的)
  • 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。(如MAX_STOCK_COUNT)
  • POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。

MySQL 规约中的建表约定第一条,表达是与否的变量采用 is_xxx 的命名方式,所以,需要在<resultMap>设置从 is_xxx 到 xxx 的映射关系。

  • 包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。(如应用工具类包名为 com.alibaba.ei.kunlun.aap.util、类名为 MessageUtils)
  • 如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式(如public class OrderFactory;)。
  • Service/DAO 层方法命名规约:
    1. 获取单个对象的方法用 get 做前缀
    2. 获取多个对象的方法用 list 做前缀,复数结尾,如:listObjects
    3. 获取统计值的方法用 count 做前缀。
    4. 插入的方法用 save/insert 做前缀
    5. 删除的方法用 remove/delete 做前缀。
    6. 修改的方法用 update 做前缀。
  • 领域模型命名规约:
    1. 数据对象DO,:xxxDO,xxx 即为数据表名,此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
    2. 数据传输对象DTO:xxxDTO,xxx 为业务领域相关的名称。Service 或 Manager 向外传输的对象。
    3. 展示对象VO:xxxVO,xxx 一般为网页名称。显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
    4. 数据查询对象Query:各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输。
    5. 业务对象BO(Business Object):可以由 Service 层输出的封装业务逻辑的对象。
2. 常量定义
  • 在 long 或者 Long 赋值时,数值后使用大写字母 L,不能是小写字母 l,小写容易跟数字混淆,造成误解
3. 代码格式
  • 左小括号和右边相邻字符之间不出现空格;右小括号和左边相邻字符之间也不出现空格
  • if/for/while/switch/do 等保留字与括号之间都必须加空格。
  • 任何二目、三目运算符的左右两边都需要加一个空格。
  • 采用 4 个空格缩进,禁止使用 Tab 字符。
  • 注释的双斜线与注释内容之间有且仅有一个空格。(如 // 注释信息
  • 在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开。(如int second = (int)first + 2; )
  • 换行示例(第二行相对第一行缩进 4 个空格
sb.append("yang").append("hao")...
	   .append("chen")...
	   .append("chen")...
       .append("chen");
  • IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要使用 Windows 格式。
4. OOP规约
  • 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可
  • Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals(如"test".equals(object), 推荐使用 JDK7 引入的工具类 java.util.Objects#equals(Object a, Object b))
  • 所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
  • 任何货币金额,均以最小货币单位且整型类型来进行存储
  • 浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals来判断。正确使用:
// 规定允许的误差范围
float diff = 1e-6F;
	
if (Math.abs(a - b) < diff) {
 	System.out.println("true");
}

// Or 使用 BigDecimal 来定义值,再进行浮点数的运算操作。
if (x.compareTo(y) == 0) {
	System.out.println("true");
}
  • 禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象,可能存在精度损失风险。
    优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
  • 所有的 POJO 类属性必须使用包装数据类型
  • RPC 方法的返回值和参数必须使用包装数据类型

因为所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。用基本数据类型接收有 NPE 风险。

  • 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中
  • POJO 类必须写 toString 方法。在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。
  • 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter / setter方法。
5. 日期时间
  • 日期格式化时,传入 pattern 中表示年份统一使用小写的 y
  • 24 小时制的是大写的 H;12 小时制的则是小写的 h。
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
  • 使用枚举值来指代月份。如Calendar.JANUARY,Calendar.FEBRUARY,Calendar.MARCH
6. 集合处理
  • ArrayList 的 subList 结果不可强转成 ArrayList。

subList()返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 本身,而是 ArrayList 的一个视图,对于 SubList 的所有操作最终会反映到原列表上。

  • 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。如:
List<String> list = new ArrayList<>(2);
list.add("guan");
list.add("bao");
String[] array = list.toArray(new String[0]); 
// 数组空间大小的 length等于 0,会动态创建与 size 相同的数组,性能最好。
  • 在使用 Collection 接口任何实现类的 addAll()方法时,都要对输入的集合参数进行NPE 判断
  • Comparator 实现类要满足如下三个条件:
    1) x,y 的比较结果和 y,x 的比较结果相反(必须考虑相等情况)
    2) x>y,y>z,则 x>z。
    3) x=y,则 x,z 比较结果和 y,z 比较结果相同。
  • 使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
  • 高度注意 Map 类集合 K/V 能不能存储 null 值的情况
    HashMap的KV均允许为null
    TreeMap的V允许为null,K不允许
    ConcurrentHashMap的KV均不允许为null
7. 并发处理
  • 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
// 自定义线程工厂,并且根据外部特征进行分组,比如,来自同一机房的调用,把机房编号赋值给
whatFeatureOfGroup
public class UserThreadFactory implements ThreadFactory {
 	private final String namePrefix;
 	private final AtomicInteger nextId = new AtomicInteger(1);
 	// 定义线程组名称,在利用 jstack 来排查问题时,非常有帮助
 	UserThreadFactory(String whatFeatureOfGroup) {
 		namePrefix = "From UserThreadFactory's " + whatFeatureOfGroup + "-Worker-";
	}
	
	@Override
	public Thread newThread(Runnable task) {
 		String name = namePrefix + nextId.getAndIncrement();
 		Thread thread = new Thread(null, task, name, 0, false);
 		System.out.println(thread.getName());
 		return thread;
 	}
}
  • 必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,
    如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。
    尽量在代理中使用 try-finally 块进行回收。
objectThreadLocal.set(userInfo);
try {
 // ...
} finally {
 objectThreadLocal.remove();
}
  • 显示锁的正确用法
Lock lock = new XxxLock();
// ...
lock.lock();
try {
 	doSomething();
 	doOthers();
} finally {
 	lock.unlock();
}
  • 在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。
Lock lock = new XxxLock();
// ...
boolean isLocked = lock.tryLock();
	if (isLocked) {
 		try {
 			doSomething();
 			doOthers();
	 	} finally {
		 	lock.unlock();
 		}
}
  • 如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3 次。
  • 资金相关的金融敏感信息,使用悲观锁策略
  • 使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown 方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行至await 方法,直到超时才返回结果。
  • 避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed导致的性能下降。
  • ThreadLocal 对象使用 static 修饰
8. 控制语句
  • 在 if/else/for/while/do 语句中必须使用大括号(即使只有一行代码)
  • 在高并发场景中,避免使用”等于”判断作为中断或退出的条件。

如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。如判断剩余奖品数量小于等于 0 时,终止发放奖品

  • 表达异常的分支时,少用 if-else 方式,这种方式可以改写成:
if (condition) {
 ...
 return obj;
}
// 接着写 else 的业务逻辑代码;
  • 公开接口需要进行入参保护,尤其是批量操作的接口。
  • 需要进行参数校验的场景:
    1) 调用频次低的方法。
    2) 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。
    3) 需要极高稳定性和可用性的方法。
    4) 对外提供的开放接口,不管是 RPC/API/HTTP 接口。
    5) 敏感权限入口。
9. 注释规约
  • 类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容*/格式,不得使用// xxx 方式。
  • 所有的类都必须添加创建者和创建日期
/**
* @author yangguanbao
* @date 2016/10/31
*/
  • 所有的枚举类型字段必须要有注释,说明每个数据项的用途。
  • 代码结构是自解释的,注释力求精简准确、表达到位。避免出现注释的一个极端:过多过滥的注释,代码的逻辑一旦修改,修改注释又是相当大的负担。
10. 前后端规约
  • 前后端交互的 API:
    • 生产环境必须使用 HTTPS协议
    • URL 路径不能使用大写,单词如果需要分隔,统一使用下划线
    • 请求内容:URL 带的参数必须无敏感信息或符合安全要求;body 里带参数时必须设置 Content-Type
    • 响应体:响应体 body 可放置多种数据类型,由 Content-Type 头来确定
  • 前后端数据列表相关的接口返回,如果为空,则返回空数组[]或空集合{}(减少前端琐碎的 null 判断)
  • 服务端发生错误时,返回给前端的响应信息必须包含 HTTP 状态码,errorCode、errorMessage、用户提示信息四个部分。
  • 在前后端交互的 JSON 格式数据中,所有的 key 必须为小写字母开始的lowerCamelCase 风格(如errorCode / errorMessage)
  • 对于需要使用超大整数的场景,服务端一律使用 String 字符串类型返回,禁止使用Long 类型。(前后端数据类型转换时可能导致精度损失)
  • 服务器内部重定向必须使用 forward外部重定向地址必须使用 URL 统一代理模块生成,否则会因线上采用 HTTPS 协议而导致浏览器提示“不安全”,并且还会带来 URL 维护不一致的问题。
  • 服务器返回信息必须被标记是否可以缓存,如果缓存,客户端可能会重用之前的请求结果。
response.setHeader("Cache-Control", "s-maxage=" + cacheSeconds);
11. 其他
  • 想要获取随机整数,直接使用 Random 对象的 nextInt 或者 nextLong 方法,而不是使用 Math.random()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值