【问题标题】:Should you check parameters passed into function before passing them, or check them in the function?您应该在传递参数之前检查传递给函数的参数,还是在函数中检查它们?
【发布时间】:2015-04-11 09:57:50
【问题描述】:

作为一种好的做法,您认为应该在参数被传递到的函数中验证传递的参数,还是只是确保该函数将始终接受正确的参数?

考虑以下代码:

Matrix * add_matrices(const Matrix * left, const Matrix * right)
{
    assert(left->rowsCount == right->rowsCount
        && left->colsCount == right->colsCount);

    int rowsCount = left->rowsCount;
    int colsCount = left->colsCount;

    Matrix * matOut = create_matrix(rowsCount, colsCount);

    int i = 0;
    int j = 0;
    for (i; i < rowsCount; ++i)
    {
        for (j; j < colsCount; ++j)
        {
            matOut->matrix[i][j] = left->matrix[i][j] + right->matrix[i][j];
        }
    }

    return matOut;
}

你认为我应该在将参数传递给函数之前还是之后检查参数,即。在功能?什么是更好的做法还是取决于程序员?

【问题讨论】:

  • 我认为这是基于意见的。在哪里检查并不重要......
  • 是的,像您所做的那样添加前提条件是一种很好的做法。调用者必须确保矩阵的大小相同,但如果矩阵是在调用之前创建的,则不需要额外的测试。
  • @AugustKarlstrom 因此,即使有人在我之后编写此代码的可能性为 0%(这只是一个学校项目),如果有人这样做,那么此代码是否易于阅读和理解?另一个程序员会明白,为了使用这个函数,必须在传递参数之前在函数外部检查正确的参数吗?
  • 这确实是一个设计决策。如果代码的用户可以保证输入是正确的,他们可能不想承担运行时检查的成本。另请注意,assert 在生产中被禁用是很常见的。
  • 是的,只要您的函数有据可查,那么调用者就可以知道提供有效输入是否是他们的责任。 C 中充满了假定被调用者传递有效输入并产生未定义行为的函数。

标签: c parameter-passing


【解决方案1】:

Inside。该函数可以被视为一个单独的组件。 它的作者最适合定义任何先决条件并检查它们。 在外面检查它们的前提是调用者知道可能不是这种情况的先决条件。

此外,通过将它们放在函数中,您可以确保每次调用都会被检查。

您还应该在离开函数之前检查所有后置条件。

例如,如果您有一个名为 int assertValid(const Matrix*matrix) 的函数来检查对象的完整性(例如,数据不是 NULL 指针),您可以在进入所有函数时以及从修改 @ 的函数返回之前调用它987654323@.

始终如一地使用前置条件和后置条件完整性是确保质量和定位故障的非常有效的方法。

在实践中,热心地遵守此规则通常会导致无法接受的性能。 assert() 宏或类似的条件编译构造是一个很好的资产。见&lt;assert.h&gt;

【讨论】:

    【解决方案2】:

    取决于函数是全局作用域还是局部static

    1. 全局函数无法控制调用它的对象。防御性编码将对收到的参数进行验证。但是要进行多少验证呢?

      int my_abs(int x) {
        assert(x >= -INT_MAX);
        return abs(x);
      }
      

    上面的示例,在调试版本中,检查以确保绝对值函数将成功,因为abs(INT_MIN) 可能是一个问题。现在这个检查是否应该在生产版本中是另一个问题。

    int some_string(char *s) {
       assert(s != NULL);       
       ...
    } 
    

    some_string() 中,可能会放弃对NULL 的测试,因为函数定义可能会声明s 必须 是一个字符串。尽管 NULL 不是 C 字符串,但测试 NULL 只是可以传递的许多不指向字符串的错误指针中的一个。所以这个测试的验证是有限的。

    1. 使用static 函数,代码受本地控制。参数验证可以由函数、调用者进行,也可以两者都不进行。该选择取决于代码。

    2. 存在用户/文件输入的反例。应及时进行基本数据验证。

      int GetDriversAge(FILE *inf) {
        int age;
        if (fscanf("%d", &age) != 1) Handle_Error();
        if (age < 16 || age > 122) Handle_Error();
        return age
      }
      
    3. 在 OP 的示例中,参数检查是由函数完成的,而不是调用者。如果没有等价测试,该函数很容易以神秘的方式失败。这里检查的成本只是代码工作的一小部分。这使它成为一个好的检查,因为昂贵的检查(时间、复杂性)可能会导致比他们解决的更多的麻烦。请注意,如果调用代码执行了此测试,并且从 N 个位置调用了 add_matrices(),那么检查代码会以各种(可能是不一致的方式)重复 N 次。

      Matrix * add_matrices(const Matrix * left, const Matrix * right) {
        assert(left->rowsCount == right->rowsCount
            && left->colsCount == right->colsCount);
      

    结论:尽管存在异常,但在函数中检查参数比在调用者中检查参数更有说服力。

    【讨论】:

      【解决方案3】:

      我所做的是检查函数内部的参数并采取相应措施(抛出异常、返回错误消息等)。我想检查传递的参数是否具有正确的数据类型并包含有效值是该函数的工作。

      【讨论】:

      • 你认为它主要依赖于程序员,还是有一些约定?对对象编程来说,我总是把所有的检查放在特定类的方法中。考虑到这一事实,我可能也应该遵循同样的结构化编程原则。
      • 我不知道任何特定的约定,但在我看来,调用函数(或方法)有责任在实际执行必须执行的操作之前检查任何输入(参数)。
      • 考虑拥有一个不进行类型或数据范围检查并从一开始就做的函数。在这种情况下,如果调用者不检查调用函数的输入参数,那么如果输入参数有问题,您可能会遇到麻烦。
      • 因此,我可以反转条件,如果传递的矩阵的行和列不相等,则返回 NULL,而不是在该位置放置断言,这会导致我的程序崩溃。谢谢你的意见,Vasilis,从现在开始我也将遵循客观编程 int 结构的原则。
      【解决方案4】:

      该函数应正确执行其任务,否则应引发异常。客户端/消费代码可能会或可能不会进行检查,这取决于数据源以及您对它的信任程度,无论哪种方式,您还应该将函数调用包含在 catch-try 块中以捕获无效参数异常。

      编辑: 抱歉,我将 C 与 C++ 混淆了。您可以返回 null,而不是抛出异常。客户端在调用之前不一定要检查数据(取决于数据源和其他因素,如性能限制),但必须始终检查 null 作为返回值。

      【讨论】:

      • 我用 C 编码,而不是 C++,Matrix 只是一个简单的结构。据我所知,C 中没有例外。
      • @OndřejŠimon 抱歉,我错过了这个。您可以改为返回 null。调用者必须检查 null 返回值,但不必先检查数据(这取决于数据源)。
      猜你喜欢
      • 2012-01-28
      • 2022-08-18
      • 1970-01-01
      • 1970-01-01
      • 2015-02-21
      • 2020-11-14
      • 2020-01-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多