【问题标题】:Should I be using assert in my PHP code?我应该在我的 PHP 代码中使用断言吗?
【发布时间】:2010-12-23 06:33:37
【问题描述】:

一位同事在我们的库中添加了几次assert 命令,在这些地方我会使用 if 语句并引发异常。 (在此之前我什至从未听说过 assert。)以下是他如何使用它的示例:

assert('isset($this->records); /* Records must be set before this is called. */');

我会做的:

if (!isset($this->records)) {
    throw new Exception('Records must be set before this is called');
}

从阅读the PHP docs on assert 看来,建议您确保断言处于活动状态并在使用断言之前添加一个处理程序。我找不到他这样做的地方。

所以,我的问题是,鉴于上述情况,使用断言是一个好主意吗?我应该更频繁地使用它而不是 if 和异常吗?

另外注意,我们计划在各种项目和服务器上使用这些库,包括我们甚至可能不参与的项目(这些库是开源的)。这对使用断言有什么影响吗?

【问题讨论】:

  • 真的是'issetassert的代码行)吗?不仅仅是isset(没有单引号,')?

标签: php assert


【解决方案1】:

适用于大多数语言的经验法则(我隐约知道的一切)是assert 用于断言条件始终为真,而if 是合适的如果可以想象它有时会失败。

在这种情况下,我会说assert 是合适的(基于我对这种情况的薄弱理解),因为records 应该在调用给定方法之前始终设置。因此,未能设置记录将是程序中的错误而不是运行时条件。在这里,assert 有助于确保(通过充分的测试)没有可能的程序执行路径会导致在没有设置records 的情况下调用由assert 保护的代码。

相对于if,使用assert 的优势在于assert 通常可以在生产代码中关闭,从而减少开销。 if 最好处理的那种情况可能会在生产系统的运行时发生,因此无法关闭它们不会造成任何损失。

【讨论】:

  • 此外,您可能不想在生产代码中禁用断言,因为它们有助于确保那些“这永远不会发生”的情况保持不变。让您的应用程序停止断言可能比让您的用户继续沿着不应该存在的执行路径前进可能更好。
  • @derekerdmann:是的。对于某些断言,记录它们(在生产中)或打印警告(在开发环境中)可能就足够了。但是由于断言通常也保护安全相关代码,您不妨启用assert_options(ASSERT_BAIL)。无论如何,它比手动 if/throw 解决方法要快。
  • @derekerdmann 我不同意这一点(在 php 中使用 assert() 的情况下)。这是一个很大的漏洞缺口,因为 assert() 将所有字符串参数视为 PHP 代码,因此(理论上)可以注入和运行任意代码。恕我直言,断言应该在生产中关闭
  • @VitaliyLebedev 如果您不想受到注入的影响,请不要将字符串传递给断言。
  • 迟到了,但 PHP.net 声明:“断言应该仅用作调试功能。”
【解决方案2】:

将断言视为“power cmets”。而不是这样的评论:

// Note to developers: the parameter "a" should always be a number!!!

使用:

assert('is_numeric(a) /* The parameter "a" should always be a number. */');

含义完全相同,面向完全相同的受众,但第一条评论很容易被遗忘或忽略(不管有多少感叹号),而“权力评论”不仅可供人类阅读并且理解,它还在开发过程中不断地经过机器测试,如果您在代码和工作习惯中设置了良好的断言处理,它就不会被忽略。

这样看来,断言与 if(error)... 和异常是完全不同的概念,它们可以共存。

是的,您应该注释您的代码,是的,您应该尽可能使用“power cmets”(断言)。

【讨论】:

  • 如果在开发中测试时您总是将良好的条件传递给断言,但在生产中如果关闭了断言 - 某些用户传递了您在测试时没有考虑过的另一个条件?否则您需要始终保持断言,但这与编写自己的支票不一样吗?
  • 那么你的程序就会失败。使用 if 语句以及您的语言和开发环境的错误处理功能正确修复它。断言可以发现问题,有更好的方法来解决问题。
  • 请注意,从 PHP 7.2 开始,将字符串传递给 assert 以进行评估已被弃用。伤心,因为它看起来很方便。
【解决方案3】:

这完全取决于您的发展战略。大多数开发人员不知道assert() 并使用下游单元测试。但主动和内置的测试方案有时可能是有利的。

