【问题标题】:Standardized returning values - is it a good or bad idea标准化的返回值 - 这是一个好主意还是坏主意
【发布时间】:2012-03-06 08:20:42
【问题描述】:

我是在PHP工作的(但在这种情况下我认为编程语言无关紧要),而在我的类方法中我通常会遇到以下情况:

  1. 方法必须返回 truefalse
  2. 方法必须返回 true错误信息
  3. 方法必须返回 true + 成功消息false + 错误消息
  4. 方法必须返回 true + 成功结果(对象、数组等)false
  5. 方法必须返回true + 成功结果(对象、数组等)false + 错误信息

我的问题是,当我在代码中的某个地方使用此类方法时,我总是必须回到该类,并检查实际返回的方法是什么:只需 true falsetrue错误信息

标准化返回值是个好主意吗?如果是,怎么做?

我的想法是:

  1. 如果函数必须返回 truefalse 则只需返回 truefalse
  2. 如果函数必须返回 true错误信息 那么:

    if (success)
    {
        return array(
            TRUE,
            null
        );
    }
    else
    {
        return array(
            FALSE,
            $error_message
        );      
    }
    
  3. 如果函数必须返回 true + 成功消息错误消息 那么:

    if (success)
    {
        return array(
            TRUE,
            $success_message,
        );
    }
    else
    {
        return array(
            FALSE,
            $error_message
        );      
    }
    

我希望你们理解我的问题,即使我的解释不太好 :) 您有什么建议或最佳实践?我该如何处理?

更新: 举个简单的例子:

function login($username, $password) 
{
    // Login logic here ..
    if ($logged_in) 
    {
        return TRUE;
    }
    {
        return $error_message;
    }
}

所以正确的做法是:返回true,或者抛出异常,并且在调用登录方法时使用try catch。因此,当出现问题(验证失败等)时,我应该使用异常。

【问题讨论】:

  • 这个问题不太适合这个网站,可能会被关闭。请查看FAQ 以了解有关在此处提出何种问题的更多信息。 Stackoverflow 并不是真正的意见或建议。
  • 我正在寻找一种在这种情况下有效的“最佳实践”。我需要一个“模式”来使用..这是一个问题,我正在寻找解决方案。我不需要意见。我需要一个有效的解决方案。我认为其他人也遇到了类似的效率问题,一个好的解决方案也可以帮助他们。

标签: php


【解决方案1】:

我想说返回布尔值的前提是错误的。

函数应该有明确的目的和明确的结果。如果可以达到这个结果,则返回结果。如果无法获得结果,该函数要么返回false,要么抛出异常。哪个更好取决于情况和您的一般错误处理理念。无论哪种方式,让函数返回错误消息通常没有用处。该消息对调用该函数的代码没有用。

除了返回false 结果:trigger_error 之外,PHP 有自己的机制来输出错误消息。它纯粹是一个帮助调试的工具,它不会取代标准的返回值。它非常适合您希望显示错误消息纯粹以帮助开发人员的情况。

如果一个函数足够复杂,可能会导致需要以不同方式处理的几种不同类型的错误,则应使用异常来执行此操作。

比如一个非常简单的函数,目的明确,只需要返回truefalse

function isUserLoggedIn() {
    return $this->user == 'logged in';
}

具有可能无法实现该目的的功能:

function convertToFoo($bar) {
    if (!is_int($bar)) {
        return false;
    }
    // here be dragons
    return $foo;
}

同样触发消息的函数,对调试很有用:

function convertToFoo($bar) {
    if (!is_int($bar)) {
        trigger_error('$bar must be an int', E_USER_WARNING);
        return false;
    }
    // here be dragons
    return $foo;
}

一个函数可能会合法地遇到调用代码需要了解的几种不同类型的错误:

function httpRequest($url) {
    ...

    if (/* could not connect */) {
        throw new CouldNotConnectException('Response code: ' . $code);
    }

    ...

    if (/* 404 */) {
        throw new PageNotFoundException('Page not found for ' . $url);
    }

    return true;
}

我也会在此处粘贴此评论:

准备、返回不应该是函数的责任 或显示最终用户错误消息。如果函数的目的 就是说,从数据库中获取一些东西,然后显示错误 消息与它无关。调用的代码 fetch-from-database 函数只需要被告知 结果;从这里开始需要有代码,它的唯一工作就是 在数据库函数无法获取的情况下显示错误消息 需要的信息。不要混合这两种职责。

