2020年目标

简单介绍一下 2020 年的目标,及具体完成情况.

目标 具体 完成情况
LeetCode 题库 无要求 已解决:9/1572
UmiJS 文档 文档配置API 已完成
Mock.JS 文档 已完成
UmiJS-Hooks 文档 已完成
科幻小说 带上她的眼睛
immer.js 文档 已完成
SSR - UmiJS 文档 已完成
MyBatis-Plus 文档 已完成
Java 开发手册(泰山版) Ali p3c 地址 已完成
CSS - Grid 布局 慕课网 9.3 完成
阅读更多
《Effective Java》目录

《Effective Java》目录

1. 使用静态工厂方法代替构造器

传统的获取一个类的实例,是通过提供类的构造器(构造方法)。

静态工厂方法:返回类的实例的静态方法,与设计模式中的“工厂模式”不同。

一、静态工厂方法的优势

静态工厂方法与构造器相比的五大优势:

  1. 静态工厂方法有名称
    • 构造器的名称必须与类名相同,因此在描述返回对象时不够确切
  2. 使用静态工厂方法时,不必每次都创建新的对象返回
    • 使用不可变类时,可以返回预先创建好的实例,或者将创建的实例缓存起来,重复利用
    • 类似享元模式,如果程序经常创建相同的对象,且对象创建的代价很高,则能有效地提升性能
    • 还能严格控制在同一时刻,共存在几个实例
  3. 静态工厂方法,能返回原返回类型的所有子类型
    • 不太明白
  4. 静态工厂方法,所返回的对象的类可以随着每次调用而发生变化
    • 如 EnumSet,当元素少于64个时,静态工厂方法会返回一个 RegalarEumSet 实例,用单个 long 支持;否则返回一个 JumboEnumSet 实例,用 long 数组支持
    • 这两个实现类,对于客户端而言是不可见的。如果 RegularEnumSet 不能再给小枚举类型带来性能优势,则可能从未来版本中删除它,且不会造成任何影响

二、静态工厂方法的缺点

三、静态工厂方法的惯用名称


MyBatis-Plus官方文档笔记

一、特性

  • 无侵入:不影响现有工程
  • 损耗小:启动即会注入基本 CURD,性能基本无损耗
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅通过少量配置即可实现大部分单表 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用
  • 支持主键自动生成:自由配置 4 种主键策略(内含分布式唯一 ID 生成器-Sequence)
  • 支持 ActiveRecord 模式:实体类只需继承 Modal 类即可进行强大的 CRUD 操作
  • 内置代码生成器:采用代码或 Maven 插件可快速生成 Mapper、Modal、Service、Controller 层代码,支持模板引擎
  • 内置分页插件:基于 MyBatis 屋里分页
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句及其执行时间,快速揪出“慢查询”
  • 内置全局拦截插件:提供全表 Delete、Update 操作智能分析阻断,也可自定义拦截规则
阅读更多
immer.js官方文档笔记

简介

Immer,用于处理不可变状态,基于“写时复制”机制。

基本思想为:将所有更改都应用于临时的 draftState,它是 currentState 的代理。一旦完成所有突变,Immer 将基于到草稿

阅读更多
SSR之UmiJS预渲染

UmiJS 服务端渲染

本文主要介绍 UmiJS 的预渲染功能。

阅读更多
Java开发手册

一、编程规约

