Java 阿里巴巴代码编程规范 重点部分笔记

7 篇文章 0 订阅

编程规约

命名风格

  • 强制 定义变量时不可用 $_ 开始,也不可以用 $_ 结束

    • ~: 表示位操作,位非
    • !: 表示逻辑非,常用于 boolean 类型操作
    • @: 表示注解
    • #: 用于注释中使用,在 IDEA 中可以使用 @see #方法名(),快速跳转至方法,例:
     /**
       * @see #b()
       */
      public void a() {
    
      }
    
      /**
       * @see #a()
       */
      public void b() {
    
      }
    
    • $: 没有什么特殊用法
    • %: 取余运算
    • ^: 异或运算
    • &: 与符号
    • *: 乘符号
    • (
    • )
    • _: 下划线可以用在变量中国,但是不能用于首尾
    • -
    • +
    • =
  • 强制 类名使用大驼峰

  • 强制 方法名、参数名、成员变量、局部变量使用小驼峰

  • 强制 常量全部大写,单词间用下划线隔开

  • 强制 抽象类以 Abstract 或者 Base 开头,异常类用 Expection 结尾,测试类命名以它要测试的类的名称开始,以 Test 结尾

  • 强制 POJO 中的 boolean 类型不要以 is 开头(boolean 对象自动生成 getter 方法时会以 is 开头)

  • 强制 包名统一使用小写,且点分隔符之间有且有一个自然语义的单词,包名统一使用单数形式(存在例外),类名如果存在复数形式可以使用复数形式

  • 强制 杜绝不规范的缩写

  • 推荐 在常量和变量的命名时,表示类型的名词放在词尾,以提升辨识度

  • 推荐 接口类中的方法不要加任何修饰符号(public也不要加),保证代码的简洁性,并加上JavaDoc注释

  • 参考 各层命名规约:

    • Service/DAO层命名方法规约
      1. 获取单个对象的方法用get做前缀
      2. 获取多个对象的方法用list最前缀,复数形式解为如: listObjects
      3. 获取统计值的方法用count做前缀
      4. 插入的方法用save/insert做前缀
      5. 删除的方法用remove/delete做前缀
      6. 修改的方法用update做前缀
    • 领域数据命名规约
      1. 数据对象: xxxDOxxx即为数据表名
      2. 数据传输对象: xxxDTOxxx为业务领域相关的名称
      3. 展示对象: xxxVOxxx一般为网页名称
      4. POJODO/DTO/BO/VO的统称,禁止命名为xxxPOJO

常量定义

  • 强制 不允许任何魔法值出现(魔法值: 未经预先定义,凭空出现在代码中的值)
  • 强制Long 或者 long 赋值时,数值后使用大写字母 L,而不是小写字母 l
  • 推荐 不要使用一个常量类来维护所有常量,要按常量功能进行分类,分开维护
  • 推荐 常量的复用层次有五层: 跨应用共享常量(放置在二方库中,通常是client.jar中的constant目录下)、应用内共享常量(放置在一方库中,通常是子模块中的constant目录下)、子工程内共享常量(在当前子工程的constant目录下)、包内共享常量(在当前包下单独的constant目录下)、类内共享常量(在类内部的private static final定义)

