何时使用异常、错误对象和纯FALSE/NULL

时间:2016-02-21 作者:Doug Wollison

我正在编写一个插件,我正在尝试衡量何时使用不同的方法来处理错误。

我正在考虑三种方法:

抛出异常(自定义类)

  • 返回错误对象(WP\\u Error的扩展)
  • 只返回null/false

    试图获取/设置存储在注册表中不存在的选项,将无效值传递给方法(这应该很少见)调用类重载无法解析的方法?由于编写WordPress插件有一些特殊的考虑,我不确定是否值得在普通PHP板上问这个问题。

  • 2 个回复
    SO网友:gmazzap

    我认为在这里不可能给出一个明确的答案,因为这样的选择是个人的偏好。

    考虑到以下是我的方法,我不认为这是正确的方法。

    我可以肯定的是,你应该避免第三种选择:

    仅返回null/false

    这在不同方面是不好的:

    返回类型一致性使函数更难进行单元测试(if (! is_null($thing))...) 使代码更难阅读,我经常使用OOP来编写插件,当出现问题时,我的对象方法经常抛出异常。

    这样做,我:

    实现返回类型一致性使代码易于单元测试不需要对返回的类型进行条件检查,但是,在WordPress插件中引发异常意味着没有任何东西会捕捉到它们,最终导致致命错误not 理想的,尤其是在生产中。

    为了避免这个问题,我通常在主插件文件中有一个“主例程”,我将其包装在try / catch 块这使我有机会捕获生产中的异常并防止致命错误。

    类的粗略示例:

    # myplugin/src/Foo.php
    
    namespace MyPlugin;
    
    class Foo {
    
      /**
       * @return bool
       */
      public function doSomething() {
         if ( ! get_option(\'my_plugin_everything_ok\') ) {
            throw new SomethingWentWrongException(\'Something went wrong.\');
         }
    
         // stuff here...
    
         return true;
      }
    }
    
    并从主插件文件中使用它:

    # myplugin/main-plugin-file.php
    
    namespace MyPlugin;
    
    function initialize() {
    
       try {
    
           $foo = new Foo();
           $foo->doSomething();      
    
       } catch(SomethingWentWrongException $e) {
    
           // on debug is better to notice when bad things happen
           if (defined(\'WP_DEBUG\') && WP_DEBUG) {
              throw $e;
           }
    
           // on production just fire an action, making exception accessible e.g. for logging
           do_action(\'my_plugin_error_shit_happened\', $e);
       }
    }
    
    add_action(\'wp_loaded\', \'MyPlugin\\\\initialize\');
    
    当然,在现实世界中,您可能会抛出和捕获不同类型的异常,并根据异常表现出不同的行为,但这应该给您一个方向。

    我经常使用的另一个选项(您没有提到)是返回包含标志的对象,以验证是否没有发生错误,但保持返回类型的一致性。

    这是这样一个对象的粗略示例:

    namespace MyPlugin;
    
    class Options {
    
       private $options = [];
       private $ok = false;
    
       public function __construct($key)
       {
          $options = is_string($key) ? get_option($key) : false;
          if (is_array($options) && $options) {
             $this->options = $options;
             $this->ok = true;
          }
       }
    
       public function isOk()
       {
         return $this->ok;
       }
    }
    
    现在,您可以在插件的任何位置执行以下操作:

    /**
     * @return MyPlugin\\Options
     */
    function my_plugin_get_options() {
      return new MyPlugin\\Options(\'my_plugin_options\');
    }
    
    $options = my_plugin_get_options();
    if ($options->isOk()) {
      // do stuff
    }
    
    注意如何my_plugin_get_options() 以上始终返回Options 类,通过这种方式,您可以始终传递返回值,甚至可以将其注入到使用类型提示的其他对象,现在您担心类型不同。

    如果函数已返回null / false 如果出现错误,在传递之前,您必须检查返回的值是否有效。

    同时,您可以清楚地了解option实例是否有问题。

    这是一个很好的解决方案,可以使用默认值或任何适合的方法轻松恢复错误。

    SO网友:shariqkhan

    @gmazzap给出的答案非常详尽。

    再补充一点:考虑一个带注册表的申请。假设您有一个用于验证用户提交的用户名的函数。现在,由于多种原因,提供的用户名可能无法通过验证,因此只需返回false 这对用户了解出了什么问题没有多大帮助。在这种情况下,更好的选择是使用WP\\u Error,并为每个失败的验证添加错误代码。

    比较以下内容:

    function validate_username( $username ) {
        
        //No empty usernames
        if ( empty( $username ) ) {
            return false;
        }
        
        // Check for spaces
        if ( strpos( $username, \' \' ) !== false ) {
            return false;
        }
    
        // Check for length
        if ( strlen( $username ) < 8 ) {
            return false;
        }
    
        // Check for length
        if ( ( strtolower( $username ) == $username ) || strtoupper( $username ) == $username ) ) {
            return false;
        }
    
        // Everything is fine
        return true;
    }
    
    使用此选项:

    function validate_username( $username ) {
        
        //No empty usernames
        if ( empty( $username ) ) {
            $error->add( \'empty\', \'Username can not be blank\' );
        }
        
        // Check for spaces
        if ( strpos( $username, \' \' ) !== false ) {
            $error->add( \'spaces\', \'Username can not contain spaces \' );
        }
    
        // Check for length
        if ( strlen( $username ) < 8 ) {
            $error->add( \'short\', \'Username should be at least 8 characters long \' );
        }
    
        // Check for length
        if ( ( strtolower( $username ) == $username ) || strtoupper( $username ) == $username ) ) {
            $error->add( \'case\', \'Username should contain a mix of uppercase and lowercase characters \' );
        }
    
        // Send the result
        if ( empty( $error->get_error_codes() ) ) {
            return $error;
        }
    
        // Everything is fine
        return true;
    }
    
    第一个版本让用户完全不知道他们选择的用户名有什么问题。第二个版本为错误添加了描述,这对用户有很大帮助。

    希望这有点帮助。