【技术风险】阿里技术风险节选

Posted on 2023-04-19 14:27  Charlie_ODD  阅读(50)  评论(0编辑  收藏  举报

技术风险假设:

参考:

PPT:https://developer.alipay.com/college/course/1558/detail/3712/1
https://yuque.antfin-inc.com/oversea/jsfx/gzfl3z


● 并发,多个地方修改——考虑并发修改下加锁(分布式锁、数据库悲观锁)
● 网络抖动,先后两次相同请求——考虑幂等
● 接口是否允许部分成功,部分成功是否幂等,未成功部分能否正确重试
● 多次请求
  ○ 完全一样的请求——是否幂等
● 下游超时处理
● 下游响应处理(错误、异常处理),是否有补偿机制
● 重试机制(端上、异步任务)
● 重入机制,任何一步挂了下次进来还能成功完成调用
● 数据库更新是否成功,是否符合预期
● 克隆:深/浅
● 序列化/反序列化异常
● NPE拦截
● 关键交互字段长度超长
● 唯一id的保证(先落流水靠DB唯一键保证)
● 事务性保证
● 金额的处理
● 时间/时区的处理
● 露出C端信息的处理(错误码映射、描述、文案长度、多语言)
● 关键分支的日志打印
● try-catch的异常处理
异常处理
1.NullPointerException 无需catch,判断处理即可
2.不要对大段代码try-catch,catch时需要区分异常类型
3.捕获异常后需要处理,不能什么都不干
  不处理的异常请抛出去
  最外层的业务使用者,必须处理异常
4.try中有事务代码的时候,catch异常后需要回滚
5.finally需要注意对资源的释放,							不能在 finally 块中使用 return
● 共享资源的线程安全问题
● 降级开关、强弱依赖分析、是否可降级
● 兼容性问题(发布过程及正式环境)
  ○ 新代码老数据
  ○ 新代码老应用
● 多表更新逻辑:先副后主,加事务
● 业务强校验,判断if还是assert
● 数据库设计长度、类型、领域语义、关联关系、唯一键、索引、sql性能(是否有慢sql)
● 错误码的使用是否正确:SYSTEM_ERROR可重试
● 首次同步,失败后异步充实能力
● 是否需要开关
● 环境隔离性
● 渠道隔离性
● 参数配置映射一致性,存在错配的风险
● 对于C端的接口,需要评估访问量级



基础编码
1. 【强制】POJO类中的任何布尔类型的变量,都不要加is前缀,否则部分框架解析会引起序列化错误。
说明:在本文MySQL规约中的建表约定第一条,表达是与否的变量采用is_xxx的命名方式,所以,需要在<resultMap>设置从is_xxx到xxx的映射关系。
反例:定义为Boolean isDeleted 的属性,它的方法也是isDeleted(),框架在反向解析的时候,“误以为”对应的属性名称是deleted,导致属性获取不到,进而抛出异常。
2. 【强制】避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可理解性降低。
说明:子类、父类成员变量名相同,即使是public也是能够通过编译,而局部变量在同一方法内的不同代码块中同名也是合法的,但是要避免使用。对于非setter/getter的参数名称也要避免与成员变量名称相同。
3. 【推荐】如果模块、接口、类、方法使用了设计模式,在命名时体现出具体模式。
说明:将设计模式体现在名字中,有利于阅读者快速理解架构设计思想。
正例:public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
4. 【强制】在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数字混淆,造成误解。
说明:Long a = 2l; 写的是数字的21,还是Long型的2?
5. 【强制】注释的双斜线与注释内容之间有且仅有一个空格。
6. 【推荐】单个方法的总行数不超过80行。
7. 【推荐】不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。
8. 【强制】避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
9. 【强制】Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。
正例:"test".equals(object);
反例:object.equals("test");
说明:推荐使用JDK7引入的工具类java.util.Objects#equals(Object a, Object b)
10. 【强制】所有整型包装类对象之间值的比较,全部使用equals方法比较。说明:对于Integer var=某个数字,但在-128至127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象。虽然这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
反例:Integer的==比较引发的血案
11. 【强制】在合适使用float或double的场景中,无论基本数据类型还是包装数据类型,都使用双精度浮点数即double或Double。
说明:一切为了精度更加高一些,一刀切规定不使用float或Float单精度浮点数。另外定义float类型还不得不在数值后边加f。
12. 【强制】任何货币金额,均以最小货币单位且整型类型来进行存储。
13. 【强制】定义数据对象DO类时,属性类型要与数据库字段类型相匹配。
14. 【强制】关于基本数据类型与包装数据类型的使用标准如下:
1) 所有的POJO类属性必须使用包装数据类型。
2) RPC方法的返回值和参数必须使用包装数据类型。
3) 所有的局部变量推荐使用基本数据类型。
说明:POJO类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。
正例:数据库的查询结果可能是null,因为自动拆箱,用基本数据类型接收有NPE风险。
反例:某业务的交易报表上显示成交总额涨跌情况,即正负x%,x为基本数据类型,调用的HSF服务,调用不成功时,返回的是默认值,页面显示:0%,这是不合理的,应该显示成中划线-。所以包装数据类型的null值,能够表示额外的信息,如:远程调用失败,异常退出。
15. 【强制】序列化类新增属性时,请不要修改serialVersionUID字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID值。
说明:注意serialVersionUID不一致会抛出序列化运行时异常。
16. 【强制】POJO类必须写toString方法。使用工具类source> generate toString时,如果继承了另一个POJO类,注意在前面加一下super.toString。
说明:在方法执行抛出异常时,可以直接调用POJO的toString()方法打印其属性值,便于排查问题。
17. 【强制】禁止在POJO类中,同时存在对应属性xxx的isXxx()和getXxx()方法。
说明:框架在调用属性xxx的提取方法时,并不能确定哪个方法一定是被优先调用到,神坑之一。
反例:珍爱生命,远离有毒getter
18. 【推荐】setter方法中,参数名称与类成员变量名称一致,this.成员名=参数名。在getter/setter方法中,尽量不要增加业务逻辑,增加排查问题难度。
19. 【推荐】循环体内,字符串的联接方式,使用StringBuilder的append方法进行扩展。说明:反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。
String str = "start"; 
for (int i = 0; i < 100; i++) {
    str = str + "hello"; 
} 

