天天看点

IDEA 阿里巴巴 P3C Java开发手册 [MD]

博文地址

我的GitHub 我的博客 我的微信 我的邮箱
baiqiantao bqt20094 [email protected]

目录

  • Java开发手册
    • 规则
    • IDEA 插件
      • 实时检测
      • 代码扫描
      • 其他说明
  • P3C-PMD
    • 检测内容简要说明
    • 对一些规则的解释
      • SimpleDateFormat
      • CountDownLatch
      • ThreadFactory
      • subList
      • lock 方法获取锁
      • ThreadLocal
      • 浮点数之间的等值判断
      • 线程池
      • Random

  • GitHub
  • 官网
  • 码出高效:Java开发手册
  • Java开发手册(嵩山版)

P3C

是世界知名的反潜机,专门对付水下潜水艇,项目组使用此名称寓意是

扫描出所有潜在的代码隐患

Forty-nine rules are realized based on PMD, Four rules are implemented within IDE plugins (IDEA and Eclipse) as follows:

  • [Mandatory] Using a deprecated class or method is prohibited 禁止.
  • [Mandatory] An overridden method from an interface or abstract class must be marked with

    @Override

    annotation.
  • [Mandatory] A static field or method should be directly referred

    by its class name

    instead of

    its corresponding object name

    .
  • [Mandatory] The usage of hashCode and equals should follow:
    • Override hashCode if equals is overridden.
      • 如果重写了equals,必须重写hashCode
      • 如果equals为true,则hashCode一定相同
      • 既然equals为false,hashCode也有可能相同
      • 如果hashCode不相同,那么equals一定为false
    • These two methods must be overridden for Set since they are used to ensure that no duplicate 重复 object will be inserted in Set.
      • 因为Set存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以Set存储的对象必须重写这两个方法。
    • These two methods must be overridden if self-defined 自定义 object is used as

      the key of Map

      . Note: String can be used as

      the key of Map

      since these two methods have been rewritten.
      • 如果自定义对象做为Map的键,那么必须重写 hashCode 和 equals
Mandatory:必须

  • IDEA 插件:Alibaba Java Code Guidelines
  • Eclipse 插件

目前插件实现了开发手册中的的

53

条规则,大部分基于

PMD

实现,其中有

4

条规则基于

IDEA

实现,并且基于

Inspection

实现了

实时检测

功能。部分规则实现了

Quick Fix

功能。

目前插件检测有两种模式:实时检测、手动触发。

实时检测功能会在开发过程中对

当前文件

进行检测,并以高亮的形式提示出来,同时也可以支持Quick Fix,该功能默认开启,可以通过配置关闭。

  • 检测结果高亮提示,并且鼠标放上去会弹出提示信息。
  • Alt+Enter键可呼出

    Intention

    菜单,不同的规则会提示不同信息的

    Quick Fix

    按钮
  • 如果我们不希望对代码提示违规信息,可以通过

    Inspection

    的设置关闭实时检测功能。
    • 通过菜单【关闭/打开实时检测功能】关闭/打开

      所有规则

      的实时检测功能
    • 通过

      File -> Settings -> Editor -> Inspections -> Ali-Check

      对每一个规则进行自由设置,比如可以关闭某条规则的实时检测功能或者修改提示级别。
PS:我觉得里面最恶心的一条规则是:不能在行尾添加注释!
提示内容:方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释。注意与代码对齐。

可以通过右键菜单、Toolbar按钮、快捷键三种方式手动触发代码检测。同时结果面板中可以对部分实现了

QuickFix

功能的规则进行快速修复。

触发方式

  • 在当前编辑的文件中点击右键,可以在弹出的菜单中触发对该文件的检测。
  • 在左侧的Project目录树种点击右键,可以触发对整个工程或者选择的某个目录、文件进行检测。
  • 也可以通过Toolbar中的按钮来触发检测,目前Toolbar的按钮触发的检测范围与您IDE当时的焦点有关,如当前编辑的文件或者是Project目录树选中的项。
  • 使用快捷键触发弹出窗口,会让你选择检测范围。

