Java开发最佳实践(二) ——《Java开发手册》之"异常处理、MySQL 数据库"

2、异常日志

(一) 异常处理

  • Java类库中定义的能够经过预检查方式规避的RuntimeException异常不该该经过catch的方式来处理,好比:NullPointerExceptionIndexOutOfBoundsException等等【说明:没法经过预检查的异常除外,好比,在解析字符串形式的数字时,可能存在数字格式错误,不得不经过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);
  • 全部日志文件至少保存15天,由于有些异常具有以“周”为频次发生的特色。网络运行状态、安全相关信息、系统监测、管理后台操做、用户敏感操做须要留存相关的网络日志很多于6个月
  • 应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:appName_logType_logName.log。logType:日志类型,如stats/monitor/access等;logName:日志描述。这种命名的好处:经过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找【说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于经过日志对系统进行及时监控。正例:force-web应用中单独监控时区转换异常,如:force_web_timeZoneConvert.log
  • 在日志输出时,字符串变量之间的拼接使用占位符的方式【说明:由于String字符串的拼接会使用StringBuilderappend()方式,有必定的性能损耗。使用占位符仅是替换动做,能够有效提高性能。正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);
  • 对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断【说明:虽然在debug(参数)的方法体内第一行代码isDisabled(Level.DEBUG_INT)为真时(Slf4j的常见实现Log4jLogback),就直接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级别只记录系统逻辑出错、异常或者重要的错误信息】
  • 尽可能用英文来描述日志错误信息,若是日志中的错误信息用英文描述不清楚的话使用中文描述便可,不然容易产生歧义【国际化团队或海外部署的服务器因为字符集问题,使用全英文来注释和描述日志错误信息。】

3、单元测试

  • 好的单元测试必须遵照AIR原则【说明:单元测试在线上运行时,感受像空气(AIR)同样并不存在,但在测试质量的保障上,倒是很是关键的。好的单元测试宏观上来讲,具备自动化、独立性、可重复执行的特色。A:Automatic(自动化)I:Independent(独立性)R:Repeatable(可重复)】
  • 单元测试应该是全自动执行的,而且非交互式的。测试用例一般是被按期执行的,执行过程必须彻底自动化才有意义。输出结果须要人工检查的测试不是一个好的单元测试。单元测试中不许使用System.out来进行人肉验证,必须使用assert来验证
  • 保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的前后次序【反例:method2须要依赖method1的执行,将执行结果做为method2的输入】
  • 单元测试是能够重复执行的,不能受到外界环境的影响。【说明:单元测试一般会被放到持续集成中,每次有代码check in时单元测试都会被执行。若是单测对外部环境(网络、服务、中间件等)有依赖,容易致使持续集成机制的不可用。正例:为了避免受外界环境影响,要求设计代码时就把SUT的依赖改为注入,在测试时用spring这样的DI框架注入一个本地(内存)实现或者Mock实现】
  • 对于单元测试,要保证测试粒度足够小,有助于精肯定位问题。单测粒度至可能是类级别,通常是方法级别【说明:只有测试粒度小才能在出错时尽快定位到出错位置。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域】
  • 核心业务、核心应用、核心模块的增量代码确保单元测试经过【说明:新增代码及时补充单元测试,若是新增代码影响了原有单元测试,请及时修正】
  • 单元测试代码必须写在以下工程目录:src/test/java,不容许写在业务代码目录下【说明:源码编译时会跳过此目录,而单元测试框架默认是扫描此目录】
  • 单元测试的基本目标:语句覆盖率达到70%;核心模块的语句覆盖率和分支覆盖率都要达到100%【说明:在工程规约的应用分层中提到的DAO层,Manager层,可重用度高的Service,都应该进行单元测试。】
  • 编写单元测试代码遵照BCDE原则,以保证被测试模块的交付质量【B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等; C:Correct,正确的输入,并获得预期的结果; D:Design,与设计文档相结合,来编写单元测试; E:Error,强制错误信息输入(如:非法数据、异常流程、业务容许外等),并获得预期的结果】
  • 对于数据库相关的查询,更新,删除等操做,不能假设数据库里的数据是存在的,或者直接操做数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据【反例:删除某一行数据的单元测试,在数据库中,先直接手动增长一行做为删除目标,可是这一行新增数据并不符合业务插入规则,致使测试结果异常】
  • 和数据库相关的单元测试,能够设定自动回滚机制,不给数据库形成脏数据。或者对单元测试产生的数据有明确的先后缀标识
  • 对于不可测的代码在适当的时机作必要的重构,使代码变得可测,避免为了达到测试要求而书写不规范测试代码
  • 在设计评审阶段,开发人员须要和测试人员一块儿肯定单元测试范围,单元测试最好覆盖全部测试用例
  • 单元测试做为一种质量保障手段,在项目提测前完成单元测试,不建议项目发布后补充单元测试用例
  • 为了更方便地进行单元测试,业务代码应避免如下状况【构造方法中作的事情过多;存在过多的全局变量和静态方法;存在过多的外部依赖;存在过多的条件语句;说明:多层条件语句建议使用卫语句、策略模式、状态模式等方式重构】
  • 不要对单元测试存在以下误解【那是测试同窗干的事情。1.本文是开发手册,凡是本文内容都是与开发同窗强相关的;2.单元测试代码是多余的。系统的总体功能与各单元部件的测试正常与否是强相关的;3.单元测试代码不须要维护。一年半载后,那么单元测试几乎处于废弃状态;4.单元测试与线上故障没有辩证关系。好的单元测试可以最大限度地规避线上故障】

