• 一个好的JAVA程序员需要掌握的JAVA开发规范
  • 发布于 1个月前
  • 83 热度
    0 评论
雷军有一句名言广为流传:你写程序有写诗一样的感觉吗?
我回答:很有写诗的感觉了,反正都是回车键嘛。
玩笑归玩笑。写诗固然要放飞想象,但也要注意格律,才会有美感。
写代码亦是如此,犹如戴着镣铐起舞,谨守章法,否则不成了社会摇了么。
比如前些年大火的“梨花体”、“乌青体”、“废话体”,更多的是戏谑的意味在里面吧,要说艺术成就,仿佛是在开一个很认真的玩笑。

假如用以上三种风格来写代码,几乎可以对程序员生涯宣判死刑了,可见诗歌圈还是很宽容的。

有人说,我就乐意用自己的风格来写,反正也能运行,效果也不错。这确实很有可能,毕竟游击队有时候也能打得过正规军呢,但是想当将军,首先就需要对编制标准了然于心。除非你是李云龙嘛。

刚进入公司写代码,我泥腿子一个,写得真是惨不忍睹。老大很耐心,建议我首先养成规范的习惯,这对于代码可读性和维护以及日后的问题定位都是大有裨益的,并给我推荐了一份Google Java Style的文档。

由于是全英文,我读得效率很低(还是要提高姿势水平),于是我找来了阿里的Java规范文档来读,阿里作为大中华Java界最坚实的堡垒,其规范很有标杆性。

文档不长,40页,比较完备,在风格约定的同时,简述了原因,其实背后还是有很多Java原理中的细节体现,我读了受益匪浅,将其中一些适合初级程序员的内容筛选总结出来,分享给大家,也约束自己渐渐养成良好的编码习惯。

像写诗一样去写代码,需要很深的功力和理解,首先能做的,是把自己的代码写得像那么回事。

编程规约
命名风格
前提

1.禁止使用拼音和英文单词混搭
2.尽量避免使用拼音
3.切忌盲目缩写。

1.类名使用 UpperCamelCase方式。
2.抽象类命名使用 Abstract开头,测试类使用 Test结尾 。
3.枚举类名后面带上 Enum,成员全部大写。
4.假如类、接口、方法使用了某设计模式,需要在名称中体现出来。
变量
1.布尔类型的变量,不要加 is前缀。否则可能会导致一些框架的解析错误,因为有的框架就是通过变量名来解析的。
2.变量、方法名使用 lowerCamelCase方式。
常量定义
1.不允许任何未经定义的字符串直接出现在代码中。可读性差,如果是一些固定值的字符串,可以将其设为常量,并规范命名。
2.将常量通过其功能分类,分开维护。
3.如果常量在一个固定范围内变化,建议使用枚举型。
代码风格

空格

1. 左右小括号不允许与括号内相邻字符间出现空格.比如,禁止:
if(空格 a==b空格 )
2. if/ for/ while/ switch/ do 等保留字与小括号之间必须空格。二目,三目运算符的左右两边必须加空格。比如,正确:
if(a==b&&a==c); 
a==b?a=c:a=d;
3. 注释的双斜线必须与内容之间有且仅有一个空格。比如,正确:
// 这是一条注释
a = b;
4. 多个参数情况下,每个参数的逗号后加一个空格。比如:
void swap(int a, int b)
缩进

1. 采用四个空格空格进行缩进,禁止使用tab,如果采用tab,必须设置为1个tab为4个空格。(使用tab还是空格键是世纪论题)

2. 单行字符数不超过120个,超出需要换行,换行时:第二行相对第一行缩进4个空格,方法调用的点参与换行;但是如果是多个方法参数需要换行时,参数间的逗号不参与。比如:

空行

1. 不同逻辑、语义、业务的代码之间可以插入一个空行,但不必插入多个空行区分。
OOP规约

Object方法