1、命名风格

  1. 代码中的命名均不能以 下划线或美元符号 开始及结束。
  2. 所有命名严禁使用拼音与英文混写,更不允许中文命名。
  3. 类名使用 UpperCamelCase 风格。
  4. 方法名、参数名、成员变量、局部变量使用 lowerCamelCase 风格。
  5. 常量命名全部大写,单词间下划线分隔。
  6. 抽象类命名使用 Abstract 或 Base 开头;异常类以 Exception 结尾;测试类以要测试的类名开头,以 Test 结尾。
  7. 类型与中括号紧挨相连来表示数组。
  8. POJO类中的任何布尔变量,都不能以 is 为前缀。
  9. 包名统一使用小写,分隔符间仅有一个自然语义的英文单词。
  10. 避免在子父类的成员变量之间、或者不同代码块的局部变量间采用完全相同的命名。
  11. 杜绝不规范的缩写,避免望文不知意。
  12. 在定义命名时,尽量使用完整的单词组合。
  13. 在常量与变量命名时,表示类型的名词放在词尾。
    • 如:startTime、workQueue、nameList、TERMINATED_THREAD_COUNT
  14. 如果模块、接口、类、方法使用了设计模式,在命名时需体现出具体模式。
    • 如:OrderFactory、LoginProxy
  15. 接口中的方法和属性不要加任何修饰符号,并加上有效的 javadoc 注释。尽量不要在接口中定义变量。
  16. 接口和实现类有两套规则:
    1. 对于 Service 和 DAO 类,基于 SOA 理念,暴露出来的服务一定是接口,内部的实现类使用 Impl 后缀。
    2. 如果是形容能力的接口名称,取对应的形容词为接口名。
  17. 枚举类名带上 Enum 后缀,枚举成员全大写,以下划线分隔。
  18. 各层命名规范:
    1. Service/DAO 层方法命名规范:以get、list、count、save、insert、remove、delete、update做前缀
    2. 领域模型命名规范:
      • 数据对象:xxDO,xx为数据表名
      • 数据传输对象:xxDTO,xxx为业务领域相关的名称
      • 展示对象:xxVO,xx一般为网阿爷名称
      • POJO 是 DO/DTO/BO/VO 的统称,禁用 xxPOJO

2、常量定义

  1. 不允许任何魔法值直接出现在代码中。
  2. 在 long 或 Long 赋值时,以 L 结尾。
  3. 不要使用一个常量类维护所有常量,要按功能进行分类,分开维护。
  4. 常量的复用层次有五层:
    1. 跨应用共享常量:放置在二方库中
    2. 应用内共享常量:放置在一方库中
    3. 子工程内部共享常量:子工程的 constant 目录中
    4. 包内共享常量:在包下的 constant 目录中
    5. 类内共享常量:类内部使用 private static final 定义
  5. 固定范围内变化的变量值,可用 enum 定义。

3、代码格式

  1. 如果大括号内为空,直接使用 {} 即可。
  2. 使用4空格缩进,禁用 tab 字符。
  3. 单行字符限制为120个,超出需换行。
  4. IDE 的 text file encoding 设置为 UTF-8;IDE 中文换行符使用 Unix 格式,禁用 Windows 格式。
  5. 单个方法总行数不超过80行。

4、OOP规约

  1. 避免通过一个类的对象引用访问此类的静态变量或静态方法,直接使用类名访问即可。
  2. 所有覆写方法,必须加 @Override 注解。
  3. 相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Object。
  4. 外部调用或二方库依赖接口,不允许修改方法签名,即不允许重写方法,避免对调用发产生影响。过期方法必须使用 @Deprecated,并清晰地说明采用的新接口或新服务。
  5. 不能使用过时方法或类。
  6. 推荐使用 java.util.Objects#equals(JDK7中引入的工具类)类比较。
  7. 所有整型包装类对象之间值的比较,全部使用equals方法比较。
    1. 如属性 Integer var = ?,如果var 在 -128~127之间,则由 IntegerCache.cache 产生,会复用已有对象,可直接使用 == 判断。
    2. 但区间之外的所有数据,都会在堆上产生,不会复用已有对象。
    3. 推荐使用 equals 方法判断。
  8. 任何货币金额,均以最小货币单位且整型类型来进行存储。
  9. 浮点数之间的等值判断,基本数据类型不能用 == 比较,包装数据类型不能用 equals 判断。
  10. 定义数据对象 DO 类时,属性类型要与数据库字段类型相匹配。
  11. 禁止使用构造方法 BigDecimal(double) 的方式,将 double 值转化为 BigDecimal 对象。
    1. BigDecimal(double) 存在精度丢失风险
  12. 所有 POJO 类属性,必须使用包装数据类型;RPC 方法的返回值和参数必须是包装数据类型;所有局部变量使用基本数据类型。
  13. 定义 POJO 类时,不要设定任何属性默认值。
  14. 序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列化失败;如果完全不兼容升级,表面反序列化混乱,那么请修改 serialVersionUID 值。
  15. 构造方法中禁止加入任何业务逻辑,如有初始化逻辑,请放在init中。
  16. POJO类,必须有 toString 方法。
  17. 禁止在 POJO 类中,同时存在属性 xx 的 isXx() 和 getXx()。
  18. 使用索引访问用 String 的 split 方法得到的数组时,需对最后一个分隔符后有无内容做检查,否则可能会抛出 IndexOutOfBoundsException。
  19. 当一个类有多个构造方法,或多个同名方法,应按顺序放置在一起,便于阅读。
  20. 类内方法定义顺序为:公有方法 > 私有方法 > getter/setter。
  21. getter/setter 方法中不要增加业务逻辑,增加排查问题的难度。
  22. 循环体内,字符串连接使用 StringBuilder 的 append 方法。
    1. for(int i=0;i<100;i++){ str += "hello"; } 示例中,在编译时每次循环会创建一个 StringBuilder ,然后进行 append 操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。
  23. final 可以声明类、成员变量、方法、本地变量:
    1. 不允许被继承的类
    2. 不允许修改引用的域对象
    3. 不允许被覆写的方法
    4. 不允许运行过程中重新赋值的局部变量
    5. 避免上下文重复使用一个变量,使用final可以强制重新定义一个变量,方便更好地进行重构
  24. 慎用 Object 的 clone 方法(浅拷贝)来拷贝对象。

