25

阿里Java开发手册快速学习

 4 years ago
source link: https://www.tuicool.com/articles/BrmEfqz
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
M7zY7bb.png!web

点击上面  蓝色字体 关注我们

技术  /  架构  /  职场  /  面试   /  内推

邴越:阿里资深工程师,专注研究分布式系统及高可用架构, 互联网开发核心技术 

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

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

下面对《阿里巴巴Java开发手册-v1.1.0版》记录一些对我比较有启发的条款,提纲挈领,快速学习,方便还没有阅读的同学快速了解。

一、编程规约

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。亦推荐如下处理:

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、发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略

版权申明:内容来源网络,版权归原创者所有。除非无法确认,我们都会标明作者及出处,如有侵权烦请告知我们,我们会立即删除并表示歉意。谢谢!

作者:邴越
来源:cnblogs.com/binyue/p/6526982.html
整编:搜云库技术团队,欢迎广大技术人员投稿
投稿邮箱:[email protected]

如果对本文的内容有疑问,请在文章留言区留言,谢谢。

公号回复关键字

回复: 【进群】 可进 , 技术架构分享群

回复: 【内推】 十大热门城市, 程序员工作内推群

回复: 【星球】 限时免费进, 技术分享知识星球

回复: 1024 送2019最新   4000G 架构师视频 

新资料、面试题、等其他资料,进群找群主

更多技术干货


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK