java开发代码规范

一、命名规范

  1. 不能以下划线或$开始或结束,不能拼音和英文混合或纯中文,杜绝不规范的缩写,尽量使用完整的单词。
  2. 类名使用大驼峰形式,但以下情形例外:DO/ BO/ DTO / VO/ AO/ PO/ UID等。方法名、参数名、变量(局部、成员)都用小驼峰。常量全部大写,单词下划线隔开。
  3. 抽象类命名使用Abstract或Base开头;异常类命名使用Exception结尾;测试类命名以它要测试的类的名称开始,以Test结尾。
  4. 定义数组时中括号在类型后面,不能在数组名后边。
  5. 布尔类型变量不能加is前缀,否则框架解析时可能会引起系列化错误。
  6. 包名小写,点分符隔开,包名用单数形式(类名可以用复数形式)。
  7. 子父类的成员变量以及不同代码块的局部变量不能用完全相同的命名。
  8. 在常量与变量的命名时,表示类型的名词放在词尾。
  9. 若模块、接口、类、方法等用了设计模式,要写清楚是什么设计模式。
  10. 接口里不能定义变量,接口中的方法和属性不能有修饰符。
  11. 接口和实现类:暴露出的服务为接口,实现类后缀为Impl。形容能力的接口用形容词做接口名(-able)eg:AbstractTranslator实现Translatable接口。
  12. 枚举类Enum为后缀,成员名称全部大写(常量)。
  13. 各层命名规约:
  •  Service/DAO层方法命名规约

1)获取单个对象的方法用get做前缀。2)获取多个对象的方法用list做前缀,复数形式结尾如:listObjects。3)获取统计值的方法用count做前缀。

4)插入的方法用save/insert做前缀。5)删除的方法用remove/delete做前缀。6)修改的方法用update做前缀。

  • 领域模型命名规约

1)数据对象:xxxDO,xxx为数据表名。2)数据传输对象:xxxDTO,xxx为业务领域相关的名称。3)展示对象:xxxVO,xxx一般为网页名称。4)POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。

二、常量

  • 不允许任何未经预先定义的常量直接出现在代码中。
  • long/Long赋值时,数值后的L大写。
  • 常量类按功能分类。
  • 常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。

1)跨应用共享常量:放置在二方库中,通常是client.jar中的constant目录下。

2)应用内共享常量:放置在一方库中,通常是子模块中的constant目录下。

3)子工程内部共享常量:即在当前子工程的constant目录下。

4)包内共享常量:即在当前包下单独的constant目录下。

5)类内共享常量:直接在类内部private static final定义。

  • 若变量值仅在一个固定范围内变化用enum类型来定义。

三、代码格式

  1. 大括号:内为空不换行——{},不为空——左括号前不换后换,右括号前换,后有else等不换,终止的必须换。
  2. 空格:
    小括号左右与字符间均不加空格;if/for/while/switch/do等保留字与括号之间都必须加空格;二,三目运算符左右都要加空格;4个空格缩进,不用tab
    双斜线注释与内容间有一个空格;强制类型转换,右括号和转换值中间不用加空格;方法传参时,多个参数逗号后加空格。
  3. 换行:单行超出120个字符换行:第二行比第一行缩进4个空格,之后的行不缩进;运算符、方法调用的点符和下文一起换到下一行;多个参数在逗号后进行换行;括号前不换行。
  4. text file encoding设置为UTF-8;中文件的换行符使用Unix格式,不要使用Windows格式。
  5. 方法总行数不超过80(尽量)