5、日期时间

  1. 日期格式化时,正确格式应为:yyyy-MM-dd HH:mm:ss
  2. 获取当前毫秒数,应该使用 System.currentTimeMillis()
  3. 不允许在程序中使用 java.sql.Datejava.sql.Timejava.sql.Timestamp
    1. java.util.Date.after(Date) 入参是 java.sql.Timestamp 时,会触发 JDK BUG,可能导致意外结果。
  4. 不要写死一年为365天。
    1. LocalDate.now().lengthOfYear() 获取今年的天数
    2. LocaDate.of(2011, 1, 1).lengthOfYear() 获取指定某年的天数
  5. 避免公历闰月2月问题。
  6. 使用枚举值来代替月份。

6、集合处理

  1. 关于 hashCode 与 equals 的处理规则:
    1. 重写 equals,必须重写 hashCode
    2. Set 存储的是不重复的对象,因此 Set 中存储的对象 必须重写这两个方法
    3. 如果对象作为Map的Key,那么必须重写这两个方法
  2. 判断集合内部元素是否为空,使用 isEmpty() 方法,而非 size() == 0、
  3. 在使用 java.util.stream.Collectors 类的 toMap() 方法转为 Map 集合时,一定要使用含有参数类型为 BinaryOperator,参数名为 mergeFunction 的方法,否则当出现相同 key 时会抛出 IllegalStateException 异常。
  4. 在使用 java.util.stream.Collectors 类的 toMap() 方法转为 Map 集合时,一定要注意当 value 为 null 时会抛出 NullPointException。
  5. ArrayList 的 subList 结果不可强转为 ArrayList,否则会抛出 ClassCaseException。
    1. subList 返回的是 ArrayList 的内部类 SubList
  6. 使用 Map 的 keySet()/values()/entrySet() 返回集合对象时,不可对其进行添加操作,否则会抛出 UnsupportedOperationException 异常。
  7. Collections 返回的对象,都是immutable list,无法进行增删操作,如 emptyList()/singletonList() 等。
  8. 在 subList 场景中,对父元素的增删,均会导致子列表的遍历、增删产生 ConcurrentModificationException 异常。
  9. 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致,长度为0的空数组。
    1. 数组长度等于0,动态创建于 size 相同的数组,性能最好
    2. 大于0但小于size,重新创建大小等于size的数组,增加GC负担
    3. 等于size,在高并发情况下,数组创建完成后,size正在变大的情况下,负面影响与2相同
    4. 大于size,空间浪费,且在size处插入null值,存在 NPE 隐患。
  10. 使用 Collection 接口任何实现类的 addAll() 方法,都要对集合参数进行 NPE 判断。
  11. 使用 Arrays.asList() 将数组转换为集合时,不能使用修改集合相关的方法。
  12. 泛型通配符 <? extends T> 来接收返回的数据,此写法的泛型集合不能使用add 方法,而 <? super T> 不能使用 get 方法,两者在接口调用赋值的场景中容易出错。
  13. 在无泛型限制定义的集合,赋值给泛型限制的集合时,需要进行 instanceOf 判断,避免出现 ClassCaseException。
  14. 不要在 forEach 循环中进行元素的 remove/add 操作。
  15. 集合初始化时,指定集合初始值大小。
  16. 使用 entrySet 遍历 Map 类集合,而不是 keySet 方式遍历。
  17. 注意 Map 类集合中 KV 不能为空的情况,如 Hashtable、ConcurrentHashMap、TreeMap、HashMap。
  18. 利用 Set 集合唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的contains遍历去重或判断包含操作。

