美文网首页Java web
Java编码规范(个人总结向)

Java编码规范(个人总结向)

作者: 纳米君 | 来源:发表于2019-09-30 16:05 被阅读0次

    基本规范

    • 禁止使用SUN包下的类,替换成公开的API,否则Maven不能打包

    • 保持代码 google 风格

    • 移除无用的代码和注释

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

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

    • 禁止JavaBean中的setter和getter里有逻辑代码

    • 定时任务时间间隔不能小于5分钟,特殊情况需要告知技术经理,并经过技术经理同意

    • 产品需求里的批量导入最多支持20条

    • 【建议】POJO 类必须写 toString 方法,方便问题查询

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

    • 【推荐】类成员、方法、构造函数访问控制从严,正确使用 public、private、protected、final

    • 【推荐】 表达异常的分支时, 少用 if-else 方式, 这种方式可以改写成:

      说明: 如果非得使用 if()...else if()...else...方式表达逻辑, 避免后续代码维护困难, 请勿超过 3 层。
      正例: 超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现。

      if (condition) {
          return obj;
      }
      // 接着写 else 的业务逻辑代码;
      

      说明: 如果非得使用 if()...else if()...else...方式表达逻辑, 避免后续代码维护困难, 请勿超过 3 层。
      正例: 超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现。

    Service/DAO层方法命名规范

    • 获取单个对象的方法用 select 作前缀。
    • 获取多个对象的方法用 selectList 作前缀。
    • 获取统计值的方法用 selectCount 作前缀。
    • 插入的方法用 insert 作前缀。
    • 删除的方法用 delete 作前缀。
    • 修改的方法用 update 作前缀。

    变量命名

    • 力求语义表达完整清楚,不要嫌名字长。

    包名

    • 统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词
    • 统一使用单数形式

    常量定义

    【推荐】常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包内共享常量、类内共享常量。

    • 跨应用共享常量:放置在二方库中,通常是 client.jar 中的 constant 目录下。
    • 应用内共享常量:放置在一方库中, 通常是子模块中的 constant 目录下。
      反例: 易懂变量也要统一定义成应用内共享常量,两位攻城师在两个类中分别定义了表示“是”的变量:
      类 A 中: public static final String YES = "yes";
      类 B 中: public static final String YES = "y";
      A.YES.equals(B.YES),预期是 true,但实际返回为 false,导致线上问题。
    • 子工程内部共享常量:即在当前子工程的 constant 目录下。
    • 包内共享常量:即在当前包下单独的 constant 目录下。
    • 类内共享常量:直接在类内部 private static final 定义。

    变量类型

    • 【强制】 所有的 POJO 类属性必须使用包装数据类型
    • 【强制】 RPC 方法的返回值和参数必须使用包装数据类型
    • 【推荐】 所有的局部变量使用基本数据类型

    final关键字

    【推荐】 final 可以声明类、成员变量、方法、以及本地变量,下列情况使用 final 关键字:

    • 不允许被继承的类,如: String 类。
    • 不允许修改引用的域对象,如: POJO 类的域变量。
    • 不允许被重写的方法,如: POJO 类的 setter 方法。
    • 不允许运行过程中重新赋值的局部变量。
    • 避免上下文重复使用一个变量,使用 final 描述可以强制重新定义一个变量,方便更好地进行重构。

    hashCode 和 equals的处理

    • 只要重写 equals,就必须重写 hashCode。
    • 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
    • 如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。(比如:String类)

    集合处理

    • 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。反例: 直接使用 toArray ()存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。

    • 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
      说明: asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。

      第一种情况: list.add("yangguanbao"); 运行时异常。
      第二种情况: str[0] = "gujin"; 那么 list.get(0)也会随之修改。

      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 对象加锁。

    • 【强制】HashMap 初始化,initialCapacity = (需要存储的元素个数 / 负载因子0.75) + 1。

    • 【推荐】使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。如果是 JDK8,使用 Map.foreach 方法。

    日期处理

    • 如果是 JDK8 的应用,可以使用 Instant 代替 Date, LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。 JDK8日期详解

    并发处理

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

    • 【强制】多线程并行处理定时任务时, Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。

    • 【推荐】避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed 导致的性能下降。
      说明: Random 实例包括 java.util.Random 的实例或者 Math.random()的方式。
      正例: 在 JDK7 之后,可以直接使用 API ThreadLocalRandom, 而在 JDK7 之前, 需要编码保证每个线程持有一个实例。

    • 【参考】 ThreadLocal 无法解决共享对象的更新问题, ThreadLocal 对象建议使用 static 修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,
      所有此类实例共享此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。

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

    • 【参考】 volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现:

      AtomicInteger count = new AtomicInteger(); 
      count.addAndGet(1);
      

      如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数) 。

    异常日志处理

    • 【强制】 finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
      说明: 如果 JDK7 及以上,可以使用 try-with-resources 方式。
    • 【推荐】方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。
    • 【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
      1)返回类型为基本数据类型, return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
      反例: public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。
      2) 数据库的查询结果可能为 null。
      3) 集合即使 isNotEmpty,取出的数据元素也可能为 null。
      4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
      5) 对于 Session 中获取的数据, 建议 NPE 检查,避免空指针。
      6) 级联调用 obj.getA().getB().getC(); 一连串调用,易产生 NPE。
      正例: 使用 JDK8 的 Optional 类来防止 NPE 问题。

    日志规范

    • 【强制】应用中不可直接使用日志系统(Log4j、 Logback) 中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      private static final Logger logger = LoggerFactory.getLogger(Abc.class);
      
    • 【强制】日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。

    • 【强制】应用中的扩展日志(如打点、临时监控、访问日志等) 命名方式:appName_logType_logName.log。logType:日志类型,推荐分类有 stats/monitor/visit 等;logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
      正例: mppserver 应用中单独监控时区转换异常,如:
      mppserver_monitor_timeZoneConvert.log
      说明: 推荐对日志进行分类, 如将错误日志和业务日志分开存放,便于开发人员查看,也便于通过日志对系统进行及时监控。

    • 【强制】对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
      说明: logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
      如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
      正例: (条件)

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

      正例: (占位符)
      logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);

    • 【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
      正例: <logger name="com.taobao.dubbo.config" additivity="false">

    • 【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
      正例: logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);

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

    • 【推荐】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出 error 级别,避免频繁报警。
      说明: 注意日志输出的级别, error 级别只记录系统逻辑出错、异常或者重要的错误信息。

    • 【强制】日志级别对应的关系

      • trace 用来跟踪线上问题,问题解决后删除
      • Warning 用来警告不符合业务规范等相关日志
      • Info 用来输出业务日志,用于排查和恢复数据
      • Error 只记录线上异常日志,方便监控报警

    单元测试

    • 单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证

    • 单元测试用例之间决不能互相调用,也不能依赖执行的先后次序

    • 编写测试代码遵守 BCDE 原则

      • B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等
      • C:Correct,正确的输入,得到预期的结果
      • D:Design,与设计文档相结合,来编写单元测试
      • E:Error,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得到预期的结果
    • 对于不可测的代码,建议做必要的重构,使代码变得可测,避免为了达到测试要求而书写不规范的代码

    相关文章

      网友评论

        本文标题:Java编码规范(个人总结向)

        本文链接:https://www.haomeiwen.com/subject/qnfhpctx.html