代码精进之路
范学雷
前 Oracle 首席软件工程师,Java SE 安全组成员,OpenJDK 评审成员
38234 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 48 讲
结束语 (1讲)
代码精进之路
15
15
1.0x
00:00/00:00
登录|注册

21 | 怎么设计一个简单又直观的接口?

完全穷尽
相互独立
MECE原则
减少依赖关系
一个接口只做一件事
一行代码一件事
方法的标识符
Java的命名规范
为什么从问题开始?
问题分解
减少依赖关系
一个接口只做一件事
一行代码一件事
方法的标识符
Java的命名规范
为什么从问题开始?
MECE原则
从真实问题开始
使用方式要“傻”
一个接口一件事情
自然而来的接口
从问题开始
使用方式要“傻”
一个接口一件事情
自然而来的接口
问题分解
参考文章
怎么设计一个简单又直观的接口?

该思维导图由 AI 生成,仅供参考

我们前面聊过接口规范,开放的接口规范是使用者和实现者之间的合约。既然是合约,就要成文、清楚、稳定。合约是好东西,它可以让代码之间的组合有规可依。但同时它也是坏东西,让接口的变更变得困难重重。
接口设计的困境,大多数来自于接口的稳定性要求。摆脱困境的有效办法不是太多,其中最有效的一个方法就是要保持接口的简单直观。那么该怎么设计一个简单直观的接口呢?

从问题开始

软件接口的设计,要从真实的问题开始。
一个解决方案,是从需要解决的现实问题开始的。要解决的问题,可以是用户需求,也可以是现实用例。面对要解决的问题,我们要把大问题分解成小问题,把小问题分解成更小的问题,直到呈现在我们眼前的是公认的事实或者是可以轻易验证的问题。
比如说,是否可以授权一个用户使用某一个在线服务呢?这个问题就可以分解为两个小问题:
该用户是否为已注册的用户?
该用户是否持有正确的密码?
我们可以使用思维导图来描述这个分解。
分解问题时,我们要注意分解的问题一定要“相互独立,完全穷尽”(Mutually Exclusive and Collectively Exhaustive)。这就是 MECE 原则。使用 MECE 原则,可以帮助我们用最高的条理化和最大的完善度理清思路。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

接口设计在软件开发中扮演着至关重要的角色。本文从问题分解的角度出发,强调了设计简单直观接口的重要性。作者通过MECE原则将大问题分解为小问题,确保问题相互独立且完全穷尽,从而避免需求膨胀和过度设计。在问题分解的过程中,软件接口和接口之间的联系自然而然地产生,使得接口的逻辑直观、职责清晰。良好的命名规范对接口设计也被强调。此外,文章还提到了减少依赖关系和接口的“皮实”使用方式,强调了接口设计的便捷性和健壮性。总之,本文为读者提供了关于接口设计的基本原则和实用建议,有助于读者快速了解接口设计的重要性和设计方法。文章中还引用了OpenJDK的代码作为例子,提出了该设计存在的缺陷,并鼓励读者思考改进的办法,为读者提供了实际问题分解的方法,引导读者了解如何设计简单直观的接口,为接口设计提供了有益的思路和方法。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《代码精进之路》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(13)

  • 最新
  • 精选
  • hua168
    1.说到接口,现在网站接口风格还是 RESTful API,GraphQL用得少吧? 2.我看了之前的文章想起了一个实现的问题: 像我们中小公司人员流失比较大,从0开发一个电商网站的话,很多开发为了赶时间都不愿意写详细的开发文档,代码只是简单的做一下注解,交接文档也写得随便,这种情况如果原始团队的开发换完了, 那不是没有人敢动代码了?或者只剩下一两原始开发没走,不是可以要挟老板升工资了?怎么避免~~ 我们是想达到:无论开发怎么换,招一个新的开发进来就能上手,像工厂流水线那样,工人怎么走都不会影响工厂的运行,招一个新工人简单做一下培训就能上岗了,不能让“没人TA就不行”这种情况发生,需要怎做? 除了你前面的强制要求代码规范(类、方法、属性、注解、代码块、缩进、空行等)、开发文档、还有哪些工作需要做?

    作者回复: #1. 我知道的,RESTful API用的多些。我对这方面的不熟,希望留言区有人可以帮着回答。 #2. 其实,短期内我们很难做到“铁打的营盘,流水的兵”。软件开发还算是一项复杂的活动,很多时候,也会体现出是“流水的营盘,铁打的将”的现象。优秀的程序员,还是要想办法留住的。有研究表明,替换一个工程师,需要花费平均6到9个月的薪水,甚至是1.5到2年的薪水。如果我们把替换成本变成现有工程师的薪水涨幅,也许事情就简单了很多。 代码没人敢动,一个很重要的原因,就是没有回归测试或者回归测试不完备,我们搞不清修改代码带来的后果。JDK运行在几十亿台设备上,每天都做很多修改。之所以能够做这么大量的修改,除了规范、文档、评审之外,还有大量的回归测试案例。代码修改提交之前,要把相关的回归测试跑一遍。一旦修改带来了兼容性问题,回归测试就会检测出来,工程师就会知道修改带来的影响,会重新考量修改方案。 另外,要用好现代的工具,不能停留在手种刀割的时代。很多工具都是开源软件,搭建起来,形成习惯就可好了。比如,bug管理工具(bug systems), 版本控制工具(git,mercurial),这些工具都会留有历史信息,用好了可以更好地理解代码和变更。不使用这些工具,好多有价值的东西,极小一部分留在工程师的脑子里,人走了,价值也就随着走了;大部分都会被岁月冲散。JDK的开发过程中,我经常需要找找十多年前历史信息,看看当初为什么那样设计,要解决的到底是什么问题,变更起来影响可以有多坏。

    2019-02-20
    15
  • 克里斯
    作者应该是看了《金字塔原理》这本书。作者基本上到目前的章节,基本都在表述程序员版的《金字塔原理》😁

    作者回复: 搜了一下,是《金字塔原理:麦肯锡40年经典培训教材》吗?你的阅读面很广啊! 嗯,有机会我也要买来看看。作为一名老旧式的五道口技校经管的学生, 推荐大家多读读经济管理的书籍,对产品设计很有帮助的,大部分的设计思想都逃脱不了经济管理的原理范畴。

    2019-05-03
    9
  • 克里斯
    这种强依赖问题属于归纳思维中的时间顺序范涛。 但是时间顺序的过程在代码里表现为 结构顺序,丢失了时间顺序的信息。 为了解决这个问题: 一种方案是假设用户调用顺序错乱,但在方法内保证按时间顺序走,具体就是在代码里确保每个方法调用前,他依赖的逻辑一定会被调用; 一种方案是假设用户能正确按时间顺序调用,这需要我们尽可能让用户知道我们方法的顺序意图,具体就是让方法名称和注释上尽量表达出时间顺序的意图,让使用者能明确获取信息。 另外,也有两种混合搭配使用的。 个人小小的观点,望指教。

    作者回复: 这两种都是很好的方法。 第一个方案的一个小缺点是如果用户传入参数,内部整理在个别的情况下没有办法自动处理,不过大部分情况下都没有问题。第二种理论上没什么问题,就是对规范和用户的要求都有一点点高,需要规范标注;仔细阅读规范;并且遵守时间顺序。😣,编码的难处就在于要反复地妥协和平衡。

    2019-05-03
    4
  • 青年祭司
    可不可以使用这样一种思路,接口的顺序随便,最后要调一次执行接口才会真正执行之前调用的接口

    作者回复: 可以的,这是现在比较流行的设计,也就是Builder模型。不过以前的设计模式,大多不是这样工作的。

    2021-12-28
    1
  • Sisyphus235
    Signature 继承 SignatureSpi,实现签名算法。 setParameter 方法通过传入参数启动 Signature engine,initVerify 初始化 verification 对象,initSign 初始化 signing 对象。 update 更新 signed 或者 verified data,sign 返回二进制签名,verify 验证传入的签名。 排版不利于阅读,整理上面逻辑花了好长时间/::-|,方便后续读者。 Signature 类容易出错,要按规定动作执行才能完成工作,先启动 engine,再分别初始化 verification 和 signing 对象。可以写成一个内部方法按照顺序完成规定动作,而不是分开交给用户调用处理。 其他信息不多,验证签名和加密签名都是基本功能,更新数据不知道具体逻辑,或许有问题

    作者回复: 嗯,估计你使用小屏幕阅读的。代码的显示在小屏幕上,的确是个问题。 这个Signature类设计的最大问题,就来源于update()这个方法。 因为签名数据可能很大很大,这种情况下,需要分批传入数据,使用update()方法。 当然,还有更好的解决办法。

    2019-05-23
    1
  • LeasonZ
    问题: 如何划分一件事? 就像吃饭,简单说吃饭就是拿东西吃,更细一点变成了拿食物->张嘴->咬->咀嚼->吞咽,再细点就变成肌肉运动,电信号之类的.所以划分维度在代码设计时该怎么去把控,这是长久困扰我的一个疑问,希望老师能解答下

    作者回复: MESE的原则是划分到事实为止。用到代码上,划分到有现成方法可以用为止。如果有吞咽这个方法,划到吞咽就够了。

    2019-03-20
    1
  • 唐名之
    我们习惯这样写,: private final void initVerify(PublicKey publicKey) throws InvalidKeyException { // snipped } private final void initSign(PrivateKey privateKey) throws InvalidKeyException { // snipped } private final void update(byte[] data) throws SignatureException { // snipped } public final byte[] sign(PrivateKey privateKey, byte[] data) throws InvalidKeyException, SignatureException { initSign(privateKey); return update(byte); } public final boolean verify(PublicKey publicKey, byte[] signature) throws InvalidKeyException,SignatureException { initVerify(publicKey); // snipped }

    作者回复: 这样适合处理小数据,如果签名数据很大,比如文件,图像,sign方法需要占用的内存太多,占用时间太长(data参数)。verify方法怎么传要签名的数据呢?

    2019-02-20
    1
  • 南山
    老师,请教个疑惑: 1.简单直接、职责单一,和接口数量爆炸冲突吗?比如查询的接口,查询条件多的话想让接口简单、单一就可能要拆分多个接口,更新也是类似 2.接口完整独立和提供一些基础、原子的接口,由调用方来编排又该怎么设计取舍呢

    作者回复: 问题一:不冲突,接口数量爆炸往往是没做好抽象以及设计层次没出来。 问题二:这是面向对象的基本问题。设计可以是多个层次的,基础的层次解决基础的问题;然后抽象更高的层次,组合下一次的接口。比如String是一个类,Person是一个类,Company也是一个类,后面的类组合使用前面的接口。

    2023-07-22归属地:江苏
  • ifelse
    是不是可以用门面模式,提供外部一个统一的调用接口?
    2022-07-23
  • ifelse
    接口设计的困境,大多数来自于接口的稳定性要求。摆脱困境的有效办法不是太多,其中最有效的一个方法就是要保持接口的简单直观。--记下来
    2022-07-23
收起评论
显示
设置
留言
13
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部