7、并发处理

  1. 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。

  2. 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。

  3. 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

  4. 线程池不允许使用 Executors 创建,而是通过 ThreadPollExecutor 的方式。

    1. FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_Value,可能会堆积大量请求,从而导致OOM(OutOfMemory 内存溢出)
    2. CachedTheadPool:允许创建的线程数为 Integer.MAX_VALUE,可能会创建大量线程,从而导致OOM
  5. SimpleDateFormat 是线程不安全的类,一般不要定义为 static,如果定义为 static 必须加锁。或使用 DateUtils 工具类。

    1. Java8 中,可使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat。
  6. 必须回收自定义的 ThreadLocal 变量,尽量在 try-finally 快进行回收。

  7. 高并发时,同步调用应该去考量锁的性能消耗。能用无锁数据结构,就不要用锁;能用锁区块,就不要锁方法体;能用对象锁,就不要类锁。

    1. 锁的代码块尽可能小,避免在锁代码块中调用RPC方法。
  8. 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。

  9. 在使用阻塞等待获取锁的方式中,必须在 tru 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。

    1. // 正例:
      Lock lock = new XxxLock();
      
      lock.lock();
      try{
          doSomething();
          doOthers();
      } finally {
          lock.unlock();
      }
  10. 在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。

    1. // 正例:
      Lock lock = new XxxLock();
      
      boolean isLocked = lock.tryLock();
      if(isLocked) {
          try{
              doSomething();
              doOthers();
          } finally {
              lock.unlock();
          }
      }
  11. 并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。

    1. 如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次。
  12. 多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。

  13. 资金相关的金融敏感信息,使用悲观锁策略。

    1. 悲观锁遵循一锁二判三更新四释放的原则
  14. 使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown 方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到。

  15. 避免 Random 实例被多线程使用,虽然共享该实例时线程安全的,但会因竞争统一 seed 导致性能下降。

    1. Random 实例包括 java.util.Random 或 Math.random()
    2. JDK7之后,可直接使用 ThreadLocalRandom;而7之前,需要编码保证每个线程持有一个单独的 Random 实例。
  16. 通过双重检查锁实现延迟初始化的优化问题隐患,推荐解决方案中较为简单的一种:将目标属性声明为 volatile 类型。

  17. volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但如果多写,同样无法解决线程安全问题。

  18. HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升。

  19. ThreadLocal 对象使用 static 修饰,ThreadLocal 无法解决共享对象的更新问题。

8、控制语句

  1. switch 括号内的变量类型为 string,且为外部参数时,需先进行 null 判断。

《Umi Hooks》官网笔记

目录


《Umijs》官方文档
阅读更多
《UmiJS》官网笔记

UmiJS API

一、基本API

dynamic

动态加载组件。

import React from 'react';
import { dynamic } from 'umi';

const delay = (timeout: number) =>
  new Promise(resolve => setTimeout(resolve, timeout));

export default dynamic({
  loader: async function() {
    await delay(1000);
    return () => <div>1s之后显示</div>;
  },
});

history

可用于获取当前路由信息。

import { history } from 'umi';

// history 栈里的实体个数
console.log(history.length);

// 当前 history 跳转的 action,有 PUSH、REPLACE 和 POP 三种类型
console.log(history.action);

// location 对象,包含 pathname、search 和 hash
console.log(history.location.pathname);
console.log(history.location.search);
console.log(history.location.hash);

也可用于路由跳转

import { history } from 'umi';

// 跳转到指定路由
history.push('/list');

// 带参数跳转到指定路由
history.push('/list?a=b');
history.push({
  pathname: '/list',
  query: {
    a: 'b',
  },
});

// 跳转到上一个路由
history.goBack();

也可用于路由监听

import { history } from 'umi';

