一般函数问题(C++ / Java / C#)

发布于 2024-10-22 07:21:43 字数 1193 浏览 3 评论 0原文

这个问题可能与语言无关,但我将重点关注指定的语言。

在处理一些遗留代码时,我经常看到这些函数的示例,这些函数(在我看来,显然)在它们内部做了太多的工作。我说的不是 5000 个 LoC 怪物,而是在它们内部实现先决条件检查的函数。

这是一个小例子:

void WorriedFunction(...) {
   // Of course, this is a bit exaggerated, but I guess this helps
   // to understand the idea.
   if (argument1 != null) return;
   if (argument2 + argument3 < 0) return;
   if (stateManager.currentlyDrawing()) return;

   // Actual function implementation starts here.

   // do_what_the_function_is_used_for
}

现在,当调用这种函数时,调用者不必担心要满足的所有先决条件,可以简单地说:

// Call the function.
WorriedFunction(...);

现在 - 如何应该处理以下问题吗?

就像,一般来说 - 这个函数应该只做它所要求的事情并将“先决条件检查”移至调用方:

if (argument1 != null && argument2 + argument3 < 0 && ...) {
   // Now all the checks inside can be removed.
   NotWorriedFunction();
}

或者- 它是否应该简单地针对每个先决条件不匹配抛出异常?

if (argument1 != null) throw NullArgumentException;

我不确定这个问题是否可以概括,但我仍然想在这里谈谈你对此的想法 - 可能有一些我可以重新思考的事情。 strong>

如果您有替代解决方案,请随时告诉我:)

谢谢。

This question is probably language-agnostic, but I'll focus on the specified languages.

While working with some legacy code, I often saw examples of the functions, which (to my mind, obviously) are doing too much work inside them. I'm talking not about 5000 LoC monsters, but about functions, which implement prerequisity checks inside them.

Here is a small example:

void WorriedFunction(...) {
   // Of course, this is a bit exaggerated, but I guess this helps
   // to understand the idea.
   if (argument1 != null) return;
   if (argument2 + argument3 < 0) return;
   if (stateManager.currentlyDrawing()) return;

   // Actual function implementation starts here.

   // do_what_the_function_is_used_for
}

Now, when this kind of function is called, the caller doesn't have to worry about all that prerequisities to be fulfilled and one can simply say:

// Call the function.
WorriedFunction(...);

Now - how should one deal with the following problem?

Like, generally speaking - should this function only do what it is asked for and move the "prerequisity checks" to the caller side:

if (argument1 != null && argument2 + argument3 < 0 && ...) {
   // Now all the checks inside can be removed.
   NotWorriedFunction();
}

Or - should it simply throw exceptions per every prerequisity mismatch?

if (argument1 != null) throw NullArgumentException;

I'm not sure this problem can be generalized, but still I want to here your thoughts about this - probably there is something I can rethink.

If you have alternative solutions, feel free to tell me about them :)

Thank you.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

﹉夏雨初晴づ 2024-10-29 07:21:43

每个函数/方法/代码块都应该有一个前提条件,它们是其设计工作的精确环境,以及后置条件,即函数返回时的状态。这些可以帮助您的程序员同事理解您的意图。

根据定义,如果前置条件为假,则代码预计不会工作,如果后置条件为假,则被视为有错误。

无论你把这些写在脑子里、写在设计文档的纸上、注释中,还是实际的代码检查中,这都是一个品味问题。

但从长远来看,如果将前置条件和后置条件编码为显式检查,那么生活会更容易。如果您编写此类检查,因为代码预计不会工作
如果前置条件错误,或者后置条件错误,则前置条件和后置条件检查应导致程序以易于发现故障点的方式报告错误。正如您的示例所示,代码不应该做的只是“返回”什么都不做,因为这意味着它已以某种方式正确执行。
(代码当然可以定义为不执行任何操作就退出,但如果是这种情况,前置条件和后置条件应该反映这一点。)

显然,您可以使用 if 语句编写此类检查(您的示例非常接近):

if (!precondition) die("Precondition failure in WorriedFunction"); // die never comes back

但通常通过为称为断言的语言定义特殊函数/宏/语句来在代码中指示前置条件或后置条件的存在,并且当断言为假时,这种特殊的构造通常会导致程序中止和回溯。

代码的编写方式应如下所示:

void WorriedFunction(...)  
 {    assert(argument1 != null); // fail/abort if false [I think your example had the test backwards]
      assert(argument2 + argument3 >= 0);
      assert(!stateManager.currentlyDrawing());
      /* body of function goes here */ 
 }  

复杂的函数可能愿意告诉调用者某些条件已失败。这就是例外的真正目的。如果存在异常,从技术上讲,后置条件应该表示“在 xyz 条件下,函数可能会因异常而退出”。