20. 【推荐】final可以声明类、成员变量、方法、以及本地变量,下列情况使用final关键字:
1) 不允许被继承的类,如:String类。
2) 不允许修改引用的域对象,如:POJO类的域变量。
3) 不允许被覆写的方法,如:POJO类的setter方法。
4) 不允许运行过程中重新赋值的局部变量。
5) 避免上下文重复使用一个变量,使用final描述可以强制重新定义一个变量,方便更好地进行重构。
21. 【推荐】慎用Object的clone方法来拷贝对象。
说明:对象的clone方法默认是浅拷贝,若想实现深拷贝需要覆写clone方法实现域对象的深度遍历式拷贝。
22. 【推荐】类成员与方法访问控制从严:
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的成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,无限制的到处跑,那么你会担心的。
23. 【强制】日期格式化时,传入pattern中表示年份统一使用小写的y。
说明:日期格式化时,yyyy表示当天所在的年,而大写的YYYY代表是week in which year(JDK7之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的YYYY就是下一年。
正例:表示日期和时间的格式如下所示:new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");反例:某业务因使用YYYY/MM/dd进行日期格式化,2017/12/31执行结果为2018/12/31,导致P4P广告在2017年12月31日当日实时消耗跌0,造成P2故障,参考:记一次日期格式化引起的P2故障
24. 【强制】在日期格式中分清楚大写的M和小写的m,大写的H和小写的h分别指代的意义。
说明:日期格式中的这两对字母表意如下:
  ○ 表示月份是大写的M;
  ○ 表示分钟则是小写的m;
  ○ 24小时制的是大写的H;
  ○ 12小时制的则是小写的h。
25. 【强制】关于hashCode和equals的处理,遵循如下规则:
1) 只要覆写equals,就必须覆写hashCode。
2) 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须覆写这两个方法。
3) 如果自定义对象作为Map的键,那么必须覆写hashCode和equals。
说明:参考hashCode 与 equals的辩证关系。String正因为覆写了hashCode和equals方法,所以我们可以非常愉快地使用String对象作为key来使用。
26. 【强制】在使用java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要注意当value为null时会抛NPE异常。
说明:在java.util.HashMap的merge方法里会进行如下的判断:
if (value == null || remappingFunction == null) throw new NullPointerException();
List<Pair<String, Double>> pairArrayList = new ArrayList<>(2);
pairArrayList.add(new Pair<>("version1", 4.22));
pairArrayList.add(new Pair<>("version2", null));
Map<String, Double> map = pairArrayList.stream().collect(
// 抛出NullPointerException异常
Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
27. 【强制】在subList场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生ConcurrentModificationException 异常。
说明: 抽查表明,九成的开发同学对此知识点都有错误的认知。
28. 【强制】在使用Collection接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断。
说明:在ArrayList#addAll方法的第一行代码即Object[] a = c.toArray(); 其中c为输入集合参数,如果为null,则直接抛出异常。
29. 【强制】不要在foreach循环里对原Collection进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator迭代器对象加锁。
30. 【推荐】高度注意Map类集合K/V能不能存储null值的情况,如下表格:反例:前期抽样表明近八成的同学认为ConcurrentHashMap是可以置入null值。在“美杜莎”的批量翻译场景中,子线程分发时,出现置入null值的情况,但主线程没有捕获到此异常,导致排查困难。
集合类	Key	Value	Super	说明
Hashtable	不允许为null	不允许为null	Dictionary	线程安全
ConcurrentHashMap	不允许为null	不允许为null	AbstractMap	锁分段技术(JDK8:CAS)
TreeMap	不允许为null	允许为null	AbstractMap	线程不安全
HashMap	允许为null	允许为null	AbstractMap	线程不安全
31. 【参考】合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。
说明:有序性是指遍历的结果是按某种比较规则依次排列的。稳定性指集合每次遍历的元素次序是一定的。如:ArrayList是order/unsort;HashMap是unorder/unsort;TreeSet是order/sort。
32. 【参考】利用Set元素唯一的特性,可以快速对另一个集合进行去重操作,避免使用List的contains()进行遍历去重或者判断包含操作。
33. 【强制】在一个switch块内,每个case要么通过continue/break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。
说明:注意break是退出switch语句块,而return是退出方法体,如ET运营平台问题排查故障所示。
34. 【强制】当switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断。
35. 【强制】在高并发场景中,避免使用“等于”判断作为中断或退出的条件。
说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。
反例:某营销活动发奖,判断剩余奖品数量等于0时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,活动无法终止,产生资损。
36. 【推荐】表达异常分支时,少用if-else方式,这种方式可以改写成:
if (condition) {     ...     return obj; } // 接着写else的业务逻辑代码;
37. 【推荐】除常用方法(如getXxx/isXxx)等外,不要在条件判断中执行复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量,以提高可读性。
说明:很多 if 语句内的逻辑表达式相当复杂,与、或、取反混合运算,甚至各种方法纵深调用,理解成本非常高。如果赋值一个非常好理解的布尔变量名字,则是件令人爽心悦目的事情。
正例:
// 伪代码如下 final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) {     ... }  
38. 【推荐】接口入参保护,这种场景常见的是用做批量操作的接口。
反例:某年支付宝发生的一个严重故障,就是一个用户批量查询的接口,API文档上有说最多查多少个,但接口实现上没做任何保护,导致调用方传了一个10万的用户id数组过来后,查询信息后,内存爆了。
39. 【参考】下列情形中,需要进行参数校验::
1) 调用频次低的方法。
2) 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。
3) 需要极高稳定性和可用性的方法。
4) 对外提供的开放接口,不管是HSF/API/HTTP接口。
5) 敏感权限入口。
40. 【参考】下列情形中,不需要进行参数校验:
1) 极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查。
2) 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般DAO层与Service层都在同一个应用中,部署在同一台服务器中,所以DAO的参数校验,可以省略。
3) 被声明成private只会被自己代码所调用的方法。如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。
41. 【强制】在翻页场景中,用户输入的参数小于1,则前端返回第一页参数给后端;后端发现用户输入的参数大于最后一页,直接返回最后一页。
42. 【推荐】前后端的时间格式统一为"yyyy-MM-dd HH:mm:ss",统一为GMT。
43. 【强制】避免用Apache Beanutils进行属性的copy。
说明:Apache BeanUtils性能较差,可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier,注意均是浅拷贝。
反例:性能提升300%:Apache的BeanUtils的坑
44. 【强制】捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它,请将该异常抛给它的调用者。最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
反例:会员角标问题排查
45. 【强制】事务场景中,抛出异常被catch后,如果需要回滚,一定要手动回滚事务。
46. 【推荐】方法的返回值可以为null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回null值。
说明:本规约明确NPE是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败,运行时异常等场景返回null的情况。
47. 【推荐】防止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。
反例:“一拍档客户”的返回值从空对象变成了null,导致线上故障,NPE无小事。
48. 【强制】日志文件推荐至少保存15天,因为有些异常具备以“周”为频次发生的特点。对于当天日志,以“应用名.log”来保存,保存在/home/admin/应用名/logs/目录下,过往日志格式为: {logname}.log.{保存日期},日期格式:yyyy-MM-dd
说明:以mppserver应用为例,日志保存在/home/admin/mppserver/logs/mppserver.log,历史日志名称为mppserver.log.2016-08-01
49. 【推荐】有时间精度要求的业务,可以使用datetime(6); 对精度没要求的,设置为datetime即可。
说明:约定采用datetime(6),精确到微秒。
50. 【推荐】对前台业务类型的SQL语句,避免使用sum,avg,distinct,group by 等聚合函数。
说明:前台业务类型尽量采用OLTP SQL,需要“小而美的SQL”提供更优雅的用户体验。
51. 【推荐】避免where 条件语句中的字段使用计算函数。
说明:OB优化器表达式规范化做得不是特别完善,有些场景无法走索引,where id*2>4 这样会使该字段上的索引失效。
52. 【强制】[DRM]单个配置项只允许推送全量更新,不允许推送增量更新。DRM只能保证同一个配置项的最后一次推送一定会送达,不保证中间变更一定送达。应用重启后也只能获取最后一次推送的值。
53. 【参考】[DRM]适用于读多写少的配置的管理。单个配置项的变更频率不宜过高,如果在日常情况下推送频率高于1次/10分钟,说明应用场景不正确。
54. 【强制】用户请求传入的任何参数必须做有效性验证。
说明:忽略参数校验可能导致:
  ○ page size过大导致内存溢出
  ○ 恶意order by导致数据库慢查询
  ○ 缓存击穿
  ○ SSRF
  ○ 任意重定向
  ○ SQL注入
  ○ Shell注入
  ○ 反序列化注入
  ○ 正则输入源串拒绝服务ReDOS,参考: ReDoS攻击简单分析
