Java开发编程规范——(摘自阿里java开发文档)

java编程规范

一、编程规约

1、常量篇

  • 在long或者Long赋值时,数值后使用大写的L,不能是小写的l,小写容易跟数字1混淆,造成误解。

    long a=2L而不要写成long a=2l

  • 不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。 说明:大而全的常量类,杂乱无章,使用查找功能才能定位到修改的常量,不利于理解和维护。

    正例:缓存相关常量放在类CacheConsts下;系统配置相关常量放在类ConfigConsts下。

  • 【强制】类型与中括号紧挨相连来 表示 数组。

    正例: 定义整形数组 int[] arrayDemo; 反例: 在 main 参数中,使用 String args[] 来定义。

  • POJO类中布尔类型的变量,都不要加is前缀,否则部分框架解析会引起序列化错误。

    反例:定义为基本数据类型Boolean isDeleted的属性,它的方法也是isDeleted(),RPC框架在反向解析的时候,“误以为”对应的属性名称是deleted,导致属性获取不到,进而抛出异常。

  • 各层命名规约:

    A) Service/DAO层方法命名规约

    1) 获取单个对象的方法用get做前缀。

    2) 获取多个对象的方法用list做前缀,复数形式结尾如:listObjects。

    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。

  • 采用4个空格缩进,禁止使用tab字符。

    说明: 如果使用 tab 缩进,必须设置 1 个 tab 为 4 个空格。

    IDEA 设置 tab 为 4 个空格时,请勿勾选 Use tab character ;而在

    eclipse 中,必须勾选 insert spaces for tabs 。

2、OOP规约

  • 所有的覆写方法,必须加@Override注解。

    说明:getObject()与get0bject()的问题。一个是字母的O,一个是数字的0,加@Override可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。

  • Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。

    正例:"test".equals(object);

    反例:object.equals("test");

  • 所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。

    说明:对于Integer var = ? 在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。(面试题出过)

  • 关于基本数据类型与包装数据类型的使用标准如下:

    1) 【强制】所有的POJO类属性必须使用包装数据类型。

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

    3) 【推荐】所有的局部变量使用基本数据类型。

    说明:POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE(空值异常Null Pointer Exception)问题,或者入库检查,都由使用者来保证。 正例:数据库的查询结果可能是null,因为自动拆箱,用基本数据类型接收有NPE风险。 反例:比如显示成交总额涨跌情况,即正负x%,x为基本数据类型,调用的RPC服务,调用不成功时,返回的是默认值,页面显示为0%,这是不合理的,应该显示成中划线。所以包装数据类型的null值,能够表示额外的信息,如:远程调用失败,异常退出。

  • 使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException的风险。

    说明: String str = "a,b,c,,"; String[] ary = str.split(","); // 预期大于3,结果是3 System.out.println(ary.length);

  • 慎用Object的clone方法来拷贝对象。

    说明:对象的clone方法默认是浅拷贝,若想实现深拷贝需要重写clone方法实现域对象的深度遍历式拷贝。

    1、浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。

    2、深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

3、集合处理

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

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

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

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

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

  • 使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大小就是list.size()。

    说明:使用toArray带参方法,入参分配的数组空间不够大时,toArray方法内部将重新分配内存空间,并返回新数组地址;如果数组元素个数大于实际所需,下标为[ list.size() ]的数组元素将被置为null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。

    正例: List<String> list = new ArrayList<String>(2); list.add("guan"); list.add("bao"); String[] array = new String[list.size()]; array = list.toArray(array); 反例:

    直接使用toArray无参方法存在问题,此方法返回值只能是Object[]类,若强转其它类型数组将出现ClassCastException错误。

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

    说明:asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。 String[] str = new String[] { "you", "wu" }; List list = Arrays.asList(str); 第一种情况:list.add("yangguanbao"); 运行时异常。 第二种情况:str[0] = "gujin"; 那么list.get(0)也会随之修改。

  • 不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。

    正例: List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if (删除元素的条件) { iterator.remove(); } }

    反例:

    for (String item : list) { if ("2".equals(item)) { list.remove(item); } } 结果是报异常:ConcurrentModificationException

    ## 总结    foreach循环里,遍历集合实际上是通过迭代器Iterator进行的,但是元素的remove/add方法却是使用的是集合自己的方法,导致在遍历的时候,会发现某个元素自己神不知鬼不觉地被删除/增加了,这时候,就会抛出一个异常,告诉用户可能会用多个线程对同一个集合发生了并发修改。

  • 【推荐】集合泛型定义时,在JDK7及以上,使用diamond语法或全省略。

    说明:菱形泛型,即diamond,直接使用<>来指代前边已经指定的类型。 正例: // <> diamond方式 HashMap<String, String> userCache = new HashMap<>(16); // 全省略方式 ArrayList<User> users = new ArrayList(10);

4、并发处理

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

    说明: Executors 返回的线程池对象 的弊端 如下 1 FixedThreadPool 和 SingleThread Pool 允许的请求队列长度为 Integer.MAX_VALUE,可 能会堆积大量的请求,从而导致 OOM。 2 CachedThreadPool 和 ScheduledThreadPool 允许的创建线程数量为 Integer.MAX_VALUE 可能会创建大量的线程,从而导致 OOM。

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

    说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法。

  • 【推荐】使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法被执行到,避免主线程无法执行至await方法,直到超时才返回结果。

    说明:注意,子线程抛出异常堆栈,不能在主线程try-catch到。

  • 【参考】volatile解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。如果是count++操作,使用如下类实现:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是JDK8,推荐使用LongAdder对象,比AtomicLong性能更好(减少乐观锁的重试次数)。

5、控制语句

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

  • 在高并发场景中,避免使用 等于 判断作为中断或退出的条件。

    说明: 如果并发控制没有处理好,容易产生等值判断被 击穿 的情况,使用大于或小于的区间 判断条件来代替。 反例: 判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变 成了负数, 这 样的话,活动无法终止。

6、MySql数据库------建表规约

  • 【强制】表达是与否概念的字段,必须使用 is_xxx的方式命名,数据类型是 unsigned tinyint(1 表示是, 0 表示否)。

    说明: 任何字段如果为非负数,必须是 unsigned。 注意: POJO类中的任何布尔类型的变量,都不要加 is前缀,所以,需要在 resultMap 设置 从 is_xxx到 Xxx的映射关系。数据库表示是与否的值,使用 tinyint类型,坚持 is_xxx的 命名方式是为了明确其取值含义与取值范围。 正例: 表达逻辑删除的字段名 is_deleted 1 表示删除, 0 表示未删除。

  • 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。 说明: MySQL在 Windows下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、 表名、字段名,都不允许出现任何大写字母,避免节外生枝。

    正例:aliyun_admin,rdc_config,level3_name

    反例:AliyunAdmin,rdcConfig,level_3_name

  • 表名不使用复数名词。

    说明:表名应该仅仅表示表里面的实体内容,不应该表示实体数量,对应于DO类名也是单数形式,符合表达习惯。

  • 主键索引名为pk字段名;唯一索引名为uk字段名;普通索引名则为idx字段名。 说明:pk 即primary key;uk_ 即 unique key;idx_ 即index的简称。

  • 小数类型为decimal,禁止使用float和double。

    说明:float和double在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过decimal的范围,建议将数据拆成整数和小数分开存储。

  • varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,如果存储长度大于此值,定义字段类型为text,独立出来一张表,用主键来对应,避免影响其它字段索引效率。

  • 表必备三字段:id, gmt_create, gmt_modified。

    说明: 其中 id必为主键,类型为 bigint unsigned、单表时自增、步长为 1 。 gmt_create, gmt_modified的类型均为 datetime类型,前者现在时表示主动创建,后者过去分词表示被 动更新。

  • 【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。

    冗余字段应遵循:

    1)不是频繁修改的字段。

    2)不是varchar超长字段,更不能是text字段。 正例:商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存储类目名称,避免关联查询。

  • 【推荐】单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。

    说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。

  • 【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。

    正例:如下表,其中无符号值可以避免误存负数,且扩大了表示范围。