扫描结果

  • 检测结果直接使用

    IDEA Run Inspection By Name

    功能的结果界面,插件的检测结果分级为

    Blocker、Critical、Major

    ,默认按等级分组。
    • Blocker

      (崩溃):阻碍开发或测试工作的问题
    • Critical

      (严重):系统主要功能部分丧失、数据库保存调用错误、用户数据丢失
    • Major

      (一般):功能没有完全实现但是不影响使用,功能菜单存在缺陷但不会影响系统稳定性
  • 默认情况需要

    双击

    具体违规项才能打开对应的源文件,开启

    Autoscroll To Source

    选项后

    单击

    面板中的文件名、或者是具体的违规项的时候就可自动打开对应的源文件。
  • 对于实现

    Quick Fix

    的规则,在结果面板中可以直接一键修复。

版本要求:

  • 最低支持IDEA版本为14.1(buildNumber 141.0)
  • 最低支持JDK版本为1.7

设置入口:

  • 顶部工具栏图标
  • 主菜单 Tools -> 阿里编码规约
  • 右键菜单(在最下面)
  • 搜索Action【阿里】

代码提交时检测

  • 在提交代码框勾选

    Alibaba Code Guidelines

    选项
  • 如果有违反手册的地方会提示是否继续提交,选择取消后会自动对修改的代码进行扫描

中文乱码解决方法

  • 将字体设置成中文字体,如微软雅黑(microsoft yahei light)
  • 主菜单 Tools -> 阿里编码规约 -> 切换语言

  • 提示语-中文
  • 提示语-英文
  • mvnrepository

插件详细信息可以在以下路径查阅:

File -> Settings -> Editor -> Inspections -> Ali-Check

IDEA 阿里巴巴 P3C Java开发手册 [MD]

Ali-Check