代码格式

  • 强制 大括号为空,简洁地写成 {} 即可,大括号中间无需换行和空格,如果非空代码块:

    • 左大括号前不换行
    • 左大括号后换行
    • 有大括号前换行
    • 右括号后还有 else 等代码则不换行,表示终止的右大括号必须换行
  • 强制 右小括号和右边相邻字符间不出现空格;右小括号和左边相邻字符之间也不出现空格;左大括号前需要加空格

  • 强制 ``if/for/while/switch/do` 等保留字与括号之间都必须加空格

  • 强制 任何二目、三目运算符的左右两边都需要加一个空格

  • 强制 采用4个空格缩进,禁止使用 Tab 字符,例:

// 本部分代码适用于以上多条与缩进有关规范
public static void main(String[] args) {
    // 缩进4个字符
    String say = "hello";
    // 运算符的左右必须有一个空格
    int flag = 0;
    // 关键词if与括号之间必须有一个空格,括号内的‘f’与左括号,‘0’与右括号不需要空格
    if (flag == 0) {
        System.out.println(say);
    }
    // 左大括号前加空格且不换行,左大括号后换行
    if (flag == 1) {
        System.out.println("world");
        // 右大括号前换行,右大括号后有else,不用换行
    } else {
        System.out.println("ok");
        // 在右大括号后直接结束,则必须换行
    }
}
  • 强制 注释的双斜线与注释内容之间有且只有一个空格

  • 强制 在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开,例: int second = (int)first + 2;

  • 强制 单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:

    • 第二行相对第一行缩进4个字符,从第三行开始,不再进行缩进
    • 运算符与下文一起换行
    • 方法调用的点符号与下文一起换行
    • 方法调用中的多个参数需要换行时,在逗号后进行
    • 在括号前不要换行
  • 强制 方法参数在定义和传入时,多个参数逗号后面必须加空格

  • 强制 IDEtext file encoding 设为 UTF-8IDE 中文件的换行符使用 Unix 格式,不要使用 Windows 格式

OOP规格

  • 强制 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器编译成本,直接用类名来访问即可

  • 强制 所有的覆写方法,必须加 @Override 注解(@Override 注解可以准确判断是否覆写成功)

  • 强制 相同参数类型,相同业务含义,才可以使用 Java 的可变参数, 避免使用Object

    • 可变参数必须放置在参数列表的最后,建议开发者尽量不要使用可变参数编程
// Person类
public class Person {
  
}

// 某方法
public void getPerson(Person... person) {
  
}

// 调用.getPerson(new Person(), new Person(), ..., new Person());
  • 强制 外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生影响;接口过时时必须加 @Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么
  • 强制 不能使用过时的类和方法
  • 强制 Objectequals() 方法容易抛出指针异常,应使用常量或确定有值得对象来调用 equals(),例:
// 正例
"test".equals(object);

// 反例
object.equals("test");

// 推荐使用
java.util.Objects.equals("test", object);
  • 强制 所有整型包装类对象之间值的比较,全部使用 equals() 方法比较(Integer 创建范围在 [-128, 127] 之间时不创建对象)
  • 强制 任何货币金额,均以最小货币单位且整数类型来进行存储(人民币用分)
  • 强制 浮点数之间的等值判断,基本数据类型不能用 == 来比较(使用 equals()),包装数据类型不能用 equals() 来判断(使用 BigDecimal compareTo())
  • 强制 定义数据对象 DO 类时,属性类型要与数据库字段类型相匹配
  • 强制 禁止使用构造方法 BigDecimal(double) 的方式把 double 值转化为 BigDecimal 对象,优先推荐入参为 String 的构造方法,或使用 BigDecimalvalueOf() 方法
  • 强制 所有的 POJO 类属性必须使用包装数据类型(Integer 默认值为 null , int 默认值为 0)
  • 强制 RPC 方法的返回值和参数必须使用包装数据类型
  • 强制 定义 DO/DTO/VOPOJO 类时, 不要设定任何默认属性值
  • 强制 序列化类新增属性时, 请不要修改 serialVersionUID 字段, 避免反序列失败; 如果完全不兼容升级, 避免反序列化混乱, 那么请修改 serialVersionUID
  • 强制 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中
  • 强制 POJO 类必须写 toString 方法, 如果继承了另一个 POJO 类, 注意在前面加一下 super.toString
  • 强制 禁止在 POJO 类中同时存在对应属性 xxxisXxx()getXxx() 方法
  • 推荐 使用索引访问用Stringsplit方法得到的数组时,需对最后一个分隔符后有无内容做检查
  • 推荐 循环体内的字符串连接应使用StringBuilderappend方法进行扩展
  • 推荐 类成员与方法访问控制从严:
    • 如果不允许外部直接通过new来创建对象,那么构造方法必须是private
    • 工具类不允许有publicdefault构造方法
    • 类非static成员变量并且与子类共享,必须是protected
    • 类非static成员变量并且尽在本类中使用,必须是private
    • static成员变量如果仅在本类中使用,必须是private
    • 若是static成员变量,考虑是否为final
    • 类成员方法只供类内部调用,必须是private
    • 类成员方法只对继承类公开,需要限制为protected

日期时间

  • 强制 日期格式化时, 传入 patterm 中表示年份统一使用小写的 y
  • 强制 在日期格式中分清楚大写的 M 和小写的 m , 大写的 H 和小写的 h 分别取代的意义
  • 强制 获取当前毫秒数使用 System.currentTimeMillis() 而不是 new Date().getTime() 如果想要更加准确的纳秒级时间值,使用 System.nanoTime 的方式
  • 强制 不允许在程序的任何地方使用 java.sql.Date java.sql.Time java.sql.Timestamp
  • 强制 不要在程序中写死一年为365天,避免在公历闰年时出现日期转换错误或程序逻辑错误,可使用:
  // 获取今年的天数
  int daysOfThisYear = LocalDate.now().lengthOfYear();
  
  // 获取指定某天的天数
  localDate.of(2000, 9, 18).lengthOfYear();

集合处理

  • 强制 关于 hashCodeequals 的处理,应遵循如下规则:

    • 只要覆写 equals,就必须覆写 hashCode
    • 因为 Set 存储的是不重复的对象,根据 hashCodeequals 进行判断,所以 Set 存储的对象必须覆写这两种方法
    • 如果自定义对象为 Map 的键,那么必须覆写 hashCodeequals
  • 强制 判断所有集合内部的元素是否为空,使用 isEmpty() 方法,而不是 .size() == 0 的方法

  • 强制/续具体实现 在使用 java.util.stream.Collevtors 类的 toMap() 方法转为 Map 集合时,一定要使用含有参数类型为 BinaryOperator,参数名为 mergeFunction 的方法,否则当出现相同 key 时会抛出 IIIegaIStateException 异常,例:

List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>("version", 12.10));
pairArrayList.add(new Pair<>("version", 12.19));
pairArrayList.add(new Pair<>("version", 6.28));

Map<String, Double> map = pairArrayList.stream().collect(
        Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2)
);

map.entrySet().stream().forEach(
        e -> System.out.println(e.getKey() + "..." + e.getValue())
);
  • 强制 在使用 java.util.Stream.Collectors 类的 toMap() 方法转为 Map 集合时,一定要注意当 valuenull 时会抛 NPE 异常

  • 强制 ArrayListsubList 结果不可强转成 ArrayList,否则会抛出 ClassCastException 异常(subList返回的是ArrayList的内部类SubList,并不是ArrayList,而是ArrayList的一个视图,对于SubList子列表的所有操作最终会反映到原列表上)

  • 说明: subList() 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 本身,而是 ArrayList 的一个视图,对于 SubList 的所有操作最终都会反映到原列表上

  • 强制 使用 Map 的方法 keySet()/values()/entrySet() 返回集合对象时,不可以对其进行添加元素操作,否则会抛出 UnsupportedOperationExpection 异常

  • 强制 Collections 类返回的对象,如: emptylist()/singletonList() 等都是 immutable list,不可对其进行添加或者删除元素的操作

  • 强制subList 场景中,高度注意 对父集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生 ConcurrentModificationExeception 异常

  • 强制 使用集合转数组的方法,必须使用集合的 toArray(T[] array) 传入的是类型完全一致、长度为0的空数组(如果使用toArray()的无参方法,返回值只能是Object[]类,如果对结果进行强转,会发生ClassCastException错误)

    List<String> list = new ArrayList<>(2); 
    list.add("guan"); 
    list.add("bao");
    String[] array = list.toArray(new String[0]);
    

    使用 toArray带参方法,数组空间大小的lengt:

    • 等于0: 动态创建与size相同的数组,性能最好
    • 大于0但小于size: 重新创建大小等于size的数组,增加GC负担
    • 等于size: 在高并发情况下,数组创建完成之后,size正在变大的情况下,负面影响与上相同
    • 大于size: 空间浪费,且在size处插入NULL值,存在NPE隐患
  • 强制 在使用 Collection 接口任何实现类的 addAll() 方法时,都要对输入的集合参数进行 NPE 判断

  • 强制 使用工具类 Arrays.asList() 把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常

  • 强制 泛型通配符 <? extends T> 来接收返回的数据,此写法的泛型集合不能使用 add 方法(这样子写泛型表示这样一个范围,而指定一个类型传入不符合这个范围,可能是现在 java 的一个不足),而 <? extends T> 不能使用 get 方法(这样子返回,因为泛型中是一个范围,所以返回只能是一个 Object),两者在接口调用赋值的场景中容易出错

  • 强制 在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行 instanceof 判断,避免抛出 ClassCastExpection 异常

  • 强制 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iteratoe 方式,如果并发操作,需要对 Iterator 对象加锁

  • 强制JDK7 版本及以上,Comparator 实现类要满足如下三个条件,不然 Arrays.sort/Collections.sort 会抛出 IllegalArgumentExpection 异常

    1. 自反性: x,y的比较结果和y,x的比较结果相反
    2. x > y,y > z,则 x > z
    3. x = y,则x,z比较结果和y,z比较结果相同
  • 推荐 集合初始化时,指定集合初始值大小

  • 推荐 使用entrySet遍历Map类集合KV,而不是KeySet方式进行遍历(遍历KeySet的方式其实是遍历了两次,第一次是先将KeySet转换为Iterator对象,第二次是取出Key所对应的Value)

并发处理

  • 强制 获取单例对象需要保证线程安全,其中的方法也要保证线程安全(资源驱动类、工具类、单例工厂类等)
  • 强制 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯
  • 强制 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程(不通过线程池,可能会造成创建大量同类线程导致消耗过多资源)
  • 强制 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式来创建
  • 强制 SimpleDataFormat是线程不安全的类,一般不要定义为statis变量,如果定义为static,必须加锁,或者使用DateUtils工具类(JDK8可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat)
  • 强制 必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄漏等问题,尽量在代理中使用try-finally块进行回收
  • 强制 高并发时,同步调用应该去考量锁的性能损耗,能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁
  • 强制 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁
  • 强制 在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁
  • 强制 在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁,锁的释放规则与锁的阻塞等待方式相同
  • 强制 并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据
  • 强制 多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题
  • 推荐 资金相关的金融敏感信息,使用悲观锁策略
  • 推荐 避免Random实例被多线程调用,虽然共享该实例是线程安全的,但会因为竞争同一个send导致性能下降(JDK7之后可以使用ThreadLocalRandom)
  • 参考 HashMap在容量不够进行resize时由于高并发可能会出现死链,导致CPU飙升,开发过程中应尽量避免使用该数据结构

控制语句

  • 强制 在一个switch块内,每个case要么通过continue/break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default

  • 强制switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断

  • 强制if/else/for/while/do语句中必须使用大括号

  • 强制 三目运算符condition ? 表达式1 : 表达式2中,高度注意表达式1和2在类型对齐时,可能抛出因自动拆箱导致的NPE异常,例:

    Integer a = 1;
    Integer b = 2;
    Integer c = null;
    Boolean flag = false;
    Integer result = (flag ? a * b : c);
    
  • 强制 在高并发场景中,避免使用 “等于” 判断作为中断或退出的条件 (剩余票数为0,可能某一个线程中数目小于0,此时程序永远不能终止)

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

    if (condition) { ... return obj;
    }
    // 接着写 else 的业务逻辑代码;
    
  • 推荐 除了常用方法(getXxx/isXxx)外,不要在条件判断语句中执行复杂的语句,这样会增加理解成本

注释规约

  • 强制 类、类属性、类方法的注释必须使用javadoc规范,使用/**内容*/格式,不得使用// xxx方式
  • 强制 所有的抽象方法(包括接口中的方法)必须使用javadoc注释,除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能
  • 强制 所有的类都必须添加创建者和创建日期
  • 强制 方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码的对齐
  • 强制 所有的美剧类型字段必须要有注释,说明每个数据的含义
  • 推荐 代码修改的同时,注释也要进行相应的修改,尤其是参数、返回值、异常、核心逻辑等的修改
  • 参考 特殊注释标记:
    • 待办事项(TODO): (标记人,标记时间,[预计处理时间])
    • 错误,不能工作(FIXME): (标记人,标记时间,[预计处理时间])

前后端规约

  • 强制 前后端交互的API,必须明确协议、域名、路径、请求方法、请求内容、状态码、响应体
    • 协议: 生产环境必须使用HTTPS
    • 路径: 每一个API需对应一个路径,表示API具体的请求地址
      • 代表一种资源,只能为名词,推荐使用复数,不能为动词,请求方法已经表达动作意义
      • URL路径不能使用大写,单词如果需要分隔,统一使用下划线
      • 路径禁止携带表示请求内容比如(.json、.xml)的后缀,通过accept头表达即可
    • 请求方式: 对具体操作的定义,常见的请求方法如下
      • GET: 从服务器取出资源
      • POST: 在服务器创建一个资源
      • PUT: 在服务器更新资源
      • DELETE: 从服务器删除资源
    • 请求内容: URL带的参数必须无敏感信息或符合安全要求,body里带参数时必须设置Content-Type
    • 响应体: 响应体body可放置多种数据类型,由Content-Type头来确定
  • 强制 前后端数据列表相关的接口返回,如果为空,则返回空数组或空集合
  • 强制 服务端发生错误时,返回给前端的响应信息必须包含HTTP状态码errorCodeerrorMessage用户提示信息四个部分
  • 强制 在前后端交互的JSON格式数据中,所有的key都因该为小写字母开始的小驼峰风格,表达完整
  • 强制 errorMessage是前后端错误追踪体制的体现,可以在前端输出到type = "hidden"文字类控件中,或者用户端的日志中,帮助我们快速定位到问题所在
  • 强制 对于需要使用超大整数的场景,服务端一律使用String字符串的类型返回,禁止使用Long类型
  • 强制 HTTP请求通过URL传递参数时,不能超过2048字节
  • 强制 HTTP请求通过body传递内容时,必须控制长度,超出最大长度后,后端解析会出错
  • 强制 在翻页场景中,用户输入参数若小于1,则前端返回第一页参数给后端;后端发现用户输入的参数大于总页数,直接返回最后一页
  • 强制 服务器内部重定向必须使用forward;外部重定向地址必须使用URL统一代理模块生成,否则会因为线上采用HTTPS协议而导致浏览器提示不安全,并且还会带来URL维护不一致的问题

其他

  • 强制 在使用正则表达式时,利用好其预编译功能,可以提升正则匹配效率(不要放在方法体内定义)
  • 强制 避免使用Apache Beanutils进行属性的copy,可以使用比如Spring BeanUtils来进行拷贝
  • 强制 后台输送给页面的变量时必须加$!{var}中的感叹号(velocity模板引擎)
  • 强制 Math.random()方法返回的是double类型,注意取值的范围0<=x<1,如果想获取整数类型的随机数,不要将x方法10的若干倍然后取整,直接使用Random对象
  • 推荐 任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存

异常日志

错误码

  • 强制 错误码的制定规则: 快速溯源、沟通标准化
    • 错误码的重点是,谁的错? 错在哪? 错误码必须能够快速知晓错误来源,可快速判断是谁的问题
    • 错误码必须能够进行清晰的比对(equals)
    • 错误码有利于团队快速对错误原因达到一致认知
  • 强制 错误码不体现版本号和错误等级信息
  • 强制 全部正常,但不得不填充错误码时返回00000
  • 强制 错误码为字符串类型,共5位,分成两个部分: 错误产生来源 + 四位数字编号
    • 错误产生来源:
      • A: 错误来源于用户,比如参数错误、用户版本过低等
      • B: 错误来源与当前系统,比如业务逻辑问题、程序健壮性问题
      • C: 错误来源于第三方服务,比如CDN服务出错,消息投递超时等
    • 数字编号参考手册末尾文档,大类步长相差100
  • 强制 编号不与公司业务架构,更不与组织架构挂钩,以先到先得的原则在统一平台上进行,审批生效,编码即被永久固定
  • 强制 错误码使用者避免随意定义新的错误码
  • 强制 错误码不能直接输出给用户作为提示信息使用

异常处理

  • 强制 Java类库中定义的可以通过预检查方式规避的RuntimeException不应该通过catch方式来处理(NullPointerExceptionIndexOutOfBoundsException)
  • 强制 异常捕获后不要用来做流程控制、条件控制(异常的处理效率比条件判断方式低得多)
  • 强制 catch时分清楚稳定代码和非稳定代码(对一大段程序块进行try...catch),稳定代码指的是不论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分异常类型,再做对应的异常处理
  • 强制 捕获异常是为了处理它,不要捕获了不做任何处理,如果不想做任何处理,把它抛给调用者;最外层的业务使用者必须处理异常,将其转化为用户可以理解的内容
  • 强制 事务场景中,抛出异常被catch后,如果需要回滚,一定要注意手动回滚事务
  • 强制 finally块必须对资源对象、流对象进行关闭,有一场也要做try-catch
  • 强制 不要在finally块中使用return(如果在try块中存在return语句,并不会马上返回,而是继续执行finally语句中的内容,而此时如果finally中存在return语句,则直接在finally中的return语句中返回)
  • 强制 捕获异常与抛异常,必须完全匹配,或者捕获异常是抛异常的父类
  • 强制 在调用RPC、二方包、或者动态生成类的相关方法时,捕获异常必须使用Throwable类来进行拦截(因为我们并不知道会抛出什么异常)
    • 一方包: 本工程中的各模块的相互依赖
    • 二方包: 公司内部的依赖库,一般指公司内部的其他项目发布的jar包
    • 三方包: 公司之外的开源库,比如: apache、ibm、google等公司发布的依赖
  • 推荐 方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下不会返回null
  • 推荐 容易产生NPE的情况:
    • 返回类型为基本数据类型,return包装数据类型的对象时,自动拆箱时可能产生NPE
    • 数据库的查询结果可能是null
    • 集合里的元素可能是null,即使该集合isNotEmpty
    • 远程调用返回对象时,一律要求进行空指针判断,防止NPE
    • 对于Session中获取的数据,建议进行NPE检查,避免空指针
    • 级联调用obj.getA().getB().getC()时,容易产生NPE

日志规约

  • 强制 应用中不可直接使用日志系统(Log4jLogback)中的API,而应依赖使用日志框架(SLF4JJCL-Jakarta Commons Logging)中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一

  • 强制 所有日志文件至少保存15天,因为有些异常具备以“周”为频次发生的特点,对于当天日志,以“应用名.log”来保存,存放在/home/admin/应用名/log/目录下,过往日志格式为: {logname}.log.{保存日期},日期格式: yyyy-MM-dd

  • 强制 根据国家相关规定,网络运行状态、网络安全事件、个人敏感信息操作等相关记录,留存日志不少于六个月,并且进行网络多机备份

  • 强制 应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:

    appName_logType_logName.loglogType: 日志类型,如: stats/monitor/access等;logName: 日志描述。这种命名的好处: 通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找

  • 强制 在日志输出时,字符串变量之间的拼接使用占位符的方式

  • 强制 对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断

    // 先进行判断,如果为真,可以输出trace或者debug级别的日志
    if (logger.isDebugEnabled()) {
        logger.debug("...");
    }
    
  • 强制 避免重复打印日志,浪费磁盘空间,务必在日志配置文件中设置additivity = false

  • 强制 生产环境禁止直接使用System.out或者System.err输出日志或使用e.printStackTrace()打印异常日志

  • 强制 异常信息应该包括两类信息: 案发现场信息和异常堆栈信息,如果不进行处理,那么通过关键字throws向上抛出

  • 强制 日志打印时禁止直接使用JSON工具将对象转换为String(因为JSON工具将对象转换为字符串时会调用get方法,如果get方法存在抛出异常的情况,就会影响业务的进行)

  • 推荐 谨慎地记录日志,生产环境禁止输出debug日志,有选择地输出info日志,如果使用warn来记录刚上线时的业务行为信息,一定要注意日志输出量的问题

  • 推荐 可以使用warn日志级别来记录用户输入参数问题错误的情况

单元测试

  • 强制 好的单元测试必须遵守AIR原则

    单元测试在线上运行时,感觉像空气(air)一样感觉不到,但在测试质量的保障上又是非常关键的,好的单元测试需要具有自动化、独立性、可持续执行的特点:

    • A: Automatic(自动化)
    • I: Independen(独立性)
    • R: Repeatable(可持续性)
  • 强制 单元测试应该是全自动执行的,并且非交互式的;测试用例通常是被定期执行的,执行过程必须完全自动化才有意义;输出结果需要人工检查的测试不是一个好的单元测试;单元测试中不准使用System.out来人工验证,必须使用assert来验证

  • 强制 为了保证单元测试稳定可靠且便于维护,单元测试用例之间不能相互调用,不能依赖执行的先后顺序

  • 强制 单元测试是可以重复执行的,不能受到外界环境的影响

  • 强制 对于单元测试,要保证测试粒度足够小,有助于精确定位问题,单测粒度至多是类级别,一般是方法级别

  • 强制 核心业务、核心应用、核心模块的增量代码确保单元测试通过

  • 强制 单元测试代码必须写在如下工程目录: src/test/java,不允许写在业务模块目录下(源码进行编译时会跳过单元测试目录,单元测试框架默认扫描该目录)

  • 推荐 单元测试的基本目标: 语句覆盖率达到70%;核心模块的语句覆盖了和分支覆盖率都要达到100%

  • 推荐 编写单元测试代码遵守BCDE原则:

    • B: Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等
    • C: Correct,正确的输入,并得到期望的结果
    • D: Design,与设计文档相结合,来编写单元测试
    • E: Error,强制错误信息输入(非法数据、异常流程、业务允许外),并得到期望的结果
  • 推荐 对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的,或者直接操作数据库把数据插入进去,请使用程序导入或者插入的方式准备数据

  • 推荐 和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识

安全规约

  • 强制 隶属于用户个人的页面或者功能必须进行权限控制校验
  • 强制 用户敏感数据禁止直接显示,必须对展示数据进行脱敏
  • 强制 用户输入的SQL参数必须严格使用参数绑定或者METADATA字段进行限定,放置SQL注入,禁止字符串拼接SQL访问数据库
  • 强制 用户传入的任何数据必须做有效性验证(可以使用正则表达式来验证用户的输入,注意特殊的字符串造成的死循环)
  • 强制 禁止向HTML页面输出未经安全过滤或未正确转义的用户数据
  • 强制 表单、AJAX提交必须执行CSRF安全验证(攻击者使用了某用户的身份,然后发送恶意请求,因为是正常用户的身份,所以在服务器的角度来看是完全合法的)
  • 强制 URL外部重定向传入的目标地址必须执行白名单过滤
  • 强制 在使用平台资源,比如短信、邮件、下盘等,必须实现正确的防重放的机制,比如数量限制、疲劳度控制、验证码校验,避免被滥刷导致资源损耗
  • 推荐 发帖、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容等违禁词过滤等风控策略

MySQL数据库

建表规约

  • 强制 表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint(1表示是,0表示否)
    • 任何字段表示非负数,必须是unsigned
    • 因为POJO类中的任何布尔类型的变量不能加is前缀,所以,需要在<resultMap>中设置从is_xxxXxx的资源映射
  • 强制 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字,数据库字段名修改代价很大,所以字段名称需要慎重考虑
  • 强制 表中不使用复数名词
  • 强制 禁用保留字,比如: descrangematch
  • 强制 主键索引名为pk_字段名;唯一索引名为uk_字段名;普通索引名为idx_字段名
  • 强制 小数类型为decimal,禁止使用floatdouble
  • 强制 如果存储的字符串长度几乎相等,使用char定长字符串类型
  • 强制 varchar是可变长度字符串,不预先分配存储空间,长度不要超过5000,如果存储长度大于此值,定义字段类型为text,独立出一张表,用主键来对应,避免影响其他字段索引效率
  • 强制 表必备三字段:
    • id: 主键,类型为bigint unsigned,单表时自增
    • create_time: datetime类型,主动创建时
    • updated_time: datetime类型,被动修改时
  • 推荐 表的命名最好是遵循业务名称_表的作用
  • 推荐 库名和应用名称尽量一致
  • 推荐 单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表

索引规约

  • 强制 业务上具有唯一性质的字段,即使是组合字段,也必须建成唯一索引
  • 强制 超过三个表禁止join,需要join的字段,数据类型保持绝对一致;多表关联查询时,保证被关联的字段需要有索引
  • 强制varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度
  • 强制 页面搜索严谨左模糊或者全模糊,如果需要请走搜索引擎来解决
  • 推荐 SQL性能优化的目标: 至少要达到range级别,要求是ref级别,如果可以consts最好

SQL语句

  • 强制 不要使用count(列名)count(常量)来替代count(*)count(*)SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关(count(*)会统计值为NULL的行,count(列名)不会统计列为NULL的行)
  • 强制 count(distinct col)计算该列除NULL之外的不重复行数,注: count(distinct col1, col2)如果其中一列全为NULL,那么即时另一列有不同的值,也返回0
  • 强制 当某一列的值全是NULL时,count(col)的返回结果为0,但sum(col)的返回结果为null,因此使用sum()时需注意NPE问题,可以使用ifnull()函数来防止NPE问题
  • 强制 使用isnull()来判断是否为NULL值(NULL与任何值的直接比较都为NULL)
  • 强制 代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句
  • 强制 不得使用外键与级联,一切外键概念必须在应用层解决
  • 强制 禁止使用存储过程,存储过程难以调试和扩展,也没有移植性
  • 强制 数据订正,特别是删除或修改记录操作时,要先select,避免出现误操作,确认无误才能执行更新语句
  • 强制 对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定

ORM映射

  • 强制 在表查询中,一律不要使用*作为查询的字段列表,需要哪些字段必须明确写明,原因:
    • 增加查询分析器解析成本
    • 增减字段容易与resultMap配置不一致
    • 无用字段增加网络消耗,尤其是text类型的字段
  • 强制 POJO类的布尔属性不能加is,数据库字段必须加is_,要求在resultMap中进行字段与属性之间的映射
  • 强制 不要用resultClass当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义<resultMap>,换句话说,每一个表也必然有一个<resultMap>与之对应
  • 强制 sql.xml配置属性使用#{}#param#,不要使用${},这种方式容易出现SQL注入
  • 强制 iBATIS自带的queryForList(String statementName, int start, int size)不推荐使用(它实现的逻辑是将整个表中的数据查出来,然后分割出所需数据)
  • 强制 不允许直接拿HashMapHashTable作为查询结果集输出
  • 强制 更新数据库表记录时,必须同时更新记录对应的update_time字段值为当前时间
  • 推荐 不要写一个大而全的数据更新接口
  • 参考 <isEqual>中的compareValue是与属性值对比的常量,一般是数字,表示相等时带上此条件;<isNotEmpty>表示不为空且不为null时执行;<isNotNull>表示不为null值时执行

工程结构

应用分层

  • 参考 分层领域模型规约:
    • DO(Data Object): 此对象与数据库表结构一一对应,通过DAO层向上传输数据源对象
    • DTO(Data Transfer Object): 数据传输对象,ServiceManager向外传输的对象
    • BO(Bussiness Object): 业务对象,由Service层输出的封装业务逻辑对象
    • AO(Application Object): 应用对象,在Web层与Service层之间抽象的复用对象模型,极为贴近展示层,复用度不高
    • VO(View Object): 显示层对象,通常是Web向模板渲染引擎层传输的对象
    • Query: 数据查询对象,各层接收上层的查询请求,注意超过2个参数的查询封装,禁止使用Map类来传输

二方库依赖

  • 强制 定义GAV规则:
    • GroudID格式: com.{公司/BU}.业务线[.子业务线],最多4级
    • ArtifactID格式: 产品线名-模块名(语义不重复不遗漏)
    • Version: 版本
  • 强制 二方库版本号命名方式: 主版本号.次版本号.修订号
    • 主版本号: 产品方向改变,或者大规模API不兼容,或者架构不兼容升级
    • 次版本号: 保持相对兼容性,增加主要功能特性,影响范围极小的API不兼容修改
    • 修订号: 保持完全兼容性,修复BUG、新增次要功能特性等
  • 强制 二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的POJO对象

服务器

  • 推荐 高并发服务器建议调小TCP协议的time_wait超时时间
  • 推荐 调大服务器所支持的最大文件句柄数(File Descriptor,简写为fd)

设计规约

  • 强制 在需求分析阶段,如果与系统交互的User超过一类并且相关的User Case超过5个,使用用例图来表达更加清晰地结构化需求
  • 强制 如果某个业务对象的状态超过3个,使用状态图来表达并且明确状态变化的各个触发条件
  • 强制 如果系统中模型类超过5个,并且存在复杂的依赖关系,使用类图来表达并且明确类之间的关系
  • 强制 如果系统中超过2个对象之间存在协作关系,并且需要表示复杂的处理流程,使用活动图来表示
  • 推荐 谨慎使用继承的方式来进行扩展,优先使用聚合/组合的方式来实现

后续添加

  • 对于暂时被注释掉,后续可能恢复使用的代码片段,在注释代码的上方统一使用三斜杠(///)来说明注释掉代码的理由
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值