后端开发:如何写出可靠的接口

毕业进入现在的公司已近一年,完整参与了部门新项目两期的开发上线过程,作为一名后端开发,觉得最痛苦的是上线前和上线后的改 bug 阶段,面对各种突如其来、莫名其妙的bug,头昏脑涨、手忙脚乱、越改越懵,经常导致实验式改 bug、改一个 bug 又出现俩 bug 的之类的惨剧,我就忍不住想,为什么每次上线前都会有这么多bug呢?

前几天还读到一篇豆瓣文章,没有总结的就不是经验,只是经历。程序员也不能业务来了就写代码,有bug了就改bug,这样技术很难提升,也就难怪每次都会有那么多bug了。有的开发人员工作多年,接口还是时不时500,前期忙着写代码,后期忙着改bug,心累。

我从一期熟悉业务、框架,写一写边缘接口,到二期负责一个小模块,尝试数据库、程序的设计,中间磕磕绊绊,昨天也顺利上线,模模糊糊也感觉到了一些经验,于是努力总结下,虽然简单,也许能给自己和读者一些启发。(本文只针对初级水平,简单的bug,不涉及高并发、海量数据等复杂问题)

一. 接口为什么出bug?

辛辛苦苦写的接口,自己测的时候好好的,怎么别人一调就出错了呢??(此处应有表情包,请自行脑补)当然可能是运行环境的问题,不过程序统一部署在服务器上,这一般是架构师或者运维负责的工作,至于编程语言或者操作系统的问题,也通通不在今天考虑范围内,今天,我们只考虑自己写出的bug。

事实上,运行中的程序所涉及的,无非三样:资源(cpu、内存等)+ 算法(程序运行流程)+ 数据(用户输入、数据库、第三方接口等)。通常我们认为资源是可靠的,出现bug主要是由于算法的不可靠或者数据的异常。

更进一步,机器严格按照0/1执行指令,算法上一次执行正常,为什么这一次会失败?本质上还是因为数据变了,而算法没能覆盖此情况,因此,要想保证接口的稳定,主要从两方面考虑:保证数据的可靠性、算法的健壮性,而算法的健壮性也就是考虑到数据的各种情况,两者密不可分。

二. 如何写出bug更少的接口?

如前分析,数据的变化是接口bug最常见、本质的原因。而其中,用户输入又是数据变化最主要的原因。而程序必然要有用户输入,否则毫无意义。

编程界有句名言:永远不要相信用户输入。你永远不知道,用户会在一个期待姓名的输入框里都输入些什么。不要因为前端做了过滤你就放心,一方面是用户可能会使用爬虫等手段直接访问你的接口,另一方面,前端也是你的用户,沟通也存在误差,前端可能会使用错误的方式调用你的接口,而这种错误可能会更加隐蔽。

第一条建议:严格校验用户的输入,包括格式、内容。

我知道很多人都懒得去逐条检验用户输入,觉得只要功能正常就ok了,但是,这经常会导致后期改bug时投入更多的经历。经常测试提了bug,你查来查去,发现是前端传错了参数,或者没有合理限制用户输入,当然你可以很刚,让前端去改,但这个过程已经浪费了你大量的时间精力,还不如一开始自己做好检验,返回合适的错误消息,会为你后期节省大量的精力。

对于PHP等动态语言,尤其如此,例如我们使用Laravel框架,我会在所有接口入口处,首先使用$request->validate()检验所有输入数据的格式,如有必要,还会写代码进一步校验输入内容,比如时间范围、请求数据是否有效等等。

第二条建议:考虑用户的骚操作,重复提交、延时提交

重复提交应该是大多数后端都能想到的情况,也就是接口的幂等性,有些资源只能操作一次,必须进行校验,其实不仅是重复提交,还包括同一事件被两人重复处理的情况。

而对于延时提交,其实是测试给我提bug后我才意识到的问题模式。例如我们通过get接口返回给用户某种资源,用户可以通过post接口回传资源id并提交修改,由于是自己的get接口返回的,我们可能想着只验证id合法就行了,看似形成严格闭环,但如果用户停留在此页面延时提交,则可能在此期间资源过期,或者资源已被他人修改,而改用户也成功修改的bug。其实进一步思考,你会发现,这跟高并发情景下的资源失效有异曲同工之处。

第三条建议:检验数据库、第三方接口的返回数据

除了用户输入,常见的数据来源还有数据库、第三方接口。相对而言,这些数据接口会可靠的多,而且内容格式也更规范。不过为了接口的稳定性,最好也做一些检验。如常见的数据为空的情况,就要及时中止程序执行并抛出合适的信息。

对了,对于数据库,我还遇到过bug,就是主从延迟导致的数据更新问题,由于经验尚浅,这类问题不很擅长,就不再写。

