zend framework 中的 php 编码标准 (五) - 错误与异常

网络整理 - 08-26

  1. Zend Framework 的代码应该不存在 E_STRICT 兼容问题。在开启错误报告 error_reporting 级别为 E_ALL | E_STRICT 时,Zend Framework 的代码不应该抛出任何警告(E_WARNING, E_USER_WARNING),任何通知(E_NOTICE, E_USER_NOTICE)以及任何兼容问题(E_STRICT)。

  这就是说,Zend Framework 尽量避免代码写法上的错误。而如果真的发生程序中断,也只能是逻辑错误。

  2. Zend Framework 不应该存在 Php 错误,如果我们不得不遇到错误的话,请用异常来处理。Zend Framework 中有专门的异常类来为用户提供友好的异常处理。

  例如 :

class Zend_Exception extends Exception
{
}

class Zend_Db_Exception extends Zend_Exception
{
}

class Zend_Db
{
    public static function factory($adapter, $config = array())
    {
        // ...
        if (!is_array($config)) {
            /**
             * @see Zend_Db_Exception
             */
            require_once 'Zend/Db/Exception.php';
            throw new Zend_Db_Exception('Adapter parameters must be in'
                                      . 'an array or a Zend_Config object');
        }
    }
}

  3. 在框架模块内的异常统一用 new 关键字构造是公认的良好习惯。

  例如 :

require_once 'Zend_Component_SpecificException.php';
class Zend_Component
{
    public function foo($condition)
    {
        if ($condition) {
            throw new Zend_Component_SpecificException(
                '一些友好的信息');
        }
    }
}

  4. 异常必须延迟加载。

  例如 :

// 正确
if ($condition) {
    require_once 'Zend_Component_SpecificException.php';
    throw new Zend_Component_SpecificException(
        '一些友好信息');
} else {
    // ...
}

// 错误
require_once 'Zend_Component_SpecificException.php';
if ($condition) {
    throw new Zend_Component_SpecificException(
        '一些友好信息');
} else {
    // ...
}

  5. 通俗地讲,如果用户希望 Zend Framework 模块做出一些超出其能力范围的工作时,那么抛出异常则是明智而正确的选择。相反,假如该模块能够处理用户的需求,但用户却给出各种意想不到的输入,这个时候,模块就不应该抛出异常,而应该正常运行下去。

  例如 :

if ($canNotPerformThisAction) {
    require_once 'Zend/Exception.php';
    throw new Zend_Exception('不能执行此动作 !');
}

$foo = 'bar';
if ($foo == 'foo') {
    echo '对的';
} else {
    // 没必要抛出异常
}

  6. 避免抛出 Exception 基类异常,而应该尽量使用派生异常类,这可以让人清楚知道问题所在。

  例如 :

class Zend_Db
{
    public static function factory($adapter, $config = array())
    {
        if (!is_array($config)) {
            throw new Exception('我们根本不知问题发生在哪儿。');
        }
        if (!is_array($config)) {
            require_once 'Zend/Db/Exception.php';
            throw new Zend_Db_Exception("我们都知道问题出在 Zend_Db 这儿。");
        }
    }
}

  7. 尽量避免去捕捉 Exception 基类异常。如果在 try 语句里面可能抛出多种异常的话,那么我们应该为各种异常准备各自独立的 catch 块,而不是仅用一个 catch 块去捕捉 Exception 基类异常。

  例如 :

// index.php
try {
    $app->run();
} catch (Zend_Db_Exception $e) {
    die('数据库异常 !');
} catch (Zend_Acl_Exception $e) {
    die('权限分配异常 !');
} catch (Zend_Auth_Exception $e) {
    die('身份认证异常 !');
} catch (Zend_Exception $e) {
    // 所有其它异常
}

  8. 我们通常需要在类中通过拓展多个异常类来区分各种不同的情况。例如,我们需要创建两个异常类来区分 "参数错误" 和 "用户缺乏权限" 两种情况。

  例如 :

