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

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

我看到很多人都有例外,比如:

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#

而且没有提到任何类似的事情,所以也许这是一个坏主意..。

67921 次浏览

However much detail you add, be sure and either

  • make it easy to cut and paste the whole thing, or
  • have a button that will report the error for them

See How to Design Exception Hierarchies on the blog of Krzysztof Cwalina, a coauthor of "Framework Design Guidelines".

I strongly recommend the advice on Krzysztof's blog and would note that in your case you seem to be trying to deal with what he calls Usage Errors.

In this case what is required is not a new type to indicate it but a better error message about what caused it. As such a helper function to either:

  1. generate the textual string to place into the exception
  2. generate the whole exception and message

Is what is required.

Approach 1 is clearer, but may lead to a little more verbose usage, 2 is the opposite, trading a terser syntax for less clarity.

Note that the functions must be extremely safe (they should never, ever cause an unrelated exception themselves) and not force the provision of data that is optional in certain reasonable uses.

By using either of these approaches you make it easier to internationalise the error message later if required.

A stack trace at a minimum gives you the function, and possibly the line number, thus you should focus on supplying information that is not easy to work out from that.

Never, ever trust a user to 'do the right thing', and include information for debugging. If you want information, you need to gather it yourself and store it somewhere where its accessible.

Also as stated, if it's hard(er) to do something, the users will avoid doing it, so again, don't depend on their goodwill and their knowledge of what they need to send.

This thinking implies a method by which you collect the information and log it, which implies using var_dump() somewhere.

Also, as said by Mark Harrison, a button which makes it easy to send an error message somewhere is fantastic for you and for the users. It makes it easy for them to report an error. You (as the recipient) get a lot of duplicates, but duplicate information is better than no information.

I won't detract from the advise regarding Krzysztof's blog, but here is a dead-easy way to create custom exceptions.

Example:

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


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

The code behind that (which I borrowed somewhere, apologies to whomever that was)

<?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()}";
}
}