关于接口可维护性的一些建议 | 京东云技术团队

news2024/11/12 20:43:08

作者:D瓜哥

在做新需求开发或者相关系统的维护更新时,尤其是涉及到不同系统的接口调用时,在可维护性方面,总感觉有很多地方差强人意。一些零星思考,抛砖引玉,希望引发更多的思考和讨论。总结了大概有如下几条建议:

  1. 在接口注释中加入接口文档链接

  2. 将调用接口处写上被调用接口文档链接

  3. 将接口源代码发布到私服仓库

  4. 对于状态值常量,优先在接口参数类或者返回值类中定义

  5. 如果使用 Map 对象作为传输载体,要提供 Key 值定义常量

  6. 针对 Map 返回值,可以考虑使用将 Map 转化成对象

  7. 尽可能简化接口依赖

  8. 只传递必要字段,尽量避免大而全的接口

  9. 将接口的参数和返回值原始数据打印到日志中

  10. 将 RPC 接口的类名及方法打印到日志中

  11. 核心思想:以人为本,就近原则,触手可及

下面,D瓜哥对每一条建议做一个详细说明。

1. 在接口注释中加入接口文档链接

在做接口开发时,无论是对自有接口的升级改造,还是针对外部接口的从头接入,都涉及到接口文档。不同之处是,前者的工作重点是书写或者更新接口文档;而后者是根据接口文档开发合适的接入代码。但是,经常遇到的一个麻烦是,找不到接口文档。在组内需要找老同事询问;如果是跨部门,还需要两层甚至三层的进行转接,非常麻烦。

D瓜哥认为,在这种情况下,为了方便大家维护,最好的办法就是将接口文档链接直接放在代码注释中,这样后续维护的人员,直接就可以点击链接直达接口文档,简单方便高效。如果是新建的接口,就可以先创建一个空文档,把链接放在注释中,后续再书写文档内容。如果是维护已有接口,可以在维护时,将缺失的链接加入到注释中,自己方便,也方便其他人进行后续的维护更新。这样,在循序渐进的过程中,逐步就可以把文档链接补充到代码中,方便维护代码,也同步更新文档。

2. 将调用接口处写上被调用接口文档链接

在调用其他系统的接口时,没有接口文档,几乎寸步难行。在第一次接入接口时,绝大多数情况下,都是参考着接口文档做接入工作。但是,目前的情况时,接入时参考文档,参考完就随手把文档给“扔了”。后续如果还需要做进一步升级维护,还需要到处找接口文档;另外,交互的系统难免有一些 Bug,在和其他系统维护人员对接处理 Bug 时,只有接口没有文档,对方可能也需要去找文档链接。无形中,很多时间都浪费在了找文档的过程中。

D瓜哥最近尝试了一个实践,就是在接口调用的地方,把接口文档链接当做注释加入到代码中。这样,无论是后续维护升级,还是沟通协调处理问题,都非常方便。别人问接口是什么,连接口+文档都可以一把复制就搞定。

经过最近一段时间的实践情况来看,这个处理非常方便,是一个非常值得推广的实践。再插一句,也可以像一条建议一样,可以在维护代码时,不断把已接入的接口文档加入到调用接口的地方,循序渐进,方便后续人维护升级。

3. 将接口源代码发布到私服仓库

接口文档链接在注释中,在构建结果中就不复存在了。所以,为了方便接口使用方可以在接口中查询到对应的接口文档,就需要把源码也发布到私服仓库中。

这里只说明一下 Java 的相关处理办法。如果使用 Maven 作为构建工具的话,默认是不会将源代码发布到私服仓库中的。关于如何将源代码发布到,在 升级 Maven 插件:将源码发布到私服仓库 中已经做过相关介绍,这里就不再赘述。

除了将源码发布到私服仓库,另外,还建议编译构建时,保持方法的原始参数命名。这个也可以通过配置 Maven 插件来完成,具体配置见: 升级 Maven 插件:字节码文件包含原始参数名称。

4. 对于状态值常量,优先在接口参数类或者返回值类中定义