4、安全规约

  • 隶属于用户我的的页面或者功能必须进行权限控制校验【说明:防止没有作水平权限校验就可随意访问、修改、删除别人的数据,好比查看他人的私信内容、修改他人的订单】
  • 用户敏感数据禁止直接展现,必须对展现数据进行脱敏【说明:中国大陆我的手机号码显示为:137****0969,隐藏中间4位,防止隐私泄露】
  • 用户输入的SQL、参数严格使用参数绑定或者 METADATA 字段值限定,防止SQL 注入,禁止字符串拼接SQL访问数据库
  • 用户请求传入的任何参数必须作有效性验证【说明:忽略参数校验可能致使:1.page size过大致使内存溢出;2.恶意order by致使数据库慢查询;3.任意重定向;4.SQL注入;5.反序列化注入;6.正则输入源串拒绝服务ReDoS;说明:Java代码用正则来验证客户端的输入,有些正则写法验证普通用户输入没有问题,可是若是攻击人员使用的是特殊构造的字符串来验证,有可能致使死循环的结果。】
  • 禁止向HTML页面输出未经安全过滤或未正确转义的用户数据
  • 表单、AJAX提交必须执行CSRF安全验证【说明:CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在CSRF漏洞的应用/网站,攻击者能够事先构造好URL,只要受害者用户一访问,后台便在用户不知情的状况下对数据库中用户参数进行相应修改。】
  • 在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放的 机制,如数量限制、疲劳度控制、验证码校验,避免被滥刷而致使资损
  • 发贴、评论、发送即时消息等用户生成内容的场景必须实现防刷、文本内容违禁词过滤等风控策略

5、MySQL数据库

(一) 建表规约

  • 表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint(1表示是,0表示否)【说明:任何字段若是为非负数,必须是unsigned。注意:POJO类中的任何布尔类型的变量,都不要加is前缀,因此,须要在<resultMap>设置从is_xxxXxx的映射关系。数据库表示是与否的值,使用tinyint类型,坚持is_xxx的命名方式是为了明确其取值含义与取值范围】

正例:表达逻辑删除的字段名is_deleted,1表示删除,0表示未删除linux

  • 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只出现数字。数据库字段名的修改代价很大,由于没法进行预发布,因此字段名称须要慎重考虑【说明:MySQL在 Windows下不区分大小写,但在Linux下默认是区分大小写。所以,数据库名、表名、字段名,都不容许出现任何大写字母,避免节外生枝】

正例:aliyun_adminrdc_configlevel3_name
反例:AliyunAdmin,rdcConfig,level_3_name程序员

  • 表名不使用复数名词【表名应该仅仅表示表里面的实体内容,不该该表示实体数量,对应于DO类名也是单数形式,符合表达习惯】
  • 禁用保留字,如desc、range、match、delayed等,请参考MySQL官方保留字
  • 主键索引名为pk_字段名;惟一索引名为uk_字段名;普通索引名则为idx_字段名【说明:pk_primary keyuk_unique keyidx_index的简称】
  • 小数类型为decimal,禁止使用floatdouble【说明:在存储的时候,floatdouble都存在精度损失的问题,极可能在比较值的时候,获得不正确的结果。若是存储的数据范围超过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
  • 库名与应用名称尽可能一致
  • 若是修改字段含义或对字段表示的状态追加时,须要及时更新字段注释
  • 字段容许适当冗余,以提升查询性能,但必须考虑数据一致【冗余字段应遵循:1)不是频繁修改的字段。2)不是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

  • 利用覆盖索引来进行查询操做,避免回表【说明:若是一本书须要知道第11章是什么标题,会翻开第11章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的做用。】

正例:可以创建索引的种类分为主键索引、惟一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用explain的结果,extra列会出现:using indexspring

  • 利用延迟关联或者子查询优化超多分页场景【说明:MySQL并非跳过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.idsql

  • 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

(三) 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问题

