请停止对代码那些小打小闹的优化
请停止对代码那些小打小闹的优化
不要钻细节的牛角尖
当我还是一个懵懂的入行新人的时候,满怀着对于编程世界的憧憬和对前辈的敬意,读了几本设计优化性能、编程哲学的书。深受其中编程思想的影响,当时我的想法是,我要让我写的每一行代码都简洁优雅功能强大,高内聚低耦合,还要性能拉满。
大师的思想固然牛逼,但是我陷入按图索骥的牛角尖。
举个简单的例子,一个销售统计接口我就挑了老代码这么多刺:
- 普通 for 直接调索引比 stream 快;
- 一个复杂操作重复查了两次单据信息;
- 函数A参数过多,应该封装一个更大的对象;
- 查询了宽表多余的字段,不符合迪拉米特原则。
当时的我还沾沾自喜,大厂程序员就这,活也太糙了,很久后我才后知后觉。
符合设计理念的实现固然好,但是要清楚知道功能的背景和目的。
假如是一个一周才会用一次的报表统计,查询用了2000ms 还是 100ms,其实对用户体验没有什么差别,但技术的实现难度不可同日而语。
- 对于循环,在后台开发中,比如 Java 这种语言架构下,应该节省程序员的时间而非机器的时间。
- 对于重复的操作,当确实重复查询耗时占比是接口速度的瓶颈,才有可能需要优化,这是充分不必要条件。
- 若无必要勿增实体,你可能会疑惑,前面还说设计理念固然好,都是花架子,现在自己又要用了。但事实是这样,初级开发在概念掌握不深的情况,经常会做矛盾的设计或者只遵守自己熟悉的设计原则。
- 对于不需要跨表查询的数据,多一个字段并不会带来多少时间开销。数据库设计还有第一第二第三范式呢,宽表本身就不符合第一范式,重复存储字段了,这本就是空间换时间的一种妥协,因为现在内存不值钱。
一、性能优化不要仅停留在代码
性能优化等代码细节无法突破架构设计带来的性能天花板,真正的性能提升来自于设计决策,而非局部代码优化。
例如一个同步接口,需要按顺序去读取一个数据库,然后把数据传到某个第三方的接口,获得额外的数据,再把返回来的数据在本地做一些处理,把结果再写回到数据库保存,最后再把结果返回给用户,你可以试着去优化你的代码逻辑。
表面上来说这这么多个步骤里面,你确实有很多可以优化的地方,比如说你可以优化你的缓存机制,你可以增加你的并发数等等,做得好的话,说不定可以从这儿扣 10 毫秒,从那扣 20 毫秒。但是最后你会发现,那个第三方接口要一秒多才能够返回结果,那结果就是说你的这个 API 的性能完全被这个第三方接口的性能上限给锁死了,无论你怎么优化,你都要承受,它就带来了一秒钟的耗时。
但是如果从一开始你就决定不造同步API,而是异步 API,那情况马上就豁然开朗了,比如把这些带有外部依赖、运行状况、运行效率都很不可控的步骤,统统转移到后台进行异步处理,在页面异步刷新,那么这些对外的API就可以做到低于100毫秒的返回,它的响应速度比起同步设计直接上升一个数量级,当我们能够获得几个数量级的速度优化,那么那些几毫秒的小打小闹自然就显得毫无意义。
比如说如果某一个功能,你本来是需要通过网络去调用一些资源的,不论你是通过 http 取数据,优化成 dubbo/mysql 数据用 TCP 取,不论你把数据放在最近的 CDN,怎么压缩成二进制传,都不如把它设计成能够在本地/缓存去调用,那么恭喜你,你还没有写一行,代码已经获得了百倍的速度提升。
比如下图的 Network Storage 到 SSD:

二、最朴实的方案往往是最好的方案
比如一个功能要读取 100G 的大数据进行计算。有些人会提出用多线程处理,有些人会说建立一个 hadoop 集群,都不如申请一个 128g 的内存条加到服务器里面。这样可以把数据直接读进内存运算。如上图的 SSD 到 DRAM,不仅比前面的方案快 1500 倍,而且更省时间,还更省钱,一条 128g 的服务器内存也才 3000 块钱,远低于其他方案的人力成本。
比如分库分表,横向拆分面临的路由问题,分布式事务,跨分片查询。反正横向也要改应用代码。这时候完全可以把纵向拆分,把比较大的表单独拆一个数据库出来这样不仅访问速度,IO 速度和 CPU 负载都可以得到优化。
三、真正影响成败的是非技术因素
- 生产事故多源于配置错误、扩容逻辑失误等人为疏忽,而非代码性能问题;
- 哪怕是讲稳定性和速度的云服务,大家也基本看价格和售后,不会在意A服务商比B服务商函数接口调用快几十毫秒,一个网络波动就磨平差异了,用户也根本不会察觉这么小的差异;
- 淘宝在电商被 PDD 后来居上当然也不是因为淘宝卡;
- 有多少开发吐槽微信支付的接口难用,却还是不得不捏着鼻子兼容两种支付模式。
四、代码的自然腐化
哪怕你 owner 期间,项目规范再好,假如项目被交接出去,亦或是不可抗力的组织结构变动,或者仅仅是一两周没顾上 code review,接班人懒得摸清你的所有逻辑。在你优雅的策略转发逻辑旁边另写了一个 if,随着时间推移,破窗带来的腐化就开始了。
五、一切决策都是取舍
应基于实际情况评估投入,而非盲目遵循规范。
- 比如一个支付接口要十几秒才能返回结果,我可能第七秒就切出去换个软件买东西了;
- 又比如股市一秒钟几百上千万资金流动,哪怕成交速度提升百分之零点一也会在规模的放大下产生巨大的效益。
开发人员应该清楚的知道公司产品价值传递的链条,然后想办法去离钱更近位置。得到更优渥的工资和不可替代性更高的岗位。
市面上大把的 xx 技术专家,可没看到业务专家流落在外的。
六、是否应该完全放弃代码的优化和重构
我起了一个有争议的标题把大家骗进来,我有罪。
虽然我前面输出了那么多优化无用论的观点,但是日常对于代码的优化和重构也并非如我前面所说的毫无意义。
毕加索有一句名言是:"我花了一辈子学会像一个孩子一样去画画",这句话其实会让不少人有误解,他们就说毕加索的意思是其实没有必要那么辛苦的去画画画,因为归根到底到最后你还是画得像个小孩一样,那么干脆一步到位,我现在直接就可以画得像个小孩子一样。
其实主要是这些人都漏掉了,这句话的前半句是:"我花了4年才画得像拉斐尔一样",这个前提可以说是后半句的充分且必要条件。
虽然绘画行业和计算机行业的区别有点大,但是这个道理是相通的。
就你在刚出道的时候,你去写那些无伤大雅的代码的优化,那些没有什么营养的重构,或者你亲手设计出来一些看上去还挺炫酷的,实际上也没什么用的那些功能逻辑,这些就跟画鸡蛋的那种素描练习一样,是让你亲身的去体会细节,这是成为大师的必由之路。