对象年龄区间类型字节表示范围
150岁之内tinyint unsigned10-255
数百岁smallint unsigned20-65535
恐龙化石数千万年int unsigned40-约42.9亿
太阳约50亿年bigint unsigned80-10^19

7、MySql数据库-----索引规约

  • 业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。

    说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

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

    说明:即使双表join也要注意表索引、SQL性能。

  • 在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。

    说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用count(distinct left(列名, 索引长度))/count(*)的区分度来确定。Example:

    select count(distinct left(c_address,20))/count(*) from customer;

  • 利用延迟关联或者子查询优化超多分页场景。

    说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。

    正例:先快速定位需要获取的id段,然后再关联:

    SELECT a.* FROM 表1 a, (select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id

  • 建组合索引的时候,区分度最高的在最左边。

    正例:如果where a=? and b=? ,如果a列的几乎接近于唯一值,那么只需要单建idx_a索引即可。

    说明:存在非等号和等号混合时,在建索引时,请把等号条件的列前置。如:where c>? and d=? 那么即使c的区分度更高,也必须把d放在索引的最前列,即索引idx_d_c。

8、sql语句

  • 不要使用count(列名)或count(常量)来替代count(),count()是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关。

    说明:count(*)会统计值为NULL的行,而count(列名)不会统计此列为NULL值的行。


    count(distinct col) 计算该列除NULL之外的不重复行数,注意 count(distinct col1, col2) 如果其中一列全为NULL,那么即使另一列有不同的值,也返回为0。


    当某一列的值全是NULL时,count(col)的返回结果为0,但sum(col)的返回结果为NULL,因此使用sum()时需注意NPE问题。

    正例:可以使用如下方式来避免sum的NPE问题:

    SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;

  • 使用ISNULL()来判断是否为NULL值。

    说明:NULL与任何值的直接比较都为NULL。 1) NULL<>NULL的返回结果是NULL,而不是false。

    2) NULL=NULL的返回结果是NULL,而不是true。

    3) NULL<>1的返回结果是NULL,而不是true。

  • 不得使用外键与级联,一切外键概念必须在应用层解决。

    说明:以学生和成绩的关系为例,学生表中的student_id是主键,那么成绩表中的student_id则为外键。如果更新学生表中的student_id,同时触发成绩表中的student_id更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。

  • 【推荐】in操作能避免则避免,若实在避免不了,需要仔细评估in后边的集合元素数量,控制在1000个之内。

  • 数据订正(特别是删除、修改记录操作)时,要先select,避免出现误删除,确认无误才能执行更新语句。

9、ORM映射

  • 在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。

    说明:

    1)增加查询分析器解析成本。

    2)增减字段容易与resultMap配置不一致。

    3)无用字段增加网络消耗,尤其是text类型的字段。

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

    说明:参见定义POJO类以及数据库字段定义规定,在<resultMap>中增加映射,是必须的。在MyBatis Generator生成的代码中,需要进行对应的修改。

  • 不要用resultClass当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个POJO类与之对应。

    说明:配置映射关系,使字段与DO类解耦,方便维护。

10、服务器

  • 高并发服务器建议调小TCP协议的time_wait超时时间。 说明:操作系统默认240秒后,才会关闭处于time_wait状态的连接,在高并发访问下,服务器端会因为处于time_wait的连接数太多,可能无法建立新的连接,所以需要在服务器上调小此等待值。

    正例:在linux服务器上请通过变更/etc/sysctl.conf文件去修改该缺省值(秒): net.ipv4.tcp_fin_timeout = 30

  • 给JVM环境参数设置-XX:+HeapDumpOnOutOfMemoryError参数,让JVM碰到OOM场景时输出dump信息。

    说明:OOM的发生是有概率的,甚至相隔数月才出现一例,出错时的堆内信息对解决问题非常有帮助。

  • 在线上生产环境,JVM的Xms和Xmx设置一样大小的内存容量,避免在GC 后调整堆大小带来的压力。

  • 服务器内部重定向使用forward;外部重定向地址使用URL拼装工具类来生成,否则会带来URL维护不一致的问题和潜在的安全风险。

  • 2
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

百里东君~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值