是否有充分的理由使纯函数不公开?


25

我和一位同事进行了一些辩论。简而言之,是否有充分的理由隐藏/封装纯函数?

“纯”是指维基百科定义

  • 始终从相同的输入返回相同的结果。(出于讨论的Foo Create(){ return new Foo(); }目的,如果Foo不具有值语义,则认为这是不纯的。)
  • 不使用可变状态(局部变量除外)或I / O。
  • 不会产生副作用。

26
可能有一个论点,就是根本不让这样的函数成为类的成员。
Pieter B

6
@PieterB-的确,尽管不是所有语言都支持,有些语言甚至允许模块内部使用免费功能。
Telastyn 2014年

3
您对此有何看法?我认为函数的纯度与它是否属于公共API无关。
Andres F.

@andresF。-辩论实际上围绕是否应公开无约束的验证规则。我认为,由于这是一个纯函数,因此几乎没有危害。对于此特定实例,它有助于可测试性,并且有可能被重用。这个问题更多地是关于该论点在多大程度上可以适用。
Telastyn 2014年

2
@Telastyn如果它是可重用的,但不是当前类职责的一部分,则应该将其放在单独的类/模块/所用语言中。然后,作为新类/模块的职责的一部分,必须将其公开。既然您提到测试,那么我会指出,您不必在执行此操作时就模拟该方法。作为“实现细节”,在测试过程中对其进行模拟几乎没有好处。
jpmc26 2014年

Answers:


76

纯函数仍然可以是实现细节。尽管该函数可能不会造成任何危害(从不破坏重要的不变式/合同的角度来看),但通过暴露该函数,该类/模块/程序包的作者和用户都将遭受损失。作者之所以失败,是因为现在即使实现更改并且该功能不再对他有用,他也无法将其删除。用户会迷失方向,因为他们不得不仔细检查并忽略与使用该API无关的其他功能,以了解它。


33
+1。类的公共成员是其API。这不是“如果公开会伤害我吗?”的问题,而是“它应该成为API的一部分吗?”的问题。
2014年

1
我唯一要补充的是,如果您开始看到在其他位置弹出类似的函数,请将其重构为另一个类,以便多个类可以使用它。或者,如果您一开始就担心这个问题,请在一个单独的类中对其进行定义。
jpmc26 2014年

1
我会密封那些不打算扩展的公共课程。
2014年

+1指出隐藏或显示函数(或类)主要是保护和维护API的问题,与它的代码类型无关。
Marco Marco

42

问题是向后的。

您并没有寻找使函数不公开的理由。一开始(我认为)是一种错误的心态。推理应该走另一条路。

换句话说-不要问“为什么要将其设为私有?”。问:“为什么要公开?”

如有疑问,请不要暴露它。这有点像奥克汉(Ockham)的剃刀-不要在不必要的时候增加实体。

编辑:解决@Telastyn在评论中提出的反对意见(以避免在那里进行扩展讨论):

我听说过一段时间,甚至在相当一段时间内都拥护它,但是以我的经验,事情往往过于私密。

是的,有时候,如果一个类是开放继承的,那会很痛苦,但是您不能覆盖某些私有方法(您想改变其行为)。

但是protected就足够了-而且它仍然是非公开的。

这会导致大量代码重复和开销,导致无法“访问”不应该公开的事物。

如果出现问题,只需将其公开!有我在谈论的必要:)

我的观点是,您不应该只是为了以防万一(YAGNI等)。

请注意,将私有功能公开比将其私有化总是容易的。后者可能会破坏现有代码。


我听说过一段时间,甚至在相当一段时间内都拥护它,但以我的经验来看,事情往往过于私密。它导致很多代码重复和开销,导致“不应该公开的东西”无论如何都可以间接访问。
Telastyn 2014年

3
@Telastyn恕我直言,听起来该应用程序遭受了一些设计失误的困扰;如果您发现自己由于需要在离散的代码路径之间调用而公开一个方法,则可能表明该方法应分解为自己的模块,可以在适当的地方插入或包含该模块,尤其是在使用纯函数的情况下。
leo

@leo-对不起,我不关注。考虑一个小于整数的函数。这是一个很好的纯函数,专门公开了它,因为可以在适当时将其注入并包含(或简单使用)。
Telastyn 2014年

5
@Telastyn在我看来,这是“一切都是对象/类”哲学的症状,加上某些OOP语言除类之外没有其他复合数据类型这一事实。某人需要同时传递两个值时,他们最终会写一个类,然后每个同事和样式检查软件都会告诉您将这些字段设为私有。
2014年

@Telastyn对。我的意思是,如果您的“ integerLessThan”方法开始时是公共方法的封装实现细节,但是您发现需要在其他地方调用私有方法,则可能表明该私有方法属于其他方法模块/包/类,以便可以独立于调用它的逻辑来导入它。简单地按原样公布该方法将意味着该方法可以任意地放置在您发现有用的第一个模块中。
leo

6

我认为隐藏/封装功能的决定不应该取决于其纯度。仅仅因为函数是纯函数并不意味着外部人员需要了解它。有趣的是,如果该函数是纯函数并且打算公开,则它甚至根本不需要成为接口的实例成员,也许它更适合作为静态函数。但是所有这些又取决于合同的意图,在这种情况下取​​决于功能的逻辑分组,而不是功能的纯度。


5

班级应遵守单一责任原则。虽然一个类可能需要调用其他功能来实现其目标,但它只应公开属于其单一职责的功能。

这只是可见性可能导致问题的一种示例。

考虑一个类化小部件的类。也许作为其基础代码的一部分,它需要一些实用程序函数来解析字符串:也许它需要以标准字符串函数不支持的方式来转换小部件名称。

由于这是一个纯函数(字符串进来,以某种方式对其进行转换,返回新字符串),因此它可以是公共的或私有的,而不会产生任何后果。可以吗

如果将其公开,则您的班级现在有两个职责:整理窗口小部件转换字符串。这违反了SRP,如果其他类开始依赖该功能,则可能导致问题。由于您认为这只是在类内部使用的,因此您可能会更改其接口或可见性。现在,系统其他部分的类已损坏。

通过保持该函数的私有性,任何人都没有机会依赖不属于该类单一责任一部分的代码。


2
我认为,它应该只包含属于其单一职责的功能,而不仅仅是暴露。
Telastyn 2014年

1
我认为那里是灰色地带。您是否真的需要一个单独的StringTransformer类来封装仅在一个地方使用的两三行代码?我同意,一旦在多个地方使用了代码,最好将代码分担到一个新的类中,但要权衡取舍。

当然。准则是准则,而不是规则。
Telastyn 2014年

在非Java中,@ snowman只是建立一个函数库。
Pieter B

@PieterB与Java v。无关。为了让真正的面向对象,你就需要一个工厂,一些抽象类,等等
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.