全部检测内容:

  • 【】ArrayList的

    subList

    结果不可强转成ArrayList,否则会抛出

    ClassCastException

    异常。
  • iBATIS自带的queryForList(String statementName,int start,int size)不推荐使用
  • 【】long或者Long初始赋值时,必须使用大写的

    L

    ,不能是小写的

    l

    ,小写容易跟数字1混淆,造成误解。
  • 【-】Map/Set的

    key

    为自定义对象时,必须重写 hashCode 和 equals。
  • 【】Object的

    equals

    方法容易抛空指针异常,应使用

    常量或确定有值的对象

    来调用 equals。
  • 【-】POJO类中的任何布尔类型的变量,都

    不要加is

    ,否则部分框架解析会引起序列化错误
  • 【-】POJO类必须写

    toString()

    方法。

    如果

    继承了另一个POJO类,注意在前面加一下

    super.toString()

  • 【-】SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。
    • 如果是JDK8的应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat
    • 官方给出的解释:simple beautiful strong immutable thread-safe
  • 【】不允许任何魔法值(即未经定义的常量)直接出现在代码中。
  • 【】不能使用过时的类或方法。
    • 接口提供方既然明确是过时接口,那么有义务同时提供新的接口
    • 作为调用方来说,有义务去考证过时方法的新实现是什么
  • 【-】不能在finally块中使用return,finally块中的return返回后方法结束执行,

    不会再执行try块中的return语句

  • 【】不要在

    foreach

    循环里进行元素的remove/add操作,remove元素请使用

    Iterator

    方式。
  • 【】中括号是数组类型的一部分,数组定义如下:

    String[] args

  • 事务场景中,抛出异常被catch后,如果需要回滚,一定要手动回滚事务。
  • 【--】使用

    CountDownLatch

    进行异步转同步操作,每个线程退出前必须调用

    countDown()

    方法,线程执行代码注意catch异常,确保

    countDown()

    方法可以执行,避免主线程无法执行至await方法,直到超时才返回结果。
    • 注意,子线程抛出异常堆栈,不能在主线程try-catch到
  • 【】使用工具类

    Arrays.asList()

    把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出

    UnsupportedOperationException

  • 【-】使用集合转数组的方法,必须使用集合的

    toArray(T[] array)

    ,传入的是类型完全一样的数组,大小就是

    list.size()

    • 反例:

      Integer[] a = (Integer[]) list.toArray();

    • 正例:

      Integer[] b = (Integer[]) list.toArray(new Integer[list.size()]);

  • 【-】基本数据类型与包装数据类型的使用标准:所有的

    POJO类属性

    必须使用包装数据类型;

    RPC方法的返回值和参数

    必须使用包装数据类型;所有的

    局部变量

    推荐使用基本数据类型。
    • POJO类属性没有

      初值

      是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。
  • 【-】创建

    线程或线程池

    时请指定

    有意义的线程名称

    ,方便出错时回溯。创建线程池的时候请使用带

    ThreadFactory

    的构造函数,并且提供自定义

    ThreadFactory

    实现或者使用第三方实现。
  • 【】

    包名

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

    类名

    如果有复数含义,类名可以使用复数形式
  • 【】单个方法的总行数不超过

    80

    行。

    除注释之外

    的方法签名、结束右大括号、方法内代码、空行、回车及任何不可见字符的总行数不超过80行。
  • 【】及时清理不再使用的代码段或配置信息。对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余。
  • 后台输送给页面的变量必须加感叹号,

    ${var}

    ——中间加感叹号!。如果

    var=null

    或者不存在,那么

    ${var}

    会直接显示在页面上。
  • 【】在if/else/for/while/do语句中必须使用大括号,

    即使只有一行代码

    ,避免使用下面的形式

    :if (condition) statements;

  • 【-】在

    subList

    场景中,高度注意对原列表的修改,会导致子列表的遍历、增加、删除均产生

    ConcurrentModificationException

  • 【】在一个switch块内,每个case要么通过

    break/return

    等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个

    default

    语句并且放在最后,即使它什么代码也没有。
  • 【】在使用正则表达式时,利用好其

    预编译

    功能,可以有效加快正则匹配速度。
    • 不要在方法体内定义:

      Pattern pattern = Pattern.compile(规则);

      ,应该定义为类属性
  • 【-】在使用阻塞等待

    获取锁

    的方式中,必须在

    try代码块之外

    ,并且在

    lock方法与try代码块之间

    没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。
    • 说明一:如果在

      lock方法与try代码块之间

      的方法调用抛出异常,那么无法解锁(因为没有try就不会执行finally),造成其它线程无法成功获取锁。
    • 说明二:如果

      lock方法在try代码块之内

      ,可能由于其它方法抛出异常,导致在finally代码块中,执行

      unlock

      时对未加锁的对象解锁,抛出IllegalMonitorStateException异常。
    • 说明三:如果

      lock方法在try代码块之内

      ,由于Lock对象的

      lock方法实现

      中可能抛出unchecked异常,产生与说明二相同的问题。
  • 【-】多线程并行处理定时任务时,

    Timer

    运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用

    ScheduledExecutorService

    则没有这个问题。
  • 【-】定义DO/DTO/VO等POJO类时,不要加任何

    属性默认值

  • 【】对于Service和DAO类,基于SOA的理念,暴露出来的服务一定是

    接口

    ,内部的实现类用

    Impl

    的后缀与接口区别
  • 【】常量命名应该全部大写,单词间用

    下划线

    隔开,力求语义表达完整清楚,

    不要嫌名字长

  • 【】异常类命名使用

    Exception

    结尾
  • 【】循环体内,字符串的联接方式,使用

    StringBuilder

    append

    方法进行扩展。
    • 反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。
  • 【-】必须回收自定义的

    ThreadLocal

    变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。
  • 【】所有的

    包装类

    对象之间

    值的比较

    ,全部使用

    equals

    方法比较。
    • 对于Integer在

      [-128,128)

      之间的赋值,Integer对象是在

      IntegerCache.cache

      产生,会复用已有对象,这个区间内的Integer值可以直接使用

      ==

      进行判断
    • 但是

      [-128,128)

      这个区间之外的所有数据,都会在

      上产生,并不会复用已有对象,这是一个大坑,推荐使用

      equals

      方法进行判断。
  • 【】所有的抽象方法(包括接口中的方法)必须要用

    javadoc

    注释,除了

    返回值、参数、异常说明

    外,还必须指出该方法做什么事情,实现什么功能。
  • 枚举类型

    字段必须要有注释,说明每个数据项的用途。
  • 【】所有的类都必须添加

    创建者

    信息。在设置模板时,注意IDEA的

    @author

    ${USER}

    ,而eclipse为

    ${user}

    ,大小写有区别,而日期的设置统一为

    yyyy/MM/dd

    的格式。
  • 【】所有的覆写方法,必须加

    @Override

    注解。加

    @Override

    可以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编译报错。
  • 【】所有编程相关的命名均不能以下划线或美元符号开始
  • 【】抽象类命名使用

    Abstract

    Base

    开头
  • 【】方法内部单行注释,在被注释语句上方

    另起一行

    ,使用

    //

    注释。方法内部多行注释使用

    /* */

    注释。注意与代码对齐。
  • 【】方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase,必须遵从驼峰形式
  • 【】日期格式化字符串

    [%s]

    使用错误,应注意使用小写

    y

    表示当天所在的年,大写

    Y

    代表week in which year。
    • 日期格式化时,

      yyyy

      表示当天所在的年,而大写的

      YYYY

      代表是week in which year(JDK7之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的

      YYYY

      就是下一年。
  • 【】注意

    Math.random()

    这个方法返回是double类型,注意取值的范围

    [0,1)

    ,如果想获取整数类型的随机数,不要将x放大10的若干倍然后取整,直接使用Random对象的

    nextInt

    或者

    nextLong

  • 【】测试类命名以它要测试的类的名称开始,以Test结尾
  • 【-】

    浮点数

    之间的等值判断,基本数据类型不能用

    ==

    来比较,包装数据类型不能用

    equals

    来判断。
    • 浮点数采用

      尾数+阶码

      的编码方式,类似于科学计数法的

      有效数字+指数

      的表示方式,二进制无法精确表示大部分的十进制小数
  • 【-】禁止使用构造方法

    BigDecimal(double)

    的方式把double值转化为BigDecimal对象
    • BigDecimal bad = new BigDecimal(0.1);

    • BigDecimal good = new BigDecimal("0.1");

      BigDecimal good = BigDecimal.valueOf(0.1);

  • 【】类、类属性、类方法的注释必须使用

    javadoc

    规范,使用

    /**内容*/

    格式,不得使用

    //xxx

    方式和

    /*xxx*/

  • 【】类名使用UpperCamelCase风格,必须遵从驼峰形式,但以下情形例外:(领域模型的相关命名)DO / BO / DTO / VO / DAO
  • 【-】线程池不允许使用

    Executors

    去创建,而是通过

    ThreadPoolExecutor

    的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
    • FixedThreadPool和SingleThreadPool

      :由于允许的

      请求队列

      长度为Integer.MAX_VALUE,可能会

      堆积

      大量的请求,从而导致OOM。
    • CachedThreadPool

      :由于允许的创建

      线程数量

      为Integer.MAX_VALUE,可能会

      创建

      大量的线程,从而导致OOM。
  • 【】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
    • 使用线程池的好处是减少在

      创建和销毁

      线程上所花的时间以及

      系统资源

      的开销,解决资源不足的问题。
    • 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者

      过度切换

      的问题。
  • 【】获取当前毫秒数:

    System.currentTimeMillis();

    而不是

    new Date().getTime();

    。如果想获取更加精确的纳秒级时间值,用

    System.nanoTime

  • 【】返回类型为基本数据类型,return包装数据类型的对象时,(

    return null

    时)自动拆箱有可能产生NPE
  • 【】避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一

    seed

    导致的性能下降。说明:Random实例包括

    java.util.Random

    的实例或者

    Math.random()

    的方式。
  • 避免用Apache Beanutils进行属性的copy,因为性能较差。可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier。
  • 【】避免通过一个类的对象引用访问此类的

    静态变量或静态方法

    ,无谓增加编译器解析成本,直接用类名来访问即可。
  • 【】避免采用

    取反

    逻辑运算符。取反逻辑不利于快速理解,并且取反逻辑写法必然存在对应的正向逻辑写法。
  • 【】除常用方法(如getXxx/isXxx)等外,不要在条件判断中执行复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔

    变量

    ,以提高可读性。
  • 【】集合初始化时,指定集合初始值大小。如果暂时无法确定集合大小,那么指定默认值

    16

    即可。

public class Test {
    private static final String FORMAT = "yyyy-MM-dd HH:mm:ss";
    private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static final ThreadLocal<DateFormat> DATE_FORMATTER = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static void main(String[] args) {
        System.out.println(new SimpleDateFormat(FORMAT).format(new Date()));
        synchronized (SIMPLE_DATE_FORMAT) {
            System.out.println(SIMPLE_DATE_FORMAT.format(new Date()));
        }
        System.out.println(DATE_FORMATTER.get().format(new Date()));
    }
}
           

public void operate(CountDownLatch countDownLatch) {
    try {
        System.out.println("business logic");
    } catch (RuntimeException e) {
        // do something
    } finally {
        countDownLatch.countDown();
    }
}
           

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

singleThreadPool.execute(() -> System.out.println(Thread.currentThread().getName()));
singleThreadPool.shutdown();
           

List<String> originList = new ArrayList<>();
originList.add("22");
List<String> subList = originList.subList(0, 1);
originList.add("23");
System.out.println(subList.size()); //java.util.ConcurrentModificationException
           

正例

Lock lock = new XxxLock();
// ...
lock.lock(); // 如果在lock方法与try代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功获取锁
try {
	//doSomething
	//doOthers
} finally {
	lock.unlock();
}
           

反例

Lock lock = new XxxLock();
// ...
try {
	// If an exception is thrown here, the finally block is executed directly
	//doSomething
	lock.lock(); //如果lock方法在try之内,可能由于其它方法抛出异常,导致在finally中执行unlock时抛出异常
	//doOthers
} finally {
	// The finally block executes regardless of whether the lock is successful or not
	lock.unlock();
}
           

public class Test {
	public static void main(String[] args) {
		UserHolder.set(new User());
		User user = UserHolder.get();
		UserHolder.remove();
	}
}

class UserHolder {
	private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
	
	public static void set(User user) {
		userThreadLocal.set(user);
	}
	
	public static User get() {
		return userThreadLocal.get();
	}
	
	public static void remove() {
		userThreadLocal.remove();
	}
}

class User {
}
           

方法一:指定一个

误差范围

,两个浮点数的差值在此范围之内,则认为是相等的

float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
float diff = 1e-6f;

if (Math.abs(a - b) < diff) {
	System.out.println("true");
}
           

方法二:使用

BigDecimal

来定义值,再进行浮点数的运算操作

BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");

BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);

if (x.equals(y)) {
	System.out.println("true");
}
           

//org.apache.commons.lang3.concurrent.BasicThreadFactory
BasicThreadFactory factory = new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build();
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, factory);
           
TimeUnit unit = TimeUnit.MILLISECONDS;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(1024);
ThreadFactory factory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, unit, workQueue, factory, handler);
pool.execute(() -> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
           

class RandomInThread extends Thread {
	//private final Random random = new Random();
	private final Random random = ThreadLocalRandom.current();
	
	@Override
	public void run() {
		System.out.println(random.nextLong());
	}
}
           

2020-05-28

本文来自博客园,作者:白乾涛,转载请注明原文链接:https://www.cnblogs.com/baiqiantao/p/12983554.html