在做接口开发时,很多数据都有一个状态值,比如订单状态,再比如接口状态等等。目前的一个情况时,这些状态值大部分书写在文档中,在接入接口时,需要接入方自定义这些状态值。这就有些繁琐了,而且状态定义也不明确,甚至有可能遗漏一些重要的状态值。有些懒省事,直接在代码中硬编码一个魔法值,后续维护的跟还需要根据上下文反推这个值的含义,非常不利于维护。

D瓜哥个人觉得,有两个处理办法:

  1. 如果状态值不是很多,优先在接口参数类或者返回值类中定义。

  2. 如果状态值很多,可以考虑单独抽取成一个常量类或者枚举类。

这样使用的时候,触手可及。不需要到处去找。

5. 如果使用 Map 对象作为传输载体,要提供 Key 值定义常量

有些系统可能考虑方便增加字段,选择使用 Map 作为数据载体。自己开发的时候很爽,但是给接口接入却非常不友好。接入方从 Map 中获取数据时,要么自己定义 Key 值;要么直接使用魔法值硬编码在代码中。使用前者方案,就需要在各个接入方都需要自定义一套;使用后者,初期是省事了,后来维护的人员就懵逼了。这都无形中增加了很多维护成本。

D瓜哥觉得一个方案更优,那就是直接由接口提供方来定义这些可以取值的 Key 值常量。这样,任何接入方都可以直接使用这些常量。

6. 针对 Map 返回值,可以考虑使用将 Map 转化成对象

针对 Map 的处理,即使按照 如果使用 Map 对象作为传输载体,要提供 Key 值定义常量 推荐的做法,定义了相关的 Key,在取值时,也略有麻烦,需要不断的 map.get(KEY)。一个更简单的方法是自定义一个类型,使用工具将 Map 对象转化成自定义类型的对象。这样就可以直接使用方法调用来取值。

在 Java 中,可以直接使用 Jackson 来完成这个转换工作。工具类代码如下:

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;

import java.util.*;java

/**
 * Map 工具类
 *
 * @author D瓜哥 · https://www.diguage.com
 */
@Slf4j
public class MapUtils {
    private static final ObjectMapper MAPPER = new ObjectMapper();

