开发总结

来源:互联网 发布:网络肥皂是什么意思啊 编辑:程序博客网 时间:2024/05/16 09:27

1.接口类中的方法和属性不要加任何修饰符号(public 也不要加) ,保持代码的简洁性,并加上有效的Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。

正例:接口方法签名:void f();

接口基础常量表示:StringCOMPANY = "alibaba";

反例:接口方法定义:public abstractvoid f();

 

2.各层命名规约:
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

 

3.不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。如:缓存相关的常量放在类: CacheConstants下; 系统配置相关的常量放在类:ConfigConstants下。

说明:大而全的常量类,非得使用查找功能才能定位到修改的常量,不利于理解和维护。

 

4.关于基本数据类型与包装数据类型的使用标准如下:
1)所有的POJO类属性必须使用包装数据类型。

2)RPC方法的返回值和参数必须使用包装数据类型。

3) 所有的局部变量【 推荐】 使用基本数据类型。
说明: POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE 问题,或者入库检查,都由使用者来保证。

正例:数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。

 

5.构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init方法中。

 

6.setter方法中,参数名称与类成员变量名称一致, this.成员名=参数名。在getter/setter 方法中,尽量不要增加业务逻辑,增加排查问题的难度。
反例:
public Integer getData(){
    if(true) {
        returndata + 100;
    } else {
        returndata - 100;
    }
}

 

7.循环体内,字符串的联接方式,使用 StringBuilder的 append 方法进行扩展。
反例:
String str = "start";
for(int i=0; i<100; i++){
    str = str +"hello";
}
说明: 反编译出的字节码文件显示每次循环都会 new出一个 StringBuilder 对象,然后进行append 操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。

 

8.慎用 Object的clone 方法来拷贝对象。
说明: 对象的 clone方法默认是浅拷贝,若想实现深拷贝需要重写 clone 方法实现属性对象的拷贝。

 

9.关于 hashCode和equals 的处理,遵循如下规则:

1) 只要重写 equals,就必须重写 hashCode。

2) 因为 Set存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。

3) 如果自定义对象做为 Map的键,那么必须重写 hashCode 和 equals。

正例:String重写了 hashCode和 equals 方法,所以我们可以非常愉快地使用 String 对象作为 key 来使用。

 

10.使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出UnsupportedOperationException 异常。

说明:asList的返回对象是一个 Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。

String[] str =new String[] { "a", "b" };

List list =Arrays.asList(str);

第一种情况:list.add("c");运行时异常。

第二种情况: str[0]="gujin";那么 list.get(0)也会随之修改。

 

11.不要在 foreach循环里进行元素的 remove/add 操作。 remove 元素请使用 Iterator
方式,如果并发操作,需要对 Iterator 对象加锁。
反例:
List<String> a = newArrayList<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();
     }
}

 

12.在 JDK7版本以上,Comparator 要满足自反性,传递性,对称性,不然 Arrays.sort,Collections.sort 会报 IllegalArgumentException异常。

说明:

1) 自反性: x, y 的比较结果和 y, x 的比较结果相反。

2) 传递性: x>y,y>z,则 x>z。

3) 对称性: x=y,则 x,z比较结果和 y, z 比较结果相同。

反例:下例中没有处理相等的情况,实际使用中可能会出现异常:

new Comparator<Student>(){
@Override
public int compare(Student o1,Student o2) {
return o1.getId() > o2.getId() ? 1: -1;
}
}

 

13.集合初始化时,尽量指定集合初始值大小。
说明: ArrayList尽量使用 ArrayList(int initialCapacity)初始化。

 

14.使用 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 值组合集合。

 

15.利用 Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的contains 方法进行遍历、对比、 去重操作。


16.线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

说明: 使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

 

17.线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明: Executors 返回的线程池对象的弊端如下:

1) FixedThreadPool 和 SingleThreadPool:

允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2) CachedThreadPool 和 ScheduledThreadPool:

允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。

 

18.SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。
正例: 注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal<DateFormat> df = newThreadLocal<DateFormat>() {
      @Override
      protected DateFormat initialValue(){
           return newSimpleDateFormat("yyyy-MM-dd");
      }
};

 

19.高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁; 能锁区块,就不要锁整个方法体; 能用对象锁,就不要用类锁。

 

20.并发修改同一记录时,避免更新丢失,要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。
说明: 如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次。

 

21.在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止; 在一个switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。

 