Every function/method/code block should have a precondition, which are the precise circumstances under which it is designed to work, and a postcondition, the state of the world when the function returns. These help your fellow programmers understand your intentions.

By definition, the code is not expected to work if the precondition is false, and is considered buggy if the postcondition is false.

Whether you write these down in your head, on paper in a design document, in comments, or in actual code checks is sort of a matter of taste.

But a practical issue, long-term, life is easier if you code the precondition and post-condition as explicit checks. If you code such checks, because the code is not expected to work
with a false precondition, or is buggy with a false postcondition, the pre- and post- condition checks should cause the program to report an error in a way that makes it easy to discover the point of failure. What code should NOT do is simply "return" having done nothing, as your example shows, as this implies that it has somehow executed correctly.
(Code may of course be defined to exit having done nothing, but if that's the case, the pre- and post- conditions should reflect this.)

You can obviously write such checks with an if statement (your example comes dangerously close):

if (!precondition) die("Precondition failure in WorriedFunction"); // die never comes back

But often the presence of a pre- or post-condition is indicated in the code by defining a special function/macro/statement... for the language called an assertion, and such special construct typically causes a program abort and backtrace when the assertion is false.

The way the code should have been written is as follows:

void WorriedFunction(...)  
 {    assert(argument1 != null); // fail/abort if false [I think your example had the test backwards]
      assert(argument2 + argument3 >= 0);
      assert(!stateManager.currentlyDrawing());
      /* body of function goes here */ 
 }  

Sophisticated functions may be willing to tell their callers that some condition has failed. That's the real purpose of exceptions. If exceptions are present, technically the postcondition should say something to the effect of "the function may exit with an exception under condition xyz".

淡看悲欢离合 2024-10-29 07:21:43

这是一个有趣的问题。查看“Design By Contract”的概念,你可能会发现它很有帮助。

That's an interesting question. Check the concept of "Design By Contract", you may find it helpful.

硪扪都還晓 2024-10-29 07:21:43

这取决于。

我想将我的答案分为情况 1、3 和情况 2。

情况 1、3

如果您可以安全地从参数问题中恢复,请不要抛出异常。一个很好的例子是 TryParse 方法 - 如果输入格式错误,它们只会返回 false。另一个例子(例外情况)是所有 LINQ 方法 - 如果 sourcenull 或强制 Func<> 之一,它们会抛出异常。但是,如果它们接受自定义 IEqualityComparerIComparer,它们不会抛出异常,而只需使用默认实现通过 EqualityComparer.DefaultComparer.Default。这完全取决于参数的使用上下文以及您是否可以安全地从中恢复。

情况 2

如果代码位于类似基础设施的环境中,我只会使用这种方式。最近,我开始重新实现 LINQ 堆栈,并且您必须实现几个接口 - 这些实现永远不会在我自己的类和方法之外使用,因此您可以在它们内部做出假设 - 外部将始终通过接口访问它们,并且无法自己创建它们。

如果您对 API 方法做出这样的假设,您的代码将在错误输入时抛出各种异常,并且用户不知道发生了什么,因为他不知道您的方法的内部。

It depends.

I'd like to seperate my answer between case 1, 3 and case 2.

case 1, 3

If you can safely recover from an argument problem, don't throw exceptions. A good example are the TryParse methods - if the input is wrongly formatted, they simply return false. Another example (for the exception) are all LINQ methods - they throw if the source is null or one of the mandatory Func<> are null. However, if they accept a custom IEqualityComparer<T> or IComparer<T>, they don't throw, and simply use the default implementation by EqualityComparer<T>.Default or Comparer<T>.Default. It all depends on the context of usage of the argument and if you can safely recover from it.

case 2

I'd only use this way if the code is in an infrastructure like environment. Recently, I started reimplementing the LINQ stack, and you have to implement a couple of interfaces - those implementations will never be used outside my own classes and methods, so you can make assumptions inside them - the outside will always access them via the interface and can't create them on their own.

If you make that assumption for API methods, your code will throw all sorts of exceptions on wrong input, and the user doesn't have a clue what is happening as he doesn't know the inside of your method.

沒落の蓅哖 2024-10-29 07:21:43

“或者 - 它应该简单地针对每个先决条件不匹配抛出异常吗?”

是的。

"Or - should it simply throw exceptions per every prerequisity mismatch?"

Yes.

行雁书 2024-10-29 07:21:43

您应该在调用和运行之前进行检查,如果您拥有该函数,则应该让它在传递的参数与预期不符时抛出异常。

在您的调用代码中应该处理这些异常。当然,传递的参数应该在调用之前进行验证。

You should do checks before calling and function and if you own the function, you should make it throw exceptions if arguments passed are not as expected.

In your calling code these exceptions should be handled. Ofcourse arguments passed should be verified before the call.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文