前言

聊一聊之前接手的一个核心老系统,该系统主要负责内容的生产和加工,内容包括:图文、视频、直播等,加工生产主要指内容的发布、修改、特征计算和处理。内容在经过加工处理后送给下游进行个性化分发。

在这个系统中见识到了许许多多令人震惊的神操作,这里面一部分是有历史原因还有相当一部分是因为不好的开发习惯以及项目缺乏维护而引起。

代码相关

1. 调用其他服务接口不判断响应结果

神操作: 在调用其他服务接口得到response后不判断结果是否符合预期径直执行后续的逻辑或者将response直接返回给上层调用方

通常我们会跟对方约定好接口返回结构及状态码,调用方可以根据不同的状态码判断是否该重试、执行逻辑分支或者提前退出业务逻辑等,这种不判断返回的做法等于默认对其他服务调用成功而实际上是存在调用失败或者对方内部出错的情况,在这种情况下不应该再继续执行业务逻辑,否则会带来数据不正确/不一致等问题。

建议: 对于其他服务的接口调用应及时判断接口的返回结果,并根据结果执行对应的业务逻辑。

2. 好异步却不知Trace为何物

神操作: 代码里通过发送event的方式生成了很多异步执行的job,但缺少可以进行链路追踪的trace信息导致问题排查困难

项目中将一些逻辑复杂且不需要即时响应的工作通过异步的方式来处理是一种常见做法,当异步job及触发异步job的事件变多之后,因缺少trace信息在排查问题时将变得异常困难,仅通过打印日志的时间无法判断一次异常的操作(例如:数据库更新)是由哪条链路的哪个event触发而产生的,尤其在job内还会触发别的job的情况下事情变得更为复杂

建议: 对每次请求都分配一个唯一的TraceID并将traceID在整个调用链(无论是异步job还是跨服务调用)中传递下去,配合日志及监控打点可以使得整条调用链有比较好的可观测性。对于服务的可观测性业内已有较为成熟的方案,可以借鉴并引入到项目中,将大大提升问题排查的效率

3. 滥用事件且监听随意

神操作: event名称与实际用途不一致,同一个event在多个不同场景下都会触发且随意增加监听event的listener进而导致逻辑混乱

异步虽好但不能贪杯。如果不对异步job进行管理和维护则容易产生滥用。例如:在某个新场景下需要触发一个异步任务,因为已经有个类似的job则偷懒在原job基础上添加了新逻辑并通过发送对应的event来触发产生异步job,但殊不知这个已有类型的event还被别的listener所监听,进而也触发了别的异步job执行带来了意想不到的结果

建议: 明确event的作用范围避免随意复用。这更多的是在项目迭代过程中需要注意维护好event和job的对应关系,但这却常常被忽视

4. 日志不全、吞异常不打日志

神操作: 日志信息记录不全甚至关键地方不打印日志

一些关键日志对于问题的定位和排查是十分重要的,然而却经常遇到某些关键链路上没有日志或者只有一句:failed to update db,你说没打日志吗?确实打了,但又跟没打一样。

还有一些场景逻辑执行产生了错误/异常并将错误/异常catch住后没有记录任何关于该异常的日志,并且返回了一个空值给上游,再加上调用方在接到空值后没有进行判断进而产生了一连串的逻辑错误。

每次查问题都要先补日志,这简直是天坑

建议: 在一些关键路径以及产生异常错误时应尽量打印完整且有用的日志信息,包括:业务信息、trace信息、有效的错误堆栈等。打印日志的时候思考几个问题:这行日志是可不打的吗?看到这个日志能获取什么信息以及可以做什么?每个字段都是必须打印的吗?出现问题时能不能提高排查效率?

5. 数据同步消息发送时机混乱导致读取到旧数据

神操作: 两个系统之间通过kakfa消息传递信息,但因消息发送时机不对导致数据同步混乱

在某个场景中内容在系统A中发生变更,系统A发送消息通知系统B内容发生了变更,系统B接到消息后调用系统A的接口获取最新数据,然而系统A却在内容变更落库前先发送消息,导致系统B调用A的接口获取的仍然是旧数据

严格来说这是个bug

建议: 明确消息的发送时机,确保获取到的数据有效性。

6. 方法名词不达意、随意复用方法引发bug

神操作: 方法名与实际逻辑不符,随意复用引发bug

在一个从方法名来看是做上报用户操作日志的方法中还连带发送了触发其他job的event,在其他地方复用该方法时触发了不该触发的job。

建议: 好好写代码,别瞎搞

7. 过长的函数

神操作: 所有逻辑在一个方法函数里完成,一个函数上千行,不易读懂和维护

一个过长的函数就像是一篇没有分段的作文,层次结构不分明,想到哪写到哪。如果一个函数的职责范围不够明确,后续有新的功能似乎都可以塞到这个函数里,长此以往这个函数变得难以读懂,不可维护。

建议: 对于较长的业务流程可以按逻辑分块,拆分到不同的函数中并且明确函数的职责范围

8. 嵌套过深的if语句

神操作: if语句嵌套过深,远观代码整体像一个向右的大箭头

嵌套过深的if语句理解起来比较费劲,后续再添加新的条件时容易出错

建议: 可以采用不满足条件提前退出的方式减少if的嵌套。或者采用责任链模式来完成数据的处理,亦或是通过查表的方式完成不同数据状态的处理

9. 数据库事务与数据唯一性混淆

神操作: 通过数据库事务来保证数据的唯一性

期望通过数据库来保证表中数据的唯一性,但未添加唯一索引。在出现重复插入的数据时一脸疑惑,以为事务失效了

建议: 摸清事务的特性,如果要通过数据库保证唯一性可以添加唯一索引

10. 异步任务共用队列,不区分优先级

神操作: 多种类型的任务共用一条队列,当突发产生大量低优先级的任务时,因消费积压导致高优先级的任务一同被淹没,不能及时得到处理

建议: 按照任务优先级送入不同的队列,保证高优先级的任务能够得到及时的处理。同理,系统中数据流与控制流也应分开避免相互影响

未完待续。。。