API设计:具体与抽象方法-最佳做法?


25

在系统之间(业务级别)讨论API时,我们团队中通常有两种不同的观点:有些人更喜欢(可以说)通用抽象方法,而另一些则是直截了当的“具体”方法。

示例:设计一个简单的“人员搜索” API。具体的版本是

 searchPerson(String name, boolean soundEx,
              String firstName, boolean soundEx,
              String dateOfBirth)

支持具体版本的人说:

  • API是自我记录的
  • 很容易理解
  • 易于验证(编译器或作为Web服务:模式验证)

我们团队中的另一组人会说“那只是搜索条件列表”

searchPerson(List<SearchCriteria> criteria)

SearchCritera {
  String parameter,
  String value,
  Map<String, String> options
}

可能使某些枚举类型的“参数”。

支持者说:

  • 在不更改API(声明)的情况下,实现可以更改,例如添加更多条件或更多选项。即使在部署时也没有同步这样的更改。
  • 即使使用具体的变体,也需要文档
  • 模式验证被高估了,通常您需要进一步验证,模式无法处理所有情况
  • 我们已经有一个与其他系统类似的API-重用

相反的论点是

  • 有关有效参数和有效参数组合的大量文档
  • 需要更多的沟通工作,因为其他团队更难以理解

有没有最佳做法?文献?


3
重复使用“ String first / name,boolean soundEx”明显是对dry的违反,这表明该设计未能解决名称与soundEx一起使用的事实。面对这样的简单设计错误,很难进行更复杂的分析
2013年

“具体”的对立面不是“泛型”,而是“抽象”。对于库或API而言,抽象非常重要,并且该讨论未能提出真正的基本问题,而是坦率地说固定在一个相当琐碎的样式问题上。FWIW,选项B的反论点听起来像是FUD的负载,如果API的设计是半清洁的并且遵循SOLID原则,则您不需要任何额外的文档或交流。
Aaronaught

@Aaronaught感谢您指出这一点(“抽象”)。可能是翻译问题,德语中的“ generisch”听起来还是可以的。对您来说,“真正的基本问题”是什么?
erik

4
@Aaronaught:问题不在于抽象。正确的修正是“通用”的对立面是“特定的”,而不是“具体的”。
Jan Hudec

对此的另一种投票不是关于通用还是抽象,而是通用还是特定。上面的“具体”示例实际上特定于名称,firstName和dateOfBirth,另一个示例对于任何参数都是通用的。两者都不是特别抽象的。我会编辑标题,但我不想发动编辑战争:-)
马特·弗雷克(Matt freake)2013年

Answers:


18

这取决于您正在谈论的领域以及如何使用它们。对于只有几个字段的高度结构化查询,混凝土是更可取的,但是如果查询趋向于是非常自由的形式,则对于三个或四个以上的字段,具体方法很快就会变得笨拙。

另一方面,保持通用API的纯净非常困难。如果您在许多地方进行简单的名称搜索,最终有人会厌倦重复相同的五行代码,然后将其包装在一个函数中。这样的API始终会演变为通用查询与最常用查询的具体包装的混合体。我认为这没有任何问题。它为您提供两全其美的体验。


7

设计好的API是一门艺术。即使时间流逝,良好的API也值得赞赏。我认为,抽象混凝土线上不应有普遍偏见。一些参数可能与一周中的几天一样具体,一些参数需要针对可扩展性进行设计(使它们具体化是非常愚蠢的,例如,函数名的一部分),而另一个参数可能甚至更进一步,以使其美观。 API之一需要提供回调,甚至特定于域的语言也将有助于对抗复杂性。

月球下很少有新事物发生。看一下现有技术,特别是已建立的标准和格式(例如,可以在提要之后对许多事物进行建模,并在ical / vcal中详细说明事件描述)。使您的API易于添加,在这里频繁出现且无所不在的实体是具体的,而预想的扩展是字典。还有一些成熟的模式可以处理特定的情况。例如,可以在具有请求和响应对象的API中对处理HTTP请求(及类似请求)进行建模。

在设计API之前,请就包括以下方面进行头脑风暴,这些方面将不包括在内,但您必须意识到。这样的示例是语言,书写方向,编码,语言环境,时区信息等。注意出现倍数的地方:使用列表,而不是单个值。例如,如果您正在为视频聊天系统设计API,则假设您有N个参与者,而不仅仅是两个参与者(即使目前的规格如此),则您的API会更加有用。

有时,抽象化可以大大降低复杂性:即使您设计的计算器仅添加3 + 4、2 + 2和7 + 6,实现X + Y可能要简单得多(在X和Y上具有技术上可行的界限) Y,并在您的API中添加ADD(X,Y),而不是ADD_3_4(),ADD_2_2(),...

总而言之,选择一种或另一种方式只是技术细节。您的文档应以具体方式描述频繁使用的情况。

无论您在数据结构方面做什么,都请提供API版本的字段。

总而言之,API应该在处理软件时最大程度地减少复杂性。要欣赏API,暴露的复杂程度应该足够。确定API的形式很大程度上取决于问题域的稳定性。因此,应该对软件及其API的发展方向做出一些估计,因为该信息可能会影响复杂性的方程式。此外,人们还可以了解API设计。如果您所在的软件技术领域有什么好的传统,请尽量不要偏离它们,因为这将有助于您理解。考虑到您写给谁。更加高级的用户会喜欢通用性和灵活性,而经验较少的用户可能会更喜欢concrets。但是,请注意那里的大多数API用户,