四、OOP规约

  1. 直接用类名访问类的静态变量和方法。
  2. 重写必须加@Override注解
  3. 尽量不用Object可变参数,用的时候必须放在最后
  4. 外部正在调用或者二方库依赖的接口,不允许修改方法签名。接口过时必须加@Deprecated注解,并清晰地说明采用的新接口或者新服务是什么。不能使用过时的类或方法。
  5. 应该用“test”.equals(Object),不能用Object调用equals;整型包装类对象间比较用equals。浮点数比较不能用==和equals。
  6. 定义数据对象DO类时,属性类型要与数据库字段类型相匹配
  7. 禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象。优先推荐入参为String的构造方法,或使用BigDecimal的valueOf方法,此方法内部其实执行了Double的toString,而Double的toString按double的实际能表达的精度对尾数进行了截断。
  8. 所有的POJO类属性、RPC方法的返回值和参数必须使用包装数据类型。所有的局部变量使用基本数据类型。(POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。)
  9. 定义DO/DTO/VO等POJO类时,不要设定任何属性默认值。
  10. 序列化类新增属性时,不要修改serialVersionUID字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,修改serialVersionUID值。(serialVersionUID不一致会抛出序列化运行时异常。)
  11. 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中。
  12. POJO类必须写toString方法。使用IDE中的工具:source> generate toString时,如果继承了另一个POJO类,注意在前面加一下super.toString。
  13. 禁止在POJO类中,同时存在对应属性xxx的isXxx()和getXxx()方法。说明:框架在调用属性xxx的提取方法时,并不能确定哪个方法一定是被优先调用到。
  14. 使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException的风险。
  15. 当一个类有多个构造方法,或者多个同名方法,这些方法应该按顺序放置在一起,便于阅读。类内方法定义的顺序依次是:公有方法或保护方法>私有方法> getter/setter方法。
  16. setter方法中,参数名称与类成员变量名称一致,this.成员名=参数名。在getter/setter方法中,不要增加业务逻辑,
  17. 循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。
  18. final可以声明类、成员变量、方法、以及本地变量,下列情况使用final关键字:不允许被继承的类;不允许修改引用的域对象;不允许被覆写的方法;不允许运行过程中重新赋值的局部变量;避免上下文重复使用一个变量,使用final可以强制重新定义一个变量,方便更好地进行重构。
  19. 慎用Object的clone方法来拷贝对象。想实现深拷贝需覆写clone方法实现域对象的深度遍历式拷贝。
  20. 如果不允许外部直接通过new来创建对象,那么构造方法必须是private;工具类不允许有public或default构造方法;类非static成员变量并且与子类共享,必须是protected;类非static成员变量并且仅在本类使用,必须是private;类static成员变量如果仅在本类使用,必须是private;若是static成员变量,考虑是否为final;类成员方法只供类内部调用,必须是private;类成员方法只对继承类公开,那么限制为protected。

五、集合

  1. equals和hashCode:重写equals必须重写hashCode;Set存储的对象必须覆写这两个方法;如果自定义对象作为Map的键,那么必须覆写hashCode和equals。
  2. ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常。
  3. 使用Map方法keySet()/values()/entrySet()返回集合对象时,不可以对其进行添加元素操作,否则会抛出UnsupportedOperationException异常。
  4. Collections类返回的对象,如:emptyList()/singletonList()等都是immutablelist,不可对其进行添加或者删除元素的操作。
  5. 在subList场景中,对原集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生ConcurrentModificationException异常。
  6. 使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一致、长度为0的空数组。(使用toArray带参方法,数组空间大小的length:=0,动态创建与size相同的数组,性能最好。大于0但小于size,重新创建大小等于size的数组,增加GC负担。等于size,在高并发情况下,创建完成之后,size正在变大的情况下,负面影响与上相同。大于size,空间浪费,且在size处插入null值,存在NPE隐患。
  7. 在使用Collection接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断。
  8. 使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。(asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。
  9. 泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用add方法,而<?superT>不能使用get方法,作为接口调用赋值时易出错。(频繁往外读取内容的,适合用<? extends T>。经常往里插入的,适合用<?superT>)。
  10. 在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行instanceof判断,避免抛出ClassCastException异常。
  11. 不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
  12. 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比较结果相同。
  13. 集合泛型定义时,在JDK7及以上,使用diamond语法或全省略。说明:diamond,直接使用<>来指代前边已经指定的类型。<>:HashMap <String, String> userCache = new HashMap<>(16);全省略方式ArrayList<User> users = new ArrayList(10);
  14. 集合初始化时,指定集合初始值大小。(HashMap使用HashMap(int initialCapacity)初始化。)
  15. 使用entrySet(1次)遍历Map类集合KV,而不是keySet(2次)方式进行遍历。
  16. 利用Set元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List的contains方法进行遍历、对比、去重操作。
  17. 以下是Map类集合K/V不能为空的情况

六、并发

  1. 工具类、工厂类等类及各种方法都要保证线程安全。
  2. 线程及线程池名称要有意义,方便问题排查。
  3. 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式。Executors返回的线程池对象的弊端如下:1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。2)CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
  4. SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。(如果是JDK8的应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat)
  5. 必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,不清理可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用try-finally块进行回收。
  6. 能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法。
  7. 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
  8. 在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。a.如果在lock方法与try代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功获取锁。b.如果lock方法在try代码块之内,可能由于其它方法抛出异常,导致在finally代码块中,unlock对未加锁的对象解锁,它会调用AQS的tryRelease方法(取决于具体实现类),抛出IllegalMonitorStateException异常;c.在Lock对象的lock方法实现中可能抛出unchecked异常,产生的后果与b.相同。
  9. 在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。Lock对象的unlock方法在执行时,它会调用AQS的tryRelease方法(取决于具体实现类),如果当前线程不持有锁,则抛出IllegalMonitorStateException异常。
  10. 并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据。说明:如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次。
  11. 多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,如果在处理定时任务时使用ScheduledExecutorService则没有这个问题。
  12. 资金相关的金融敏感信息,使用悲观锁策略。
  13. 使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法被执行到,避免主线程无法执行至await方法,直到超时才返回结果。说明:注意,子线程抛出异常堆栈,不能在主线程try-catch到。
  14. 避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致的性能下降。说明:Random实例包括java.util.Random的实例或者Math.random()的方式。
  15. 在并发场景下,通过双重检查锁(double-checkedlocking)实现延迟初始化的优化问题隐患,推荐解决方案:将目标属性声明为volatile型。
  16. volatile解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。如果是count++操作,使用如下类实现:AtomicInteger count = new AtomicInteger();count.addAndGet(1);如果是JDK8,推荐使用LongAdder对象,比AtomicLong性能更好(减少乐观锁的重试次数)。
  17. HashMap在容量不够进行resize时由于高并发可能出现死链,导致CPU飙升,在开发过程中可以使用其它数据结构或加锁来规避此风险。
  18. ThreadLocal对象使用static修饰,ThreadLocal无法解决共享对象的更新问题。说明:这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。

七、控制语句

  1. 在一个switch块内,每个case要么通过continue/break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。(break是退出switch语句块,而return是退出方法体。)
  2. 当switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断。
  3. 在if/else/for/while/do语句中必须使用大括号。即使只有一行代码,避免采用单行的编码方式。
  4. 在高并发场景中,避免使用”等于”判断作为中断或退出的条件。容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。
  5. 表达异常的分支时,少用if-else方式。如果非使用if()...else if()...else...方式表达逻辑,请勿超过3层。超过3层的if-else的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现,其中卫语句即代码逻辑先考虑失败、异常、中断、退出等直接返回的情况,以方法多个出口的方式,解决代码中断分支嵌套的问题。
  6. 除常用方法(如getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名。
  7. 不要在其它表达式(尤其是条件表达式)中,插入赋值语句。赋值语句需要清晰地单独成为一行。
  8. 循环体中的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的try-catch操作。
  9. 避免采用取反逻辑运算符。
  10. 需要进行参数校验:1)调用频次低的方法。2)执行时间开销很大的方法。3)需要极高稳定性和可用性的方法。4)对外提供的开放接口,不管是RPC/API/HTTP接口。5)敏感权限入口。
  11. 不需要进行参数校验:1)极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求。2)底层调用频度比较高的方法。一般DAO层与Service层都在同一个应用中,部署在同一台服务器中,所以DAO的参数校验,可以省略。3)被声明成private只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。

