在这个 PHP 特定情况下,GOTO 是一个好的实践吗?

3

我在向用户展示错误信息的方式上遇到了一些问题。我通过使用两个Goto指令来“解决”这个问题。请看代码:

<?php require_once("registration/include/membersite_config.php"); ?>
    <!DOCTYPE html>
    <html lang="en">
    <head><?php include_once("parts/head.php"); ?></head>
<body>
<div id="footerfix">
<?php include_once("parts/header.php"); ?>
    <div class="container">
        <div class="hero-unit">
<?php
if (isset($_GET['i'])) {
    unlink("users/thumbs/" . $_SESSION["user_code"] . ".jpg");
    header('Location: profile.php?i=mycv');
}
if (isset($_FILES['avatar']['tmp_name'])) {
    $file_ext = end(explode('.', $_FILES['avatar']['name']));
    if (in_array($file_ext, array('jpg', 'jpeg', 'png', 'gif')) == false) {
        echo("<h2>Error!</h2><p>Your profile photo have to be a picture file.</p>");
        goto nomore;
    }
    $src_size = getimagesize($_FILES['avatar']['tmp_name']);
    if ($src_size['mime'] == 'image/jpeg') {
        $src_img = imagecreatefromjpeg($_FILES['avatar']['tmp_name']);
    } elseif ($src_size['mime'] == 'image/png') {
        $src_img = imagecreatefrompng($_FILES['avatar']['tmp_name']);
    } elseif ($src_size['mime'] == 'image/gif') {
        $src_img = imagecreatefromgif($_FILES['avatar']['tmp_name']);
    } else {
        echo("<h2>Error!</h2><p>Incorrect file format.</p>");
        goto nomore;
    }
    $thumb_w = 150;
    if ($src_size[0] <= $thumb_w) {
        $thumb = $src_img;
    } else {
        $new_size[0] = $thumb_w;
        $new_size[1] = ($src_size[1] / $src_size[0]) * $thumb_w;
        $thumb = imagecreatetruecolor($new_size[0], $new_size[1]);
        imagecopyresampled($thumb, $src_img, 0, 0, 0, 0, $new_size[0], $new_size[1], $src_size[0], $src_size[1]);
    }
    imagejpeg($thumb, "users/thumbs/" . $_SESSION["user_code"] . ".jpg");
    //header('Location: profile.php?i=mycv');
    echo('<h2>Ready!</h2><p>Your profile picture is updated. <a href="profile.php">Go back</a>.</p>');
    nomore:
    echo "</div></div>";
    include_once("parts/footer.php");
    echo "</div></body></html>";
}
?>

我从来不明白为什么goto语句是代码中最糟糕的事情(至少,每个人都这么说),我想听听你们对此的看法,如果它真的是有史以来最糟糕的事情,那么如何在没有它们的情况下使用我的代码?谢谢!

enter image description here


11
抱歉直言,但使用“goto”语句并不是你最大的问题。最显著的问题是代码本身一开始就难以阅读。 - Daniel Brockman
1
请参考 Steve McConnell 的《代码大全》中的 http://www.stevemcconnell.com/ccgoto.htm。有时间的话,请阅读整本书。 - Michael Berkowski
1
这样的讨论有助于认识到该漫画中的 goto 和 PHP 中的 goto 构造具有极大的不同行为。 - mario
1
请在“PHP实践”的背景下定义“好”。 - hakre
你知道那堆字母里面是什么吗?我觉得可能是PHP,但我看不太清楚。 - Bojan Kogoj
6个回答

7
GOTO语句为什么不好?简短的回答是可读性会受到影响。请考虑以下代码:
<?php require_once("registration/include/membersite_config.php"); ?>
<!DOCTYPE html>
<html lang="en">
<head><?php include_once("parts/head.php"); ?></head>
  <body><div id="footerfix">
  <?php include_once("parts/header.php"); ?>
    <div class="container">
      <div class="hero-unit">
