最佳实践-如果在函数调用周围进行包装,而在函数处于保护状态时添加早退出


9

我知道这可能是非常特定于用例的,但是我发现自己经常想知道这一点。是否有一般首选的语法。

我不是在问函数中最好的方法是什么,我是在问应该早退还是应该不调用函数。

如果在函数调用周围包装


if (shouldThisRun) {
  runFunction();
}

具有if保护)功能

runFunction() {
  if (!shouldThisRun) return;
}

如果多次调用此函数,则后一种选项显然有可能减少代码重复,但是有时在此处添加它会带来错误,因为那样您可能会丢失该函数的单一职责


这是一个例子

如果我有一个updateStatus()函数,该函数仅更新某些事物的状态。如果状态已更改,我只希望状态更新。我知道代码中状态可能发生变化的地方,也知道其他地方可能发生了变化。

我不确定它是否只是我自己,但是检查此内部函数感觉有点脏,因为我想使该函数尽可能保持纯净-如果我调用它,则希望状态得到更新。但是我无法说出将电话包裹在支票中的几个更好的地方,我知道它可能不会改变。



3
@gnat不,这个问题本质上是“提前退出时首选的语法是什么”,而我的是“我应该提前退出还是不应该调用该函数”?
马修·穆林

4
您不能相信开发人员,甚至您自己,也无法在调用该函数的任何地方正确检查该函数的前提条件。出于这个原因,我建议该函数可以在内部验证所有必要条件。
TZHX

“如果状态已更改,我只希望状态更新”-如果相同的状态已更改,您是否要更新状态(=更改)?听起来很圆。您能否说明一下这是什么意思,所以我可以在此答案中添加一个有意义的示例?
布朗

例如,@ DocBrown让我说要保持两个不同的对象状态属性同步。当任何一个对象更改时,我都会调用syncStatuses()-但这可能会因许多不同的字段更改(不仅是状态字段)而被触发。
马修·穆林

Answers:


15

在函数调用周围包装if:
这是决定是否应完全调用函数的方法,它是程序决策过程的一部分。

函数中的Guard子句(提前返回):
这是为了防止被无效参数调用

以这种方式使用的保护子句将功能保持为“纯”(您的用语)。它仅用于确保函数不会因错误的输入数据而中断。

关于是否完全调用该函数的逻辑是更高的抽象级别,即使仅仅是。它应该存在于函数本身之外。如DocBrown所说,您可以具有执行此检查的中间功能,以简化代码。

这是一个很好的问题,属于导致认识抽象级别的一系列问题中。每个函数应在单个抽象级别上运行,并且函数中的程序逻辑和函数逻辑对您来说都是错的-这是因为它们处于不同的抽象级别。该功能本身是较低级别的。

通过将它们分开,可以确保您的代码更易于编写,阅读和维护。


很棒的答案。我喜欢这样的事实,它给了我一个清晰的思考方式。if应该放在外面,因为它是“是否首先调用该函数”的“决策过程的一部分”。与功能本身无关。将一个意见答案标记为正确的感觉很奇怪,但是我将在几个小时后再次进行查看。
马修·穆林

如果有帮助,我不认为这是“意见”的答案。我注意到它“感觉”是错误的,但这是因为不同级别的抽象不是分开的。我从您的问题中得到的是,您可以看到它不是“正确的”,但是由于您没有考虑抽象级别,因此很难量化,因此很难用语言来表达。
鲍德里克

7

您可以同时拥有一个不检查参数的函数,以及一个不检查参数的函数(如下所示)(可能返回有关调用是否完成的信息):

bool tryRunFunction(...)
{
    bool shouldThisRun = /* some logic using data not available inside "runFunction"*/;
    if (shouldThisRun)
        runFunction();
    return shouldThisRun;
}

这样,您可以通过提供可重用的代码来避免重复的逻辑,tryRunFunction并且仍然具有不会进行检查的原始(也许是纯函数)功能。

请注意,有时您需要tryRunFunction独有的功能,例如集成检查,因此可以将检查集成到中runFunction。或者,您无需在程序的任何地方再次使用该检查,在这种情况下,您可以让它留在调用函数中。

但是,请尝试通过给函数指定适当的名称来使调用者透明,以防发生什么情况。因此,如果调用者必须自己进行检查,或者调用的函数已经为他们完成检查,则不必猜测或调查实现。像这样的简单前缀try通常就足够了。


1
必须承认,“ tryXXX()”这一惯用法似乎总是有些偏离,在这里是不合适的。您并没有试图做一些可能会出错的事情。您正在更新,如果它很脏。
user949300 '19

@ user949300:选择一个好的名称或命名方案取决于实际的用例,真实的函数名称,而不是诸如之类的人为名称runFunction。的功能updateStatus()可能会伴随的另一个功能updateIfStatusHasChanged()。但这是100%的案例依存关系,对此没有“一刀切”的解决方案,所以是的,我同意,“尝试”成语并不总是一个好的选择。
布朗

没有名为“ dryRun”的东西吗?或多或少会成为没有副作用的常规执行。如何禁用副作用是另一个故事
Laiv

3

至于谁决定是否运行,答案来自GRASP,谁是知道的“信息专家”。

做出决定后,请考虑重命名该功能以使其更加清晰。

如果函数确定,则类似以下内容:

 ensureUpdated()
 updateIfDirty()

或者,如果呼叫者应决定:

 writeStatus()

2

我想扩展@Baldrickk的答案。

您的问题没有一般性的答案。它取决于要调用的函数的含义(合同)和条件的性质。

因此,让我们在示例调用的上下文中进行讨论updateStatus()。合同可能是更新某些状态,因为发生了一些影响状态的事情。我希望即使没有真正的状态变化也允许对该方法的调用,并且如果有真正的变化就必须进行该方法的调用。

因此,如果呼叫站点updateStatus()知道(在其域范围内)没有任何相关的更改,则可以跳过呼叫。在这种情况下,调用应被适当的if构造包围。

updateStatus()函数内部,可能存在这种情况(从其域范围内的数据中)检测到无事可做的情况,并且应在此情况下尽早返回。

因此,问题是:

  • 从外部来看,考虑到函数的约定,什么时候允许/要求调用函数?
  • 在函数内部,是否存在无需任何实际工作即可提前返回的情况?
  • 是否调用函数/是否提前返回的条件是属于函数的内部域还是属于外部的域?

有了一个updateStatus()功能,我希望能看到这两种情况,即调用什么都不知道的站点,跳过该调用,以及实现尽早检查“无变化”的情况,即使这样,某些条件也会被检查两次。内在和外在。


2

有很多很好的解释。但我想以一种不同寻常的方式看待:假设您使用这种方式:

if (shouldThisRun) {
   runFunction();
}

runFunction() {
   if (!shouldThisRun) return;
}

而且您需要在这样的runFunction方法中调用另一个函数:

runFunction() {
   if (!shouldThisRun) return;
   someOtherfunction();
}

你会怎么做?您是否从上到下复制所有验证?

someOtherfunction() {
   if (!shouldThisRun) return;
}

我不这么认为。因此,我通常采用相同的方法:验证输入并检查public方法中的条件。 公共方法应该自己进行验证,甚至调用者也要检查所需的条件。但是,让私有方法只做自己的事。某些其他函数可能在runFunction未进行任何验证或检查任何条件的情况下调用。

public someFunction() {
   if (shouldThisRun) {
      runFunction();
   }
}

private runFunction() {
 // do your business.
}
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.