55. 【强制】文件上传功能,对于文件大小、类型严格控制。
说明:攻击者可以利用上传漏洞,上传恶意文件到服务器,并且远程执行,达到控制网站服务器的目的。
56. 【强制】表达是与否概念的字段,必须使用is_xxx的方式命名,数据类型是unsigned tinyint(1表示是,0表示否),此规则同样适用于odps建表。
说明:任何字段如果为非负数,必须是unsigned。
注意:POJO类中的任何布尔类型的变量,都不要加is前缀,所以,需要在<resultMap>设置从is_xxx到xxx的映射关系。数据库表示是与否的值,使用tinyint类型,坚持is_xxx的命名方式是为了明确其取值含义与取值范围。
正例:表达逻辑删除的字段名is_deleted,1表示删除,0表示未删除。
57. 【强制】唯一索引名为uk_字段名;普通索引名则为idx_字段名。
说明:uk_ 即 unique key;idx_ 即index的简称。
58. 【强制】小数类型为decimal,禁止使用float和double。
说明:在存储的时候,float 和 double 都存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数并分开存储。
59. 【强制】varchar是可变长字符串,不预先分配存储空间,长度不要超过5000,如果存储长度大于此值,定义字段类型为TEXT,独立出来一张表,用主键来对应,避免影响其它字段索引效率。
60. 【推荐】单表行数超过500万行或者单表容量超过2GB,才推荐进行分库分表。
说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
反例:某业务三年总数据量才2万行,却分成1024张表,问:你为什么这么设计?答:分1024张表,不是标配吗?
61. 【强制】业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验和控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
62. 【强制】超过三个表禁止join。需要join的字段,数据类型保持绝对一致;多表关联查询时,保证被关联的字段需要有索引。
说明:即使双表join也要注意表索引、SQL性能。
63. 【推荐】利用覆盖索引来进行查询操作,来避免回表操作。
说明:如果一本书需要知道第11章是什么标题,会翻开第11章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。
正例:IDB能够建立索引的种类分为【主键索引、唯一索引、普通索引】,而覆盖索引是一种查询的一种效果,用explain的结果,extra列会出现:using index.
64. 【推荐】利用延迟关联或者子查询优化超多分页场景。
说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。
正例:先快速定位需要获取的id段,然后再关联:
SELECT t1.* FROM 表1 as t1, (select id from 表1 where 条件 LIMIT 100000,20 ) as t2 where t1.id=t2.id
反例:“服务市场”某交易分页超过1000页,用户点击最后一页时,数据库基本处于半瘫痪状态。
65. 【推荐】对使用的sql进行explain的大致评估,有利于提早发现慢SQL问题
66. 【推荐】SQL性能优化的目标:至少要达到 range 级别,要求是ref级别,如果可以是const最好。
说明:
1)const 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2)ref 指的是使用普通的索引。(normal index)
3)range 对索引进行范围检索。
反例:explain表的结果,type=index,索引物理文件全扫描,速度非常慢,这个index级别比较range还低,与全表扫描是小巫见大巫。
67. 【推荐】建组合索引的时候,区分度最高的在最左边(最左匹配原则)。
正例:如果where a=? and b=? ,a列的几乎接近于唯一值,那么只需要单建idx_a索引即可。
68. 【强制】不要使用count(列名)或count(常量)来替代count(*),count(*)就是SQL92定义的标准统计行数的语法,跟数据库无关,跟NULL和非NULL无关。
说明:count(*)会统计值为NULL的行,而count(列名)不会统计此列为NULL值的行。
69. 【强制】count(distinct col) 计算该列除NULL之外的不重复数量。注意 count(distinct col1, col2) 如果其中一列全为NULL,那么即使另一列有不同的值,也返回为0。
70. 【强制】对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定。
说明:对多表进行查询记录、更新记录、删除记录时,如果对操作列没有限定表的别名(或表名),并且操作列在多个表中存在时,就会抛异常。
正例:select t1.name from table_first as t1 , table_second as t2 where t1.id=t2.id;
反例:在飞猪某业务中,由于多表关联查询语句没有加表的别名(或表名)的限制,正常运行两年后,最近在某个表中增加一个同名字段,在预发布环境做数据库变更后,线上查询语句出现出1052异常:Column 'name' in field list is ambiguous,导致票务交易下跌。
71. 【强制】在代码中写分页查询逻辑时,若count为0应直接返回,避免执行后面的分页语句。
72. 【推荐】in操作能避免则避免,若实在避免不了,需要仔细评估in后边的集合元素数量,控制在1000个之内。
73. 【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。
说明:1)增加查询分析器解析成本。2)增减字段容易与resultMap配置不一致。3)多余字段增加网络消耗,尤其是 text 类型的字段。
反例:B类电商中台交易下跌的故障案例
74. 【强制】POJO类的布尔属性不能加is,而数据库字段必须加is_,要求在resultMap中进行字段与属性之间的映射。
说明:参见定义POJO类以及数据库字段定义规定,在sql.xml增加映射,是必须的。
75. 【强制】不要用resultClass当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个与之对应。
说明:配置映射关系,使字段与DO类解耦,方便维护。
76. 【强制】更新数据表记录时,必须同时更新记录对应的gmt_modified字段值为当前时间。
77. 2. 【参考】(分层异常处理规约)在DAO层,产生的异常类型有很多,无法用细粒度异常进行catch,使用catch(Exception e)方式,并throw new DAOException(e),不需要打印日志,因为日志在Manager/Service层一定需要捕获并打到日志文件中去,如果同台服务器再打日志,浪费性能和存储。
●  在Service层出现异常时,必须记录日志信息到磁盘,尽可能带上参数信息,相当于保护案发现场。
●  Manager层与Service同机部署,日志方式与DAO层处理一致,如果是单独部署,则采用与Service一致的处理方式。
●  Web层绝不应该继续往上抛异常,因为已经处于顶层,无继续处理异常的方式,如果意识到这个异常将导致页面无法正常渲染,那么就应该直接跳转到友好错误页面,尽量加上友好的错误提示信息。
● 开放接口层要将异常处理成错误码和错误信息方式返回。
78. 【参考】分层领域模型规约:
● DO(Data Object):与数据库表结构一一对应,通过DAO层向上传输数据源对象。
● DTO(Data Transfer Object):数据传输对象,Service或Manager向外传输的对象。
● BO(Business Object):业务对象。可以由Service层输出的封装业务逻辑的对象。
● Query:数据查询对象,各层接收上层的查询请求。额外规定:【强制】超过2个参数的查询封装,禁止使用Map类来传输。
● VO(View Object):显示层对象,通常是Web向模板渲染引擎层传输的对象。
79. 缓存:
1. 【强制】任何操作,都是先保存数据库成功后,再进行缓存的新增、更新、清除操作。
80. 序列化:
1. 【强制】二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的POJO对象。
说明:由于升级原因,导致双方的枚举类不尽相同,在接口解析,类反序列化时出现异常。
2. 【强制】[SOFA RPC] Hessian 序列化,子类不能包含与父类相同名称的域变量。

