在程序中打错误日志的主要目标是为更好地排查问题和解决问题提供重要线索和指导。但是在实际中打的错误日志内容和格式变化多样,错误提示上可能残缺不全、没有相关背景、不明其义,使得排查解决问题成为非常不方便或者耗时的操作。
而实际上,如果编程的时候稍加用心,就会减少排查问题的很多无用功。在阐述如何编写有效的错误日志之前,了解错误是怎么产生的,非常重要。
错误是如何炼成的对于当前系统来说,错误的产生由三个地方引入:
1.上层系统引入的非法参数。对于非法参数引入的错误,可以通过参数校验和前置条件校验来截获错误;2.与下层系统交互产生的错误。与下层交互产生的错误,有两种:a.下层系统处理成功了,但是通信出错了,这样会导致子系统之间的数据不一致;
对于这种情况,可以采用超时补偿机制,预先将任务记录下来,通过定时任务在后续将数据订正过来。
有什么更好的设计方案,也可以留言。
b.通信成功了,但是下层处理出错了。
对于这种情况,需要与下层开发人员沟通,协调子系统之间的交互;
需要根据下层返回的错误码和错误描述做适当的处理或给予合理的提示信息。
无论哪一种情况,都要假设下层系统可靠性一般,做好出错的设计考虑。
3.本层系统处理出错。本层系统产生错误的原因:
原因一:疏忽导致。疏忽是指程序员能力完全可避免此类错误但实际上没做到。比如将敲成了,==敲成了=;边界错误,复合逻辑判断错误等。疏忽要么是程序员注意力不够集中,比如处于疲倦状态、加班通宵、边开会边写程序;要么是急着实现功能,没有顾及程序的健壮性等。
改进措施:使用代码静态分析工具,通过单元测试行覆盖可有效避免此类问题。
原因二:错误与异常处理不够周全导致的。比如输入问题。计算两个数相加,不仅要考虑计算溢出问题,还要考虑输入非法的情形。对于前者,可能通过了解、犯错或经验就可以避免,而对于后者,则必须加以限定,以使之处于我们的智商能够控制的范围内,比如使用正则表达式过滤掉不合法的输入。对于正则表达式必须进行测试。对于不合法输入,要给出尽可能详细、易懂、友好的提示信息、原因及建议方案。
改进措施:尽可能周全地考虑各种错误情形和异常处理。在实现主流程之后,增加一个步骤:仔细推敲可能的各种错误和异常,返回合理错误码和错误描述。每个接口或模块都有效处理好自己的错误和异常,可有效避免因场景交互复杂导致的bug。
譬如,一个业务用例由场景A.B.C交互完成。实际执行A.B成功了,C失败了,这时B需要根据C返回合理的代码和消息进行回滚并返回给A合理的代码和消息,A根据B的返回进行回滚,并返回给客户端合理的代码和消息。这是一种分段回滚的机制,要求每个场景都必须考虑异常情况下的回滚。
原因三:逻辑耦合紧密导致。由于业务逻辑耦合紧密,随着软件产品一步步发展,各种逻辑关系错综复杂,难以看到全局状况,导致局部修改影响波及到全局范围,造成不可预知的问题。
改进措施:编写短函数和短方法,每个函数或方法最好不超过50行。编写无状态函数和方法,只读全局状态,相同的前提条件总是会输出相同的结果,不会依赖外部状态而变更自己的行为;定义合理的结构、接口和逻辑段,使接口之间的交互尽可能正交、低耦合;对于服务层,尽可能提供简单、正交的接口;持续重构,保持应用模块化和松耦合,理清逻辑依赖关系。
对于有大量业务接口相互影响的情况,必须整理各个业务接口的逻辑流程及相互依赖关系,从整体上进行优化;对于有大量状态的实体,也需要梳理相关的业务接口,整理状态之间的转换关系。
原因四:算法不正确导致。
改进措施:首先将算法从应用中分离出来。若算法有多种实现,可以通过交叉校验的单元测试找出来,比如排序操作;如果算法具有可逆性质,可以通过可逆校验的单元测试找出来,比如加密解密操作。
原因五:相同类型的参数,传入顺序错误导致。比如,modifyFlow(intrx,inttx),实际调用为modifyFlow(tx,rx)
改进措施:尽可能使类型具体化。该用浮点数就用浮点数,该用字符串就用字符串,该用具体对象类型就用具体对象类型;相同类型的参数尽可能错开;如果上述都无法满足,就必须通过接口测试来验证,接口参数值务必是不同的。
原因六:空指针异常。空指针异常通常是对象没有正确初始化,或者使用对象之前没有对对象是否非空做检测。
改进措施:对于配置对象,检测其是否成功初始化;对于普通对象,获取到实体对象使用之前,检测是否非空。
原因七:网络通信错误。网络通信错误通常是因为网络延迟、阻塞或不通导致的错误。网络通信错误通常是小概率事件,但小概率事件很可能会导致大面积的故障、难以复现的BUG。
改进措施:在前一个子系统的结束点和后一个子系统的入口点分别打INFO日志。通过两者的时间差提供一点线索。
原因八:事务与并发错误。事务与并发结合在一起,很容易产生非常难以定位的错误。
改进措施:对于程序中的并发操作,涉及到共享变量及重要状态修改的,要加INFO日志。
如果有更有效的做法,欢迎留言指出。
原因九:配置错误。
改进措施:在启动应用或启动相应配置时,检测所有的配置项,打印相应的INFO日志,确保所有配置都加载成功。
原因十:业务不熟悉导致的错误。在中大型系统,部分业务逻辑和业务交互都比较复杂,整个的业务逻辑可能存在于多个开发同学的大脑里,每个人的认识都不是完整的。这很容易导致业务编码错误。
改进措施:通过多人讨论和沟通,设计正确的业务用例,根据业务用例来编写和实现业务逻辑;最终的业务逻辑和业务用例必须完整存档;在业务接口中注明该业务的前置条件、处理逻辑、后置校验和注意事项;当业务变化时,需要同步更新业务注释;代码REVIEW。业务注释是业务接口的重要文档,对业务理解起着重要的缓存作用。
原因十一:设计问题导致的错误。比如同步串行方式会有性能、响应慢的问题,而并发异步方式可以解决性能、响应慢的问题,但会带来安全、正确性的隐患。异步方式会导致编程模型的改变,新增异步消息推送和接收等新的问题。使用缓存能够提高性能,但是又会存在缓存更新的问题。
改进措施:编写和仔细评审设计文档。设计文档必须阐述背景、需求、所满足的业务目标、要达到的业务性能指标、可能的影响、设计总体思路、详细方案、预见该方案的优缺点及可能的影响;通过测试和验收,确保改设计方案确实满足业务目标和业务性能指标。
原因十二:未知细节问题导致的错误。比如缓冲区溢出、SQL注入攻击。从功能上看是没有问题的,但是从恶意使用上看,是存在漏洞的。再比如,选择jackson库做JSON字符串解析,默认情况下,当对象新增字段时会导致解析出错。必须在对象上加
JsonIgnoreProperties(ignoreUnknown=true)注解才能正确应对变化。如果选用其他JSON库就不一定有这个问题。改进措施:一方面要通过经验积累,另一方面,考虑安全问题和例外情况,选择成熟的经过严格测试的库。
原因十三:随时间变化而出现的bug。有些解决方案在过去看来是很不错的,但在当前或者未来的情景中可能变得笨拙甚至不中用,也是常见的事情。比如像加密解密算法,在过去可能认为是完善的,在破解之后就要慎重使用了。
改进措施: