Java作为一门名副其实的工业级语言,语法友好,学习简单,大规模的应用给代码质量的管控带来了困难,特别是团队开发中,开发过程中的规范会直接影响最终项目的稳定性。
善医者“未有形而除之”,提高工程健壮性最好的方式是在代码出现问题之前就排除掉,不给Bug出现的机会。一份好的开发规范就可以起到这样的作用,大大减少产品上线后的问题。
《阿里巴巴Java开发手册》是阿里巴巴的内部编码规范,阿里官方的Java代码规范标准, 手册以Java应用开发为维度,分为编程规约、异常日志规约、MYSQL规约、工程规约、安全规约五个章节,给出了强制、推荐、参考三个级别,每条规范都有推荐的约束力度,从命名到项目拆分,不仅规范了一些开发细节,也提出了很多工程开发的哲学,值得好好阅读。
点击下载《阿里巴巴Java开发手册》(v1.1.0版)
下面记录一些比较有启发的条款,提纲挈领,快速学习。
将设计模式体现在名字中,有利于阅读者快速理解架构设计思想。
可变参数必须放置在参数列表的最后,尽量不用可变参数编程。
接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。
1) 所有的POJO类属性必须使用包装数据类型
2) RPC方法的返回值和参数必须使用包装数据类型
3) 所有的局部变量【推荐】使用基本数据类型
POJO 类属性没有初值是醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。数据库的查询结果可能是null,因为自动拆箱,用基本数据类型接收有NPE风险。
序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。
使用 IDE 的中工具:source> generate toString 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。 在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。
1) 不需要重新赋值的变量,包括类属性、局部变量
2) 对象参数前加final,表示不允许修改引用的指向
3) 类方法确定不允许被重写
对象的 clone 方法默认是浅拷贝,若想实现深拷贝需要重写 clone 方法实现属性对象 的拷贝。
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 的成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,如果无限制的到处跑,那么你会担心的。
subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是 ArrayList 的一个视图,对于SubList子列表的所有操作最终会反映到原列表上。
使用add/remove/clear 方法会抛出 UnsupportedOperationException 异常。asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。
remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
资源驱动类、工具类、单例工厂类都需要注意。
这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 Executors 返回的线程池对象的弊端如下: 1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
如果定义为static,必须加锁,或者使用 DateUtils 工具类。 注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal df = new ThreadLocal() { @Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
} };
能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
要么在应用层加锁,要么在缓存加锁,要么在 数据库层使用乐观锁,使用 version 作为更新依据。 如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于 3 次。
方法,线程执行代码注意 catch 异常,确保 countDown 方法可以执行,避免主线程无法执行 至 await 方法,直到超时才返回结果。注意,子线程抛出异常堆栈,不能在主线程 try-catch 到。
Random 实例包括 java.util.Random 的实例或者 Math.random()实例。
对于一写多读,是可以解决变量同步问题, 但是如果多写,同样无法解决线程安全问题。如果是 count++操作,使用如下类实现: AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)。
这个变量是针对一个线程内所有操作共有的,所以设置为静态变量,所有此类实例共享 此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。
catch 时请分清稳定代码和非稳 定代码,稳定代码指的是无论如何不会出错的代码。对于非稳定代码的catch尽可能进行区分 异常类型,再做对应的异常处理。
如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
对于公司外的 http/api 开放接口必须 使用“错误码”;而应用内部推荐异常抛出;跨应用间 RPC 调用优先考虑使用 Result 方式,封 装 isSuccess、“错误码”、“错误简短信息”。
随意复制和粘贴代码,必然会导致代码的重复,在以后需要修改时,需要修改所有的副本,容易遗漏。
式
如果不处理,那么往上抛。
float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
其中id必为主键,类型为unsigned bigint、单表时自增、步长为1。gmtcreate, gmtmodified 的类型均为 date_time 类型。
如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。避免过度设计。
没必要对全字段建立索引,根据实际文本区分度决定索引长度。 说索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分 度会高达 90%以上,可以使用 count(distinct left(列名, 索引长度))/count(*)的区分度 来确定。
能够建立索引的种类:主键索引、唯一索引、普通索引,而覆盖索引是一种查询的一种 效果,用explain的结果,extra列会出现:using index。如果索引包含所有满足查询需要的数据的索引成为覆盖索引(Covering Index),也就是平时所说的不需要回表操作
MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
1)consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。 2)ref 指的是使用普通的索引(normal index)。 3)range 对索引进行范围检索。
count()就是 SQL92 定义 的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。 count()会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。
注意,NULL与任何值的直接比较都为 NULL
外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
其实现方式是在数据库取到 statementName 对应的 SQL 语句的所有记录,再通过 subList 取 start,size 的子集合,线上因为这个原因曾经出现过 OOM。
传入为 POJO 类,不管是不是自己的目标更新字段, 都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。
执行 SQL 时,尽量不要更新无改动的字段,一是易出错;二是效率低;三是 binlog 增加存储。
操作系统默认 240 秒后,才会关闭处于 timewait 状态的连接,在高并发访问下,服 务器端会因为处于 timewait 的连接数太多,可能无法建立新的连接,所以需要在服务器上 调小此等待值。 正例:在 linux 服务器上请通过变更/etc/sysctl.conf 文件去修改该缺省值(秒): net.ipv4.tcpfintimeout = 30
主流操作系统的设计是将 TCP/UDP 连接采用与文件一样的方式去管理,即一个连接对应于一个 fd。主流的 linux 服务器默认所支持最大 fd 数量为 1024,当并发连接数很大时很 容易因为 fd 不足而出现“open too many files”错误,导致新的连接无法建立。 建议将 linux 服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)。
防止没有做水平权限校验就可随意访问、操作别人的数据,比如查看、修改别人的订单。
查看个人手机号码会显示成:158****9119,隐藏中间 4 位,防止隐私泄露。
忽略参数校验可能导致: page size过大导致内存溢出 恶意order by导致数据库慢查询 任意重定向 SQL注入 反序列化注入 正则输入源串拒绝服务ReDoS——Java 代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题, 但是如果攻击人员使用的是特殊构造的字符串来验证,有可能导致死循环的效果。
CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在 CSRF 漏洞的应用/网站,攻击者可以事先构造好 URL,只要受害者用户一访问,后台便在用户 不知情情况下对数据库中用户参数进行相应修改。
如注册时发送验证码到手机,如果没有限制次数和频率,那么可以利用此功能骚扰到其 它用户,并造成短信平台资源浪费。