1.
 Object的equals方法容易抛空指针异常,所以应该使用常量或确定有值的对象来调用此方法。比如:

2. 所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。因为比如 Integer, String在某种情况下,会引用缓存池中的对象,而另外的情况会在堆上生成对象,而 ==比较的是引用的地址。(具体什么时候指向缓存,什么时候指向堆中新建的对象可以查资料)
3. 谨慎使用 Object的 clone方法来拷贝对象,因为这是浅拷贝,想要深拷贝可以对 clone方法进行重写。
POJO类

1. POJO类的属性必须使用包装类型,提醒使用者来进行初始化赋值。而基本数据类型会产生默认初始值,这虽然使程序正常运行,但是是不正确的。
2.定义 DO/DTO/VO 等 POJO 类时,不要设定任何属性默认值。
3. 构造器中禁止任何业务逻辑,如果有必要的初始化逻辑,可以放在 init()中。
4. POJO类必须写 toString方法。主要是为了在发生异常时,可以直接调用toString来打印属性值,便于排查问题。
其他

1.类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter 方法。(因为公有方法是调用者或维护者最关心的,这条规则我之前一直做反了)

2.循环中的字符串连接方式,使用 StringBuilder的 append方法,不要直接使用字符串相加,因为这会在堆中循环创建新的字符串对象,造成内存浪费(具体参考 StringBuilder, StringBuffer, String,这三者的区别以及 String的不可变性也是面试的一个初级高频问题)

3. 方法权限从严控制,严禁宽泛访问权限。这有利于模块解耦,也有利于维护时更加清晰。

集合处理
1. Set之所以可以储存不可重复对象,是因为根据对象的 equals和 hashcode方法来判断的。所以这两个方法有一定的关联性,重写 equals就务必要重写 hashcode。我们平时常采用的 String类型作为 Map的键来用,因为 String中已经帮我们把这两个方法重写过了。如果要用其他类型的对象作为 Map的键,务必重写这两个方法。而关于 HashSet,可以点开 HashSet的源码,发现其中主要是一个 HashMap。

2.在使用泛型通配符时,有这两种形式: <?extendsT>和 <?superT> 前者的容器无法接收返回的数据,而后者只能接收 T类型及其子类的对象,其原因都是编译器提供了一种类型安全的保护。

3. 不要在 foreach 循环里进行元素的 remove/add 操作。 remove 元素请使用 Iterator 方式。具体为什么这么做,可以看一下手册中的举例以及这个博客的解答foreach循环中为什么不要进行remove/add操作

4.集合初始化时,尽量根据实际情况指定集合的大小,因为集合动态扩容是很消耗性能的,具体可以查看源码或者相关博客,也是初级高频面试问题。

5. 使用 entrySet遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。

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

并发处理

1. 创建线程或线程池时,指定有意义的名字,方便维护和检查。
2. 线程资源必须通过线程池分配,不能显示创建。这样是为了节省线程之间切换导致的过度开销。
3. 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险(这一点感觉有点苛刻,个人认为 Executors提供的四种线程池能较好地解决问题时,不需要自己去构建一个 ThreadPoolExecutor对象):

1.FixedThreadPool和SingleThreadPool:
允许的请求队列长度为 
Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2.CachedThreadPool和 ScheduledThreadPool:
允许的创建线程数量为 
Integer
.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

4. 在高并发时,需要考虑锁的性能损耗,不要多余加锁,并且将锁的范围控制在最小化。

5. HashMap是线程不安全的,在并发情况下容易在扩容时出现死链,导致CPU飙升。可以使用其他数据结构比如 ConcurrentHashMap来替代,或者加锁。
控制语句

