多个返回值指示成功/失败。

时间:2020-03-05 18:56:01  来源:igfitidea点击:

我对从某处获得的关于此技术的一些反馈很感兴趣。

当函数可以成功或者失败时,我会用到它,但是我们想获得有关失败原因的更多信息。执行此操作的标准方法是使用异常处理,但是对于这种事情,我经常发现它有点过头,而且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个"错误"返回值:false,0、0.0," 0",null,""和(对象)null。流都错了。有人告诉我,使用像" enum"这样的常量都等于" false"的常量是"" ick"。

因此,重申一下这个问题:这样的做法是否可以接受?我们会建议采用其他方式来实现同一目标吗?

解决方案

回答

只要它被记录在案并签订合同,并且不要太WTFy,那么就不会有问题。

再说一次,我建议对这种情况使用例外。这更有意义。如果我们可以使用PHP5,那将是最佳选择。否则,我们别无选择。

回答

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

另一种方法是使用古老的unix工具方法,编号为成功的错误代码返回0,而对于各种错误情况,则返回任何整数(映射到某些错误)。

与大多数例外相比,其中的大多数遭受了痛苦,而我已经看到它们被使用了。

只是为了回应安德鲁的评论
我同意last_error不应是全球性的,也许我回答中的"某个地方"有点模糊,其他人已经建议了更好的地方,所以我不会再重复这些了

回答

通常,我们将返回0表示成功,而返回1,2,3等,以表示不同的失败。操作方式有点怪异,因为我们只能有那么多错误,而且这种编码迟早会给我们带来麻烦。

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

这使记录变得非常容易,因为我们可以将状态结构传递到记录器中,然后将其插入适当的日志条目。

回答

我认为,仅当故障是方法/功能的"正常操作部分"时,才应使用此技术。例如,呼叫成功与失败的可能性一样大。如果失败是异常事件,则应使用异常处理,以便程序可以尽早而优雅地终止。

至于我们使用不同的"假"值,我最好返回带有适当错误代码的自定义"结果"类的实例。就像是:

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

回答

ck

在Unix前置例外中,这是通过errno完成的。返回0表示成功,返回-1表示失败,然后我们可以获取一个值,该值可以使用整数错误代码进行检索以获取实际错误。这在所有情况下都有效,因为我们没有(实际)错误代码数量的限制。 INT_MAX肯定大于7,并且我们不必担心类型(errno)。

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

回答

查看COM HRESULT,了解执行此操作的正确方法。

但是例外通常更好。

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

if (succeeded(result = MyFunction()))
  ...
else
  ...

回答

我同意其他人所说的,这在WTFy方面有点。如果明确记录了功能,那么问题就不那么多了,但是我认为采用另一种方法比较安全,即成功返回0,错误代码返回整数。如果我们不喜欢这种想法,也不喜欢全局最后一个错误变量的想法,请考虑将函数重新定义为:

function createUser($username, &$error)

然后,我们可以使用:

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

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

回答

如果我们确实想做这种事情,则每个错误应该有不同的值,并检查是否成功。就像是

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

并检查:

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

}
else {
    //Fail
}

回答

成功执行返回true确实有意义。处理一般错误会容易得多:

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

但是将含义与不同类型的错误相关联根本没有任何意义。 False意味着一件事和一件事,无论其类型或者编程语言如何对待它。如果仍要定义错误状态常量,最好坚持使用开关/外壳

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并对其进行适当处理。这并不总是一个好主意,它取决于使用方式。

回答

how acceptable is a practice like this?

我会说这是不可接受的。

  • 需要===运算符,这是非常危险的。如果用户使用==,则将导致非常难以发现错误。
  • 在将来的PHP版本中,使用" 0"和""表示false可能会发生变化。另外,在许多其他语言中," 0"和""不会计算为false,这会导致很大的混乱

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

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

else
{
    // error handling all goes inside this block
    switch ( getMakeClientError() )
    { case: // .. }
}

回答

当没有例外时,我将使用PEAR模型并在所有类中提供isError()功能。

回答

我喜欢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);
}

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

回答

其他方式包括例外:

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

我不愿意成为使用我们最初建议的代码模式的人。

我的意思是"跟着你来",就像"跟着你去工作并必须遵守守则",而不是"带着恶意地跟着你来",尽管两者都是可以选择的。

回答

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

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

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

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

但是PHP是一种松散类型的语言,这是有原因的,并且不应抛出具有不同类型但求值为相同" false"的错误代码。

当我们用尽内置类型时会发生什么?

当我们有了新的编码器并必须解释这件事如何工作时会发生什么?说,在6个月内,我们将不会记得。

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

放下