优秀的代码胜过冗长的文档。
0,避免bug的好习惯
1)循环体内不可修改循环变量
反例:
- sql先分页查where user=‘luo’
- 然后对查出来的数据进行修改:update user = ‘wen’
- 再次查询会跨过第二页数据直接处理第三页。
2)cotroller在接受参数时,禁止使用大写变量名
大写变量名会自动转换为驼峰!!! 如果一定要用,需要指定序列化字段。
3)禁止在函数中修改入参
如果需要修改入参,new一个然后return返回。
4)循环条件不能包含计算
如for(i; i<mm+n;i++)==>应该单独把a=mm+n提出来
1,命名规范
驼峰风格(UpperCamelCase):不用下划线,各个单词拼一起,首字母大写以区别。变量名和方法名首字母小写,类名首字母大写。
统一、达意、简洁、英文。
1.1 类名命名
首字母大写、驼峰风格。
后缀:类名往往用不同的后缀表达额外的意思,如下表。
后缀名 | 意义 |
---|---|
Service | 表明这个类是个服务类,里面包含了给其他类提同业务服务的方法 |
Impl | 这个类是一个实现类,而不是接口 |
Inter | 这个类是一个接口 |
Dao | 这个类封装了数据访问方法 |
Action | 直接处理页面请求,管理页面逻辑了类 |
Listener | 响应某种事件的类 |
Event | 这个类代表了某种事件 |
Servlet | 一个Servlet |
Factory | 生成某种对象工厂的类 |
Adapter | 用来连接某种以前不被支持的对象的类 |
Job | 某种按时间运行的任务 |
Wrapper | 这是一个包装类,为了给某个类提供没有的能力 |
Bean | 这是一个POJO |
Exception | 异常类命名 |
Test | 测试类命名,并且应该以它要测试的类的名称开始。 |
Enum | 枚举类 |
- 抽象类命名使用Abstract或Base开头
- 如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式(有利于阅读者快速理解架构设计理念)。正例:
public class OrderFactory;public class LoginProxy; public class ResourceObserver;
1.1.1 POJO(Plain Ordinary Java Object,无规则简单java对象)
一个中间对象,可以转化为PO、DTO、VO。
- POJO持久化之后:PO
在运行期,由Hibernate中的cglib动态把POJO转换为PO,PO相对于POJO会增加一些用来管理数据库entity状态的属性和方法。PO对于programmer来说完全透明,由于是运行期生成PO,所以可以支持增量编译,增量调试。
PO中不应该包含任何对数据库的操作。 - POJO传输过程中:DTO(Data Transfer Object 数据传输对象)
用在需要跨进程或远程传输时,它不应该包含业务逻辑。 - POJO用作表示层:VO(value object 值对象 / view object 表现层对象)
主要对应页面显示(web页面/swt、swing界面)的数据对象。
可以和表对应,也可以不,这根据业务的需要。 - BO:business object 业务对象
- DAO:data access object数据访问对象
主要用来封装对DB的访问(CRUD操作)。
通过接收Business层的数据,把POJO持久化为PO。
POJO类中布尔类型的变量,都不要加is,否则部分框架解析会引起序列化错误。反例:定义为基本数据类型boolean isSuccess 的属性,它的方法也是isSuccess(),RPC框架在反向解析的时候,以为对应的属性名称是success,导致属性获取不到,进而抛出异常。
1.2 方法名
首字母小写,驼峰命名。
动词在前,如 addOrder()。
前缀:动词前缀往往表达特定的含义,如下表。
前缀名 | 意义 |
---|---|
create | 创建 |
delete | 删除 |
add | 创建,暗示新创建的对象属于某个集合 |
remove | 删除 |
init或则initialize | 初始化,暗示会做些诸如获取资源等特殊动作 |
destroy | 销毁,暗示会做些诸如释放资源的特殊动作 |
open | 打开 |
close | 关闭 |
read | 读取 |
write | 写入 |
get | 获得 |
set | 设置 |
prepare | 准备 |
copy | 复制 |
modity | 修改 |
calculate | 数值计算 |
do | 执行某个过程或流程 |
dispatch | 判断程序流程转向 |
start | 开始 |
stop | 结束 |
send | 发送某个消息或事件 |
receive | 接受消息或时间 |
respond | 响应用户动作 |
find | 查找对象 |
update | 更新对象 |
1.3 域(field)名
1.3.1 静态常量
全大写用下划线分割,如:
public static find String ORDER_PAID_EVENT = "ORDER_PAID_EVENT";
1.3.2 枚举
全大写,用下划线分割,如
public enum Events {
ORDER_PAID,
ORDER_CREATED
}
1.3.3 其他、局部变量名
首字母小写,骆驼法则,如:
public String orderName;
1.4 包名
- 使用小写字母
- 点分隔符之间有且仅有一个自然语义的英语单词。
- 包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
正例:应用工具类包名为com.alibaba.ai.util、类名为MessageUtils
2,编程风格
用空格字符缩进源代码,不要用tab,每个缩进4个空格。
2.1 源文件编码
源文件使用utf-8编码,结尾用\n
分格。
如果源文件不是以“回车换行”结束,则目标文件将会多出2字节。
\r 回车 告诉打字机把打印头定位在左边界;
\n 换行 告诉打字机把纸向下移一行.
刚开始存储器很贵,一些科学家认为在每行结尾加两个字符太浪费了,加一个就可以。于是,就出现了分歧。
操作系统的不同,换行符操也不同:
/r Mac
/n Unix/Linux
/r/n Windows
一个直接后果是,Unix/Mac系统下的文件在Windows里打 开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个^M符号。
2.1.1 源文件结构
一个源文件包含(按顺序地):
许可证或版权信息(如有需要)
package语句
import语句
一个顶级类(只有一个)
以上每个部分之间用一个空行隔开。
2.2 行宽
行宽度不要超过130。
2.3 包的导入
删除不用的导入,尽量不要使用整个包的导入。在eclipse下经常使用快捷键 ctrl+shift+o 修正导入。mac:control+option+o
2.3.1 顺序和间距
import语句可分为以下几组,按照这个顺序,每组由一个空行分隔:
所有的静态导入独立成组
com.google imports(仅当这个源文件是在com.google包下)
第三方的包。每个顶级包为一组,字典序。例如:android, com, junit, org, sun
java imports
javax imports
组内不空行,按字典序排列。
2.3.2 import不要使用通配符
即,不要出现类似这样的import语句:import java.util.*;
2.4 类格式
2.5 域格式
每行只能声明一个域。
域的声明用空行隔开。
2.5 方法格式
2.6 代码块格式
2.6.1 缩进风格
大括号的开始在代码块开始的行尾,闭合在和代码块同一缩进的行首.
2.6.2 表示分割时用一个空格
if ( a > b ) {
//do something here
};
a + b = c;
b - d = e;
return a == b ? 1 : 0;
call(a, b, c);
2.6.3 空行的使用
空行可以表达代码在语义上的分割,注释的作用范围,等等。将类似操作,或一组操作放在一起不用空行隔开,而用空行隔开不同组的代码。
3,注释规范
少而精。
注释要和代码同步,过多的注释会成为开发的负担注释不是用来管理代码版本的,如果有代码不要了,直接删除,svn会有记录的,不要注释掉,否则以后没人知道那段注释掉的代码该不该删除。
对于垃圾代码或过时配置,坚决清理干净。过时注释也立刻删除。
3.1 Java Doc
表明类、域和方法等的意义和用法等的注释,要以javadoc的方式来写。Java Doc是个类的使用者来看的,主要介绍 是什么,怎么用等信息。凡是类的使用者需要知道,都要用Java Doc 来写。非Java Doc的注释,往往是个代码的维护者看的,着重告述读者为什么这样写,如何修改,注意什么问题等。 如下:
/**
* This is a class comment
*/
public class TestClass {
/**
* This is a field comment
*/
public String name;
/**
* This is a method comment
*/
public void call() {
}
}
3.2 块级别注释
3.3.1 块级别注释,单行时用 //, 多行时用 /* … */。
3.3.2 较短的代码块用空行表示注释作用域
3.3.3 较长的代码块要用
/*------ start: ------*/
和
/*-------- end: -------*/
包围
举例:
/*----------start: 订单处理 ------- */
//取得dao
OrderDao dao = Factory.getDao("OrderDao");
/* 查询订单 */
Order order = dao.findById(456);
//更新订单
order.setUserName("uu");
orderDao.save(order);
/*----------end: 订单处理 ------- */
3.3.4 可以考虑使用大括号来表示注释范围
使用大括号表示注释作用范围的例子:
/*----------订单处理 ------- */
{
//取得dao
OrderDao dao = Factory.getDao("OrderDao");
/* 查询订单 */
Order order = dao.findById(456);
//更新订单
order.setUserName("uu");
orderDao.save(order);
}
3.4 行内注释
行内注释用 // ;
不要用行尾注释。
4,其他编程注意
4.1 使用log而不是System.out.println()
log可以设定级别,可以控制输出到哪里,容易区分是在代码的什么地方打印的,而System.out.print则不行。而且,System.out.print的速度很慢。所以,除非是有意的,否则,都要用log。至少在提交到svn之前把System.out.print换成log。
4.2 每个if while for等语句,都不要省略大括号{}
4.3 善用TODO:
在代码中加入 //TODO: ,大部分的ide都会帮你提示,让你知道你还有什么事没有做。
4.4 在需要留空的地方放一个空语句或注释,告诉读者,你是故意的
比如:
if (!exists(order)) {
;
}
或:
if (!exists(order)) {
//nothing to do
}
python里甚至有pass语句作为空语句进行占位。
4.5 减少代码嵌套层次
代码嵌套层次达3层以上时,一般人理解起来都会困难。
减少措施:
- 合并条件
- 利用 return 以省略后面的else
- 利用子方法
4.6 程序职责单一
让程序单元的职责单一,可以使你在编写这段程序时关注更少的东西,从而降低难度,减少出错。
4.7 变量的声明,初始化和被使用尽量放到一起
比方说如下代码:
int orderNum= getOrderNum();
//do something withou orderNum here
call(orderNum);
上例中的注释处代表了一段和orderNum不相关的代码。orderNum的声明和初始化离被使用的地方相隔了很多行的代码,这样做不好,不如这样:
//do something withou orderNum here
int orderNum= getOrderNum();
call(orderNum);
4.8 缩小变量的作用域
能用局部变量的,不要使用实例变量,能用实例变量的,不要使用类变量。变量的生存期越短,意味着它被误用的机会越小,同一时刻程序员要关注的变量的状态越少。
实例变量和类变量默认都不是线程安全的,局部变量是线程安全的。
4.9 尽量不要用参数来带回方法运算结果
参数作用是存储数值。职责单一原则,参数尽量不做返回值。
4.10 不使用魔法值
尤其对于计算的数值一次性算出来,避免多次计算。如:
long hour = 1000*60*60L;
应该写成:longhour=3600 000;
4.11 long类型变量以L结尾
小写L结尾容易引起混淆。
4.12 单个方法的总行数不超过 80 行
4.13 不能使用过时的类或方法
4.14 Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals
正例:
"test".equals(object);
4.15 在getter/setter方法中,不要增加业务逻辑
会增加排查问题的难度。
4.16 循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展
反例:
String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello"; //每次都new新的对象
}
4.17 可以使用 warn 日志级别来记录用户输入参数错误的情况
避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。
说明:注意日志输出的级别,error 级别只记录系统逻辑出错、异常或者重要的错误信息。
5,集合注意
hashCode和equals
- 只要重写equals,就必须重写hashCode。
- 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。
- 如果自定义对象作为Map的键,那么必须重写hashCode和equals。
6,并发处理
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor的方式
这样的处理方式让其他同学更加明确线程池运行规则,避资源耗尽风险。
说明: Executors返回的线程池对象 的弊端如下 :
- FixedThreadPool l和SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
- CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类
正例:注意线程安全,使用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。
锁最小化原则
能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
并发修改同一记录时,避免更新丢失,需要加锁
- 在应用层加锁
- 在缓存加锁
- 在数据库层使用乐观锁,使用version作为更新依据
说明:如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次。
使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法被执行到,避免主线程无法执行至await方法,直到超时才返回结果
注意,子线程抛出异常堆栈,不能在主线程try-catch到。