代码CR指南

“串”引用

● 金额计算:addTo方法等要注意
● 对于非基础类型或者非字符串,直接从入参A中get出来set到新的对象B中,存在共用引用的问题,存在引用“串掉”的问题(即:A、B对象的属性将会相互影响)建议:在给新对象设置属性时,不能直接其他对象中取出复杂对象属性进行设置,应该根据对应属性值重新构造一个新“引用”,切断两个对象直接的“引用”关联
● 浅clone很危险
● 全局变量要注意线程安全

交互处理

● 处理成功(业务成功)、受理成功(响应成功)要区分
● 对外围系统返回的结果,有至少四种情况的判断(成功,失败,重试,未知异常)
● 单位等约定要清晰
● 业务失败必须现实回滚,不仅仅依靠事物模版的回滚能力,要从业务上实现可回滚

并发与幂等控制(业务正确性)

● 静态 Util 或单例必须是线程安全的
● 并发处理必须遵循:一锁,二判,三更新的规范
● 缓存刷新与业务处理并发时,是否影响业务正常处理
● 给出以什么单号做幂等性控制,确定调用方是否可以以此单号做幂等性控制。
● 给出接收到重复调用后,返回的错误码,确定调用方没有把此错误码当做失败处理。
● 给出幂等控制的底层机制,并确定底层机制确实存在,且抛出的异常被正确处理
● 对于分布式事务,需要考虑事务悬挂的各种场景(TX_ID相同、不同,幂等控制条件相同、不同相关场景的组合)
● 调用的外围系统、机构或商户的接口是否具备幂等性控制能力,并且自身已经处理了幂等的场景