const unlisten = history.listen((location, action) => {
  console.log(location.pathname);
});
unlisten();

二、路由

import React from 'react';
import { Link } from 'umi';

export default () => {
  return (
    <div>
      {/* 跳转指定路由 */}
      <Link to="/about">About</Link>
      {/* 跳转至附带参数的路由 */}
      <Link to="/about?id=123">About 123</Link>
      {/* 跳转至指定路由,附带query、hash、state */}
      <Link
        to={{
          pathname: '/list',
          search: '?sort=name',
          hash: '#the-hash',
          state: { fromDashboard: true },
        }}
      >
        List
      </Link>
      {/* 跳转至指定路由,附带所有当前 location 上的参数 */}
      <Link
        to={location => {
          return { ...location, pathname: '/profile' };
        }}
      >
        Profile
      </Link>
      {/* 跳转至指定路由,但会替换当前history stack中的记录 */}
      <Link to="/course" replace>
        Course
      </Link>
      {/* innerRef 允许你获取基础组件 */}
      <Link to="/course" innerRef={node => {}}>
        Course
      </Link>
    </div>
  );
};

特殊版本的 <Link />。当指定路由命中时,可以附着特定样式。(可以用作顶部导航的选中定位;或者菜单栏的选中定位等)

import { NavLink } from 'umi';

export default () => {
  return (
    <div>
      {/* 和 Link 等价 */}
      <NavLink to="/about">About</NavLink>

      {/* 当前路由为 /faq 时,附着 class selected */}
      <NavLink to="/faq" activeClassName="selected">
        FAQs
      </NavLink>

      {/* 当前路由为 /faq 时,附着 style */}
      <NavLink
        to="/faq"
        activeStyle={{
          fontWeight: "bold",
          color: "red",
        }}
      >
        FAQs
      </NavLink>

      {/* 当前路由完全匹配为 /profile 时,附着 class */}
      <NavLink exact to="/profile" activeClassName="selected">
        Profile
      </NavLink>

      {/* 当前路由为 /profile/ 时,附着 class */}
      <NavLink strict to="/profile/" activeClassName="selected">
        Profile
      </NavLink>

      {/* 当前路由为 /profile,并且 query 包含 name 时,附着 class */}
      <NavLink
        to="/profile"
        exact
        activeClassName="selected"
        isActive={(match, location) => {
          if (!match) {
            return false;
          }
          return location.search.includes("name");
        }}
      >
        Profile
      </NavLink>
    </div>
  );
};

Prompt

用户离开页面时的提示选择。

import React from 'react';
import { Prompt } from 'umi';

export default function index() {
  return (
    <div>
      <h1>FAQS</h1>
      {/* 离开页面时提示 */}
      <Prompt message="你确定要离开么?" />
      {/* 用户跳转指定页面是提示 */}
      <Prompt
        message={location => {
          return location.pathname === '/me'
            ? '您确定要跳转到个人中心吗'
            : true;
        }}
      />
      {/* 根据一个状态来确定是否提示 */}
      <Prompt when={true} message="您确定要半途而废吗?" />
    </div>
  );
}

withRouter

高阶组件,可以通过 withRouter 获取到 historylocationmatch 对象。

import { withRouter } from "umi";

export default withRouter(({ history, location, match }) => {
  return (
    <div>
      <ul>
        <li>history: {history.action}</li>
        <li>location: {location.pathname}</li>
        <li>match: {`${match.isExact}`}</li>
      </ul>
    </div>
  );
});

useHistory

hooks,用于获取 history 对象

useLocation

hooks,用于获取 location 对象

useParams

hooks,用于获取 params 对象。params 对象为动态路由(如 /user/:id)里的参数键值对。

useRouteMatch

hooks,用于当前路由的信息匹配。

import { 
  useHistory,
  useLocation,
  useParams,
  useRouteMatch
} from "umi";

// 假设当前路由为:/user/10086?type=user&deleted=false
export default () => {
  const history = useHistory()
  const location = useLocation()
  const params = useParams()
  const match = useRouteMatch()
  return (
    <div>
      <ul>
        <li>history: {history.action}</li>
        <li>location: {location.pathname}</li>
        <li>params: {JSON.stringify(params)}</li>
        <li>match: {JSON.stringify(match.params)}</li>
      </ul>
    </div>
  );
};