检查并在每个函数调用上抛出异常

时间:2013-12-20 08:10:25

标签: php exception exception-handling

我正在用PHP编写一个函数,以便在给定其url的情况下将图像保存到本地文件。这就是我想出的:

private function retrieve_image_url($image_url, $upload_path) {
    $img_data = @file_get_contents($image_url);
    if ($img_data === false) {
      throw new ImageRetrieverException('Invalid image source: '. $image_url);
    }
    $file = @fopen($upload_path, "w+");
    if ($file === false) {
      throw new ImageRetrieverException('Cannot open for writting: '. $upload_path);
    }
    if (fwrite($file, $img_data) === false) {
      throw new ImageRetrieverException('Writing failed: '. $result);
    }
    if (fclose($file) === false) {
      throw new ImageRetrieverException('Cannot close: '. $file);
    }
}

你可以看到函数看起来太复杂/难以阅读,因为我正在检查并在每个函数调用上抛出异常。这样做是不好的做法吗?

2 个答案:

答案 0 :(得分:1)

我想你只发布了retrieve_image_url方法的一部分。

让我们谈谈那段代码。

是否有“不良做法”?

这取决于。大多数“良好实践”都是关于代码应该如何的提示,但不是强制性的 如果您遵循所有代码的所有良好实践,您可能会引入过度工程,在大多数情况下不需要。 (这是一个糟糕的做法大声笑。看看YAGNI

何时需要良好实践?

当代码将成为未来高级维护(修改,删除等)的一部分时,通常需要良好的实践。但是,如果你的代码将永远像它的方式,现在你将埋葬并明天忘记它,那么可能不需要“良好实践”。

此外,如果您的代码将与其他人(您的同事等)共享,那么遵循良好实践是一个很好的步骤,以便您的同事可以轻松地理解代码并轻松修改代码(如果要修改它) )。

我的代码违反了一些良好做法

初看起来,您的代码会插入一些cyclomatic complexity

它也违反了open/closed principle,它表示你必须开放以进行扩展,但不能进行修改。

为什么该方法是私有的?当这些方法要从类中的多个地方调用时,我经常使用私有方法。如果这个方法只是从一个地方调用,最好内联调用它的方法。

我如何遵循良好的做法,因为我的代码会发生很大变化,而且我的老板一整天都在改变要求,我的同事需要了解我的代码并能够改变它?

在我看来,我会将您的代码介绍给单元测试 测试将暗示什么是坏的,什么是好的 这听起来很疯狂,但随着时间的推移,你会学会“听到测试对你的代码说什么”。

但首先它需要一些集成测试,一个安全网,它将确认我们的重构没有破坏代码。

建议的解决方案

首先,我们对所有异常情况和有效案例进行集成测试:

public class retrieveImageUrl extends PHPUnit_Framework_TestCase
{
    public static function casesProvider()
    {
        return array(
            array("bad URL", "Bad Upload PATH"), //Keep adding all the invalid cases here
            array("replace here with good URL", "Bad upload PATH"), //etc etc
        );
    }
    /**
     * @expectedException ImageRetrieverException
     * @dataProvider casesProvider
    */
    public function testRetrieveImageThrowsInvalidImageSource($url, $path) {
          $yourClass = new YourClass();
          $yourClass->retrieve_image_url($url, $path);
    }


    public function testRetrieveImageValidCase() {
          $yourClass = new YourClass();
          $yourClass->retrieve_image_url("replace with good url", "replace with good path");
    }
}

测试将失败,因为该方法是私有的。 我们将方法公之于众(我们可以测试它)

public function retrieve_image_url($image_url, $upload_path) {
    $img_data = @file_get_contents($image_url);
    if ($img_data === false) {
      throw new ImageRetrieverException('Invalid image source: '. $image_url);
    }
    $file = @fopen($upload_path, "w+");
    if ($file === false) {
      throw new ImageRetrieverException('Cannot open for writting: '. $upload_path);
    }
    if (fwrite($file, $img_data) === false) {
      throw new ImageRetrieverException('Writing failed: '. $result);
    }
    if (fclose($file) === false) {
      throw new ImageRetrieverException('Cannot close: '. $file);
    }
}

然后我们使用复合模式遵循开放/封闭原则。

我们创建了一个“Validator”类,它将验证实际代码中的两个约束。

class imageValidator implements InterfaceValidator {
    private $validators;

    public __construct(array $validators = array()) {
        $this->validators = $validators;
    }

    public function validate($file = null, $imgData = null) {
        foreach($this->validators as $validator) 
            $validator->validate($file, $imgData);
    }
}

我们创建了将成为所有验证器实现的接口。

interface imageInterfaceValidator {
    public function validate($file = null, $imgData = null);
}

接下来要查看验证方法输入的约束:

$img_data === false -> throw new ImageRetrieverException('Invalid image source: '. $image_url);

$file === false -> throw new ImageRetrieverException('Cannot open for writting: '. $upload_path);

并将每个实现在实现验证器接口的类中。例如,第一个验证器将是:

class validatorImageFalse implements imageInterfaceValidator {
    public function validate($file = null, $imgData = null) {
        if($img_data === false) {
            throw new ImageRetrieverException('Invalid image source: '. $image_url);
        }
    }
}

依此类推,其他约束。

完成代码后,或多或少应该看起来像这样:

public function retrieve_image_url($image_url, $upload_path) {
    $validatorComposite = new imageValidator(array(/** Put here all the validators classes **/));
    $img_data = @file_get_contents($image_url);
    $file = @fopen($upload_path, "w+");
    $validatorComposite->validate($file, $imgData);

    if (fwrite($file, $img_data) === false) {
      throw new ImageRetrieverException('Writing failed: '. $result);
    }
    if (fclose($file) === false) {
      throw new ImageRetrieverException('Cannot close: '. $file);
    }
}

在我看来,这是一种提高可维护性的方法。因为,如果将来你需要在图片网址中添加正则表达式检查,例如。您只需添加另一个验证器。

接下来就是申请inversion of control

public function retrieve_image_url($image_url, $upload_path, imageInterfaceValidator $validatorComposite) {
    $img_data = @file_get_contents($image_url);
    $file = @fopen($upload_path, "w+");
    $validatorComposite->validate($file, $imgData);

    if (fwrite($file, $img_data) === false) {
      throw new ImageRetrieverException('Writing failed: '. $result);
    }
    if (fclose($file) === false) {
      throw new ImageRetrieverException('Cannot close: '. $file);
    }
}

就是这样。您将看到集成测试继续传递,这是非常好的。

答案 1 :(得分:0)

没关系。

Symfony有类似的文件系统代码:https://github.com/symfony/Filesystem/blob/master/Filesystem.php:)