5

我有兴趣获得一些关于我从某个地方获得的这项技术的反馈。

当函数可以成功或失败时,我会使用它,但您想获得有关它失败原因的更多信息。做同样事情的标准方法是使用异常处理,但我经常发现这种事情有点过头了,而且 PHP4 不提供这个。

基本上,该技术涉及返回 true 表示成功,以及返回false表示失败。这是一个例子来说明我的意思:

define ('DUPLICATE_USERNAME', false);
define ('DATABASE_ERROR', 0);
define ('INSUFFICIENT_DETAILS', 0.0);
define ('OK', true);

function createUser($username) {
    // create the user and return the appropriate constant from the above
}

这样做的美妙之处在于,在您的调用代码中,如果您不关心用户创建失败的原因,您可以编写简单易读的代码:

if (createUser('fred')) {
    // yay, it worked!
} else {
    // aww, it didn't work.
}

如果您特别想检查它为什么不起作用(用于记录、向用户显示或做任何事情),请使用带有 === 的身份比较

$status = createUser('fred');
if ($status) {
    // yay, it worked!
} else if ($status === DUPLICATE_USERNAME) {
    // tell the user about it and get them to try again.
} else {
    // aww, it didn't work. log it and show a generic error message? whatever.
}

在我看来,这样做的好处是,正常的预期是成功执行这样的函数会返回 true,而失败则返回 false。

缺点是你只能拥有7 "error" return values: false, 0, 0.0, "0", null, "", and (object) null.如果你忘记使用身份检查,你可能会让你的程序流程完全错误。其他人告诉我,使用像enum它们都等同于 false 的常量是"ick".


所以,重申一下这个问题:这样的做法在多大程度上可以接受?你会推荐一种不同的方式来实现同样的事情吗?

4

14 回答 14

13

我同意其他人的观点,即这有点偏袒 WTFy。如果它有明确记录的功能,那么问题就不那么重要了,但我认为采取另一种方法更安全,即返回 0 表示成功,返回整数表示错误代码。如果您不喜欢这个想法或全局最后一个错误变量的想法,请考虑将您的函数重新定义为:

function createUser($username, &$error)

然后你可以使用:

if (createUser('fred', $error)) {
    echo 'success';
}
else {
    echo $error;
}

在 createUser 内部,只需使用您遇到的任何错误填充 $error,由于引用,它将可以在函数范围之外访问。

于 2008-09-16T14:13:53.900 回答
2

只要它被记录和签约,而不是太WTFy,那么就不应该有问题。

再说一次,我建议对这样的事情使用异常。这更有意义。如果您可以使用 PHP5,那将是可行的方法。否则你没有太多选择。

于 2008-09-16T14:02:01.260 回答
2

当异常不可用时,我看到的一种更常见的方法是将错误类型存储在某个地方的“last_error”变量中,然后在发生故障时(即它返回 false)查找错误。

另一种方法是使用古老的 unix 工具方法编号错误代码 - 成功返回 0 并为各种错误条件返回任何整数(映射到某些错误)。

然而,当我看到它们被使用时,其中大多数都与异常相比受到影响。

只是为了回应安德鲁的评论 - 我同意 last_error 不应该是全球性的,也许我的答案中的“某处”有点模糊 - 其他人已经建议了更好的地方,所以我不会费心重复它们

于 2008-09-16T14:05:25.043 回答
2

通常您会返回 0 表示成功,返回 1、2、3 等表示不同的失败。你这样做的方式有点老套,因为你只能有这么多的错误,而且这种编码迟早会咬你。

我喜欢定义一个结构/对象,其中包含一个表示成功的布尔值,一个错误消息或其他值表示发生了哪种错误。您还可以包含其他字段以指示执行了哪种操作。

这使得记录非常容易,因为您可以将状态结构传递给记录器,然后它将插入适当的日志条目。

于 2008-09-16T14:06:52.060 回答
2

这样的做法有多可接受?

我会说这是不可接受的。

  1. 需要 === 运算符,这是非常危险的。如果用户使用 ==,则会导致很难找到 bug。
  2. 使用 "0" 和 "" 表示 false 可能会在未来的 PHP 版本中发生变化。加上在许多其他语言中,“0”和“”不会评估为假,这会导致很大的混乱

使用 getLastError() 类型的全局函数可能是 PHP 中的最佳实践,因为它与语言很好地结合在一起,因为 PHP 仍然主要是一种程序语言。我认为您刚才给出的方法的另一个问题是很少有其他系统可以像这样工作。程序员必须学习这种错误检查方式,这是错误的根源。最好让事情像大多数人期望的那样工作。