<?php
if(isset($_GET['i'])){ unlink("users/thumbs/".$_SESSION["user_code"].".jpg"); header('Location: profile.php?i=mycv');}
if(isset($_FILES['avatar']['tmp_name'])){
    $file_ext = end(explode('.',$_FILES['avatar']['name']));
    if(in_array($file_ext,array('jpg','jpeg','png','gif'))==false){
        echo("<h2>Error!</h2><p>Your profile photo have to be a picture file.</p>");
    }
    else {
        $src_size=getimagesize($_FILES['avatar']['tmp_name']);
        if($src_size['mime']=='image/jpeg') {
            $src_img=imagecreatefromjpeg($_FILES['avatar']['tmp_name']);
        } elseif($src_size['mime']=='image/png') {
            $src_img=imagecreatefrompng($_FILES['avatar']['tmp_name']);
        } elseif($src_size['mime']=='image/gif') {
            $src_img=imagecreatefromgif($_FILES['avatar']['tmp_name']);
        } else {
            echo("<h2>Error!</h2><p>Incorrect file format.</p>");
        }
        if(!empty($src_img)) {
            $thumb_w = 150;
            if($src_size[0]<=$thumb_w){
                $thumb=$src_img;
            }else{ 
                $new_size[0] = $thumb_w;
                $new_size[1] = ($src_size[1]/$src_size[0])*$thumb_w;
                $thumb=imagecreatetruecolor($new_size[0],$new_size[1]);
                imagecopyresampled($thumb,$src_img,0,0,0,0,$new_size[0],$new_size[1],$src_size[0],$src_size[1]);
            }
            imagejpeg($thumb,"users/thumbs/".$_SESSION["user_code"].".jpg");
            //header('Location: profile.php?i=mycv');
            echo('<h2>Ready!</h2><p>Your profile picture is updated. <a href="profile.php">Go back</a>.</p>');
        }
    }
}
?>
    </div></div>
    <?php include_once("parts/footer.php"); ?>
</div>
</body>
</html>

无论如何,你应该考虑将模板与逻辑分离(搜索“MVC”),并且对于复杂操作至少使用函数。

请注意,您代码中的头部行将无法工作,因为您已经开始输出。 - fboes

5

我认为你在这里使用goto是合理的,因为你基本上使用了以下模式:

if (error_condition_1) {
    goto handle_errors;
} else {
    // do stuff
}
if (error_condition_2) {
    goto handle_errors;
} else {
    // do stuff
}
// ...
if (last_error_condition) {
    goto handle_errors;
} else {
    // do stuff
}
// do stuff
handle_errors:
    // handling errors

有其他方法可以完成这项任务而不需要使用goto,例如,你可以使用一个do ... while (false)循环,然后在适当的位置使用break语句。然而,这只是掩盖了你正在使用goto的事实。
在计算机程序员中,goto已经成为了一种被避讳的状态,但现在大多数情况下这是不公平的;有一些情况下使用goto是完全合理的选择(我要赶紧补充一句,这并不是绝大多数情况),但通常,即使它是一个有效的设计选择,使用goto也可能会引起人们的关注和批评。
正如Daniel Brockman所说,你的主要问题在于代码难以阅读。它似乎是挤压在尽可能少的行数上,非常密集。看看其他网站上的代码,了解其他人如何分隔他们的代码,并思考是否认为使用其他约定的代码更具可读性。
在PHP中,需要记住goto仅在版本5.3及以上才可用,因此,如果你的代码需要可移植性,你可能需要考虑其他选项。

1
丹尼尔·布罗克曼似乎已经删除了他的回答,但我不知道为什么。--哦,这只是一条评论,而不是一个答案。请忽略此评论。 - Hammerite
还应该注意的是,一旦完成可读性和文档化版本,您可以将其打包以供以后分发,以节省空间等。当您编写程序时,没有必要尽可能地将大量代码压缩到一行中。 - ShadowScripter
你所描述的模式最好使用异常来处理。 - Nick Johnson

2
你的代码很混乱。如果实现GOTO语句,它会变成噩梦。 就像XKCD展示的那样,可怕的事情会发生。我记得当我开始学习C++时,“意大利面条式代码”常常指的是使用goto。它总是会搞乱你的执行流程。更不用说它破坏了可读性。
无论如何,我已经为您重新组织了程序,现在应该可以工作了。 脚本
<?php require_once("registration/include/membersite_config.php"); 
    //redirect user if i is defined
    if(isset($_GET['i'])){ unlink("users/thumbs/".$_SESSION["user_code"].".jpg"); header('Location: profile.php?i=mycv');}
