开发实践思考(七)
1. REST风格接口
REST
风格接口: 使用URL
定位资源,用HTTP
方法(GET,POST,PUT,DELETE
)描述操作:
- GET:获取资源
- POST:新建/更新资源
- PUT:更新资源
- DELETE:删除资源
通过HTTP
状态码知道结果。
在设计RestFul API
,原则上不使用动词,而是使用名词,推荐使用复数。例如:
api.qc.com/v1/newsfeed 获取某人的新鲜事
api.qc.com/v1/friends 获得某人的好友列表
api.qc.com/v1/profile 获得某人的详细信息
保证HEAD
和GET
方法是安全的,不会改变资源状态。
2. 需求与文档
- 文档即使没人看,也要写。它起到记录作用,记录沟通过程中确认的事项。
如果大部分需求是通过口头沟通、讨论群确认,但不反应到需求文档中,后续很容易扯皮。 - 业务高于技术
- 禁止一句话需求
3. 日志
日志非常重要,在关键路径和关键状态上打关键信息日志,好过看发生问题后的代码分析和一通乱猜测。
- 关键路径帮助排除不会出错的地方,缩小问题范围。
- 关键状态会减少需要作出的猜测,更肯定地排查问题。
- 关键信息有助于快速锁定问题对应的代码位置。
4. 可维护性和可读性的核心
可维护性、可读性的核心内涵是什么?
笔者认为,是 语义 和 职责。把握好这两点,不管是程序、模块、系统都可以适用。
5. 错误提示语设计
错误提示语设计要满足总分原则,具体解释如下:
首句话说明操作结果,成功还是失败,后面再跟上具体说明,操作成功有关键业务标识说明,失败有错误码和错误信息。
这里指的是广义的错误,既可能是接口返回的,也可能是业务错误。
自定义的错误信息需要准确直接,无重复,这样便于从错误信息定位到具体代码。
接口中获取的错误信息要原样显示,并且附加上接口错误码,方便后台定位问题。
- 例如:连接XX网关失败的操作,可以这样提示:"连接XX网关失败,错误码XXX,错误信息XXX",其中XXX是本地设置的。
- 例如:获取响应数据失败,可以这样提示:"获取XX数据失败,错误码XXX,错误信息XXX",其中XXX可以是业务层解析失败,也可以是接口响应失败。这两类的错误码的有效范围要提前定义好,不能重叠。
6. 业务分支处理
在非期望分支中,不要返回默认正确值,而是要返回异常值。警惕以下这种处理逻辑:
char cMarket = MARKET_SZ;
if (xxx)
{
cMarket = MARKET_SH;
}
上面这种写法,会使得 cMarket
永远有效,这是不严谨的,不建议这样用。
7. 接口对接建议
与后端接口对接,有以下实践经验:
- 保持接口返回的数据变量名与接口文档中的字段名保持一致,一致命名,增强可读性。
- 接口保存原始数据,如果约定的需要进行特殊处理,接口层可提供一系列辅助转换函数,用于上层显示、逻辑判断等。但不主动去进行转换,提供转换工具,是否转换由上层来决定。
8. 第三方库使用
- 涉及到的第三方库接口功能越基础越好,数量越少越好。
- 尽量使用第三方库的基础功能,当需要到高级功能时,通过封装基础功能来实现
- 凡是第三方库,在项目中引入时,要封装一层,对外提供与项目风格一致的接口,这样方便后续扩展以及变更。
使用第三方库前,要有如下测试:
- 功能测试
- 性能测试
以上是判断第三方库是否符合业务功能、性能要求以及一般的惯用法。 - 压力测试
- 故障测试
以上两者需要第三方库以单独服务形式部署,对外提供服务时的测试。
如果第三方库升级了,应用依赖的第三方库要不要升级?
方案一:升级至稳定版本,在升级之前,跑自己业务所使用功能的单元测试,判断新版本是否提供同等约定。
方案二:基本不升级,自己团队维护某个特定版本
方案三:不跟随升级,遇到第三方库的Bug,从应用层想办法缓解。
9. 设计小技巧
-
设计函数时,考虑扩展性,可以以
struct
形式来包裹入参,即使目前只有一个成员,也要这样做,这有利于后续扩展。 -
利用宏来整齐格式化代码。
// 旧的实现
#define FUNCID_Req_AccountInfo "1"
#define FUNCID_Req_AccountList "2"
const char* GetFunNameByFunId(const char* pFuncId)
{
if (nullptr == pFuncId)
return NULL;
if (0 == strcmp(pFuncId, FUNCID_Req_AccountInfo))
return "请求账户信息";
else if (0 == strcmp(pFuncId, FUNCID_Req_AccountList))
return "请求账户列表";
// ...
}
// 新的实现
const char* GetFunIdDescByFunId(const char* pFunId)
{
if (nullptr == pFuncId)
return NULL;
#define IF_CASE(fundid, msg) \
if (0 == strcmp(pFuncId, fundid)) \
return msg;\
IF_CASE(FUNCID_Req_AccountInfo, "请求账户信息");
IF_CASE(FUNCID_Req_AccountList, "请求账户列表");
#undef IF_CASE
}
- 当感觉用常数不合适,又不知道把新增的定义放在哪里时,新建一个文件来存放。
- 语义性定义,代码从命名上代表的意思与它实际想表达的涵义之间的距离,该距离越短越好。
- 返回值和入参,能加
const
,尽量加const
。 - 只做该做的事情,那些可以推迟的操作,就推迟到首次使用时才操作。
10. 后台服务常规模式
-
定时任务
后台程序经常需要定时处理一些事情,比如维护心跳、超时检测、空闲任务等。
-
收发请求处理机制
一般来说,是将收到的请求以及一些上下文信息打包成内部结构,丢到请求队列中去,唤醒线程池中,
由工作线程从请求队列中获取请求,进行后续的业务处理。 -
后台日志要以最短的长度,包含尽可能多有助于调试的信息。
对于每一项操作,错误的日志一定要清晰明确,正确日志看重要性决定输出。
-
优雅退出。
考虑异常情况下的退出,先停止业务处理,清理相关资源,最后销毁自身。