【小家java】《阿里巴巴 Java开发手册》读后感---拥抱规范,远离伤害

前言

对于编码规范,本人一直是公司里的大力推崇者。其实不乏发现有很多人并不注重编码的规范性,觉得只要功能实现了就ok了,其实这种认识是非常初级的。


了解了规范,可以提前避免一些开发盲区,大大提高团队协作的效率。规范的编程习惯,更能提升我们coder的职业素养,向代码致敬

一个成熟的项目要想长足发展(其实我还没看过什么项目只考虑work几个月就够了的),后期维护成本是设计者不得不做长远设计和考虑的

阿里巴巴作为国内Java行业的领头羊,各个领域都具有极高的发言权和权威。2017年开春之际,阿里诚意献上重磅大礼:《阿里巴巴Java开发手册》,首次公开阿里官方Java代码规范标准。这套Java统一规范标准将有助于提高行业编码规范化水平,帮助行业人员提高开发质量和效率、大大降低代码维护成本

引用书上的一句话:

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

作者(孤尽)在知乎回答的一句话:

翻完了不代表记住了,记住了不代表理解了,理解了不代表能够应用上去,真正的知识是实践,实践,实践。

北京的冬天外面非常的冷,所以有个周末,天气阴沉沉的,比较适合在室内呆着。看看我的书籍,才发现当时618买了一堆书,这堆书还有没撕包装的呢(其实有好几本书还是全新的呢)….于是我翻出了最薄的一本《阿里巴巴 Java开发手册》:

说明:

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

Java相关

  1. POJO是DO/DTO/BO/VO的统称,禁止命名为xxxPOJO
  2. 获取多个对象的方法中list作为前缀(不用清一色的get)
  3. 获取统计值的方法用count作为前缀(比get…Count这种命名简略)
  4. POJO类中的布尔类型(Boolean)的变量都不要加is前缀,否则部分框架解析会引起序列化错误

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

  1. 如果是形容能力的接口名称,取对应的形容词为接口名(通常是-able的形式)
  2. 不允许任何魔法值(未经预先定义的常量)直接出现在代码中(这个要尽量避免,因为后人来阅读的你代码,根本不知道魔法值代表什么)
  3. 所有POJO类的属性全部使用包装数据类型,RPC的返回值和参数必须使用包装数据类型,所有的局部变量都使用基本数据类型。定义VO/DTO/DO等POJO类时,不要设定任何属性的默认值

简单的说就是0是有意义的,而很多时候我们只想表达null,那基本类型就无能为力了(局部变量有基本类型是因为我清楚意义,效率稍微高那么一点点)

  1. 构造方法禁止加入任何的业务逻辑,如果初始化逻辑可以放在init方法中。set/get方法也不要增加业务逻辑。此处指的是业务逻辑,比如一些基本的数据赋值,这种还是允许的

如果set/get方法放入业务逻辑,有时候排查问题就变得很麻烦了

  1. 工具类Arrays.asList()把数组转成List时,不能使用其修改集合的相关方法。比如说add、clear、remove (因为他产生的是一个不可变的List)
  2. 使用entrySet遍历Map类集合K/V,而不是用keySet方式遍历

keySet遍历了两次,一次是转成Iterator对象,一次是从hashMap中取出key所对应的value,如果JDK8可以使用Map.foreach方法

其实在源码里边我们可以发现,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);
        }
    }
  1. 线程资源必须由线程池提供不允许在应用中自行显示创建线程。线程池不允许用Executors创建【小家java】Java中的线程池,你真的用对了吗?(教你用正确的姿势使用线程池,Executors使用中的坑),通过ThreadPoolExecutor的方式创建,这样的处理方式能够让编写代码的工程师更加明确线程池的运行规则,规避资源耗尽的风险。
  2. SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类

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

下面这么使用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()));
  1. 避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致性能下降

在JDK7之后,可以直接使用API ThreadLocalRandom,而在JDK7 之前,需要编码保证每个线程持有一个实例。

  1. 类、类属性、类方法的注释必须使用 Javadoc 规范,*使用 /内容/ 格式,不得使用 //xxx 方式
  2. 对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码的上方,使用三个斜杠///来****说明注释代码的理由
  3. 使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用 countDown方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行至 await 方法,直到超时才返回结果。说明: 注意,子线程抛出异常堆栈,不能在主线程 try-catch 到。
  4. 对于一写多读,是可以解决变量同步问题, 但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现: AtomicInteger count = new AtomicInteger(); count.addAndGet(1);如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。
  5. 使用JDK8的Optional类来防止NPE问题。

数据库相关

  1. 表达是否概念的字段,必须使用isxxx的方式命名,数据类型是unsigned tinyint(1表示是,0表示否),如删除标记字段:is_deleted
  2. 请用表达清晰的字段意思来表示字段的意义。比如该字段就是表示是否删除的状态,亲不要使用意义不明显的status,而是使用is_deleted。具象 > 抽象
  3. 小数类型用decimal,禁止使用float和double。
  4. varchar是可变字符串,不预选分配存储空间的话,长度不要超过5000个字符。如果超过则用text,独立一张表,用主键对应,避免影响到其他字段的索引效率。
  5. 每张表必备的三个字段:id(unsigned bigint)、gmt_create(datetime类型)、gme_modified(datetime类型)
  6. 字段允许适当冗余,以提高查询性能,但必须考虑数据一致性。冗余的字段必须不是频繁修改的字段不是varhar超长字段(更不能是text字段)。
  7. 单表行数超过500万行或者单表容量超过2GB才推荐进行分库分表(如果预计三年都达不到这个数据量,不要在创建表的时候就分库分表!)
  8. 超过三个表禁止使用join,两张表也尽量不要使用join
  9. 在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,页面搜索严禁左模糊或者全模糊,如果需要则通过搜索引擎来解决。

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

  1. 利用延迟关联或者子查询优化超多也分页场景。 pageIndex很大的分页问题 我们知道,当我们表里数据多,我们可以使用分页查询,但是当我们页数很多的时候,我们页码很大的情况下,效率也是会非常低的。

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

按照这么建议,我们这样来优化我们的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

解释:其实这里就是通过使用覆盖索引查询返回需要的主键,再根据主键关联原表获得需要的数据。这样就是充分利用了索引! 12. 如果有全球化需要,均以utf-8编码。如果需要存储表情,选择utf8mb4进行存储。

未完,待续(日志规范、设计规范等等)

由于时间有限,今天重点就自己实施验证了上面一些例子,关于其它的,后续有空还会补充完整这篇博文的。

借用灰太狼一句名言:我一定会回来的

个人建议的编码规范

阿里手册已经涉及到的,此处不再提了。本处仅提出个人的一些建议,不喜勿喷

仅代表着我个人的意见,我觉得OK的地方。大家如果觉得好的话,可以帮顶,如果大家对我个人提出的规范有什么想法和建议,非常非常欢迎提出来或者指正,毕竟我个人的经验还不是很足,需要指点、优化。

  1. 约定大于配置,配置大于编码

能约定好的规范,就不要多写一个适配器去兼容

  1. 除非需要更高的可见性,否则应将所有的域都声明为私有域。
  2. 除非需要某个域是可变的,否则应将其声明为final域

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券