    static {
        MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * 将 Map 转换成指定类型的对象
     *
     * @author D瓜哥 · https://www.diguage.com
     */
    public static <T> T convertToObject(Map<String, Object> data, Class<T> clazz) {
        try {
            T result = MAPPER.convertValue(data, MAPPER.getTypeFactory().constructType(clazz));
            if (log.isInfoEnabled()) {
                log.info("converted {} to a {} object: {}",
                        JsonUtils.toJson(data), clazz.getSimpleName(), JsonUtils.toJson(result));
            }
            return result;
        } catch (Exception e) {
            log.error("converting failed! data: {}, class: {}",
                    JsonUtils.toJson(data), clazz.getSimpleName(), e);
        }
        return null;
    }

    /**
     * 将 Map 转换成指定类型的对象
     *
     * @author D瓜哥 · https://www.diguage.com
     */
    public static <T> List<T> convertToObjects(List<Map<String, Object>> datas, Class<T> clazz) {
        if (CollectionUtils.isEmpty(datas) || Objects.isNull(clazz)) {
            return Collections.emptyList();
        }
        List<T> result = new ArrayList<>(datas.size());
        if (CollectionUtils.isNotEmpty(datas)) {
            for (Map<String, Object> data : datas) {
                T t = convertToObject(data, clazz);
                result.add(t);
            }
        }
        return result;
    }
}



7. 尽可能简化接口依赖

现在,很多对外暴露接口的定义是,接口定义放在一个模块中;模型定义在一个模块中;有些工具类又定义在一个模块中。接口依赖模型模块;模型模块又依赖工具类模块;而工具类依赖了一大堆外部依赖。个人觉得这是一个非常不好的实践。会导致很多不必要的依赖被间接引入到了接口使用方的系统中,无形中增加很多维护成本。

D瓜哥推荐的一个实践是:将接口和模型定义放在一个模块中,对外暴露也只需要这一个模块即可。接口使用方只需要引入这一个依赖。避免引入很多无用的其他外部依赖。如果模型需要依赖一些公共的父类,可以考虑将这些单独定义在一个模块中,这个模块只保存多个系统依赖的公共类,并且剔除掉一些工具类的定义,这样就可以保证接口依赖的纯净性。如果其他系统需要工具类,让其明确去引入,而不是被动依赖。

对于前面 对于状态值常量,优先在接口参数类或者返回值类中定义 中提到了“如果状态值很多,可以考虑单独抽取成一个常量类或者枚举类。” 这里存在一种情况需要特别说明,状态值的定义需要在本系统的业务模块的代码中使用,可以将接口的依赖加入到改业务模块的依赖中,而不是反过来。为什么会这样的操作?一个核心思想是保持对外暴露接口的纯净性。这样既可以减少状态定义的重复性,又可以减少接口的外部依赖。

8. 只传递必要字段,尽量避免大而全的接口

观察很多系统,尤其是一些以业务为核心的系统的对外暴露接口,很多接口是大而全的接口,一个接口就可以把指定数据的所有信息全部返回出去。这样,很多字段需要去识别,也要在众多字段中区筛选出来符合自己要求的数据,无形中浪费了很多心智,不利于维护。

D瓜哥认为,在做接口开发时,一定要做一个“吝啬的守财奴”。把数据当做财富一样守护,对外只提供必要的数据,做到“够用就行”。

这一点不仅仅是维护上的考虑,还有数据传输效率的点。在其他条件相同的情况下,更小的数据,无论是机器处理效率,还是传输效率,都会更快更高。

关于传输效率上的一些思考,结合 Hessian、Msgpack 和 JSON 实例对比 以及 “Hessian 协议解释与实战” 等文章来看,有几个原则值得重视的:

  1. 优先使用 boolean 型;

  2. boolean 型满足不了,次优选择 int 整型数据;再次可以考虑 long 型;

  3. 日期优先使用内置的日期类型(含 Java Time API 类型),而不是格式化成字符串。

  4. 对于以上类型不满足,则选择使用字符串。

  5. 集合类型,链表优先使用 ArrayList,也可以考虑使用 Iterator;哈希优先使用 HashMap;

