标准化返回值 - 这是一个好主意还是坏主意


Standardized returning values - is it a good or bad idea

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

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

我的问题是,当我在代码中的某个地方使用此类方法时,我总是必须回到类,并检查该方法实际返回的内容:简单的真或错误消息等。

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

我的想法是:

    如果函数必须返回真或假
  1. ,则只需返回
  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
        );      
    }
    
  4. 等。

我希望你们理解我的问题,甚至认为我的解释不是那么好:)您的建议或最佳实践是什么?我应该如何处理?

更新:让我们举一个简单的例子:

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

因此,执行此操作的正确方法是:返回 true,或抛出异常,并且在调用 login 方法时使用 try catch。因此,当某些人出错(验证失败等(时,我应该使用异常。

我会

说返回布尔值和其他东西的前提是错误的。

函数应该有明确的目的和明确的结果。如果可以实现此结果,则返回结果。如果无法实现结果,该函数将返回false或引发异常。哪个更好取决于情况和您的一般错误处理理念。无论哪种方式,让函数返回错误消息通常都没有用。该消息对调用函数的代码没有用。

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

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

例如,一个非常简单的函数,目的明确,只需要返回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;
}

我也会在这里粘贴这条评论:

准备、返回不应该是函数的责任 或显示最终用户错误消息。如果函数的目的 就是,比如说,从数据库中获取一些东西,然后显示错误 消息不关它的事。调用 从数据库获取函数只需要被告知 结果;从这里开始,需要有代码,其唯一工作是 显示一条错误消息,以防数据库函数无法获取 必填信息。不要将这两种责任混为一谈。

在特定情况下,返回具有多个元素的数组的解决方案可能是可接受的解决方案,该元素不是同类实体的集合。但总的来说,这是一种代码气味。

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

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

如果一个方法确实无法执行它应该执行的操作,则它不应返回任何内容,而应引发异常。

如果您的方法执行多个任务,则必须重构。

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

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

其他一些语言(即用,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() .

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

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

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

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

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

尝试异常

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

其次,你不再需要真或假的返回代码:如果一切按预期工作,你就知道这一点,因为没有抛出异常。

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

可重用类

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

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

一个标准的方法显然是这样的:返回结果或假。
出错时引发异常。

这取决于你的应用正在做什么。在一个案例中,我必须设计一个基于 REST API 的大型应用程序,我希望错误能够冒泡并详细到 API 的任何部分,直到用户级别。所以我所做的是:

  1. 每个函数返回一个错误/成功代码,这应该是有意义的。
  2. OK 为 1,"常规故障"为 0,因此符合布尔值。
  3. 专用错误代码是小于 0 的值。
  4. 有一个枚举所有值的类,因此您不必记住代码。
  5. 此类还具有用于显示和日志记录的错误的文本表示形式。
  6. 这个类还有 2 个方法OK($errorCode)FAIL($errorCode),可以在 if S 内部使用

该类看起来像

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(( 函数与布尔返回函数兼容。