八、注释

类、类属性、类方法的注释必须使用Javadoc规范,使用/**内容*/格式,不得使用//xxx方式。

所有的抽象方法(包括接口中的方法)必须要用Javadoc注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。

所有的类都必须添加创建者和创建日期。

方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐。

所有的枚举类型字段必须要有注释,说明每个数据项的用途。

代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改。

谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。

待办事宜(TODO):(标记人,标记时间,[预计处理时间])只能应用于类,接口和方法(因为它是一个Javadoc标签)。错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])在注释中用FIXME标记某代码是错误的,而且不能工作,需要及时纠正的情况。

九、其他

  1. 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度
  2. velocity调用POJO类的属性时,直接使用属性名取值即可,模板引擎会自动按规范调用POJO的getXxx(),如果是boolean基本数据类型变量(boolean命名不需要加is前缀),会自动调用isXxx()方法。如果是Boolean包装类对象,优先调用getXxx()的方法。
  3. 后台输送给页面的变量必须加$!{var}——中间的感叹号。如果var等于null或者不存在,那么${var}会直接显示在页面上。
  4. Math.random()这个方法返回是double类型,注意取值的范围0≤x<1(能够取到零值,注意除零异常),如果想获取整数类型的随机数,不要将x放大10的若干倍然后取整,直接使用Random对象的nextInt或者nextLong方法。
  5. 获取当前毫秒数System.currentTimeMillis();而不是newDate().getTime();说明:如果想获取更加精确的纳秒级时间值,使用System.nanoTime()的方式。在JDK8中,针对统计时间等场景,推荐使用Instant类。
  6. 日期格式化时,传入pattern中表示年份统一使用小写的y。说明:日期格式化时,yyyy表示当天所在的年,而大写的YYYY代表是week in which year,意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的YYYY就是下一年。另外需要注意:表示月份是大写的M;表示分钟则是小写的m;24小时制的是大写的H;12小时制的则是小写的h。表示日期和时间的格式如下所示:new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  7. 不要在视图模板中加入任何复杂的逻辑。任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。及时清理不再使用的代码段或配置信息。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值