稳定性

● 线程上下文变量的设置与清除必须配对(必须在最初使用之前相应的finally中进行清除)
● 对于线程,线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
● 缓存刷新失败是否影响业务逻辑
● 不建议在业务处理过程中刷新缓存
● 是否存在堆外内存溢出(NIO,JNDI使用时尤为注意)
● 对于文件、流的 IO 操作,必须通过 finally 关闭

参数设置

● 超时(客户端、服务端)
● 分渠道维度的参数映射

DB/存储操作

● 数据锁(死锁): 为记录加锁时,需要保持一致的加锁顺序,否则可能会造成死锁
● 不建议循环操作DB
● 数据库等存储热点
● SQL语句查询条件中是否包含索引字段(除主键外的)
● 对于业务逻辑上要求数据完整性的数据(例如同时操作多个表,对同一个表反复迚行操作等),必须采用事务的方式进行处理

批量处理

● 批量处理注意控制处理频率,同时需要考虑多台机器并发处理同一批记录的问题,避免集群处理并发量造成对外围系统冲击
● 批量处理逻辑,必须增加数量限制
● 并发处理批量更新流水时,是否在SQL的where条件中增加了原有流水的状态,且检查了update方法返回的记录条数是否满足预期
● 并发锁是否合理,在吞吐量范围内是否会造成请求积压

兼容性

● 是否已考虑了发布过程中新数据老服务、及生产环境老数据新服务相关场景的兼容处理
异常考虑
● 最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容
● 禁止使用double、float可能存在精度问题的相关类型
● 对于日志的打印,任何情况下都不允许日志错误导致业务失败