《阿里巴巴 Java开发手册》读后感小结

前言

只有光头才能变强

前一阵子一直在学Redis,结果在黄金段位被虐了,暂时升不了段位了,每天都拿不到首胜(好烦)。

趁着学校校运会,合理地给自己放了一个小长假,然后就回家了。回到家才发现当时618买了一堆书,这堆书还有没撕包装的呢....于是我翻出了最薄的一本《阿里巴巴 Java开发手册》

这本书一共就90多页,一天就可以通读完了,看完之后我又来水博文了。

注意:

  • 书上很多的规范是可以用IDE来避免的,也有很多之前已经知道的了。
  • 所以,这篇文章只记录我认为比较重要,或者说是我之前开发时没有注意到的一些规范(知识点)。
  • 该文章的内容肯定没有书上写得那么全的,如果感兴趣的同学可以去买一本来读一下~

PDF官方地址:https://github.com/alibaba/p3c

一、Java相关

1.POJO是DO/DTO/BO/VO的统称,禁止命名为xxxPOJO

2.获取多个对象的方法中list作为前缀

3.获取统计值的方法用count作为前缀

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

  • 如果你的变量名带is的话,比如isActive,框架解析的时候可能就当成active了。

5.如果是形容能力的接口名称,取对应的形容词为接口名(通常是-able的形式)

6.不允许任何魔法值(未经预先定义的常量)直接出现在代码中

7.Object的euqals方法容易抛出空指针异常,应使用常量或者有值的对象来调用equals。推荐使用java.util.Object#equals工具类

8.所有POJO类的属性全部使用包装数据类型,RPC的返回值和参数必须使用包装数据类型,所有的局部变量都使用基本数据类型。定义VO/DTO/DO等POJO类时,不要设定任何属性的默认值

  • 如果你的类属性使用int这样的基本数据类型,默认值是0。一般情况下该变量没有赋值,一般想表达的是不存在(null),而不是0。

9.构造方法禁止加入任何的业务逻辑,如果初始化逻辑可以放在init方法中。set/get方法也不要增加业务逻辑。 •如果set/get方法放入业务逻辑,有时候排查问题就变得很麻烦了

10.工具类Arrays.asList()把数组转成List时,不能使用其修改集合的相关方法。比如说add、clear、remove

11.在JDK7以及以上版本中,Comparator要满足三个条件,不然调用Arrays.sort()或者Collections.sort()会报异常。 •x,y 的比较结果和 y,x 的比较结果相反

  • 传递性:x>y并且y>z,那么x一定大于z
  • 对称性:x=y,则 x,z 比较结果和y,z比较结果相同

12.使用entrySet遍历Map类集合K/V,而不是用keySet方式遍历 •keySet遍历了两次,一次是转成Iterator对象,一次是从hashMap中取出key所对应的value,如果JDK8可以使用Map.foreach方法

13.线程资源必须由线程池提供,不允许在应用中自行显示创建线程。线程池不允许用Executors创建,通过ThreadPoolExecutor的方式创建,这样的处理方式能够让编写代码的工程师更加明确线程池的运行规则,规避资源耗尽的风险。

14.SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类

  • 如果是JDK8应用,可以使用Instant(针对时间统计等场景)代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat

15.避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致性能下降 •在JDK7之后,可以直接使用API ThreadLocalRandom,而在JDK7 之前,需要编码保证每个线程持有一个实例。

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

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

18.对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码的上方,使用三个斜杠///来说明注释代码的理由

19.保证单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试之间不能互相调用,也不能依赖执行的先后顺序。

20.高并发服务器建议调小TCP协议的time_await超时时间,调大最大事件句柄数(fd),

1.1值得说明的点

一、不允许任何魔法值(未经预先定义的常量)直接出现在代码中

