我应该在PHP代码中使用assert吗?


87

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

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');
}

通过阅读assert上的PHP文档,似乎建议您确保assert处于活动状态并在使用assert之前添加一个处理程序。我找不到他这样做的地方。

因此,我的问题是,鉴于上述情况,使用assert是否是一个好主意,我应该更频繁地使用它,而不是if和except吗?

另一个需要注意的是,我们计划在各种项目和服务器上使用这些库,包括我们甚至可能都不参与的项目(这些库是开源的)。这对使用assert有什么区别吗?


真的'isset(代码行中assert)吗?不只是isset(没有单引号')?
彼得·莫滕森

Answers:


79

适用于大多数语言(我略微知道的所有语言)的经验法则是,“ an”assert用于断言条件始终为真,而“ an”if在可以想象有时会失败的情况下是适当的。

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

的使用的优点assert,而不是ifassert通常可在这样的生产代码减少开销被关闭。if可以想象,最好处理的情况是在生产系统的运行时发生的,因此,由于无法将其关闭而不会造成任何损失。


4
除此之外,您可能不想禁用生产代码中的断言,因为它们有助于确保那些“绝不应该发生”的条件保持不变。让您的应用程序从断言中停止可能比让您的用户继续执行不应该存在的执行路径更好。
德里克德曼

2
@derekerdmann:是的。对于某些断言,将其记录(在生产中)或打印警告(在开发环境中)可能就足够了。但是由于经常断言也可以保护与安全相关的代码,因此您最好启用assert_options(ASSERT_BAIL)。无论如何,它比手动的/抛出解决方法要快。
mario 2010年

4
@derekerdmann我对此不太同意(在php中使用assert()的情况下)。这是一个很大的漏洞缺口,因为assert()将所有字符串参数都视为PHP代码,因此(理论上)可以注入并运行任意代码。恕我直言,断言应该在生产上关闭
Vitaliy Lebedev

2
@VitaliyLebedev如果您不想受到注入的影响,请不要传递字符串来断言。
Damon Snyder 2014年

9
晚会晚了,但是PHP.net指出:“断言只能用作调试功能。”
科恩

25

将断言视为“权力评论”。而不是像这样的评论:

// 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)...和异常完全不同的概念,它们可以共存。

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


如果在开发过程中进行测试时,您始终将良好条件传递给断言,而在生产中,如果断言被关闭-某些用户通过了您在测试时未考虑的另一种条件?否则,您需要始终保持断言,但这是否与编写自己的支票不同?
Darius.V 2014年

然后,您的程序将失败。使用if语句以及您的语言和开发环境的错误处理功能正确修复它。断言可以发现问题,有更好的解决问题的方法。
DaveWalley 2014年

请注意,自PHP 7.2起,不建议将字符串传递给assert进行评估。很难过,因为它看上去很方便。
Jannie Theunissen

16

这完全取决于您的发展策略。大多数开发人员不了解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将包含它们但不检查”是什么意思?
卡扎伊2012年

1
PHP 5.4具有,支持和检查断言。
DaveWalley 2014年


6

关于PHP中早于7的assert的重要说明。与其他具有assert构造的语言不同,PHP不会完全抛出assert语句-它将其视为函数(在由断言调用的函数中执行debug_backtrace())。关闭断言似乎只是使函数热衷于在引擎中不执行任何操作。请注意,可以通过将zend.assertions设置为0而不是更普通的值1(on)或-1(off)来使PHP 7模仿此行为。

问题是断言将接受任何参数-但是,如果参数不是字符串,则断言无论断言是打开还是关闭都将获取表达式的结果。您可以使用以下代码块进行验证。

<?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.\')');

鉴于assert的意图,这是一个错误,并且是一个长期存在的错误,因为它是从PHP 4中引入assert以来一直使用的语言。

评估传递给assert的字符串,以及随之而来的所有性能隐患和危害,但这是使assert语句按其应有的方式在PHP中工作的唯一方法(此行为在PHP 7.2中已弃用)。

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


1
在PHP 7中,有/将会有zend.assertionsini设置来完全关闭assert()
Kontrollfreak 2015年

这真是个好消息-但是根据文档,那里看上去是对PHPUnit的补丁,它是有序的,添加了一个断言回调处理程序,以在PHP 5.x下断言失败时抛出AssertionException。这样,单元测试可以使用注释@ExpectedException AssertionException不管他们是否在PHP 5.x或7上运行
迈克尔·莫里斯

3

断言仅应在开发中使用,因为它对调试很有用。因此,如果您愿意,可以使用它们来开发网站,但是对于实时网站,应该使用例外。


7
但是,在代码中仍然会有断言。他们只是在生产环境中不活跃。
aaronasterling 2010年

1
我将它们保留在生产环境中,然后相应地调整我的错误处理程序。
Daniel W.

3

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

断言应仅用作调试功能。您可以将它们用于完整性检查,以测试应该始终为TRUE的条件,如果条件不正确则指示某些编程错误,或者检查是否存在某些功能,例如扩展功能或某些系统限制和功能。

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

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

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.');

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


3

您的同事真的是在尝试按合同申请设计基于埃菲尔语言(DbC),并基于这本书:面向对象的软件构造,第二版。

如他所使用的,断言将是Hoare逻辑或Hoare Triple:{P} C {Q}的{P}部分,其中{P}是前提断言,{Q}是后置条件断言。

我会特别注意有关PHP中有错误的断言功能的建议。您不想使用错误代码。您真正想要的是PHP的制造商来修复断言中的错误。除非这样做,否则您可以使用断言,但请牢记当前的越野车状态。

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

最后,如果您按合同进行设计研究,您会发现根据面向对象的经典继承使用布尔断言会带来后果,也就是说,您绝不能削弱先决条件,也绝不能削弱后置条件。这样做可能会对您的多态后代对象彼此交互产生危险。直到您了解这意味着什么之前,我都不会管它!

此外,我强烈建议PHP的制造商对合同进行全面的设计研究,并尝试将其尽快纳入PHP!然后,我们所有人都可以从拥有DbC意识的编译器/解释器中受益,该编译器/解释器将处理答案中提到的问题(上述):

  1. 正确实现按合同设计的设计编译器(希望)是没有错误的(不同于当前的PHP断言)。
  2. 一个正确实现的按合同设计的编译器将为您处理多态断言逻辑管理的细微差别,而不是让您全神贯注于此!

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

愉快的学习和学习。

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.