Many people accused me recently for just mentioning a single word - "goto".
It makes me wonder, why it is considered such a nasty word. I am aware of several previous discussions on the topic, but it doesn't convince me - some of the answers just says "it's bad" not even trying to explain and some bring reasons, irrelevant for scripting languages like PHP, IMO.Anyway, I am going to ask a very particular question:
Let's comparegoto
and throw
statements.
Both, in my opinion, do the same thing: avoiding execution of some portion of code based on some condition.
If so - do throw
have same disadvantages as goto? (if any)?
If not - whit is the difference then.
Anyone experienced enough, who can tell the fundamental, conceptual difference between code structures of the two following code snippets?
Regarding these very code snippets, not "in theory" or "in general" or "here is a nice XKCD comic!". Why the first one considered to be brilliant and latter one considered to be deadliest of sins?#$a=1;
#$b=2;
/* SNIPPET #1 */
try {
if (!isset($a)) {
throw new Exception('$a is not set');
}
if (开发者_如何学Python!isset($b)) {
throw new Exception('$b is not set');
}
echo '$a + $b = '.($a + $b)."<br>\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "<br>\n";
}
/* SNIPPET #2 */
if (!isset($a)) {
$message = '$a is not set';
goto end;
}
if (!isset($b)) {
$message = '$b is not set';
goto end;
}
echo '$a + $b = '.($a + $b)."<br>\n";
end:
if (!empty($message)) {
echo 'Caught exception: ', $message, "<br>\n";
}
Note that I am aware of the fact that throw is more powerful and flexible in making spaghetti. It does not make the principal difference for me. It is matter of use, not concept.
EDIT
I've been told by many people that first example should be newer used. Reason: Exceptions should be used to handle errors only, not to implement business logic. It looks sensible.Therefore, the only way left to stop useless code execution is goto (not to mention some substitutes, such as while, return etc, which are the same matter but harder to implement)?
Throw/catch are much more flexible than goto: you can catch from the functions which call the code throwing the exception. Additionally, the required destructors are called automagically.
I think you need to ask the question the other way around: why would you use goto
when exceptions are perfectly suited to the situation? That's what exceptions are for.
You may as well ask "why use while
, for
and functions when I can just as easily use goto
?"
goto
hard codes the execution path into the code. Exceptions on the other hand allow the execution path to be determined at runtime.
For example, let's assume you have a database class that throws an exception on error. With exceptions, you could capture that error, and do something else before rendering the error page (Like clean up allocated resources, or "rollback" prior changes if you use a non-transactional db type. If you used a goto, you wouldn't have the chance to do that, since the goto would have rendered the error page.
Remember, keep your code reusable and flexible. goto
is the antithesis of both...
In your example there is effectively no difference as you are checking the error condition and either raising the exception or calling goto
if the condition fails. Your example could be recoded to remove the need for either construct.
Where exceptions are useful is where you are calling methods that may have an error state but can't handle it themselves as the following pseudo code illustrates:
try
{
$c = add($a, $b);
echo '$a + $b = '.($c)."<br>\n";
}
catch (Exception $e)
{
echo 'Caught exception: ', $e->getMessage(), "<br>\n";
}
The add
method does the error checking:
if (!isset($a))
{
throw new Exception('$a is not set');
}
if (!isset($b))
{
throw new Exception('$b is not set');
}
then returns the result of the addition.
This means that your main code flow shows the expected path through the program.
Since no one has yet provided an answer to this question that the OP found acceptable, I’ll throw my hat in the ring.
…the fundamental, conceptual difference between code structures of the two following code snippets?
There is no conceptual difference at all between the two code snippets you provided. In both instances a condition is tested, and if met, a message is thrown to another part of the program for handling.
Why the first one considered to be brilliant and latter one considered to be deadliest of sins?
Thank you Col. Shrapnel for opening the door to the following rant.
<rant>
Every programming construct holds the potential for abuse. The dogmatic persecution of goto
reminds me of the similar bashing of PHP. PHP leads to bad programming practices. Compared to: Goto leads to spaghetti code.
Simply put: goto
is “evil” because Edsger Dijkstra said so and Niklaus Wirth gave it his seal of approval. ;-p
</rant>
For your reading enjoyment: http://en.wikipedia.org/wiki/Goto
Probably the most famous criticism of GOTO is a 1968 letter by Edsger Dijkstra called Go To Statement Considered Harmful. In that letter Dijkstra argued that unrestricted GOTO statements should be abolished from higher-level languages because they complicated the task of analyzing and verifying the correctness of programs (particularly those involving loops). An alternative viewpoint is presented in Donald Knuth's Structured Programming with go to Statements which analyzes many common programming tasks and finds that in some of them GOTO is the optimal language construct to use.
It goes on to say,
Some programmers, such as Linux Kernel designer and coder Linus Torvalds or software engineer and book author Steve McConnell also object to Dijkstra's point of view, stating that GOTOs can be a useful language feature, improving program speed, size and code clearness, but only when used in a sensible way by a comparably sensible programmer.
That said, I personally haven’t found a practical use for GOTO since Commodore BASIC, but that’s neither here nor there. :-)
goto
is usfull for doing things like this:
foreach ($items as $item) {
if ($item->is_match()) goto match;
}
$item = new Item();
match:
render('find_item.php', $item);
as apposed to this:
$matching_item = null;
foreach ($items as $item) {
if ($item->is_match()) {
$matching_item = $item;
break;
}
}
if ($matching_item === null) {
$item = new Item();
}
render('find_item.php', $item);
goto
may have a little performance boost comparing to throw, since it doesn't create any exception stack.
In practical terms, the difference between your two code segments is that you'll have to spend more time explaining yourself to other programmers with the "goto" code.
For better or for worse, most programmers believe you should never use a goto. Perhaps you can prove that this is unfounded, and that your goto code is the best way to implement that function. Even so, you'll have to fight with coworkers or anyone else with whom you collaborate to get them to accept it.
If you are working by yourself and no one will see your code - use whatever you like. But if you're working on a team, sometimes the path of least resistance is most prudent.
The thing that's "wrong" is that goto
it can implement many kinds of control mechanisms, not just throw
, and therefore has little inherent structure. Because of this, there are several reasons why a script programmer would be interested in using goto
alternatives:
First, we are starting to see scripts moving from purely interpreted to byte-code compiled. Good examples of this can be found anywhere, such as Python, Perl 6, Scala (Lift), Clojure (Compojure). Even PHP has byte-code compilers. Since goto
has little structure, compilation of control mechanisms can't happen, which is a considerable performance loss because modern compilers are generally very good at this. For throw
, compilers can start assuming that an exception being thrown is not common, and therefore it will optimize the "normal" control path often at the cost of a less optimal "abnormal" control path.
Even if you don't have a full blown compiler, interpreters can assist a programmer considerably more when the programmer uses control mechanisms that have more structure. For example, for
loop blocks are lexically scoped by the compiler/interpreter, and so a programmer can use this scoping to modularize chunks of code. Otherwise, with goto
you are forced to pollute the variable namespace.
Furthermore, if your language allows you to declare methods that throw
exceptions, the compiler/interpreter can inform you that you haven't handled all the possible exceptions that the methods you are calling may experience (similar to throws
Java, if you have experience there). With goto
, the compiler has no idea what you're trying to do, so it won't help you out during compilation and testing.
Goto also requires the programmer to recover from the error. Imagine if you had some code like this one (snatched and edited from your snippets):
if (!isset($a)) {
$message = '$a is not set';
goto errorA;
}
if (!isset($b)) {
$message = '$b is not set';
goto errorB;
}
if (!moonInRightPhase) {
goto errorP
}
echo '$a + $b = '.($a + $b)."<br>\n";
errorA:
// Do something non-trivial and unique to handle $a not being set.
goto afterError
errorP:
// Do something non-trivial and unique to handle moon phase
// This error requires drastic failure of code path:
goto failure
errorB:
// Do something non-trivial and unique to handle $b not being set.
goto afterError
afterError:
// Program continues
return SUCCESS
failure:
return FAILURE
The problem here is that the programmer is required to do the right thing. If he forgets to goto afterError
, he will run through the next exception path. With throw
the mechanisms are a bit less error prone.
Finally, my personal experience makes me dislike the goto
statement simply because it is not easy to read. When I'm trying to understand a piece of code with four or five labels named some-error
, fail
, retry
, and they're not located close to the offending/excepting code, it can be a nightmare to untangle. And if it's not easy to read, then even the programmer trying to review what he or she did a year ago may lead to mistakes.
As a post-script to this answer, I don't believe "wrong" is the right way to describe goto
. That might have come from sensationalizing the famous Dijkstra paper. "Powerful" may be a better term, because it implies that it can do pretty much everything, but even the simplest mistakes make "wrong" turn into "horribly wrong". We shouldn't ban tools because they're really dangerous; we should better educate people about their danger, and let them decide.
I don't hate GOTO as much as many do, but I think its main problem is that it's not restricted to a specific "scope". With every other control structure, you know where it starts and where it ends. A GOTO label, on the other hand, may be placed in a lot more unpredictable places. You can of course search the label, but it breaks the readability.
Of course all is up to how it's used.
Don't use either. Use a "while(1)" around the bulk of your code and "break" statements where you want to jump to the end.
while(1)
{
if (!isset($a)) {
$message = '$a is not set';
break;
}
if (!isset($b)) {
$message = '$b is not set';
break;
}
echo '$a + $b = '.($a + $b)."<br>\n";
break;
}
if (!empty($message)) {
echo 'Caught exception: ', $message, "<br>\n";
}
精彩评论