assert 很有用,因为它可以启用和禁用。如果没有定义这样的断言处理程序,它不会消耗性能。你的同事没有,你应该设计一些代码在开发环境中临时启用它(如果 E_NOTICE/E_WARNINGs 开启,那么断言处理程序也应该开启)。我偶尔会在我的代码无法忍受混合变量类型的情况下使用它——我通常不会在弱类型 PHP 中进行严格的输入,但会有随机用例:

 function xyz($a, $b) {
     assert(is_string($a));
     assert(is_array($b));

例如,这将弥补类型说明符 string $a, array $b 的不足。 PHP5.4 将支持它们,但不检查。

【讨论】:

  • “php 5.4 will have them but not check”是什么意思?
  • PHP 5.4 拥有、支持和检查断言。
【解决方案4】:

Assert 不能替代 if 或异常之类的正常流控制,因为它仅用于在开发期间进行调试。

【讨论】:

    【解决方案5】:

    关于早于 7 的 PHP 中的断言的重要说明。与其他具有断言构造的语言不同,PHP 不会完全抛出断言语句 - 它会将其视为一个函数(在由断言)。关闭断言似乎只是将函数热连接到引擎中什么都不做。请注意,PHP 7 可以通过将 zend.assertions 设置为 0 来模拟这种行为,而不是更正常的值 1(打开)或 -1(关闭)。

    问题出现在 assert 将接受任何参数 - 但如果参数不是字符串,则无论 assert 是打开还是关闭,assert 都会获取表达式的结果。您可以使用以下代码块验证这一点。

    <?php
      function foo($a) { 
        echo $a . "\n"; 
        return TRUE;
      }
      assert_options(ASSERT_ACTIVE, FALSE);
    
      assert( foo('You will see me.'));
      assert('foo(\'You will not see me.\')');
    
      assert_options(ASSERT_ACTIVE, TRUE);
    
      assert( foo('Now you will see'));
      assert('foo(\'both of us.\')');
    

    鉴于断言的意图,这是一个错误,而且自从 PHP 4 中引入断言以来它就一直存在于该语言中。

    传递给 assert 的字符串被评估,伴随着所有的性能影响和危险,但它是让 assert 语句按照 PHP 中应有的方式工作的唯一方法(此行为在 PHP 7.2 中已弃用) .

    编辑:在上面更改以注意 PHP 7 和 7.2 中的更改

    【讨论】:

    • 在 PHP 7 中有/将会有 zend.assertions ini 设置来完全关闭 assert()
    • 这是个好消息——但根据那里的文档,PHPUnit 的补丁看起来是有序的,添加一个断言回调处理程序以在 PHP 5.x 下断言失败时抛出 AssertionException。这样,单元测试可以使用注解 @expectedException AssertionException,无论它们是在 PHP 5.x 还是 7 上运行。
    【解决方案6】:

    您的同事确实在尝试应用 Eiffel 语言中的 design by contract (DbC),并基于这本书:面向对象的软件构建,第 2 版。

    他使用的断言将是 Hoare Logic 或 Hoare Triple 的 {P}-部分:{P} C {Q},其中 {P} 是前置条件 assert(ion) 和 { Q} 是后置条件断言(离子)。

    对于 PHP 中的断言功能存在错误的建议,我会认真记录。您不想使用错误代码。您真正想要的是 PHP 的制造商修复断言中的错误。在他们这样做之前,您可以使用断言,但要注意其当前的错误状态。

    此外,如果断言功能有问题,那么我建议您不要在生产代码中使用它。尽管如此,我还是建议您在适当的情况下在开发和测试代码中使用它。

    最后——如果你研究契约式设计,你会发现根据面向对象的经典继承使用布尔断言是有后果的——也就是说——你绝不能弱化一个前提条件,也不能弱化一个帖子-健康)状况。这样做可能会对您的多态后代对象相互交互造成危险。直到你明白这意味着什么——我不会管它的!

    此外,我强烈建议 PHP 的开发者全面研究合同设计,并尝试尽快将其放入 PHP 中!然后我们所有人都可以从拥有 DbC 感知编译器/解释器中受益,它可以处理答案中提到的问题(上面):

    1. 正确实现的设计感知型编译器(希望)没有错误(与当前的 PHP 断言不同)。
    2. 正确实施的设计感知型编译器将为您处理多态断言逻辑管理的细微差别,而不是为此事绞尽脑汁!

    注意:即使您使用if-statement 作为断言(前置条件)的替代品,如果用于加强前置条件或削弱后置条件,也会遭受可怕的后果。要了解这意味着什么,您需要通过合同学习设计才能知道! :-)

    学习愉快。

    【讨论】:

      【解决方案7】:

      Assert 只能在开发中使用,因为它对调试很有用。因此,如果您愿意,可以使用它们来开发您的网站,但您应该为实时网站使用例外。

      【讨论】:

      • 但是代码中仍然会有断言。他们只是不会在生产环境中活跃。
      • 我会将它们保留在生产环境中并相应地调整我的错误处理程序。
      【解决方案8】:

      不,您的同事不应该将其用作通用错误处理程序。根据手册:

      断言只能用作调试功能。您可以将它们用于完整性检查,以测试应始终为 TRUE 的条件,如果不是,则表明某些编程错误,或检查某些功能(如扩展功能或某些系统限制和功能)的存在。

      断言不应用于正常的运行时操作,如输入参数检查。根据经验,如果未激活断言检查,您的代码应该始终能够正常工作。

      如果您熟悉自动化测试套件,“assert”动词通常用于验证某些方法或函数的输出。例如:

      function add($a, $b) {
          return $a + $b;
      }
      
      assert(add(2,2) == 5, 'Two and two is four, dummy!');
      assert(is_numeric(add(2,2)), 'Output of this function to only return numeric values.');
      

      您的同事不应将其用作通用错误处理程序,在这种情况下用作输入检查。您图书馆的某些用户可能没有设置记录字段。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-27
        • 1970-01-01
        • 2011-06-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多