例子:

  Negative example:
  //Magic values, except for predefined, are forbidden in coding.
  if (key.equals("关注公众号:Java3y")) {
    //...
  }

  Positive example:
  String KEY_PRE = "关注公众号:Java3y";
  if (KEY_PRE.equals(key)) {
    //...
  }

ps:我猜是把先常量定义出来,后续引用/修改的时候就很方便了。

二、Object的euqals方法容易抛出空指针异常,应使用常量或者有值的对象来调用equals。推荐使用java.util.Object#equals工具类

java.util.Object#equals的源码(已经判断null的情况了)

 public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
  }

三、工具类Arrays.asList()把数组转成List时,不能使用其修改集合的相关方法。

因为返回的ArrayList是一个内部类,并没有实现集合的修改方法。后台的数据仍是数组,这里体现的是适配器模式。

四、在JDK7以及以上版本中,Comparator要满足自反性,传递性,对称性,不然调用Arrays.sort()或者Collections.sort()会报异常。

The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y. (This implies that compare(x, y) must throw an exception if and only if compare(y, x) throws an exception.)

The implementor must also ensure that the relation is transitive: ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0.

Finally, the implementor must ensure that compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z.

1) x,y 的比较结果和 y,x 的比较结果相反。

2) 传递性:x>y,y>z,则 x>z。

3) 对称性:x=y,则 x,z 比较结果和 y,z 比较结果相同。

反例:下例中没有处理相等的情况,实际使用中可能会出现异常:

new Comparator<Student>() {
  @Override
  public int compare(Student o1, Student o2) {
    return o1.getId() > o2.getId() ? 1 : -1;
  }
}

使用entrySet遍历Map类集合K/V,而不是用keySet方式遍历

首先我们来看一下使用keySet是如何遍历HashMap的:

new Comparator<Student>() {
  @Override
  public int compare(Student o1, Student o2) {
    return o1.getId() > o2.getId() ? 1 : -1;
  }
}

再来看一下源码:

// 1. 得到keySet,如果不存在,则创建
public Set<K> keySet() {
  Set<K> ks = keySet;
  if (ks == null) {
    ks = new KeySet();
    keySet = ks;
  }
  return ks;
}

// 2.初始化ks (实际上就是Set集合[HashMap的内部类],在初始化时需要顺便初始化iterator)
ks = new AbstractSet<K>() {
  public Iterator<K> iterator() {
    return new Iterator<K>() {
      private Iterator<Entry<K,V>> i = entrySet().iterator();

      public boolean hasNext() {
        return i.hasNext();
      }

      public K next() {
        return i.next().getKey();
      }

      public void remove() {
        i.remove();
      }
    };
  }

};

再来看一下entrySet,可以直接拿到key和value,不用再使用get方法来得到value,所以比keySet更加推荐使用!

  public static void main(String[] args) throws InterruptedException {

    HashMap<String, String> hashMap = new HashMap<>();
    hashMap.put("关注公众号:", "Java3y");
    hashMap.put("坚持原创", "Java3y");
    hashMap.put("点赞", "关注,转发,分享");

    // 得到entrySet,遍历entrySet得到结果
    Set<Map.Entry<String, String>> entrySet = hashMap.entrySet();
    Iterator<Map.Entry<String, String>> iterator = entrySet.iterator();
    while (iterator.hasNext()) {
      Map.Entry<String, String> entry = iterator.next();
      System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue());
    }
  }

如果是JDK8的话,推荐直接使用Map.forEach()就好了,我们也来看看用法:

public static void main(String[] args) throws InterruptedException {

  HashMap<String, String> hashMap = new HashMap<>();
  hashMap.put("关注公众号:", "Java3y");
  hashMap.put("坚持原创", "Java3y");
  hashMap.put("点赞", "关注,转发,分享");

  // forEach用法
  hashMap.forEach((key, value) -> System.out.println("key = " + key + ", value = " + value));
}

