不要让接口过早失去可选项

在一些数据处理场景中,我们经常会设计这样的接口:

复制代码
byte[] GetImageArgb();

它看起来很自然:调用方法,得到一段结果数据。

但在某些情况下,这类接口会带来一个不容易察觉的问题:

后续的优化空间,被提前限制了。

问题从哪里开始?

这个接口同时做了两件事:

  • 表达能力:获取 ARGB 数据

  • 引入决策:用 byte[] 承载,并由方法内部完成分配

这两件事本质上属于不同的关注点:

  • 前者是"能力表达"

  • 后者是"使用体验优化 / 默认策略"

问题在于:

它们在接口层被混在了一起。

当关注点没有分离

一旦能力与策略耦合:

  • 默认决策成为唯一路径

  • 调用方无法绕开

  • 系统行为被提前固定

换句话说:

可选项在接口层被提前收敛了。

而这违背了一个很基本的设计准则:

尽可能保留更多的可选项。

这类问题并不局限于接口

类似的情况,在更大尺度的设计中也很常见:

  • 过早绑定某种数据库、图像处理库

  • 过早确定系统形态(如 B/S、C/S)

它们的共同点是:

在能力尚未被充分表达之前,就提前锁定了实现路径。

也就是说,本该后置的决策,被前移到了更早的阶段。

回到接口设计

但在这篇文章中,我们只关注一个更基础、也更容易被忽视的层面:

接口设计。

因为一旦在这里没有处理好,这种"过早做决定"的问题,就会以更隐蔽的方式渗透进整个系统,并在后续被不断放大。

在图像处理这类数据密集场景中,这种影响尤为直接:

  • 数据被强制写入新的缓冲区

  • 内存分配策略无法控制

  • 上下游无法复用同一块数据

最终结果是:

零拷贝链路在接口处被打断;离高性能目标更远。

一旦数据在这里被"落地"为一个新对象,

后续即使再优化,也很难恢复原本可以存在的数据流动方式。

更合理的设计方式

更健康的方式是把关注点拆开:

先表达能力本身,再叠加默认使用方式。

例如:

复制代码
void GetImageArgb(Span<byte> destination);

这个接口只表达能力:

  • 不分配内存

  • 不决定数据归属

  • 只负责写入

在此之上,再提供便利接口:

复制代码
byte[] GetImageArgb()
{
    var buffer = new byte[...];
    CopyImageArgbTo(buffer);
    return buffer;
}

这样:

  • 想简单用 → 有默认路径

  • 想优化 → 可以绕开默认路径

写在结尾

在设计接口时,可以问一个问题:

这个接口中的默认决策,是否可以被绕开?

如果可以,它只是优化;

如果不可以,它就开始限制系统的能力。

问题从来不在于是否做了优化,而在于:

优化是否在表达能力时,就已经参与并固定了决策。

一旦如此,可选项就会在接口层被提前收敛。

因此,一个更稳妥的设计目标不是"替调用方做出更方便的决定",而是:

在提供默认路径的同时,仍然保留做出不同选择的可能。