【讨论】:

  • 我喜欢你在这里给出的例子。我有兴趣看到一个返回多个错误的示例,例如在验证表单时,可能有 3 或 4 个未填写的必填字段,在这种情况下异常不会真正起作用?
  • 如果验证失败,可能会引发包含“字段”-“错误消息”对的异常。例如,这就是在 Kohana 中 ORM 验证的工作原理。
  • @bumper 这不是单个函数的责任。应该有一个(或多个)函数尝试保存数据(或者您是否正在使用它),一些其他代码在这些函数不成功的地方设置错误消息,一些代码决定是否将事物作为整体成功与否,如果没有,一些代码会显示错误消息。将所有这些职责塞进一个函数并期望它return 有一个问题。
【解决方案2】:

在特定情况下,您返回包含多个元素的数组的解决方案可能是可接受的解决方案。但总的来说,这是一种代码味道。

这样的设计可以暗示一些问题:

  • 您没有正确使用异常处理
  • 你的设计违背了单一职责原则
  • 您总是在等待返回,直到方法结束

如果一个方法真的没有做它应该做的事情,它不应该返回任何东西,它应该抛出一个异常。

如果您的方法执行的任务不止一项,则必须重构。

尽早返回。编写方法更像这样:

if (!$something)
{
    return FALSE;
}

//do some other stuff
return 'great, it worked';

您的特定登录功能不应返回消息。此类特定于操作的用户消息应由消息队列解耦和处理。

因此,您可以将一个 Messenger 类注入到您的控制器中,这样您就可以在任何地方使用它并将消息添加到队列中。

function login($username, $password) 
{
    // Login logic here ..
    if ($logged_in) 
    {
        $this->messenger->addMessage('success', 'You are loggend in.');
        return TRUE;
    }
    {
        $this->messenger->addMessage('error', $message);
        return FALSE;
    }
}