22.推荐尽量少用 else, if-else 的方式可以改写成:
if(condition){
      ...
      return obj;
}
// 接着写 else 的业务逻辑代码;

说明: 如果非得使用 if()...else if()...else...方式表达逻辑,【强制】请勿超过 3 层,超过请使用状态设计模式。

正例: 逻辑上超过 3层的 if-else 代码可以使用卫语句,或者状态模式来实现。

 

23.循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的 try-catch操作( 这个 try-catch 是否可以移至循环体外) 。

 

24.注意Math.random()这个方法返回是 double类型,注意取值的范围 0≤x<1能够取到值,注意除零异常,如果想获取整数类型的随机数,不要将 x放大 10的若干倍然后取整,直接使用Random 对象的nextInt或者nextLong 方法。


25.不要捕获 Java 类库中定义的继承自 RuntimeException 的运行时异常类,如:IndexOutOfBoundsException / NullPointerException,这类异常由程序员预检查来规避,保证程序健壮性。
正例: if(obj != null) {...}
反例: try { obj.method() } catch(NullPointerException e){...}


26.对大段代码进行 try-catch,这是不负责任的表现。 catch 时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理。


27.捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。


28.try 块放到了事务代码中, catch 异常后,如果需要回滚事务,一定要注意手动回滚事务。


29.finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch


30.不能在finally 块中使用 returnfinally 块中的 return 返回后方法结束执行,不会再执行try 块中的 return 语句。


31.防止NPE,是程序员的基本修养,注意 NPE 产生的场景:
1返回类型为包装数据类型,有可能是 null,返回int 值时注意判空。
反例: public int f(){ return Integer对象}; 如果为 null,自动解箱抛 NPE
2数据库的查询结果可能为 null
3集合里的元素即使 isNotEmpty,取出的数据元素也可能为null
4远程调用返回对象,一律要求进行 NPE 判断。
5对于 Session 中获取的数据,建议 NPE 检查,避免空指针。
6级联调用 obj.getA().getB().getC(一连串调用,易产生 NPE


32.避免出现重复的代码Dont Repeat Yourself,即 DRY 原则。
说明: 随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是共用模块。


33.异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么往上抛。
正例: logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);


34.谨慎地记录日志。生产环境禁止输出debug 日志有选择地输出 info 日志如果使warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
说明: 大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。 记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?


35.建表表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是 unsigned tinyint1表示是, 0 表示否) 


36.唯一索引名为 uk_字段名; 普通索引名则为 idx_字段名。说明: uk_ 即 unique key; idx_ 即 index 的简称。


37.表必备三字段: id, gmt_create, gmt_modified。
说明: 其中 id 必为主键,类型为 unsigned bigint、单表时自增、步长为 1。 gmt_create, gmt_modified 的类型均为 date_time 类型。


38.表的命名最好是加上“业务名称_表的作用”。正例:tiger_task / tiger_reader / mpp_config


39.字段允许适当冗余,以提高性能,但是必须考虑数据同步的情况。冗余字段应遵循:
1) 不是频繁修改的字段。
2) 不是 varchar 超长字段,更不能是 text 字段。
正例: 商品类目名称使用频率高, 字段长度短,名称基本一成不变, 可在相关联的表中冗余存
储类目名称,避免关联查询。


40.业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
说明: 不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的; 另外,即使在应用层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。


41.超过三个表禁止 join。需要 join 的字段,数据类型保持绝对一致; 多表关联查询时,保证被关联的字段需要有索引。


42.页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。
说明: 索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。


43.不要使用 count(列名)或 count(常量)来替代 count(*), count(*)就是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
说明: count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。


44.不得使用外键与级联,一切外键概念必须在应用层解决。
说明: ( 概念解释) 学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,则为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群; 级联更新是强阻塞,存在数据库更新风暴的风险; 外键影响数据库的插入速度。


45.数据订正时,删除和修改记录时,要先 select,避免出现误删除,确认无误才能执行更新语句。


46.TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。
说明: TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。


47.POJO 类的 boolean 属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射。


48.不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。


49.不要写一个大而全的数据更新接口,传入为 POJO 类,不管是不是自己的目标更新字段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL时,尽量不要更新无改动的字段,一是易出错; 二是效率低; 三是 binlog 增加存储。


50.@Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等。


51.Web 层绝不应该继续往上抛异常,因为已经处于顶层,无继续处理异常的方式,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,尽量加上友好的错误提示信息。开放接口层要将异常处理成错误码和错误信息方式返回。

0 0
原创粉丝点击