RuntimeException
异常不该该经过catch
的方式来处理,好比:NullPointerException
,IndexOutOfBoundsException
等等【说明:没法经过预检查的异常除外,好比,在解析字符串形式的数字时,可能存在数字格式错误,不得不经过catch NumberFormatException
来实现】正例:
if (obj != null) {...}
反例:try { obj.method(); } catch (NullPointerException e) {…}java
catch
时请分清稳定代码和非稳定代码,稳定代码指的是不管如何不会出错的代码。对于非稳定代码的catch
尽量进行区分异常类型,再作对应的异常处理【说明:对大段代码进行try-catch
,使程序没法根据不一样的异常作出正确的应激反应,也不利于定位问题,这是一种不负责任的表现。】try
块放到了事务代码中,catch
异常后,若是须要回滚事务,必定要注意手动回滚事务finally
块必须对资源对象、流对象进行关闭,有异常也要作try-catch
。【若是JDK7及以上,可使用try-with-resources
方式】finally
块中使用return
【说明:try
块中的return
语句执行成功后,并不立刻返回,而是继续执行finally
块中的语句,若是此处存在return
语句,则在此直接返回,无情丢弃掉try
块中的返回点。】// 反例 private int x = 0; public int checkReturn() { try { // x 等于 1,此处不返回 return ++x; } finally { // 返回的结果是 2 return ++x; } }
RPC、二方包、或动态生成类
的相关方法时,捕捉异常必须使用Throwable
类来进行拦截。【说明:经过反射机制来调用方法,若是找不到方法,抛出NoSuchMethodException
。什么状况会抛出NoSuchMethodError
呢?二方包在类冲突时,仲裁机制可能致使引入非预期的版本使类的方法签名不匹配,或者在字节码修改框架(好比:ASM)动态建立或修改类时,修改了相应的方法签名。这些状况,即便代码编译期是正确的,但在代码运行期时,会抛出NoSuchMethodError
。】null
,不强制返回空集合,或者空对象等,必须添加注释充分说明什么状况下会返回null
值【说明:本手册明确防止NPE
是调用者的责任
。即便被调用方法返回空集合或者空对象,对调用者来讲,也并不是高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回null
的状况。】NPE
,是程序员的基本修养,注意NPE
产生的场景1)返回类型为基本数据类型,
return
包装数据类型的对象时,自动拆箱有可能产生 NPE。【反例:public int f() { return Integer 对象}
,若是为null
,自动解箱抛NPE
】
2)数据库的查询结果可能为null
3)集合里的元素即便isNotEmpty
,取出的数据元素也可能为null
。
4)远程调用返回对象时,一概要求进行空指针判断,防止NPE
5)对于Session
中获取的数据,建议进行NPE
检查,避免空指针。
6)级联调用obj.getA().getB().getC();
一连串调用,易产生NPE
。正例:使用JDK8的Optional
类来防止NPE问题。mysql
unchecked / checked
异常,避免直接抛出new RuntimeException()
,更不容许抛出Exception
或者Throwable
,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException
等。http/api
开放接口必须使用“错误码”;而应用内部推荐异常抛出;跨应用间RPC
调用优先考虑使用Result
方式,封装isSuccess()
方法、“错误码”、“错误简短信息”【说明:关于RPC
方法返回方式使用Result
方式的理由:1)使用抛异常返回方式,调用方若是没有捕获到就会产生运行时错误。2)若是不加栈信息,只是new自定义异常,加入本身的理解的error message
,对于调用端解决问题的帮助不会太多。若是加了栈信息,在频繁调用出错的状况下,数据序列化和传输的性能损耗也是问题。】Don't Repeat Yourself
),即DRY原则【说明:随意复制和粘贴代码,必然会致使代码的重复,在之后须要修改时,须要修改全部的副本,容易遗漏。必要时抽取共性方法,或者抽象公共类,甚至是组件化】Log4j、Logback
)中的API,而应依赖使用日志框架SLF4J
中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Test.class);
appName_logType_logName.log
。logType:日志类型,如stats/monitor/access
等;logName:日志描述。这种命名的好处:经过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找【说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于经过日志对系统进行及时监控。正例:force-web
应用中单独监控时区转换异常,如:force_web_timeZoneConvert.log
】String
字符串的拼接会使用StringBuilder
的append()
方式,有必定的性能损耗。使用占位符仅是替换动做,能够有效提高性能。正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
】trace/debug/info
级别的日志输出,必须进行日志级别的开关判断【说明:虽然在debug(参数)的方法体内第一行代码isDisabled(Level.DEBUG_INT)
为真时(Slf4j的常见实现Log4j
和Logback
),就直接return
,可是参数可能会进行字符串拼接运算。此外,若是debug(getName())
这种参数内有getName()
方法调用,无谓浪费方法调用的开销。】// 正例 // 若是判断为真,那么能够输出trace和debug级别的日志 if (logger.isDebugEnabled()) { logger.debug("Current ID is: {} and name is: {}", id, getName()); }
log4j.xml
中设置additivity=false
【正例:<logger name="com.taobao.dubbo.config" additivity="false">
】案发现场信息
和异常堆栈信息
。若是不处理,那么经过关键字throws
往上抛出【正例:logger.error(各种参数或者对象 toString() + "_" + e.getMessage(), e);
】debug
日志;有选择地输出info
日志;若是使用warn
来记录刚上线时的业务行为信息,必定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志【说明:大量地输出无效日志,不利于系统性能提高,也不利于快速定位错误点。记录日志时请思考:这些日志真的有人看吗?看到这条日志你能作什么?能不能给问题排查带来好处?
】warn
日志级别来记录用户输入参数错误的状况,避免用户投诉时,无所适从。如非必要,请不要在此场景打出error
级别,避免频繁报警【说明:注意日志输出的级别,error
级别只记录系统逻辑出错、异常或者重要的错误
信息】AIR
原则【说明:单元测试在线上运行时,感受像空气(AIR)同样并不存在,但在测试质量的保障上,倒是很是关键的。好的单元测试宏观上来讲,具备自动化、独立性、可重复执行的特色。A:Automatic
(自动化)I:Independent
(独立性)R:Repeatable
(可重复)】System.out
来进行人肉验证,必须使用assert
来验证method2
须要依赖method1
的执行,将执行结果做为method2
的输入】check in
时单元测试都会被执行。若是单测对外部环境(网络、服务、中间件等)有依赖,容易致使持续集成机制的不可用。正例:为了避免受外界环境影响,要求设计代码时就把SUT的依赖改为注入,在测试时用spring
这样的DI
框架注入一个本地(内存)实现或者Mock
实现】src/test/java
,不容许写在业务代码目录下【说明:源码编译时会跳过此目录,而单元测试框架默认是扫描此目录】DAO
层,Manager
层,可重用度高的Service
,都应该进行单元测试。】BCDE
原则,以保证被测试模块的交付质量【B:Border
,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等; C:Correct
,正确的输入,并获得预期的结果; D:Design
,与设计文档相结合,来编写单元测试; E:Error
,强制错误信息输入(如:非法数据、异常流程、业务容许外等),并获得预期的结果】权限控制校验
【说明:防止没有作水平权限校验就可随意访问、修改、删除别人的数据,好比查看他人的私信内容、修改他人的订单】脱敏
【说明:中国大陆我的手机号码显示为:137****0969,隐藏中间4位,防止隐私泄露】SQL 注
入,禁止字符串拼接SQL访问数据库page size
过大致使内存溢出;2.恶意order by
致使数据库慢查询;3.任意重定向;4.SQL注入;5.反序列化注入;6.正则输入源串拒绝服务ReDoS;说明:Java代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,可是若是攻击人员使用的是特殊构造的字符串来验证,有可能致使死循环的结果。】CSRF(Cross-site request forgery)
跨站请求伪造是一类常见编程漏洞。对于存在CSRF漏洞的应用/网站,攻击者能够事先构造好URL,只要受害者用户一访问,后台便在用户不知情的状况下对数据库中用户参数进行相应修改。】is_xxx
的方式命名,数据类型是unsigned tinyint
(1表示是,0表示否)【说明:任何字段若是为非负数,必须是unsigned。注意:POJO类中的任何布尔类型的变量,都不要加is
前缀,因此,须要在<resultMap>
设置从is_xxx
到Xxx
的映射关系。数据库表示是与否的值,使用tinyint
类型,坚持is_xxx
的命名方式是为了明确其取值含义与取值范围】正例:表达
逻辑删除
的字段名is_deleted
,1表示删除,0表示未删除linux
正例:
aliyun_admin
,rdc_config
,level3_name
反例:AliyunAdmin,rdcConfig,level_3_name程序员
DO
类名也是单数形式,符合表达习惯】desc、range、match、delayed
等,请参考MySQL
官方保留字pk_字段名
;惟一索引名为uk_字段名
;普通索引名则为idx_字段名
【说明:pk_
即primary key
;uk_
即unique key
;idx_
即index
的简称】decimal
,禁止使用float
和double
【说明:在存储的时候,float
和double
都存在精度损失的问题,极可能在比较值的时候,获得不正确的结果。若是存储的数据范围超过decimal
的范围,建议将数据拆成整数和小数并分开存储】char
定长字符串类型varchar
是可变长字符串,不预先分配存储空间,长度不要超过5000
,若是存储长度大于此值,定义字段类型为text
,独立出来一张表,用主键来对应,避免影响其它字段索引效率id, create_time, update_time
【说明:其中id
必为主键,类型为bigint unsigned
、单表时自增、步长为1。create_time, update_time
的类型均为datetime
类型。】alipay_task / force_project / trade_config
】varchar
超长字段,更不能是text
字段。3)不是惟一索引的字段】(正例
:商品类目名称使用频率高,字段长度短,名称基本一不变,可在相关联的表中冗余存储类目名称,避免关联查询)500万
行或者单表容量超过2GB
,才推荐进行分库分表
【说明:若是预计三年后的数据量根本达不到这个级别,请不要在建立表时就分库分表】对象 | 年龄区间 | 类型 | 字节 | 表示范围 |
---|---|---|---|---|
人 | 150岁以内 | tinyint unsigned | 1 | 无符号值:0到255 |
龟 | 数百岁 | smallint unsigned | 2 | 无符号值:0到65535 |
恐龙化石 | 数千万年 | int unsigned | 4 | 无符号值:0到约42.9亿 |
太阳 | 约50亿年 | bigint unsigned | 8 | 无符号值:0到约10的19次方 |
惟一特性
的字段,即便是多个字段的组合,也必须建成惟一索引【说明:不要觉得惟一索引影响了insert
速度,这个速度损耗能够忽略,但提升查找速度是明显的;另外,即便在应用层作了很是完善的校验控制,只要没有惟一索引,根据墨菲定律,必然有脏数据产生】join
。须要join
的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段须要有索引
【说明:即便双表join也要注意表索引、SQL性能】varchar
字段上创建索引时,必须指定索引长度,不必对全字段创建索引,根据实际文本区分度决定索引长度便可【说明:索引的长度与区分度是一对矛盾体,通常对字符串类型数据,长度为20的索引,区分度会高达90%以上,可使用count(distinct left(列名, 索引长度))/count(*)
的区分度来肯定】B-Tree
的最左前缀匹配
特性,若是左边的值未肯定,那么没法使用此索引】order by
的场景,请注意利用索引的有序性。order by
最后的字段是组合索引的一部分,而且放在索引组合顺序的最后,避免出现file_sort
的状况,影响查询性能正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引若是存在范围查询,那么索引有序性没法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 没法排序。web
正例:可以创建索引的种类分为主键索引、惟一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用
explain
的结果,extra
列会出现:using index
spring
offset
行,而是取offset+N
行,而后返回放弃前offset
行,返回N
行,那当offset
特别大的时候,效率就很是的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写】正例:先快速定位须要获取的id段,而后再关联:
SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
sql
range
级别,要求是ref
级别,若是能够是consts
最好【说明:1)consts
单表中最多只有一个匹配行(主键或者惟一索引),在优化阶段便可读取到数据。2)ref
指的是使用普通的索引(normal index)。3)range
对索引进行范围检索。】反例:
explain
表的结果,type=index,索引物理文件全扫描,速度很是慢,这个index
级别比较range
还低,与全表扫描是小巫见大巫。数据库
where c>? and d=?
那么即便c的区分度更高,也必须把d放在索引的最前列,即索引idx_d_c
】正例:若是
where a=? and b=?
,若是a列的几乎接近于惟一值,那么只须要单建idx_a
索引便可编程
1)宁滥勿缺。认为一个查询就须要建一个索引
2)宁缺勿滥。认为索引会消耗空间、严重拖慢记录的更新以及行的新增速度
3)抵制唯一索引。认为业务的唯一性一概须要在应用层经过“先查后插”方式解决json
count(列名)
或count(常量)
来替代count(*)
,count(*)
是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关【说明:count(*)
会统计值为NULL
的行,而count(列名)
不会统计此列为NULL值的行】count(distinct col)
计算该列除NULL以外的不重复行数,注意count(distinct col1, col2)
若是其中一列全为 NULL,那么即便另外一列有不一样的值,也返回为0count(col)
的返回结果为 0,但sum(col)
的返回结果为NULL,所以使用sum()
时需注意NPE
问题正例:使用以下方式来避免sum的NPE问题:
SELECT IFNULL(SUM(column), 0) FROM table;
ISNULL()
来判断是否为NULL
值【说明:NULL与任何值的直接比较都为NULL。 1)NULL<>NULL
的返回结果是NULL
,而不是false
。 2)NULL=NULL
的返回结果是NULL
,而不是true
。 3)NULL<>1
的返回结果是NULL
,而不是true
。】count
为0应直接返回,避免执行后面的分页语句student_id
是主键,那么成绩表中的student_id
则为外键。若是更新学生表中的student_id
,同时触发成绩表中的student_id
更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度】in
操做能避免则避免,若实在避免不了,须要仔细评估in后边的集合元素数量,控制在1000
个以内utf-8
编码,注意字符统计函数的区别。【说明:SELECT LENGTH("轻松工做");
返回为12;SELECT CHARACTER_LENGTH("轻松工做");
返回为4;若是须要存储表情,那么选择utf8mb4
来进行存储,注意它与utf-8
编码的区别。】TRUNCATE TABLE
比DELETE
速度快,且使用的系统和事务日志资源少,但TRUNCATE
无事务且不触发trigger
,有可能形成事故,故不建议在开发代码中使用此语句【说明:TRUNCATE TABLE
在功能上与不带WHERE
子句的DELETE
语句相同】*
做为查询的字段列表,须要哪些字段必须明确写明【说明:1)增长查询分析器解析成本。2)增减字段容易与resultMap
配置不一致。3)无用字段增长网络消耗,尤为是text
类型的字段】is
,而数据库字段必须加is_
,要求在resultMap
中进行字段与属性之间的映射。【说明:参见定义POJO类以及数据库字段定义规定,在<resultMap>
中增长映射,是必须的。在MyBatis Generator
生成的代码中,须要进行对应的修改】resultClass
当返回参数,即便全部类属性名与数据库字段一一对应,也须要定义;反过来,每个表也必然有一个POJO
类与之对应【说明:配置映射关系,使字段与DO
类解耦,方便维护。】sql.xml
配置参数使用:#{},#param#
不要使用${}
此种方式容易出现SQL 注入HashMap
与Hashtable
做为查询结果集的输出【说明:resultClass=”Hashtable”
,会置入字段名和属性值,可是值的类型不可控】gmt_modified
字段值为当前时间update table set c1=value1,c2=value2,c3=value3;
这是不对的。执行SQL时,不要更新无改动的字段,一是易出错;二是效率低;三是增长binlog
存储@Transactional
事务不要滥用。事务会影响数据库的QPS
,另外使用事务的地方须要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等<isEqual>
中的compareValue是与属性值对比的常量,通常是数字,表示相等时带上此条件;<isNotEmpty>
表示不为空且不为null时执行;<isNotNull>
表示不为null值时执行。在
DAO
层,产生的异常类型有不少,没法用细粒度的异常进行catch,使用catch(Exception e)
方式,并throw new DAOException(e)
,不须要打印日志,因
为日志在Manager/Service
层必定须要捕获并打印到日志文件中去,若是同台服务器再打日志,浪费性能和存储。在Service
层出现异常时,必须记录出错日志到磁盘,尽量带上参数信息,至关于保护案发现场。若是Manager
层与Service
同机部署,日志方式与DAO层处理一致,若是是单独部署,则采用与Service
一致的处理方式。Web 层毫不应该继续往上抛异常,由于已经处于顶层,若是意识到这个异常将致使页面没法正常渲染,那么就应该直接跳转到友好错误页面,加上用户容易理解的错误提示信息。开放接口层要将异常处理成错误码和错误信息方式返回。
DO
(Data Object):此对象与数据库表结构一一对应,经过DAO层向上传输数据源对象
DTO
(Data Transfer Object):数据传输对象,Service或Manager向外传输的对象
BO
(Business Object):业务对象,由Service层输出的封装业务逻辑的对象
AO
(Application Object):应用对象,在Web层与Service层之间抽象的复用对象模型,极为贴近展现层,复用度不高
VO
(View Object):显示层对象,一般是Web向模板渲染引擎层传输的对象
Query
:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用Map类来传输
1)
GroupID
格式:com.{公司/BU }.业务线 [.子业务线]
,最多4级。正例:com.taobao.jstorm
或com.alibaba.dubbo.register
2)ArtifactID
格式:产品线名-模块名
。语义不重复不遗漏,先到中央仓库去查证一下。正例:dubbo-client / fastjson-api / jstorm-tool
3)Version
:主版本号.次版本号.修订号。【1.主版本号:产品方向改变,或者大规模 API 不兼容,或者架构不兼容升级;2.次版本号:保持相对兼容性,增长主要功能特性,影响范围极小的 API 不兼容修改;3.修订号:保持彻底兼容性,修复 BUG、新增次要功能特性等】
说明:注意起始版本号必须为:1.0.0,而不是0.0.1,正式发布的类库必须先去中央仓库进行查证,使版本号有延续性,正式版本号不容许覆盖升级。如当前版本:1.3.3,那么下一个合理的版本号:1.3.4或1.4.0或2.0.0
<dependencies>
语句块中,全部版本仲裁放在<dependencyManagement>
语句块中【说明:<dependencyManagement>
里只是声明版本,并不实现引入,所以子项目须要显式的声明依赖,version
和scope
都读取自父pom。而<dependencies>
全部声明在主pom的<dependencies>
里的依赖都会自动引入,并默认被全部的子项目继承】1)精简可控原则。移除一切没必要要的API和依赖,只包含Service API、必要的领域模型对象、Utils类、常量、枚举等
2)稳定可追溯原则。每一个版本的变化应该被记录,二方库由谁维护,源码在哪里,都须要能方便查到
TCP
协议的time_wait
超时时间【操做系统默认240
秒后,才会关闭处于time_wait
状态的链接,在高并发访问下,服务器端会由于处于time_wait
的链接数太多,可能没法创建新的链接,因此须要在服务器上调小此等待值】正例:在linux服务器上请经过变动
/etc/sysctl.conf
文件去修改该缺省值(秒):net.ipv4.tcp_fin_timeout = 30
File Descriptor
,简写为 fd)【说明:主流操做系统的设计是将TCP/UDP
链接采用与文件同样的方式去管理,即一个链接对应于一个fd。主流的linux服务器默认所支持最大fd数量为1024
,当并发链接数很大时很容易由于fd不足而出现“open too many files”
错误,致使新的链接没法创建。建议将linux服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)】-XX:+HeapDumpOnOutOfMemoryError
参数,让JVM
碰到OOM
场景时输出dump
信息【说明:OOM的发生是有几率的,甚至相隔数月才出现一例,出错时的堆内信息对解决问题很是有帮助】以上内容即是我一边读,一边思考整理出来的。《Java开发手册》是实践开发中提炼出来的精华
,当中有不少小点都是值得拿出来仔细推敲,每每小的规约背后隐藏着大学问。或许你和我同样,有些地方并不熟悉或者暂时并不理解为何这样规约,我的以为这些困惑的点读一遍、思考一遍远远不够,应该收藏下来,有空的时候多读、多思考。若是开发中遇到了手册中相似的场景,那么收获会更大,理解会更深。最后,正如手册中提到的愿景,“码出高效,码出质量”,但愿你我都能码出一个新的高度。