在文学方面,我可能会推荐“美丽代码”的主要程序员,由格雷格·威尔逊(Andrew Oram)和格雷格·威尔逊(Greg Wilson)解释他们的想法。


1

我个人的偏好是抽象,但是我公司的政策使我无法具体化。我的辩论到此结束了:)

在列出两种方法的利弊方面,您做得很好,而且如果您继续挖掘,就会发现很多有利于双方的论点。只要您的API架构开发正确(意味着您已经考虑过今天如何使用它以及将来如何发展和增长),那么无论哪种方法都应该不错。

这是我有相反观点的两个书签:

有利的抽象类

有利的接口

问问自己:“ API是否满足我的业务需求?我是否有明确定义的成功标准?它可以扩展吗?”。这些似乎是遵循的非常简单的最佳实践,但是说实话,它们比具体的与通用的更为重要。


1

我不会说抽象API肯定更难验证。如果条件参数足够简单,并且彼此之间几乎没有依赖关系,则无论您分别传递参数还是以数组形式传递参数都没有太大区别。您仍然需要对它们全部进行验证。但这取决于标准参数和对象本身的设计。

如果API足够复杂,则不能选择使用具体方法。在某些时候,您可能最终会使用带有太多参数的方法,或者使用太多无法涵盖所有​​必需用例的简单方法。根据我在设计使用的API方面的个人经验,最好在API级别上具有更多通用方法,并在应用程序级别上实现特定的必需包装器。


1

更改参数应与YAGNI一起取消。基本上,除非您实际上至少有3个不同的用例以不同的方式使用通用API,否则您进行设计的机会非常小,这样在下一个用例出现时(并且在您有情况下,您显然需要通用接口(句号)。因此,请勿尝试并准备好进行更改。

在两种情况下,都无需同步更改以进行部署。以后再泛化接口时,始终可以提供更具体的接口以实现向后兼容。但是实际上,任何部署都会有很多更改,因此您无论如何都要对其进行同步,因此您不必测试中间状态。我也不认为这是论点。

至于文档,这两种解决方案都可能易于使用且显而易见。但这是重要的论据。实施该接口,以便在您的实际情况下易于使用。有时特定可能会更好,而有时可能会通用。


1

我倾向于抽象接口方法。向此类(搜索)服务查询是一个常见问题,并且很可能会再次发生。此外,您很可能会找到更多适合重用更通用接口的候选服务。为了能够为那些服务提供一致的通用接口,我不会在接口定义中枚举当前标识的查询参数。

正如之前指出的那样-我喜欢在不修改接口的情况下更改或扩展实现的机会。添加其他搜索条件不必反映在服务定义中。

尽管毫无疑问,要设计定义明确,简洁明了的接口,但您将始终必须另外提供一些文档。为有效的搜索条件添加定义范围不是这样的负担。


1

我见过的最好的总结是Rusty的规模,现在称为Rusty的API设计宣言。我只能强烈推荐那个。为了完整起见,我引用了第一个链接的量表摘要(顶部越好,下面越差):

好的API

  • 不可能出错。
  • 编译器/链接器不会让您误解。
  • 如果弄错了,编译器会发出警告。
  • 显而易见的用法是(可能)正确的用法。
  • 名称告诉您如何使用它。
  • 正确执行此操作,否则它将始终在运行时中断。
  • 遵循通用约定,您会正确无误。
  • 阅读文档,您将正确使用。
  • 阅读实施,您将正确进行。
  • 阅读正确的邮件列表主题,您会发现正确的地方。

错误的API

  • 阅读邮件列表线程,您会出错。
  • 阅读实现,您会出错。
  • 阅读文档,您会出错。
  • 遵循常规约定,您会出错。
  • 正确执行此操作有时会在运行时中断。
  • 该名称告诉您如何不使用它。
  • 明显的用法是错误的。
  • 如果正确,编译器将发出警告。
  • 编译器/链接器不会让您正确。
  • 不可能正确。

此处此处的两个详细信息页面都对每个要点进行了深入的讨论。对于API设计人员来说,这确实是必读的。谢谢Rusty,如果您读过这篇文章。


0

用外行的话来说:

  • 抽象方法的优点是可以围绕它构建具体的方法。
  • 反之亦然

UDP的优点是允许您构建自己的可靠流。那么,为什么几乎每个人都使用TCP?
svick

还考虑了大多数用例。某些情况下可能需要如此频繁,因此可以使这些情况变得特殊。
罗曼·苏西

0

如果延长SearchCriteria的想法一点点,它可以给你的灵活性,例如创建ANDOR等标准。如果您需要这样的功能,这将是更好的方法。

否则,请设计可用性。使使用该API的人员容易使用该API。如果您有一些经常需要的基本功能(例如按名称搜索某人),请直接提供它们。如果高级用户需要高级搜索,他们仍然可以使用SearchCriteria


0

API背后的代码是做什么的?如果它很灵活,那么灵活的API就是很好的选择。如果API背后的代码非常具体,那么灵活地对待它就意味着该API的用户可能会沮丧而烦恼,而API所假装的一切都是可能的,但实际上无法实现。

对于您的人员搜索示例,是否需要所有三个字段?如果是这样,那么标准列表就不好了,因为它允许多种用途,而这些用途只是普通的行不通。如果不是这样,那么要求用户指定不需要的输入是不好的。在V2中添加按地址搜索的可能性有多大?灵活的界面比不灵活的界面更易于添加。

并非每个系统都需要具有超强的灵活性,因此架构宇航员也应尽一切努力。灵活的弓箭射箭。灵活的剑和橡皮鸡一样有用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.