自定义异常消息:最佳实践

Posted

技术标签:

【中文标题】自定义异常消息:最佳实践【英文标题】:Custom Exception Messages: Best practices 【发布时间】:2010-10-12 07:41:29 【问题描述】:

想知道在创建异常消息时我应该付出多少努力来强制提供有用的调试信息,还是应该只相信用户提供正确的信息,或者将信息收集推迟到异常处理程序?

我看到很多人都在做他们的例外,比如:

throw new RuntimeException('MyObject is not an array')

或使用自定义异常扩展默认异常,这些异常不会做太多,但会更改异常的名称:

throw new WrongTypeException('MyObject is not an array')

但这并没有提供太多调试信息......并且不会强制使用错误消息进行任何格式设置。因此,您最终可能会遇到完全相同的错误,产生两条不同的错误消息......例如“数据库连接失败”与“无法连接到数据库”

当然,如果它冒泡到顶部,它会打印堆栈跟踪,这很有用,但它并不总是告诉我我需要知道的一切,通常我最终不得不开始使用 var_dump()语句来发现哪里出了问题……虽然这可能会被一个体面的异常处理程序所抵消。

我开始考虑类似下面的代码,其中我要求异常的抛出者提供必要的参数来产生正确的错误消息。我认为这可能是这样做的方式:

必须提供最低限度的有用信息 产生一些一致的错误消息 异常消息的模板都在一个位置(异常类),因此更容易更新消息...

但我认为缺点是它们更难使用(需要您查找异常定义),因此可能会阻止其他程序员使用提供的异常...

我想对这个想法发表一些评论,以及一致、灵活的异常消息框架的最佳实践。

/**
* @package MyExceptions
* MyWrongTypeException occurs when an object or 
* datastructure is of the incorrect datatype.
* Program defensively!
* @param $objectName string name of object, eg "\$myObject"
* @param $object object object of the wrong type
* @param $expect string expected type of object eg 'integer'
* @param $message any additional human readable info.
* @param $code error code.
* @return Informative exception error message.
* @author secoif
*/
class MyWrongTypeException extends RuntimeException 
    public function __construct($objectName, $object, $expected, $message = '', $code = 0) 
        $receivedType = gettype($object) 
        $message = "Wrong Type: $objectName. Expected $expected, received $receivedType";
        debug_dump($message, $object);
        return parent::__construct($message, $code);
    

....

/**
 * If we are in debug mode, append the var_dump of $object to $message
 */
function debug_dump(&$message, &$object) 
     if (App::get_mode() == 'debug') 
         ob_start();
         var_dump($object);
         $message = $message . "Debug Info: " . ob_get_clean();
    

然后像这样使用:

// Hypothetical, supposed to return an array of user objects
$users = get_users(); // but instead returns the string 'bad'
// Ideally the $users model object would provide a validate() but for the sake
// of the example
if (is_array($users)) 
  throw new MyWrongTypeException('$users', $users, 'array')
  // returns 
  //"Wrong Type: $users. Expected array, received string

我们可能会在自定义异常处理程序中执行类似 nl2br 的操作,以使 html 输出变得更好。

正在阅读: http://msdn.microsoft.com/en-us/library/cc511859.aspx#

而且没有提到这样的事情,所以也许这是个坏主意......

【问题讨论】:

很确定这里的构造函数必须匹配RuntimeException::__construct() 【参考方案1】:

我强烈推荐Krzysztof's blog 上的建议,并注意在您的情况下,您似乎正在尝试处理他所谓的使用错误。

在这种情况下,需要的不是一个新类型来指示它,而是一个更好的错误消息来说明它的原因。作为这样的辅助函数:

    生成要放入异常的文本字符串 生成整个异常和消息

是必需的。

方法 1 更清晰,但可能会导致使用更冗长,2 则相反,用更简洁的语法换来不太清晰的用法。

请注意,这些函数必须非常安全(它们本身绝不应该导致无关的异常),并且不会强制提供在某些合理用途中可选的数据。

通过使用这两种方法中的任何一种,您可以在以后根据需要更轻松地国际化错误消息。

堆栈跟踪至少会为您提供函数,可能还有行号,因此您应该专注于提供不容易从中得出的信息。

【讨论】:

