之所以想起写这篇文章,是因为最近看到的一个著名的开源项目在内部使用时的各种问题,不得不说,很多的开源的东西思想是不错的,但离真正工程化都有不小的距离,所以没什么商业公司采用的开源产品如果要引入的话一定要慎重,通常会有N多的坑等着你去填,而比较成功的开源项目的背后多数都会有商业公司在背后不断的改进。
遥想我2009年开始学习写php代码时,觉得写代码也不难呀,无非就是学学语法规则、库就可以写出来,记得有一次我实习面试的时候是让我在一个下午左右的时间写一个完整的留言板,那也就是刷刷刷就写好了,但随着后来工作,尤其是加入阿里以后,越来越明白高质量的工程代码为什么难写。
在写代码初期,最关注的是如何用代码实现需求,如果是仅仅实现业务需求的话,即使是刚上手的程序员,只要解题能力还OK,基本上都是可以写出代码来的。所以我自己一直认为数学成绩是程序员的一个非常重要的要求,数学好的人通常解题和逻辑思维能力是还不错的。
上面的这个基本的写代码的过程中,写的更好的同学的体现会在对业务的深刻理解以及抽象上,写出的代码会具备一定的复用能力,这个在这里不多加探讨。
但代码是不是实现了业务需求就结束了呢,其实远没有,这其实只是写代码的开始,除了正向的逻辑实现外,任何一个点的异常的分支逻辑怎么处理才是工程化的代码中更难处理的部分,这个问题在单机式的系统中会相对还好处理,在分布式的环境会变得非常的复杂,例如调用其他机器的功能超时了,出错了,到底该怎么处理,这也是为什么有了那么多的分布式的理论的东西出来,在增加了异常分支的处理逻辑后,通常会发现这个时候正向逻辑的代码在整个代码的占比中会大幅下降。
异常分支逻辑处理好后,通常还需要增加必要的日志信息,以便在出问题时方便排查,而不是到了要排查问题的时候,一点目前系统的状况都搞不清楚,所以吃掉重要的异常信息不抛出这种行为在写代码中是非常可耻的。
在处理好上面异常的相关动作后,代码的健壮性也要处理好,这个主要指:
1. 自我保护能力
对外提供的接口是否具备足够的自我保护能力,就是即使使用的人没仔细看API文档随便乱用也不会导致系统出问题,这种案例非常的多,例如对外提供了一个批量查询接口,结果用户一下传了一个里面有上千个用户id的数组,查询一下直接把内存耗光,像这种情况下不能怪使用的人,而应该怪实现API的这一端的保护做的不够好,按照这样的标准去看,会发现开源的很多东西的API都不太合格。
还有一种就是能力保护,如果超出了处理的并发量的能力,这个时候会发生什么。
2. 对资源的使用限制
这也是代码新手或一些开源产品中做的比较差的地方,很容易出现规模一上去,资源使用量也一直涨,没有限制,然后导致系统挂掉,很常见的案例是对线程池的使用,例如像Java中的Executors.newCachedThreadPool,这个接口很多人会用到,但很多用的人都没有仔细想过会不会在某种情况下这里创建出巨多的线程;还有例如用Map做cache,也没考虑大小限制的问题,结果就是随着数据量增长,某天突然就挂了。
健壮性是代码中比较复杂的部分,通常也是比较展现代码能力的部分,可能看起来就几行代码,但其实背后反映的差距是巨大的。
开源产品除了在健壮性上的差距外,通常还会出现的一个巨大差距就是整个系统的设计的伸缩能力,伸缩能力不够的话通常会导致结构性的重构,另外常见的就是在并发的处理上不够高效,例如锁的合理使用、无锁算法的引入等等,而这些需要非常强的系统设计和代码功底能力。
除了上面说的这些外,高质量的工程代码还需要考虑可维护(例如监控信息暴露)、安全性等,对我而言,我一直认为所谓的工程化其实就是把一些玩具性质的代码变成可在商业系统中真正健壮运行的代码。
上面的内容写的比较简略,不过应该也能看出,对于高质量的工程代码而言,其实实现业务逻辑只是其中占比很小的一部分,甚至花的时间是相对最少的一部分,所以我确实非常赞同面试的时候让同学写代码,这个时候很容易看出同学写代码的功力;有些时候为了考察同学写代码的熟练程度,我会问问IDE的快捷键,或者让手写一段不是太复杂的代码。