  6. 以上情况都不符合要求才选择自定义对象。

9. 将接口的参数和返回值原始数据打印到日志中

据观察,一些开发人员没有将接口,尤其是 RPC 接口的参数及返回值打印到日志中。这对定位问题非常不利。说的更直白一点,非常不利于甩锅。当出了问题,不能第一时间就凭借参数及返回值顺利甩锅。可能导致自己花很多时间去排查问题,最后发现是自己依赖的其他系统的问题。

所以,一定要谨记,将接口的参数和返回值原始数据打印到日志中。D瓜哥凭借这个实践,在一些客诉及反馈中,顺利脱身,实现完美甩锅。

10. 将 RPC 接口的类名及方法打印到日志中

D瓜哥也在尝试一个实践:将 RPC 接口的类名和方法,再加上参数或者返回结果,同时打印到日志中。

这里为什么和上面的 将接口的参数和返回值原始数据打印到日志中 单独列出来?因为,在这个实践中,强调的是 “RPC 接口”。相对来说, RPC 接口存在更多容易出错的问题,经常需要脱离系统去单独测试 RPC 接口的可用性。把类名就方法名可以更方便在出现问题时,就可以及时根据日志中的信息,去单独测试 RPC 的可用性。

11. 核心思想:以人为本,就近原则,触手可及

洋洋洒洒总结了这么几条建议。这里做一个总结。

对于可维护性建议的一个核心思想就是:以人为本,就近原则,触手可及。通常来说,人都是有一定的惰性的。如果把饭端到眼前,相信任何正常人无法抗拒美食的诱惑。而这里提到的一些可维护性的点,就是尽可能照顾人“懒”的特性,在第一次时,就把该做的工作做到位,减少后续人员不必要的麻烦,让人可以“合法偷懒”。

加油!争取让更多人可以更好地偷懒。💪🏻💪🏻💪🏻

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/536069.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TOB企业生态体系构建的核心要素有哪些?

To B市场作为一个非常庞大的领域&#xff0c;其复杂度和多元化水平&#xff0c;要远远要高于针对于消费者群体推进的市场。尤其近年来&#xff0c;消费互联网成为过去式&#xff0c;爆发式增长的时代结束&#xff0c;让资本、媒体的目光开始聚焦到以B2B企业所代表的产业互联网身…

人工智能与机器人|机器学习

原文链接&#xff1a; https://mp.weixin.qq.com/s/PB_n8woxdsWPtrmL8BbehA 机器学习下包含神经网络、深度学习等&#xff0c;他们之间的关系表示如图2-7所示。 图2-7 关系图 那么什么是机器学习、深度学习、他们的区别又是什么呢&#xff1f; 2.7.1 什么是机器学习&#x…

Unity中Camera.main和Camera.current的区别

在Unity中&#xff0c;Camera.main和Camera.current都是用来获取相机&#xff0c;那到底有什么区别呢&#xff1f; 一、异同及注意事项 1、相同点&#xff1a; Camera.main和Camera.current都是用于获取相机的属性。它们都是静态属性&#xff0c;可以通过Camera类访问。它们…

【Redis】Redis set类型实现点赞功能

文章目录 set 数据类型介绍不排序实现排序实现 set 数据类型介绍 Redis中的set类型是一组无序的字符串值。 set通过其独特的数据结构和丰富的命令提供了在存储和处理集合元素方面的一些非常有用的功能。下面列出了主要的set类型命令&#xff1a; SADD key member1 [member2]&a…

国产 API 工具天花板,用来搞项目真的不错

一、API爆炸的时代 随着最近行业的移动化、物联网化、数字化转型、微服务等多种概念的提出&#xff0c;对应的 API 数量已经呈现出爆炸式增长&#xff0c;由此带来的问题就是前后端的接口对接问题越来越来突出&#xff0c;我们能很难找到一个合适的技术工具提高我们的效率。由此…

irq_domain msi_domain 的 使用和关系

文章目录 如何创建一个 irq_domain如何创建一个 msi_domainirq domain 和 msi domain 的关系MSI对IRQdomain打的补丁变量结构体API初始化时的API申请IRQ时的API 实例 GICV3-ITS & GICV3-ITS-PCI 相关结构体变量所在文件 如何创建一个 irq_domain 内核文档说,我们可以调用如…

基于 FPGA 的彩色图像灰度化的设计实现

文章目录 前言一、系统整体设计二、各模块的功能三、彩色图像灰度化处理模块的设计1.基本原理2.彩色图像灰度化处理方法介绍方法 1&#xff1a;分量法方法 2&#xff1a;最大值法方法 3&#xff1a;平均值法平均值法的实现方法 4 加权平均法加权平均法的实现rgb2gray 模块rgb2g…

LeetCode965. 单值二叉树,100. 相同的树

965. 单值二叉树&#xff0c;100. 相同的树 965描述示例解题思路以及代码 100描述示例解题思路以及代码 965 描述 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。 只有给定的树是单值二叉树时&#xff0c;才返回 true&#xff1b;否则返回 fals…

获得GitHub Copilot并结合VS Code使用

一、什么是GitHub Copilot GitHub Copilot是一种基于AI的代码生成工具。它使用OpenAI的GPT&#xff08;生成式预训练Transformer&#xff09;技术来提供建议。它可以根据您正在编写的代码上下文建议代码片段甚至整个函数。 要使用GitHub Copilot&#xff0c;您需要在编辑器中…

(四)Photon Voice2 的使用

一、入门 1.Voice简介 Photon Voice 2 是一个 SDK&#xff0c;可以轻松地向 Unity 应用程序添加高质量的低延迟语音聊天。它建立在 Photon Realtime 之上&#xff0c;并继承了它的所有功能&#xff0c;包括配对和兴趣小组。客户端加入房间并创建传出流&#xff08;本地语音&a…

双目、结构光、tof,三种深度相机的原理区别看这一篇就够了!

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是助手…

win10屏幕录像哪个好用?这2款录屏软件值得推荐!

案例&#xff1a;win10电脑如何录屏&#xff1f; 【我使用的电脑是win10系统&#xff0c;我想对它进行屏幕录制。有没有小伙伴知道win10电脑如何录屏&#xff1f;win10录屏软件哪款比较好用&#xff1f;】 在如今互联网时代&#xff0c;屏幕录像是一项非常实用的功能&#xf…

【大数据之Hadoop】三十二、MapReduce生产经验

1 MapReduce跑的慢的原因 MapReduce程序效率的瓶颈在于两点&#xff1a; 1&#xff09;计算机性能&#xff1a;CPU、内存、磁盘、网络 2&#xff09;I/O操作优化 &#xff08;1&#xff09;数据倾斜 &#xff08;2&#xff09;Map运行时间太长&#xff0c;导致Reduce等待过久 …

高斯-约旦消元法

1&#xff0c;思想 该算法基于高斯消元&#xff0c;但是思想是把矩阵化为对角阵&#xff0c;从而避免回代的操作思路是一列一列处理&#xff0c;每次选为处理过的行中主元最大&#xff08;极大避免精度损失)的行处理。把这一列除了主元这一行的元素&#xff0c;其余该列的其他…

c#笔记-委托

委托 委托是方法的类型。 有了类型就可以声明方法的变量&#xff0c;参数&#xff0c;字段。然后再调用他。 很多新人很困惑&#xff0c;为什么要把方法做成变量&#xff0c;不直接去调用它呢&#xff1f; 这是因为在目前你的认知里&#xff0c;代码都是你一个人写出来的。 如…

计算机图形学-GAMES101-5

一、观测变换 通过Viewing(观测)transformation&#xff0c;最终我们将世界坐标系中的物体投影到了一个xyz都处于【-1,1】的立方体中。那么接下来&#xff0c;我们该做什么&#xff1f; 二、光栅化(Rasterization) &#xff08;1&#xff09;透视投影的定义 定义透视投影的视…

〖大学生·技术人必学的职业规划白宝书 - 职业规划篇①〗- 大学生选择职业前的自我认知与剖析

历时18个月&#xff0c;采访 850 得到的需求。 不管你是在校大学生、研究生、还是在职的小伙伴&#xff0c;该专栏有你想要的职业规划、简历、面试的答案。说明&#xff1a;该文属于 大学生技术人职业规划白宝书 专栏&#xff0c;购买任意白宝书体系化专栏可加入TFS-CLUB 私域社…

linux之线程同步

1. 互斥锁 定义锁&#xff1a; pthread_mutex_t 初始化锁函数&#xff1a; int pthread_mutex_init(pthread_mutex_t* mutex,pthread_mutexattr_t* attr); 第一个参数是定义的互斥锁的地址&#xff0c;第二个参数是锁的属性&#xff0c;一般传NULL 互斥锁的属性在创建…

(C语言版)力扣(LeetCode)+牛客网(nowcoder)二叉树基础oj练习

二叉树基础oj练习 965. 单值二叉树题目解法 100. 相同的树题目解法 101. 对称二叉树题目解法 144. 二叉树的前序遍历题目解法 94. 二叉树的中序遍历题目解法 145. 二叉树的后序遍历题目解法 572. 另一棵树的子树题目解法 KY11 二叉树遍历题目解法 结语 965. 单值二叉树 题目 …

Linux安装MongoDB数据库并内网穿透在外远程访问

文章目录 前言1.配置Mongodb源2.安装MongoDB数据库3.局域网连接测试4.安装cpolar内网穿透5.配置公网访问地址6.公网远程连接7.固定连接公网地址8.使用固定公网地址连接 转发自CSDN cpolarlisa的文章&#xff1a;Linux服务器安装部署MongoDB数据库 - 无公网IP远程连接「内网穿透…