其实在源码里边我们可以发现,forEach实际上就是封装了entrySet,提供forEach给我们可以更加方便地遍历Map集合

 // forEach源码
  default void forEach(BiConsumer<? super K, ? super V> action) {
    Objects.requireNonNull(action);
    for (Map.Entry<K, V> entry : entrySet()) {
      K k;
      V v;
      try {
        k = entry.getKey();
        v = entry.getValue();
      } catch(IllegalStateException ise) {
        // this usually means the entry is no longer in the map.
        throw new ConcurrentModificationException(ise);
      }
      action.accept(k, v);
    }
  }

五、SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。

有以下的例子可以正确使用SimpleDateFormat:

// 1. 在方法内部使用,没有线程安全问题
private static final String FORMAT = "yyyy-MM-dd HH:mm:ss";
public String getFormat(Date date){
  SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT);
  return dateFormat.format(date);
}

// 2. 每次使用的时候加锁
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public void getFormat(){
  synchronized (SIMPLE_DATE_FORMAT){
  SIMPLE_DATE_FORMAT.format(new Date());
  ….;
}

// 3. 使用ThreadLocal,每个线程都有自己的SimpleDateFormat对象,互不干扰
private static final ThreadLocal<DateFormat> DATE_FORMATTER = new ThreadLocal<DateFormat>() {
  @Override
  protected DateFormat initialValue() {
    return new SimpleDateFormat("yyyy-MM-dd");
  }
};

// 4. 使用DateTimeFormatter(This class is immutable and thread-safe.)

  DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  System.out.println(timeFormatter.format(LocalDateTime.now()));

如果是JDK8应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat。

二、数据库相关

1.表达是否概念的字段,必须使用isxxx的方式命名,数据类型是unsigned tinyint(1表示是,0表示否)

2.小数类型用decimal,禁止使用float和double。

3.varchar是可变字符串,不预选分配存储空间的话,长度不要超过5000个字符。如果超过则用text,独立一张表,用主键对应,避免影响到其他字段的索引效率。

4.表必备的三个字段:id(类型是unsigned bigint),gmt_create(创建时间),gme_modified(修改时间)

5.字段允许适当冗余,以提高查询性能,但必须考虑数据一致性。冗余的字段必须不是频繁修改的字段,不是varhar超长字段(更不能是text字段)。

6.单表行数超过500万行或者单表容量超过2GB才推荐进行分库分表(如果预计三年都达不到这个数据量,不要在创建表的时候就分库分表!)

7.超过三个表禁止使用join,需要join的字段,数据类型必须保持一致,当多表关联查询时,保证被关联的字段需要有索引!

8.在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,页面搜索严禁左模糊或者全模糊,如果需要则通过搜索引擎来解决。

  • 充分利用好最左前缀匹配特性!

9.利用延迟关联或者子查询优化超多也分场景。

10.如果有全球化需要,均以utf-8编码。如果需要存储表情,选择utf8mb4进行存储。

2.1值得说明的点

一、利用延迟关联或者子查询优化超多也分场景。

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

例子:

// 优化前

SELECT id, cu_id, name, info, biz_type
  , gmt_create, gmt_modified, start_time, end_time, market_type
  , back_leaf_category, item_status, picuture_url
FROM relation
WHERE biz_type = '0'
  AND end_time >= '2014-05-29'
ORDER BY id ASC
LIMIT 149420, 20;

// 优化后

SELECT a.*
FROM relation a, (
    SELECT id
    FROM relation
    WHERE biz_type = '0'
      AND end_time >= '2014-05-29'
    ORDER BY id ASC
    LIMIT 149420, 20
  ) b
WHERE a.id = b.id

解释:其实这里就是通过使用覆盖索引查询返回需要的主键,再根据主键关联原表获得需要的数据。这样就是充分利用了索引!

三、未解决的问题

在看《手册》的时候还有一些知识点没看过、没实践过、涉及到的知识点比较多的,在这里先mark一下,后续再遇到或者有空的时候再回来补坑~

  • 使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用 countDown方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行至 await 方法,直到超时才返回结果。说明: 注意,子线程抛出异常堆栈,不能在主线程 try-catch 到。
  • 对于一写多读,是可以解决变量同步问题, 但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现: AtomicInteger count = new AtomicInteger(); count.addAndGet(1);如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。
  • 使用JDK8的Optional类来防止NPE问题。

当然了,如果你有比较好的资料阅读,也可以在评论区告诉我。我也会mark住好好看看。

比如说:“3y,我发现Optional类有篇文章写得很不错,url是xxxx(书籍的名称是xxx)

由于现在没有一定的经验积累,所以以下的章节得回头看:

  • 《手册》中的“日志规约”,“工程结构”、“设计规范”

最后

看我上面写的内容就知道,除了一些规范外,还有很多实用的小技巧,这些对我们开发是有帮助的。我这个阶段也有一些没怎么接触过的("日志","设计","二方库"),这些都需要我在成长中不断的回看才行。

ps:我会回来补坑的。

引用书上的一句话:很多编程方式客观上没有对错之分,一致性很重要,可读性很重要,团队沟通效率很重要。程序员天生需要团队协作,而协作的正能量要放在问题的有效沟通上。个性化应尽量表现在系统架构和算法效率的提升上,而不是在合作规范上进行纠缠不休的讨论、争论,最后没有结论。

作者(孤尽)在知乎回答的一句话:翻完了不代表记住了,记住了不代表理解了,理解了不代表能够应用上去,真正的知识是实践,实践,实践。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • JAVA开发中的一些规范讲解(阿里巴巴Java开发规范手册)

    一.编程规约 (一) 命名规约 1.   [强制]所有编程相关命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束.反例: _name / __name / $Object / name_ / name$ / Object$ 2.   [强制]所有编程相关的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式.说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义.注意,即使纯拼音命名方式也要避免采用. 反例: DaZhePromotion [打折] / getPingfen

  • 《阿里巴巴 Java开发手册》读后感小结

    前言 只有光头才能变强 前一阵子一直在学Redis,结果在黄金段位被虐了,暂时升不了段位了,每天都拿不到首胜(好烦). 趁着学校校运会,合理地给自己放了一个小长假,然后就回家了.回到家才发现当时618买了一堆书,这堆书还有没撕包装的呢....于是我翻出了最薄的一本<阿里巴巴 Java开发手册> 这本书一共就90多页,一天就可以通读完了,看完之后我又来水博文了. 注意: 书上很多的规范是可以用IDE来避免的,也有很多之前已经知道的了. 所以,这篇文章只记录我认为比较重要,或者说是我之前开发时没有

  • IntelliJ IDEA安装插件阿里巴巴Java开发手册(Alibaba Java Coding Guidelines)

    以前看到过个:Java开发手册(阿里巴巴-公开版),这是个pdf文档,里面描述了一些Java开发的规约,里面确实有很多好用的规约,要是在学校就有机会看看的话,那么,在毕业之后,实际工作中就会少很多坑.现在,阿里巴巴又一次对这个文档进行了升级,直接变成了一个插件.你需要的就是:知道有这么个插件,然后,还得安装这个插件,那么以后,你在写代码的时候,这个插件就会自动的纠正你在写代码的时候的一些很low的不规范代码. 下面看怎么在这个 IntelliJ IDEA 上安装这个插件. 通过Jetbrains

  • java开发MVC三层架构上再加一层Manager层原理详解

    目录 MVC三层架构 MVC架构弊端 Manager层的特征 Manager层使用案例 MVC三层架构 我们在刚刚成为程序员的时候,就会被前辈们 "教育" 说系统的设计要遵循 MVC(Model-View-Controller)架构.它将整体的系统分成了 Model(模型),View(视图)和 Controller(控制器)三个层次,也就是将用户视图和业务处理隔离开,并且通过控制器连接起来,很好地实现了表现和逻辑的解耦,是一种标准的软件分层架构. MVC分层架构是架构上最简单的一种分层

  • [JAVA]十四种Java开发工具点评

    在计算机开发语言的历史中,从来没有哪种语言象Java那样受到如此众多厂商的支持,有如此多的开发工具,Java菜鸟们如初入大观园的刘姥姥,看花了眼,不知该何种选择.的确,这些工具各有所长,都没有绝对完美的,就算是老鸟也很难做出选择.在本文中我简要介绍了常见的十四种Java开发工具的特点,管中窥"器",希望能对大家有所帮助. 1.JDK (Java Development Kit) 2.Java Workshop 3.NetBeans 与Sun Java Studio 5 4.Borlan

  • Java开发人员需知的十大戒律

    本文讲述了Java开发人员需知的十大戒律.分享给大家供大家参考,具体如下: 作为一个Java开发人员提高自己代码的质量,可维护性,是个恒久不变的话题,网上看到这篇文章,拿来自勉. 对Java开发者来说,有许多的标准和最佳实践.本文列举了每一个开发人员必须遵从的十大基本法则:如果有了可以遵从的规则而不遵从,那么将导致的是十分悲惨的结局. 1. 在你的代码里加入注释 每个人都知道这点,但不知何故忘记了遵守.算一算有多少次你"忘记"了添加注释?这是事实:注释对程序在功能上没有实质的贡献.但是

  • 必须详细与全面的Java开发环境搭建图文教程

    在项目产品开发中,开发环境搭建是软件开发的首要阶段,也是必须阶段,只有开发环境搭建好了,方可进行开发,良好的开发环境搭建,为后续的开发工作带来极大便利. 对于大公司来说,软件开发环境搭建工作一般是由运维来做,然而,对于小公司来说,这个工作就交给开发人员来做了,如开发经理.不管这个工作是交给运维人员做,还是 交给开发人员做,能确定的是:做这件事的人,一定是个资深的人,如此,方可让开发环境稳定运行,从而为后续的开发提供便利. 现实中,只有极少部分开发人员接触服务器(能接触的人,基本都是开发组长及其以

  • Java开发神器Lombok使用详解

    最近正在写SpringBoot系列文章和录制视频教程,每次都要重复写一些Getter/Setter.构造器方法.字符串输出的ToString方法和Equals/HashCode方法等.甚是浪费时间,也影响代码的可读性.因此,今天就给大家推荐一款Java开发神器--Lombok,让代码更简单易读. 什么是Lombok Lombok是一款Java开发插件,可以通过它定义的注解来精简冗长和繁琐的代码,主要针对简单的Java模型对象(POJO). 好处就显而易见了,可以节省大量重复工作,特别是当POJO

  • java开发AOP面向切面编程入门

    目录 引言 不好的解决方案 面向过程的解决方案 使用继承解决方案 使用聚合的解决方案 面向切面的编程基本概念 基于Spring面向切面程序实现 小结 引言 在实际应用场景中,我们封装一个学生的类,这个类用于封装学生的日常行为,如:上学.吃饭.上课等.然而,在疫情期间,学生上学时入校.吃饭时进入餐厅,需要测温查验证件等行为,拿到这样的需求我们怎么办? 不好的解决方案 面向过程的解决方案 遇到问题解决问题,在上学.吃饭方法中加上测温.查验证件方法,或者在学生类中提炼一个测温查验证件私有的方法,在需要

  • java开发SSM框架具有rest风格的SpringMVC

    目录 RESTful架构 资源 表现层(Representation) 状态转化(State Transfer) 简单小结 SpringMVC对RESTful架构的支持 利用ajax实现前后端完全分离 RESTful架构 REST(Resource Representational State Transfer)即"资源表现层状态转化",省略了Resource (资源).允许客户端发出以统一资源标识符访问和操作网络资源的请求,而与预先定义好的无状态操作集一致化. 资源 指网络上的一个具

随机推荐