【讨论】:

    【解决方案3】:

    来自其他一些语言(C#)的常见习语是 Do/TryDo 方法配对。

    /**
     * @param  MyInput $input
     * @return MyOutput
     * @throws MyException
     */
    function myOperation(MyInput $input) 
    {
    
    }
    

    myOperation 必须抛出一个异常(MyException在这种情况下)指示操作失败。成功时,返回结果值(MyOutput 在这种情况下)。

    /**
     * @param  MyInput  $input
     * @param  MyOutput $output
     * @return bool
     */
    function tryMyOperation(MyInput $input, &$output = null) 
    {
    
    }
    

    tryMyOperation必须返回一个布尔值,表示操作失败成功。它不得抛出异常(直接关系到操作的成败)。该值被分配给通过引用传递的参数($output 在这种情况下)。通常,try* 方法可以代理非try* 方法。

    一个人为的例子:

    /**
     * @param  string $input
     * @return string
     * @throws TypeException
     */
    function stringToUpper($input) 
    {
        if (!is_string($input)) 
        {
            throw new TypeException('String expected');
        }
        return strtoupper($input);
    }
    
    /**
     * @param  string $input
     * @param  string $output
     * @return boolean
     */
    function tryStringToUpper($input, &$output) 
    {
        try 
        {
            $output = stringToUpper($input);
            return true;
        } 
        catch (TypeException $exception) 
        {
            $output = null;
            return false;
        }
    }
    

    并且将被用作:

    try 
    {
        $output = stringToUpper($input);
        // use $output
    } 
    catch (TypeException $exception) 
    {
        // recover
    }
    
    if (tryStringToUpper($input, &$output)) 
    {
        // use $output
    } 
    else 
    {
        // recover
    }
    

    通过遵循这样的约定,您的代码的语义和意图会变得更加清晰。如果要捕获错误信息,请使用myOperation()catch 错误信息。如果您不太关心 为什么 东西坏了,而不是 如果 东西坏了,请使用 tryMyOperation()

    【讨论】:

    • +番茄使用try{...} catch (...) {...}
    • @crypticツ 哈哈,另一种存在? try { } ehh maybe_later;
    • 或者更好,try { } miss { /* cleanup in aisle 3 */ }
    【解决方案4】:

    错误信息与返回值无关。 Antipatterns 可能有助于避免已知的编码错误。函数应该返回与其目的一致的值,即:

    canWriteFile() { return true or false }
    writeFile() { should return void }
    

    writeFile() 根据名字程序员不期望任何价值,他必须研究文档,这需要时间,不直观并且可能导致错误。发明这样的名称,不需要任何文件。

    您绝对不应该使用带有第一项 bool,第二项错误消息的数组 - 这会返回复杂的数据类型而不是简单的直观值,您最终会为常用函数编写适配器,并且您的代码很快就会变坏。

    如何处理错误有3种可能性:

    1) 错误标志/状态对通知有用

    $error = "";
    function foo() {
      if($somethingBad) $error = "error occured";
      return !$somethingBad;
    }
    

    2) 错误处理程序对大多数错误很有用

    function handleError($message) {
      ...
    }
    
    function foo() {
      if($somethingBad) handleError("error occured");
      return !$somethingBad;
    }
    

    3) 错误捕获对于无法处理的错误很有用(即服务器在请求期间离线)

    function foo() {
      try {
        // dangerous code here
      }
      catch($e) {
        // handle error here
      }
      return !$somethingBad
    }
    

    【讨论】:

    • 尽管可用,但我不推荐 1 或 2。1,因为全局状态。 2,因为3更好。
    • @Bracketworks:从人类的角度来看,是的。但 3 在大多数情况下要慢得多。
    • 慢与否,我认为很多人不会认为将已经原生支持的语言结构的自制实现作为一个可取的做法。
    【解决方案5】:

    尝试例外

    我建议稍微深入研究一下异常,因为它们可以派上用场。首先,您摆脱了返回错误消息:改为抛出异常。

    其次,您不再需要 true 或 false 返回代码:如果一切都按预期工作,您知道这一点,因为没有抛出异常。

    如果您在类中处理成功和错误消息,您可能还想摆脱它们。这些事情应该在一个非常接近前端的类中处理,该类检查异常,然后根据异常或成功消息设置错误消息。

    可重用类

    方法应该返回应用程序可以使用的对象。当您开始使用返回值作为通过系统传递的消息时,您就依赖于这些消息,这使得替换底层类变得困难。

    一种好的思维方式如下:我可以在另一个项目中使用我的课程吗?如果您在其中有自定义错误消息,则可能很难,因为您可能需要另一个项目中的其他人并且必须更改您的课程。因此,您只想处理全局成功或错误(通过异常),然后在前端附近添加自定义错误消息。

    【讨论】:

      【解决方案6】:

      标准方法显然是这样的:return result or false。
      出错时抛出异常。

      【讨论】:

      • 返回混合值意味着在每次调用后测试类型:这是 PHP 的坏习惯,绝对不标准。
      • 是的,不幸的是,这是 PHP 的标准处理方式;它是一致的(有时),所以这是值得的,但它有点臭。
      【解决方案7】:

      这取决于您的应用在做什么。在一种情况下,我必须设计一个基于 REST API 的大型应用程序,并且我希望错误能够冒出并详细到 API 的任何部分,直至用户级别。所以我所做的是:

      1. 每个函数都返回一个错误/成功代码,这应该是有意义的。
      2. OK 为 1,“General Failure”为 0,因此符合布尔值。
      3. 专门的错误代码是小于 0 的值。
      4. 您有一个枚举所有值的类,因此您不必记住代码。
      5. 此类还具有用于显示和记录错误的文本表示形式。
      6. 这个类还有两个方法OK($errorCode)FAIL($errorCode),在ifs里面使用

      这个类看起来像

      class Error {
      
          const OK = 1;
          //general error
          const FAIL = 0;
          //invalid url
          const RequestParseError = -1;
          //resource not found
          const ResourceNotFound = -2;
      
          ....
      
          static public function _($errCode) {
      
              switch ($errCode) {
      
                  case Error::OK:
                      return "OK";
      
                  case Error::FAIL:
                      return "General Failure";
      
                  case Error::RequestParseError:
                      return "Request Parse Error";
      
              .....
          }
      
      }
      

      这样,应用程序的每个级别都可以返回一个错误代码,并且它会根据您的需要向上冒泡。并且 OK() 和 FAIL() 函数与布尔返回函数兼容。

      【讨论】:

      • 所以你在复制 PHP 的异常处理?
      • 我讨厌例外,我能说什么。
      • 让我详细说明一下:如果 PHP 的大多数库函数不引发异常,那么对异常执行此操作意味着始终将一种约定转换为另一种约定,这很烦人。在 python 中,一切都会引发异常,它更简单并嵌入到语言哲学中。
      • 嗯,很公平,但是set_error_handler + ErrorException = 明显优于原生错误或自制异常。此外,Error::OK 在语义上不等同于......好吧,没有错误?
      猜你喜欢
      • 2011-10-21
      • 2010-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-11
      • 2011-10-26
      相关资源
      最近更新 更多