?>
<!DOCTYPE html>
<html lang="en">
<head><?php include_once("parts/head.php"); ?></head>
  <body>
    <div id="footerfix">
        <?php include_once("parts/header.php"); ?>
        <div class="container">
            <div class="hero-unit">
<?php

check_file();

function check_file(){
    //check for file 
    if(isset($_FILES['avatar']['tmp_name'])){
        //get the file extension
        $file_ext = end(explode('.',$_FILES['avatar']['name']));

        //validate extension
        if(in_array($file_ext,array('jpg','jpeg','png','gif')) ==false){
            echo "<h2>Error!</h2><p>Your profile photo have to be a picture file.</p>"; 
            return -1;
        }

        //get the image dimensions
        $src_size = getimagesize($_FILES['avatar']['tmp_name']);

        switch($src_size['mime']){
            case 'image/jpeg':
                $src_img=imagecreatefromjpeg($_FILES['avatar']['tmp_name']); break;
            case 'image/png':
                $src_img=imagecreatefrompng($_FILES['avatar']['tmp_name']); break;
            case 'image/gif':
                $src_img=imagecreatefromgif($_FILES['avatar']['tmp_name']); break;
            default:
                echo("<h2>Error!</h2><p>Incorrect file format.</p>");
                return -1;
        }

        $thumb_w = 150;

        //if image is within allowed dimensions, set thumbnail as image
        if($src_size[0]<=$thumb_w){
            $thumb = $src_img;
        }else{ 
            $new_size[0] = $thumb_w;
            $new_size[1] = ($src_size[1]/$src_size[0])*$thumb_w;
            $thumb= imagecreatetruecolor($new_size[0],$new_size[1]);
            imagecopyresampled($thumb,$src_img,0,0,0,0,$new_size[0],$new_size[1],$src_size[0],$src_size[1]);
        }

        //create the updated image
        imagejpeg($thumb,"users/thumbs/".$_SESSION["user_code"].".jpg");

        echo '<h2>Ready!</h2><p>Your profile picture is updated. <a href="profile.php">Go back</a>.</p>';
    }
}
?>
                </div>
            </div>
            <?php include_once("parts/footer.php"); ?>
        </div>
    </body>
</html>

如您所见,我将整个过程封装在一个函数中。这样,当您遇到错误时,可以使用return语句。将其封装成一个函数还可以快速重用。您可以添加一些参数使其更具动态性。

1

Goto语句会导致代码混乱不堪!换句话说,你将很难理解代码块中的代码流程!使用结构化控制语句完全可以避免使用Goto语句!正如著名计算机科学家艾兹格·沃·迪科斯彻在他1968年的文章《反对GO TO语句》(由编辑尼古劳斯·维尔特改名为“考虑到语句的有害性”)中所暗示的那样。请参阅维基百科关于控制结构的条目http://en.wikipedia.org/wiki/Control_structures作为起点。

这个概念适用于所有编程语言!


使用控制结构来消除goto语句是可行的,但这样做是否总能提高代码的可读性呢? - Hammerite
1
@Hammerite,我认为答案几乎普遍是肯定的,因为与所有其他流程控制原语不同,goto不表现出局部性 - 它可以跳转到任何地方,独立于代码结构。我唯一能想到的例子是使用计算goto的字节码解释器。 - Nick Johnson
“goto不表现出局部性”,这是事实,但它仍然可以以有助于“代码结构”的方式使用;例如,允许执行跳转到主过程逻辑之后放置的错误处理块。在这种情况下,通过替换goto,代码可读性是否没有显着提高,这难道不是真的吗? - Hammerite

0

GOTO 在任何地方都不是一个好的实践。


3
“_GOTO在任何地方都不是一个好的编程实践_” [需要引证] 我并不反对这个说法,但需要更多具体的内容来支持它。 - Michael Berkowski
6
这是一份宗教信仰声明。在某些情况下,使用goto语句并不是大问题,而且可能是最易读的控制流实现方式。但是,在绝大多数情况下,使用goto是不合适的。 - Hammerite

0
在我看来,继续或者中断会更好。如果你想使用goto,请考虑一下你的脚本流程,很可能可以改进。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接