1.在一个 switch块中,必须包含一个 default语句,即使为空。
2. 在判断或循环中,必须使用大括号。即使只有一行代码,避免采用一行流。
3.在高并发场景下,尽量避免使用“=”来作为中断或退出的条件,可以使用“<”、“>”作为区间来判断。假设一个电商场景,商品数量为0时关闭购买入口,但是假如并发出现了错误,数量可能瞬间变为负值,那么程序会一直继续下去。
4. 在使用判断时,若存在复杂的逻辑语句,可以使用一个命名规范的布尔值来代替这个冗长的语句,以提高代码的可读性。比如:
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if
 (existed) {
 ...
} 
异常日志
异常处理

1. 区分稳定代码与不稳定代码,避免粗暴地对大段代码进行 try-catch,这不利于维护和阅读。同时,尽量区分可能抛出的异常类型,再进行相应的处理。
2. 捕获异常是为了处理它,如果当前处理不了或者不想处理,可以抛给它的调用者。
3. finally中必须对资源对象、流对象进行关闭。并且不能在内使用 return,因为这会导致方法结束,而try中的 return得不到执行。
4. 可能出现的空指针异常:

.包装类型自动拆箱成基本类型

.数据库查询结果为null

.集合中即使有元素,取出的元素也可能为null(比如 HashMap允许存入null)
.远程调用返回对象时,一律需要判断是否为空。
.对于session中获取的值,建议进行空指针检查。
5.  级联调用容易产生空指针异常,比如 obj.getA().getB().getC()
日志规约

1. 对于 trace/ debug/ info级别的日志输出,必须使用条件或占位符的方式,否则可能会出现执行了操作,浪费了资源,但是日志没有打印的情况。建议占位符方式:

logger.debug(
"Processing trade with id: {} and symbol : {} "
, id, symbol); 

2.异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过 关键字 throws 往上抛出。,比如:

logger.error(各类参数或者对象 toString + 
"_"
 + e.getMessage(), e);

3. 注意日志输出量的问题,并记得及时删除观察日志。

4. 注意日志输出的级别。

MYSQL数据库
建表规约
数据类型
1. 表达布尔概念的字段,必须使用 is_xxx来命名,由于mysql中不存在布尔类型的数据类型,使用 unsignedtinyint(0和1来表示)

2. 小数类型为 decimal,禁止使用 float 和 double。因为后两者存在精度丢失的问题,导致在值比较时出现差错。

3. 使用 char定长字符串类型(会预先分配空间)来储存字符串长度几乎相等的数据,比如手机号。 varchar是可变长字符串(不会预先分配空间),但是长度不要超过5000。
命名方式

1. 任何库名、表名、字段名都只能使用小写字母、数字和下划线,并且数字不能放在开头和两个下划线之间
2. 任何库名、表名、字段名都禁用保留字(这算一个坑,踩过,具体有哪些保留字可以去查文档)表名不能使用复数形式。
3. 表必备三字段:id, gmtcreate, gmtmodified。
4. 表的命名最好是加上“业务名称_表的作用”。
5. 主键索引名为 pk字段名;唯一索引名为 uk字段名;普通索引名则为 idx_字段名。
分库分表

1. 单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
索引规约

1. 具有唯一特性的字段,必须建成唯一索引。唯一索引对insert操作的损耗是可以忽略不计的,而对于提升查找速度的效果非常明显。
工程结构

1. 开放接口层:可直接封装 Service 方法暴露成 RPC 接口;通过 Web 封装成 http 接口;进行 网关安全控制、流量控制等。
2. 终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染, JSP 渲染,移动端展示等。
3. Web 层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理等。
4. Service 层:相对具体的业务逻辑服务层。
5. Manager 层:通用业务处理层,它有如下特征:
1.对第三方平台封装的层,预处理返回结果及转化异常信息;
2.对 Service 层通用能力的下沉,如缓存方案、中间件通用处理;
3.与 DAO 层交互,对多个 DAO 的组合复用。

6. DAO 层:数据访问层,与底层 MySQL、Oracle、Hbase 等进行数据交互。
7. 外部接口或第三方平台:包括其它部门 RPC 开放接口,基础平台,其它公司的 HTTP 接口。
用户评论