代码中的奥卡姆剃刀原理
如无必要,勿增实体。
过早的优化是万恶之源。
背景
一个抽奖活动,要求在展示奖品股票时,显示股票价格。开发在实现这个功能的时候,用redis缓存每只股票的价格,每两小时调用行情服务更新一次。
问为什么要做个缓存,而不是直接访问行情服务直接读取,给出了几个理由:
减少对行情的请求,直接访问redis一次就能查很多。
用缓存速度快。
奖品展示、获奖排行榜都要用行情数据,防止不一致。
用行情的地方很多,不用每次请求行情服务。
前端需要拉取的数据,在需求范围内,可以接受两小时不变,不用每次重新计算。
但是这些理由都站不住脚,直接访问行情服务并不会有问题。
优化过渡,过早加了缓存,增加了代码的复杂度,产生了数据的不一致性。
没有解决具体的问题,看到的只是一些表象,看似解决了问题。
思考
逐条思考下上面的几点理由。
理由1. 减少对行情的请求,直接访问redis一次就能查很多。
说法是对的,确实减少了对行情的访问。但是为什么要减少对行情的访问呢?
行情的服务很稳定,而且性能也很好,并没有什么问题。没有解决具体问题。
理由2. 用缓存速度快。
如果直接访问行情数据源速度是瓶颈吗,有遇到性能问题吗?没有。行情也是内存访问,速度不一定比redis慢的。而且开发也没有数据支撑。
理由3. 奖品展示、获奖排行榜都要用行情数据,防止不一致。
为什么要一致,不一致有什么问题?对于排行榜,大家都有预期,是一定时间才更新一次的,只要在排行榜界面有说明即可。就像游戏的排行榜,很多都是24小时更新一次。没有必要为了和排行榜数据一致,而修改代码。这个问题在产品形态上就能解决,也是个伪问题。
理由4. 用行情的地方很多,不用每次请求行情服务。
同理由1,多怎么了?正常访问就可以了,如果性能瓶颈再优化。
理由5. 前端需要拉取的数据,在需求范围内,可以接受两小时不变,不用每次重新计算。
具体的产品形态,可以有加缓存的余地,但不意味着必须要加缓存。除非遇到有性能问题,否则代码的可维护性、稳定性是最重要的。
再换个角度,如果行情的接口性能真的很慢,要怎么优化呢?
不是直接暴露redis接口的,有更好的方法。
把行情获取部分单独封装成为一个独立函数,对外不依赖具体的内部实现。
不管是直接访问行情服务、读缓存,还是访问一个单独封装的优化行情服务模块,对外接口都不变。
整理下常见的避免过渡优化场景:
1、能同步实现的代码,就不要用异步;
2、能实时实现的代码,就不要用定时;
3、能用数据源读取的,就不要用缓存。
先保证程序简洁,满足性能要求,等遇到问题的时候,再去优化,避免过早优化,引入问题。
总结
解决问题前,先思考是否有问题,问题是什么,而不是直接去做。
「如无必要,勿增实体」代码模块中,在没有遇到问题时,不要引入过多的模块,缓存,增加代码的复杂性。既不易于阅读,也不易于维护。
遇到性能优化,不优化有问题,优化过渡,即使没产生问题,也是浪费。