是的,现在我明白了。我正在做很多使用错误和健全性检查,尽管健全性检查可能应该转移到 assert() 调用 php.net/manual/en/function.assert.php。 Krzysztof 的博客建议重用现有类型。 IE。 FileNotFoundException、NullArgumentException 等。然而,PHP 只有一种异常类型——异常。它可以扩展,但这是它默认包含的唯一一个,并且 idk 本身就特别有用...... 提示:标准 PHP 库 (SPL) 提供了大量内置异常。 php.net/manual/en/spl.exceptions.php【参考方案2】:

我不会贬低关于 Krzysztof 博客的建议,但这里有一种创建自定义异常的简单方法。

例子:

<?php
   require_once "CustomException.php";
   class SqlProxyException extends CustomException 

   throw new SqlProxyException($errorMsg, mysql_errno());     
?>

其背后的代码(我在某处借来的,向谁道歉)

<?php

interface IException

    /* Protected methods inherited from Exception class */
    public function getMessage();                 // Exception message
    public function getCode();                    // User-defined Exception code
    public function getFile();                    // Source filename
    public function getLine();                    // Source line
    public function getTrace();                   // An array of the backtrace()
    public function getTraceAsString();           // Formated string of trace

    /* Overrideable methods inherited from Exception class */
    public function __toString();                 // formated string for display
    public function __construct($message = null, $code = 0);


abstract class CustomException extends Exception implements IException

    protected $message = 'Unknown exception';     // Exception message
    private   $string;                            // Unknown
    protected $code    = 0;                       // User-defined exception code
    protected $file;                              // Source filename of exception
    protected $line;                              // Source line of exception
    private   $trace;                             // Unknown

    public function __construct($message = null, $code = 0)
    
        if (!$message) 
            throw new $this('Unknown '. get_class($this));
        
        parent::__construct($message, $code);
    

    public function __toString()
    
        return get_class($this) . " '$this->message' in $this->file($this->line)\n"
                                . "$this->getTraceAsString()";
    

【讨论】:

+1 是我发现的第一个真正告诉我如何定义 PHP 异常以及如何抛出它的地方 我最大的问题是你到底是怎么覆盖FINAL public function getCode()的? 天哪,那段代码最初是 7 年前写的(我才发现是谁写的)。它仍然是 php.net 的异常手册页上的倒数第二条评论。复制时我正在运行 php 5.3,现在正在运行 php 5.4。 php 8 中的最终方法可能是也可能不是最终方法(我在那里的评论是面向未来的)与我无关 :) php.net/manual/en/language.exceptions.php#91159 无论谁反对这个答案,请不要在解释的情况下这样做。我刚刚对照 PHP 7.0.6 和 PHP 5.4,43 进行了检查,没有问题。【参考方案3】:

在“框架设计指南”的合著者 Krzysztof Cwalina 的博客上查看 How to Design Exception Hierarchies。

【讨论】:

那里有一些优点,但我不是 100% 被卖掉的。我喜欢这个概念: "数据访问层中发生的异常被记录下来,然后包装在另一个异常中,为调用层提供更有意义的信息... ...。在业务组件层中,异常在传播之前被记录下来。业务组件层中出现的任何包含敏感信息的异常都将替换为不再包含此信息的异常。 ... ...这些被发送到用户界面 (UI) 层并显示给用户。”来自msdn.microsoft.com/en-us/library/cc511859.aspx# 这让我想到了责任链设计模式:@987654323 @ @mickmackusa 在 2009 年,没有那么多。【参考方案4】:

永远不要相信用户会“做正确的事”,并包含用于调试的信息。如果您需要信息,则需要自己收集并将其存储在可访问的地方。

同样如前所述,如果做某事很难(呃),用户会避免这样做,所以再次强调,不要依赖他们的善意和他们对需要发送什么的了解。

这种想法意味着一种收集信息并记录它的方法,这意味着在某处使用 var_dump()。

此外,正如 Mark Harrison 所说,一个可以轻松在某处发送错误消息的按钮对您和用户来说都非常棒。这使他们很容易报告错误。您(作为收件人)会收到很多重复信息,但重复信息总比没有信息好。

【讨论】:

【参考方案5】:

无论你添加多少细节,一定要确保

可以轻松地剪切和粘贴整个内容,或者 有一个按钮可以为他们报告错误

【讨论】:

...虽然这更像是最后的接触措施而不是架构问题

以上是关于自定义异常消息:最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

PHP中日志自定义异常的最佳实践

Java Exception最佳实践(转)

验证 Mongoose Schema 并显示自定义错误消息的最佳实践

自定义异常中的损坏消息

自定义异常中的默认消息 - Python

用于检索自定义消息而不是异常详细信息的 PLSQL 异常处理