开发实践思考(七)

1. REST风格接口

REST风格接口: 使用URL定位资源,用HTTP方法(GET,POST,PUT,DELETE)描述操作:

  1. GET:获取资源
  2. POST:新建/更新资源
  3. PUT:更新资源
  4. DELETE:删除资源

通过HTTP状态码知道结果。

在设计RestFul API,原则上不使用动词,而是使用名词,推荐使用复数。例如:


api.qc.com/v1/newsfeed   获取某人的新鲜事
api.qc.com/v1/friends    获得某人的好友列表
api.qc.com/v1/profile    获得某人的详细信息

保证HEADGET方法是安全的,不会改变资源状态。

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. 接口对接建议

与后端接口对接,有以下实践经验:

  1. 保持接口返回的数据变量名与接口文档中的字段名保持一致,一致命名,增强可读性。
  2. 接口保存原始数据,如果约定的需要进行特殊处理,接口层可提供一系列辅助转换函数,用于上层显示、逻辑判断等。但不主动去进行转换,提供转换工具,是否转换由上层来决定。

8. 第三方库使用

  • 涉及到的第三方库接口功能越基础越好,数量越少越好。
  • 尽量使用第三方库的基础功能,当需要到高级功能时,通过封装基础功能来实现
  • 凡是第三方库,在项目中引入时,要封装一层,对外提供与项目风格一致的接口,这样方便后续扩展以及变更。

使用第三方库前,要有如下测试:

  1. 功能测试
  2. 性能测试
    以上是判断第三方库是否符合业务功能、性能要求以及一般的惯用法。
  3. 压力测试
  4. 故障测试
    以上两者需要第三方库以单独服务形式部署,对外提供服务时的测试。

如果第三方库升级了,应用依赖的第三方库要不要升级?
方案一:升级至稳定版本,在升级之前,跑自己业务所使用功能的单元测试,判断新版本是否提供同等约定。
方案二:基本不升级,自己团队维护某个特定版本
方案三:不跟随升级,遇到第三方库的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. 后台服务常规模式

  1. 定时任务

    后台程序经常需要定时处理一些事情,比如维护心跳、超时检测、空闲任务等。

  2. 收发请求处理机制

    一般来说,是将收到的请求以及一些上下文信息打包成内部结构,丢到请求队列中去,唤醒线程池中,
    由工作线程从请求队列中获取请求,进行后续的业务处理。

  3. 后台日志要以最短的长度,包含尽可能多有助于调试的信息。

    对于每一项操作,错误的日志一定要清晰明确,正确日志看重要性决定输出。

  4. 优雅退出。

    考虑异常情况下的退出,先停止业务处理,清理相关资源,最后销毁自身。

posted @ 2022-02-12 11:12  浩天之家  阅读(54)  评论(0编辑  收藏  举报