检查每个函数调用并引发异常



我正在用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);
}
}

当我检查并在每个函数调用上抛出异常时,您可以看到函数看起来太复杂/难以阅读。这样做不好吗?

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

让我们来谈谈这段代码。

它有"不良做法"吗

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

何时需要良好做法

当代码将来要成为高级别维护(修改、删除等)的一部分时,通常需要良好的实践。但是,如果你的代码将永远保持原样,而现在你将埋葬它,明天就会忘记它,那么可能就不需要"良好实践"了。

此外,如果你的代码要与其他人(你的同事等)共享,这是遵循良好做法的一个好步骤,可以让你的同事轻松理解代码,也可以轻松修改代码(如果要修改代码)。

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

乍一看,您的代码插入了一点圈复杂度。

这也违反了开放/封闭原则,即必须对扩展开放,但不能对修改开放。

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

我该如何遵循良好的实践,因为我的代码会发生很大的变化,而且我的老板整天都在更改需求,我的同事需要理解我的代码并能够更改它

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

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

建议的解决方案

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

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);
}
}

在我看来,这将是一种提高可维护性的方法。例如,如果将来需要在图像url中添加正则表达式检查,那该怎么办。您只需要添加另一个验证器。

下一步是应用控制的反转

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);
}
}

就是这样。你会看到集成测试不断通过,这非常好。

还好。

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

最新更新