正例:使用以下方式来避免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更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度】
  • 禁止使用存储过程,存储过程难以调试和扩展,更没有移植性
  • 数据订正(特别是删除、修改记录操做)时,要先select,避免出现误删除,确认无误才能执行更新语句
  • in操做能避免则避免,若实在避免不了,须要仔细评估in后边的集合元素数量,控制在1000个以内
  • 若是有国际化须要,全部的字符存储与表示,均以utf-8编码,注意字符统计函数的区别。【说明:SELECT LENGTH("轻松工做");返回为12;SELECT CHARACTER_LENGTH("轻松工做"); 返回为4;若是须要存储表情,那么选择utf8mb4来进行存储,注意它与utf-8编码的区别。】
  • TRUNCATE TABLEDELETE速度快,且使用的系统和事务日志资源少,但TRUNCATE无事务且不触发trigger,有可能形成事故,故不建议在开发代码中使用此语句【说明:TRUNCATE TABLE在功能上与不带WHERE子句的DELETE语句相同】

(四) ORM映射

  • 在表查询中,一概不要使用*做为查询的字段列表,须要哪些字段必须明确写明【说明:1)增长查询分析器解析成本。2)增减字段容易与resultMap配置不一致。3)无用字段增长网络消耗,尤为是text类型的字段】
  • POJO类的布尔属性不能加is,而数据库字段必须加is_,要求在resultMap中进行字段与属性之间的映射。【说明:参见定义POJO类以及数据库字段定义规定,在<resultMap>中增长映射,是必须的。在MyBatis Generator生成的代码中,须要进行对应的修改】
  • 不要用resultClass当返回参数,即便全部类属性名与数据库字段一一对应,也须要定义;反过来,每个表也必然有一个POJO类与之对应【说明:配置映射关系,使字段与DO类解耦,方便维护。】
  • sql.xml配置参数使用:#{},#param#不要使用${}此种方式容易出现SQL 注入
  • 不容许直接拿HashMapHashtable做为查询结果集的输出【说明:resultClass=”Hashtable”,会置入字段名和属性值,可是值的类型不可控】
  • 更新数据表记录时,必须同时更新记录对应的gmt_modified字段值为当前时间
  • 不要写一个大而全的数据更新接口。传入为POJO类,无论是否是本身的目标更新字段,都进行update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行SQL时,不要更新无改动的字段,一是易出错;二是效率低;三是增长binlog存储
  • @Transactional事务不要滥用。事务会影响数据库的QPS,另外使用事务的地方须要考虑各方面的回滚方案,包括缓存回滚、搜索引擎回滚、消息补偿、统计修正等
  • <isEqual>中的compareValue是与属性值对比的常量,通常是数字,表示相等时带上此条件;<isNotEmpty>表示不为空且不为null时执行;<isNotNull>表示不为null值时执行。

6、工程结构

  • 分层异常处理规约

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.jstormcom.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

  • 底层基础技术框架、核心数据管理平台、或近硬件端系统谨慎引入第三方实现
  • 全部pom文件中的依赖声明放在<dependencies>语句块中,全部版本仲裁放在<dependencyManagement>语句块中【说明:<dependencyManagement>里只是声明版本,并不实现引入,所以子项目须要显式的声明依赖,versionscope都读取自父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服务器所支持的最大句柄数调高数倍(与服务器的内存数量相关)】
  • 给JVM环境参数设置-XX:+HeapDumpOnOutOfMemoryError参数,让JVM碰到
    OOM场景时输出dump信息【说明:OOM的发生是有几率的,甚至相隔数月才出现一例,出错时的堆内信息对解决问题很是有帮助】
  • 在线上生产环境,JVM的Xms和Xmx设置同样大小的内存容量,避免在GC后调整堆大小带来的压力

7、设计规约

  • 存储方案和数据结构须要认真地进行设计和评审
  • 需求分析与系统设计在考虑主干功能的同时,须要充分评估异常流程与业务边界
  • 谨慎使用继承的方式来进行扩展,优先使用聚合/组合的方式来实现
  • 系统设计主要目的是明确需求、理顺逻辑、后期维护,次要目的用于指导编码。
  • 设计的本质就是识别和表达系统难点,找到系统的变化点,并隔离变化点
  • 系统架构设计的目的【1.肯定系统边界。肯定系统在技术层面上的作与不作;2.肯定系统内模块之间的关系。肯定模块之间的依赖关系及模块的宏观输入与输出; 3.肯定指导后续设计与演化的原则。使后续的子系统或模块设计在规定的框架内继续演化;4.肯定非功能性需求。非功能性需求是指安全性、可用性、可扩展性等】

我的小结

以上内容即是我一边读,一边思考整理出来的。《Java开发手册》是实践开发中提炼出来的精华,当中有不少小点都是值得拿出来仔细推敲,每每小的规约背后隐藏着大学问。或许你和我同样,有些地方并不熟悉或者暂时并不理解为何这样规约,我的以为这些困惑的点读一遍、思考一遍远远不够,应该收藏下来,有空的时候多读、多思考。若是开发中遇到了手册中相似的场景,那么收获会更大,理解会更深。最后,正如手册中提到的愿景,“码出高效,码出质量”,但愿你我都能码出一个新的高度。