第四条建议:程序算法尽可能覆盖异常情况

这条实际上是对前三条的补充,有些不合法的用户输入你可以直接中止程序并返回错误信息,但有些情况可能需要程序继续运行,进行特殊处理,这些情况你在程序设计之初应该尽量考虑周全,后期bug会少很多,也更容易维护。

三. 如何写出更高效的接口

最后,再写一点点关于关于接口效率、代码质量的思考。

1. 影响接口效率的主要是数据库操作
以我有限的经历来看,接口耗时长基本都是因为数据库操作不合理,我们大多数的业务代码并不会有性能问题。我见过不少在for循环里查询数据库的代码,一定要避免,我们可以先一次性取出所有数据,然后逐个去处理。例如我们会在框架层记录所有数据库操作,调试接口时即可看到所有数据库操作以及相应耗时,该合并的查询要合并,该优化的耗时查询相应去优化。

2. 合理使用Exception,日志

这条主要针对php语言,由于历史原因,我看到不少代码靠return中止程序并传递错误信息,这样在代码复杂、调用层次深了以后极难维护,远没有Exception机制直观方便。还有,重要信息一定要写日志,便于后期发现问题及调试,也可用来自证清白。

3. 代码要合理划分、抽象

不要复制粘贴代码,重复的功能要独立出来;设计时要合理考虑需求变更、扩展;写小而专注的函数,不要把复杂功能一坨实现;这样写的代码才易于修改、测试以及扩展。这块我做的也不好,上线后看自己的代码都是一坨一坨,难以维护,接下来还要多思考,多实践。

四. 结束语

祝大家写的代码都没有bug!

本帖由系统于 1个月前 自动加精
lixiang9194
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 17
monkey

很多 Bug 是因为对业务不熟悉导致的,个人觉得熟悉业务+测试脚本能避免 80% 的问题

2个月前 评论

@monkey业务确实非常重要,我也体会到了,不过这次只总结了技术的方面;因为业务更难讲,需要更多的经验去沟通,尤其当你碰上了个不靠谱的产品

2个月前 评论

还会有同事的神代码,神助攻

2个月前 评论

@人厶八夂 所以检验他人接口的返回数据,并返回合适的提示信息,就非常重要,能及时定位到是谁的问题,减少无意义沟通;关键信息写日志,便于自证清白

2个月前 评论
wanghan

api接口重复提交怎么避免呢?有的人就是喜欢疯狂点击

2个月前 评论
WytheHuang

@wanghan 可以用redis来限制

2个月前 评论
Complicated

现在我们每次上线出现的Bug都是因为部署到正式环境的时候,不是缺这文件就是少那文件,数据库不是缺这配置项,就是那个字段初始化不对!请问你们都是怎么解决这些问题的?

2个月前 评论
wanghan

@Wsmallnews 具体怎么做呢?

2个月前 评论

@wanghan 我理解的,如果是为了防止业务上的重复造成逻辑错误,对于 修改资源的接口,一般会提交资源 id,那么修改前通过 id 定位资源并判断是否已修改,是否可以再次修改;对于新建资源的接口,那么插入前通过关键字段查询数据库,判断是否是重复插入;

如果是单纯的防止多次点击性能问题,那么可以在框架层统一处理,例如 laravel 有throttle等组件用于限流;

最后,你可以搜索接口的幂等性深入了解

2个月前 评论

@Complicated 这块我也没什么经验,都是架构师和运维负责的,我了解到的,就是我们在上线前会有一个预上线过程,就是提前部署到正式环境,测试环境是否正常,数据库能否迁移,能否顺利回滚,然后记录问题并回滚线上环境,接着在测试环境修复问题,这样下次正式上线就会好很多。

2个月前 评论
Complicated

那你的意思就是 你们把代码部署到正式环境,然后测试一下(在正式环境下测试),然后测试没问题,才会正式 发布使用?

2个月前 评论

@Complicated 我们一般都是这样做的
测试环境没问题在部署正式环境
develop 分支
premaster 分支
master 分支
自己修复bug的分支 hotfix/...
新功能分支feture/xxx
一般修复小bug 我们都会合并到develop 测试无问题在合并premaster
最后合并master

2个月前 评论

@Complicated 把需要的文件加到版本库,数据库迁移

1个月前 评论

受教了,写的很好

1个月前 评论

你居然在php社区讨论算法 :smiley:

1个月前 评论

@罪人 php 开发过程中会用到哪些算法 我很不太了解 你开发时候用的多吗

2周前 评论

@fengzb 我是写api接口的 有点涉及排序 其他的数据结构我都是拼出来的

2周前 评论

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!