疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

阿里巴巴Java开发手册快速学习


 

   Java作为一门名副其实的工业级语言,语法友好,学习简单,大规模的应用给代码质量的管控带来了困难,特别是团队开发中,开发过程中的规范会直接影响最终项目的稳定性。

  善医者“未有形而除之”,提高工程健壮性最好的方式是在代码出现问题之前就排除掉,不给Bug出现的机会。一份好的开发规范就可以起到这样的作用,大大减少产品上线后的问题。

  《阿里巴巴Java开发手册》是阿里巴巴的内部编码规范,阿里官方的Java代码规范标准, 手册以Java应用开发为维度,分为编程规约、异常日志规约、MYSQL规约、工程规约、安全规约五个章节,给出了强制、推荐、参考三个级别,每条规范都有推荐的约束力度,从命名到项目拆分,不仅规范了一些开发细节,也提出了很多工程开发的哲学,值得好好阅读。

  下面记录一些对我比较有启发的条款,提纲挈领,快速学习。

  一、编程规约

  1.如果使用到了设计模式,建议在类名中体现出具体模式

  将设计模式体现在名字中,有利于阅读者快速理解架构设计思想。

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

  可变参数必须放置在参数列表的最后,尽量不用可变参数编程。

  3.对外暴露的接口签名,原则上不允许修改方法签名,避免对接口调用方产生影响

  接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。

  4.关于基本数据类型与包装数据类型的使用标准如下

  1) 所有的POJO类属性必须使用包装数据类型

  2) RPC方法的返回值和参数必须使用包装数据类型

  3) 所有的局部变量【推荐】使用基本数据类型

  POJO 类属性没有初值是醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。数据库的查询结果可能是null,因为自动拆箱,用基本数据类型接收有NPE风险。

  5.注意 serialVersionUID 不一致会抛出序列化运行时异常

  序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。

  6.POJO 类必须写 toString 方法

  使用 IDE 的中工具:source> generate toString 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。 在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。

  7.final 可提高程序响应效率,声明成 final 的情况:

  1) 不需要重新赋值的变量,包括类属性、局部变量

  2) 对象参数前加final,表示不允许修改引用的指向

  3) 类方法确定不允许被重写

  8.慎用 Object 的 clone 方法来拷贝对象

  对象的 clone 方法默认是浅拷贝,若想实现深拷贝需要重写 clone 方法实现属性对象 的拷贝。

  9.类成员与方法访问控制从严

  1) 如果不允许外部直接通过new来创建对象,那么构造方法必须是private

  2) 工具类不允许有public或default构造方法

  3) 类非static成员变量并且与子类共享,必须是protected 4) 类非static成员变量并且仅在本类使用,必须是private

  5) 类static成员变量如果仅在本类使用,必须是private

  6) 若是static成员变量,必须考虑是否为final

  7) 类成员方法只供类内部调用,必须是private

  8) 类成员方法只对继承类公开,那么限制为protected

  任何类、方法、参数、变量,严控访问范围。过宽泛的访问范围,不利于模块解耦。思考:如果是一个 private 的方法,想删除就删除,可是一个 public 的 Service 方法,或者一个 public 的成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,如果无限制的到处跑,那么你会担心的。

  10.ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException 异常

  subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是 ArrayList 的一个视图,对于SubList子列表的所有操作最终会反映到原列表上。

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

  使用add/remove/clear 方法会抛出 UnsupportedOperationException 异常。asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。

  12.不要在 foreach 循环里进行元素的 remove/add 操作

  remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

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

  资源驱动类、工具类、单例工厂类都需要注意。

  14.线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式

  这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 Executors 返回的线程池对象的弊端如下: 1)FixedThreadPool 和 SingleThreadPool:

  允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

  2)CachedThreadPool 和 ScheduledThreadPool:

  允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

  15.SimpleDateFormat 是线程不安全的类,一般不要定义为static变量

  如果定义为static,必须加锁,或者使用 DateUtils 工具类。 注意线程安全,使用 DateUtils。亦推荐如下处理:

  private static final ThreadLocal df = new ThreadLocal() { @Override

  protected DateFormat initialValue() {

  return new SimpleDateFormat("yyyy-MM-dd");

  } };

  16.高并发时,同步调用应该去考量锁的性能损耗

  能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。

  17.并发修改同一记录时,避免更新丢失

  要么在应用层加锁,要么在缓存加锁,要么在 数据库层使用乐观锁,使用 version 作为更新依据。 如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次。

  18.对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁

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

  方法,线程执行代码注意 catch 异常,确保 countDown 方法可以执行,避免主线程无法执行 至 await 方法,直到超时才返回结果。注意,子线程抛出异常堆栈,不能在主线程 try-catch 到。

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

  Random 实例包括 java.util.Random 的实例或者 Math.random()实例。

  21.volatile 解决多线程内存不可见问题

  对于一写多读,是可以解决变量同步问题, 但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现: AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。

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

  这个变量是针对一个线程内所有操作共有的,所以设置为静态变量,所有此类实例共享 此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。

  二、异常日志

  1.对大段代码进行 try-catch,这是不负责任的表现

  catch 时请分清稳定代码和非稳 定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分 异常类型,再做对应的异常处理。

  2.捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之

  如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。

  3.在代码中使用“抛异常”还是“返回错误码”

  对于公司外的 http/api 开放接口必须 使用“错误码”;而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封 装 isSuccess、“错误码”、“错误简短信息”。

  4.避免出现重复的代码(Don’t Repeat Yourself),即DRY原则

  随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。

  5.对trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方

  式

  6.异常信息应该包括两类信息:案发现场信息和异常堆栈信息

  如果不处理,那么往上抛。

  三、MySQL 规约

  1.表达是与否概念的字段,必须使用 is_xxx 的方式命名,数据类型是 unsigned tinyint ( 1表示是,0表示否),此规则同样适用于odps建表。 任何字段如果为非负数,必须是unsigned。

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

  float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。

  3.表必备三字段:id, gmtcreate, gmtmodified

  其中id必为主键,类型为unsigned bigint、单表时自增、步长为1。gmtcreate, gmtmodified 的类型均为 date_time 类型。

  4.单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表

  如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。避免过度设计。

  5.业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引

  6.在 varchar 字段上建立索引时,必须指定索引长度

  没必要对全字段建立索引,根据实际文本区分度决定索引长度。 说索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分 度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度 来确定。

  7.利用覆盖索引来进行查询操作,来避免回表操作

  能够建立索引的种类:主键索引、唯一索引、普通索引,而覆盖索引是一种查询的一种 效果,用explain的结果,extra列会出现:using index。如果索引包含所有满足查询需要的数据的索引成为覆盖索引(Covering Index),也就是平时所说的不需要回表操作

  8.利用延迟关联或者子查询优化超多分页场景

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

  9.SQL 性能优化的目标

  至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。

  1)consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。 2)ref 指的是使用普通的索引(normal index)。 3)range 对索引进行范围检索。

  10.不要使用 count(列名)或 count(常量)来替代 count(*)

  count()就是 SQL92 定义 的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。 count()会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。

  11.使用 ISNULL()来判断是否为 NULL 值

  注意,NULL与任何值的直接比较都为 NULL

  12.不得使用外键与级联,一切外键概念必须在应用层解决

  外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。

  13.iBATIS 自带的 queryForList(String statementName,int start,int size)不推荐使用

  其实现方式是在数据库取到 statementName 对应的 SQL 语句的所有记录,再通过 subList 取 start,size 的子集合,线上因为这个原因曾经出现过 OOM。

  14.不要写一个大而全的数据更新接口

  传入为 POJO 类,不管是不是自己的目标更新字段, 都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。

  执行 SQL 时,尽量不要更新无改动的字段,一是易出错;二是效率低;三是 binlog 增加存储。

  四、工程规约

  1.高并发服务器建议调小 TCP 协议的 time_wait 超时时间

  操作系统默认 240 秒后,才会关闭处于 timewait 状态的连接,在高并发访问下,服 务器端会因为处于 timewait 的连接数太多,可能无法建立新的连接,所以需要在服务器上 调小此等待值。 正例:在 linux 服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒): net.ipv4.tcpfintimeout = 30

  2.调大服务器所支持的最大文件句柄数(File Descriptor,简写为fd)

  主流操作系统的设计是将 TCP/UDP 连接采用与文件一样的方式去管理,即一个连接对应于一个 fd。主流的 linux 服务器默认所支持最大 fd 数量为 1024,当并发连接数很大时很 容易因为 fd 不足而出现“open too many files”错误,导致新的连接无法建立。 建议将 linux 服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)。

  五、安全规约

  1. 隶属于用户个人的页面或者功能必须进行权限控制校验

  防止没有做水平权限校验就可随意访问、操作别人的数据,比如查看、修改别人的订单。

  2. 用户敏感数据禁止直接展示,必须对展示数据脱敏

  查看个人手机号码会显示成:158****9119,隐藏中间 4 位,防止隐私泄露。

  3. 用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入, 禁止字符串拼接 SQL 访问数据库

  4. 用户请求传入的任何参数必须做有效性验证

  忽略参数校验可能导致: page size过大导致内存溢出 恶意order by导致数据库慢查询 任意重定向 SQL注入 反序列化注入 正则输入源串拒绝服务ReDoS——Java 代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题, 但是如果攻击人员使用的是特殊构造的字符串来验证,有可能导致死循环的效果。

  5. 禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据

  6. 表单、AJAX 交必须执行 CSRF 安全过滤

  CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在 CSRF 漏洞的应用/网站,攻击者可以事先构造好 URL,只要受害者用户一访问,后台便在用户 不知情情况下对数据库中用户参数进行相应修改。

  7. 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放限制, 如数量限制、疲劳度控制、验证码校验,避免被滥刷、资损

  如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其 它用户,并造成短信平台资源浪费。

  8. 发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略