if ( makeClient() )
{ // happy scenario goes here }

else
{
    // error handling all goes inside this block
    switch ( getMakeClientError() )
    { case: // .. }
}
于 2008-09-16T14:39:36.240 回答
1

当异常不可用时,我会使用PEAR模型并在您的所有类中提供 isError() 功能。

于 2008-09-16T14:43:59.257 回答
1

在这里重新发明轮子。使用正方形。

好的,PHP 4 中没有例外。欢迎来到 1982 年,看看 C。

你可以有错误代码。考虑负值,它们看起来更直观,因此您只需检查是否 (createUser() > 0)。

如果需要,您可以有一个错误日志,将错误消息(或只是任意错误代码)推送到一个数组中,然后进行优雅处理。

但是 PHP 是一种松散类型的语言是有原因的,并且抛出具有不同类型但评估为相同“假”的错误代码是不应该做的事情。

当您用完内置类型时会发生什么?

当你得到一个新的编码器并且必须解释这个东西是如何工作的时会发生什么?说,在 6 个月内,你不会记得了。

PHP === 运算符是否足够快以通过它?它比错误代码快吗?或任何其他方法?

扔掉它。

于 2008-09-17T19:35:05.493 回答
0

伊克。

在 Unix 预异常中,这是通过 errno 完成的。您返回 0 表示成功或 -1 表示失败,然后您可以使用整数错误代码检索一个值以获取实际错误。这适用于所有情况,因为您对错误代码的数量没有(实际)限制。INT_MAX肯定大于7,不用担心类型(errno)。

我投票反对问题中提出的解决方案。

于 2008-09-16T14:08:46.423 回答
0

如果你真的想做这种事情,你应该为每个错误设置不同的值,并检查是否成功。就像是

define ('OK', 0);
define ('DUPLICATE_USERNAME', 1);
define ('DATABASE_ERROR', 2);
define ('INSUFFICIENT_DETAILS', 3);

并检查:

if (createUser('fred') == OK) {
    //OK

}
else {
    //Fail
}
于 2008-09-16T14:22:23.537 回答
0

成功执行返回 true 是有道理的。处理一般错误会容易得多:

if (!createUser($username)) {
// the dingo ate my user.
// deal with it.
}

但是将意义与不同类型的错误联系起来根本没有意义。False 应该只表示一件事和一件事,而不管类型或编程语言如何处理它。如果您要定义错误状态常量,最好坚持使用 switch/case

define(DUPLICATE_USERNAME, 4)
define(USERNAME_NOT_ALPHANUM, 8)

switch ($status) {
case DUPLICATE_USERNAME:
  // sorry hun, there's someone else
  break;
case USERNAME_NOT_ALPHANUM:
  break;
default:
  // yay, it worked
}

同样使用这种技术,您将能够按位 AND 和 OR 状态消息,因此您可以返回带有多个含义的状态消息,DUPLICATE_USERNAME & USERNAME_NOT_ALPHANUM并适当地对待它。这并不总是一个好主意,这取决于你如何使用它。

于 2008-09-16T14:29:27.763 回答
0

我喜欢 COM 可以同时处理异常和非异常调用者的方式。下面的示例显示了如何测试 HRESULT 并在失败的情况下引发异常。(通常在 tli 文件中自动生成)

inline _bstr_t IMyClass::GetName ( ) {
    BSTR _result;
    HRESULT _hr = get_name(&_result);
    if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
    return _bstr_t(_result, false);
}

使用返回值会影响可读性,因为错误处理分散,最坏的情况是,代码永远不会检查返回值。这就是为什么我更喜欢违反合同时的例外情况。

于 2008-09-16T15:17:15.293 回答
0

其他方式包括例外:

throw new Validation_Exception_SQLDuplicate("There's someone else, hun");),

返回结构,

return new Result($status, $stuff);
if ($result->status == 0) {
    $stuff = $result->data;
}
else {
    die('Oh hell');
}

我不想成为追随您使用您最初建议的代码模式的人。

我的意思是“追随你”,就像“在工作中跟随你并且不得不维护代码”而不是“追随你”“带着婚礼”,尽管两者都是选项。

于 2008-09-16T15:29:25.777 回答
-1

在我看来,只有当失败是你的方法/函数的“正常操作部分”时,你才应该使用这种技术。例如,调用成功的可能性与失败的可能性一样大。如果失败是一个异常事件,那么您应该使用异常处理,以便您的程序可以尽早且优雅地终止。

至于您使用不同的“假”值,我最好返回一个带有正确错误代码的自定义“结果”类的实例。就像是:

class Result
{
    var $_result;
    var $_errormsg;

    function Result($res, $error)
    {
       $this->_result = $res;
       $ths->_errorMsg = $error
    }

    function getResult()
    {
       return $this->_result;
    }

    function isError()
    {
       return ! ((boolean) $this->_result);
    }

    function getErrorMessage()
    {
       return $this->_errorMsg;
    }
于 2008-09-16T14:07:37.380 回答
-2

查看 COM HRESULT 以获得正确的方法。

但例外情况通常更好。

更新:正确的方法是:定义尽可能多的错误值,而不仅仅是“假”的。使用 function succeeded() 检查函数是否成功。

if (succeeded(result = MyFunction()))
  ...
else
  ...
于 2008-09-16T14:09:47.170 回答