class Zend_Db_Exception extends Zend_Exception
{
    // 数据库异常基类
}

class Zend_Db_Select_Exception extends Zend_Db_Exception
{
    // 用于处理 select 类异常
}

class Zend_Db_Table_Exception extends Zend_Db_Exception
{
    // 用于处理数据库表的异常
}

  9. 不要把所有诊断信息都放在异常的 message 里,我们可以在任何需要的时候创建自己的异常类的成员和方法,来为 catch 语句提供帮助。我们需要做的就是在 constructor 构建异常类时,传入正确的参数信息。

  例如 :

class Zend_Exception extends Exception
{
}

class My_Exception extends Zend_Exception
{
    private $_importantDiagnostic;
    public function setImportantDiagnostic($value)
    {
        $this->_importantDiagnostic = $value;
    }
    public function getImportantDiagnostic()
    {
        return $this->_importantDiagnostic;
    }
    public function __construct($message = null, $code = 0, $value)
    {
        parent::__construct($message, $code);
        $this->setImportantDiagnostic($value);
    }
}

try {
    if ($isMyFault) {
        throw new My_Exception('没有信息', 0, '信息在这儿');
    }
} catch (My_Exception $e) {
    echo $e->getImportantDiagnostic();
}

  10. 在错误发生的时候,程序不应该保持沉默,甚至对异常置之不理。而是应该要么修正它,要么抛出新的异常来代替它。

  例如 :

try {
} catch (My_Exception $e) {
    tryToCorrectIt($e);
} catch (My_Exception $e) {
    throw new My_Exception($e->getMessage(), '110');
} catch (My_Exception $e) {
    // 不作为是愚蠢的行为!
}

  11. 我们应该为我们程序的不同层面准备不同的异常处理。例如,我们不应该把数据逻辑层的错误(即俗称 SQLException)搬到业务逻辑层。

  例如 :

class My_Dao_User_Exception extends Zend_Db_Table_Exception
{
    // 数据层 User 异常类
}

class My_Dao_User extends Zend_Db_Table_Abstract
{
    // 数据层 User 对象
}

class My_Bis_User_Exception extends Zend_Exception
{
    // 业务层 User 异常类
}

/**
* 业务层 User 对象
*/
class My_Bis_User
{
    private $_daoUser = null;

    public function setDaoUser($user)
    {
        $this->_daoUser = $user;
    }

    public function getDaoUser()
    {
        return $this->_daoUser;
    }

    public function throwSomething()
    {
        try {
            if ($wrong) {
                throw new My_Dao_User_Exception('你不应该把数据层的异常搬到这里!');
            } else if ($right) {
                throw new My_Bis_User_Exception('这才是正确的。');
            }
        } catch (Zend_Exception $e) {
            // ...
        }
    }
}

  12. 不要把异常处理机制当成控制流程,或者仅仅是返回某些值。

  例如

function itIsWrong()
{
    try {
        // 做些小动作
    } catch (Exception_One $e) {
        doSomething($e);
    } catch (Exception_Two $e) {
        doSomething($e);
    } catch (Exception_Three $e) {
        return true;
    }
}

function isRight()
{
    try {
        // 做些小动作
    } catch (Exception_One $e) {
        correctIt($e);
    } catch (Exception_Two $e) {
        correctIt($e);
    } catch (Exception_Three $e) {
        // 从不 return;
        throw new Exception('我们抛出它,而不是返回什么东西!');
    }

  13. 在用 catch 语句块处理异常的时候,我们应该首先释放多余的内存资源,如数据库连接,网络资源连接等。Php 并不提供类似 finally 之类的语句来进行垃圾处理。

  例如 :

try {
    $db = getDbConnection();
    throw new Exception('发生了错误。');
} catch (Exception $e) {
    unset($db);
    handleExceptionAndGoOn($e);
}