针对作用于数组的函数的API设计

7
我正在使用Java设计一个API,用于一组作用于double数组的数字算法(实时金融统计)。出于性能原因,API必须使用基本数据类型数组,因此List < Double >等不是选项。
典型的使用案例可能是一个算法对象,它接受两个输入数组,并返回一个包含从两个输入计算出的结果的输出数组。
我想确立一致的约定,以便在API中如何使用数组参数,特别是:
- 我是否应该在所有函数中都包括偏移量,使用户可以操作较大数组的部分,例如 someFunction(double[] input, int inputOffset, int length)? - 如果一个函数需要输入和输出参数,那么输入或输出应该首先出现在参数列表中吗? - 调用者是否应分配输出数组并将其作为参数传递(可能会被重用),还是函数每次调用时都创建并返回一个输出数组?
目标是实现效率、API用户的简单性和API内部以及已建立的约定之间的一致性的平衡。
显然,有很多选择,那么什么是最佳的API设计呢?
6个回答

3

所以这听起来像是三个问题,以下是我的观点。

当然,这非常主观 - 因此 - 你的经验可能会有所不同:

  1. 是的。始终包括偏移量和长度。如果特定函数的大多数用例不需要这些参数,请重载该函数,以便输入和长度不是必需的。

  2. 对于这个问题,我会遵循arraycopy使用的标准:

    arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

  3. 这里的性能差异将是可以忽略不计的,除非调用者反复调用您的实用程序函数。如果它们只是一次性的事情,就不应该有任何区别。如果它们被反复调用,那么您应该让调用者向您发送已分配的数组。


2
  1. 如果可以的话,请提供一个默认选项(从0开始,完整长度)。
  2. 我认为大多数用户都希望得到第二个输出结果。但是,如果您可能使用varargs,那么可能会改变您的想法。
  3. 我喜欢调用者传递输出数组,但是有一个选项可以为null,这意味着该方法将进行分配。

关于可变参数的评论,假设您有一个添加两个数组的方法。如果您将输出数组参数作为第一个参数,并将2个输入数组放在最后,那么将该方法扩展为添加N个数组是微不足道的。

进一步解释#3,让调用者传递输出数组,有时更加高效。即使收益微不足道,您的用户也可能来自C或FORTRAN背景,并认为收益将很大,如果您不允许他们“高效”,他们会抱怨的。 :-)


2
假设您正在处理足够小以在堆栈或伊甸园中分配的数组,则分配非常快。因此,让函数分配自己的数组以返回结果是没有问题的。这对于可读性来说是一个巨大的优势。
我建议首先使您的函数操作整个数组,并引入一个选项,仅在发现它有用时才使用切片调用函数。

我认为他在用Java写代码,所以“on the stack”这个评论在那种情况下没有意义。 - user949300
1
不,HotSpot将不逃逸的小对象分配在堆栈上。 - Russell Zahniser
但是由于这些数组作为结果返回,它们肯定会逃逸。 - user949300
不需要,因为Hotspot可以内联方法调用。实际上,我的观点只是,“让代码看起来干净整洁,将优化留给JVM处理。” - Russell Zahniser
95%的时间我同意你的观点。 - user949300

1
API设计中暴露多个函数的主要问题在于其内部一致性。其他所有事情都是次要的。
是否传递索引/长度对取决于API的预期使用方式。如果您希望用户编写一系列方法调用,这些方法在同一数组的不同段中获取或放置数据,例如System.arrayCopy,则需要索引/长度对。否则,这是一种过度设计。
输入优先还是输出优先由您决定,但一旦做出决定,请在所有具有类似签名的方法中坚持该决定。
仅当客户端重复使用缓冲区时,传递输出缓冲区才是合理的选择。否则,在构建和维护额外的API方法集方面会浪费精力。当然,这个决定与您选择使用索引/长度对的决定密切相关:如果您采用索引和长度,则还应该采用输出缓冲区。

1

我认为API设计在很大程度上是主观的,或者应该受到API“用例”的重要影响。而另一方面,你的API的用例完全取决于客户端代码。

话虽如此,就个人而言,我会利用方法重载并采用以下结构:

一个带有所有参数的方法:

void someFunction(int[] input1, int[] input2, int offset, int length, int[] output)

这是主要函数。所有其他函数只需使用适当的参数调用此函数。

int[] someFunction(int[] input1, int[] input2, int offset, int length)

这将调用第一个函数,但代表调用者分配并返回输出数组。

void someFunction(int[] input1, int[] input2, int[] output)

int[] someFunction(int[] input1, int[] input2)

请注意,一般策略是通过消除“可选”参数来缩短参数列表。

一般来说,我倾向于避免根据参数(如输出数组)是否为null而更改方法行为,因为那样会使捕捉微妙错误变得更加困难。因此,我更喜欢两种不同的调用风格——一种提供(并且需要)输出参数,另一种则是方法返回其输出。

0

我会使用 List<Double> 并让方法将输出作为新的 List 返回:

public List<Double> someFunction(List<Double> input)

通常是好建议,但在我的情况下不可行 - 由于性能原因,API必须使用原始数组工作。 - mikera

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接