自定义异常消息:最佳做法


72

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

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

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

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

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

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

当然,如果它冒泡到顶部,它将打印堆栈跟踪,这很有用,但是它并不总是告诉我我需要知道的所有信息,通常我最终不得不开始拍摄var_dump()语句才能发现出了什么问题以及在哪里...尽管这可以通过一个体面的异常处理程序来弥补。

我开始考虑下面的代码,在该代码中,我要求异常的抛出者提供必要的arg以产生正确的错误消息。我认为这可能是解决问题的方法:

  • 必须提供最低级别的有用信息
  • 产生一些一致的错误消息
  • 异常消息的模板全部集中在一个位置(异常类),因此更容易更新消息...

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

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

/**
* @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#

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


1
可以肯定的是,这里的构造函数必须匹配RuntimeException::__construct()
Jacob Thomason

Answers:


33

我强烈推荐Krzysztof博客上的建议,并请注意,在您的情况下,您似乎正在尝试解决他所说的“用法错误”。

在这种情况下,所需的不是指示它的新类型,而是关于引起它的原因的更好的错误消息。这样的助手功能是:

  1. 生成文本字符串以放入异常
  2. 生成整个异常和消息

是必需的。

方法1更清晰,但可能会导致更冗长的用法,方法2相反,为降低清晰度而使用了terser语法。

请注意,这些功能必须极其安全(它们绝不应该导致自身无关的异常),并且不得在某些合理使用中强制提供可选的数据。

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

堆栈跟踪至少可以为您提供功能以及行号,因此,您应该集中精力提供不容易从中得出的信息。


是的,现在我明白了。我正在做很多使用错误和健全性检查,尽管健全性检查可能应该移到assert()调用php.net/manual/en/function.assert.php
timoxley

Krzysztof的博客建议重用现有类型。即。FileNotFoundException,NullArgumentException等。但是,PHP仅具有一种异常类型-异常。它可以扩展,但是它是默认情况下唯一包含的一个组件,它本身特别有用... idk
neubert

提示:标准PHP库(SPL)提供了大量内置异常。php.net/manual/en/spl.exceptions.php
GDmac

21

我不会偏离有关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
+1是我发现的第一个地方,实际上告诉我如何定义PHP异常以及如何引发它
Brian Underwood 2015年

我的最大问题是,您到底如何覆盖FINAL公共函数getCode()
Alex Barker

天哪,该代码最初是7年前编写的(我刚刚找到是谁编写的)。它仍然是php.net异常手册页的第二至上注释。复制时我正在运行php 5.3,现在正在运行php 5.4。php 8中的最终方法是什么(或可能不是最终结果)(我的评论在这里过时
Orwellophile

3
对于拒绝该答案的人,请不要解释。我只是对照PHP 7.0.6和PHP 5.4,43对其进行了检查,没有任何问题。
Orwellophile

11

请参阅“框架设计准则”的合著者Krzysztof Cwalina的博客上的“如何设计异常层次结构”。


那里有一些优点,但我不是100%出售。我喜欢这个概念:
timoxley

“记录在数据访问层中的异常,然后将其包装在另一个异常中,该异常为调用层提供了更有意义的信息……
timoxley,2009年

…… 在业务组件层中,在传播异常之前记录它们。业务组件层中发生的任何包含敏感信息的异常都将替换为不再包含此信息的异常。...
timoxley

...将它们发送到用户界面(UI)层并显示给用户。” from msdn.microsoft.com/zh-cn/library/cc511859.aspx#, 这使我考虑了责任链设计模式:en.wikipedia.org/wiki/Chain_of_responsibility_pattern#PHP
timoxley,2009年

1
@mickmackusa 2009年,不是很多。
约翰·桑德斯

3

永远不要相信用户“做正确的事”,并提供调试信息。如果需要信息,则需要自己收集信息并将其存储在可访问的位置。

同样,如前所述,如果很难做某事,那么用户将避免这样做,因此,再次,不要依赖于他们的信誉和对发送内容的了解。

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

而且,正如Mark Harrison所说,一个按钮可以使您轻松地将错误消息发送到某个地方,这对您和用户来说都是不错的选择。这使他们很容易报告错误。您(作为收件人)会收到很多重复的信息,但是重复的信息总比没有信息要好。


0

无论您添加了多少细节,请确保

  • 使整个内容易于剪切和粘贴,或者
  • 有一个按钮将为他们报告错误

2
...虽然这更多的是最终的解决方案,而不是架构问题
timoxley 2009年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.