每个人都知道Dijkstra的Letters to the editor: go to statement considered harmful(也是here .html transcript和here .pdf)并且从那时起就有一个强大的推动力,只要有可能就避开goto语句。虽然可以使用goto来生成不可维护的庞大代码,但它仍然存在于modern programming languages中。甚至Scheme中的高级continuation控制结构也可以被描述为复杂的goto。
什么情况下可以使用goto?什么时候最好避免?
作为后续问题:C提供了一对函数setjmp和longjmp,它们不仅可以在当前堆栈帧内,而且可以在任何调用帧中进行转换。这些应该被视为像goto一样危险吗?更危险吗?
Dijkstra本人对这个头衔表示遗憾,他对此不负责任。在EWD1308(也是here .pdf)结束时,他写道:
最后是一个短篇小说的记录。 1968年,ACM的通讯以“被认为有害的goto声明”的标题发表了我的一篇文章,遗憾的是,在后来的几年里,这一期刊最常被引用,但是,经常被作者看到的不多于标题,通过成为一个模板成为我的名声的基石:我们会看到几乎任何X的标题“X被认为有害”的各种文章,包括一个标题为“Dijkstra被认为有害”的文章。但是发生了什么?我提交了一篇题为“反对goto声明的案例”的论文,为了加快其出版速度,编辑已经变成了“致编辑的信”,并在此过程中给了它一个新的他自己发明的头衔!编辑是Niklaus Wirth。
关于这个主题的经过深思熟虑的经典论文,与Dijkstra相匹配的是由Donald E. Knuth撰写的Structured Programming with go to Statements。阅读都有助于重新建立背景和对主题的非教条性理解。在本文中,Dijkstra对此案的观点得到了报道,甚至更为强烈:
Donald E. Knuth:我相信通过提出这样一种观点,我实际上并不同意Dijkstra的观点,因为他最近写了以下内容:“请不要陷入相信我非常悲惨的陷阱。我有一种令人不舒服的感觉,就是其他人正在创造一种宗教信仰,好像编程的概念问题可以通过一个简单的编码规则来解决!
以下陈述是概括;虽然总是可以恳求例外,但通常(根据我的经验和拙见)并不值得冒这个风险。
以上脚注:
关于第2点,请考虑以下代码:
a = b + 1
/* do something with a */
在代码中的“做某事”点,我们可以高度自信地说明a
大于b
。 (是的,我忽略了未整数溢出的可能性。让我们不要陷入一个简单的例子。)
另一方面,如果代码以这种方式读取:
...
goto 10
...
a = b + 1
10: /* do something with a */
...
goto 10
...
获得标签10的多种方式意味着我们必须更加努力地对a
和b
之间的关系充满信心。 (事实上,在一般情况下,它是不可判定的!)
关于第4点,代码中“走向某个地方”的整个概念只是一个隐喻。除了电子和光子(废热)之外,CPU内部的任何地方都没有“真正”的“走向”。有时我们会放弃另一个更有用的隐喻。我记得曾经(几十年前)遇到过一种语言
if (some condition) {
action-1
} else {
action-2
}
通过将action-1和action-2编译为out-of-line无参数例程,然后使用单个双参数VM操作码,使用条件的布尔值来调用其中一个,从而在虚拟机上实现。这个概念只是“选择现在要调用的东西”,而不是“去这里或去那里”。再一次,只是一个隐喻的变化。
在某些情况下,Go To可以为“真正的”异常处理提供一种替代。考虑:
GOTO
显然,这段代码被简化为占用更少的空间,所以不要太过于挂在细节上。但是考虑一下我在生产代码中看到过多次的替代方法,编码人员为了避免使用goto而荒谬的长度:
ptr = malloc(size);
if (!ptr) goto label_fail;
bytes_in = read(f_in,ptr,size);
if (bytes_in=<0) goto label_fail;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) goto label_fail;
从功能上讲,这段代码完全相同。实际上,编译器生成的代码几乎完全相同。然而,在程序员热衷于安抚Nogoto(可怕的学术谴责之神)的热情中,这位程序员完全打破了success=false;
do {
ptr = malloc(size);
if (!ptr) break;
bytes_in = read(f_in,ptr,size);
if (count=<0) break;
bytes_out = write(f_out,ptr,bytes_in);
if (bytes_out != bytes_in) break;
success = true;
} while (false);
循环所代表的基本习惯,并对代码的可读性做了一个真实的数字。这不是更好。
所以,故事的寓意是,如果你发现自己为了避免使用goto而采取真正愚蠢的事情,那就不要了。
Donald E. Knuth在1992年的CSLI“Literate Programming”一书中回答了这个问题。在p。 17有一篇文章“while
”(PDF)。我认为这篇文章也可能已在其他书籍中发表过。
这篇文章描述了Dijkstra的建议,并描述了这种情况的有效性。但他也提供了许多反例(问题和算法),这些例子只能使用结构化循环来轻松复制。
本文包含问题的完整描述,历史,示例和反例。
被Jay Ballou吸引并添加一个答案,我将加上0.02英镑。如果Bruno Ranschaert还没有这样做,我会提到Knuth的“使用GOTO语句进行结构化编程”一文。
我没有看过的一件事就是在Fortran教科书中讲授的一些代码,虽然不常见,但却很常见。像DO循环和开放编码子程序的扩展范围(记住,这将是Fortran II,Fortran IV或Fortran 66 - 而不是Fortran 77或90)。语法细节至少有可能是不精确的,但概念应该足够准确。每种情况下的片段都在一个函数内。
请注意,Kernighan&Plauger撰写的优秀但过时(绝版)的书籍“Structured Programming with goto Statements”包含了一些现实生活中的例子,这些例子表明GOTO在其时代(70年代后期)的编程教科书中被滥用。但是,下面的材料不是那本书。
The Elements of Programming Style, 2nd Edn
这种废话的一个原因是好老式的打卡。您可能会注意到标签(很好地不按顺序,因为这是规范样式!)在第1列(实际上,它们必须在第1-5列中),代码在第7-72列中(第6列是延续标记栏)。第73-80列将被赋予序列号,并且有机器将打卡机卡片分类为序列号顺序。如果您的程序在顺序卡上并且需要在循环中间添加一些卡(行),则必须在这些额外行之后重新启动所有内容。但是,如果你用GOTO的东西替换了一张卡片,你可以避免重新测序所有的卡片 - 你只需要在例程结束时用新的序列号将新卡片塞进去。将其视为“绿色计算”的第一次尝试 - 节省打卡(或者更具体地说,节省重新输入劳动力 - 并节省相应的重新加密错误)。
哦,你可能还会注意到我在欺骗而不是大喊大叫 - Fortran IV通常用大写字母写成。
do 10 i = 1,30
...blah...
...blah...
if (k.gt.4) goto 37
91 ...blah...
...blah...
10 continue
...blah...
return
37 ...some computation...
goto 91
标签76和54之间的GOTO是计算goto的版本。如果变量i的值为1,则转到列表中的第一个标签(123);如果它的值为2,则转到第二个,依此类推。从76到计算goto的片段是开放编码的子例程。它是一段执行的代码,就像一个子程序,但写在一个函数体中。 (Fortran还具有语句功能 - 这些功能是嵌入在单行上的功能。)
有比构成的goto更糟糕的构造 - 你可以为变量分配标签,然后使用指定的goto。谷歌搜索 ...blah...
i = 1
goto 76
123 ...blah...
...blah...
i = 2
goto 76
79 ...blah...
...blah...
goto 54
...blah...
12 continue
return
76 ...calculate something...
...blah...
goto (123, 79) i
54 ...more calculation...
goto 12
告诉我,它已从Fortran 95中删除了。用一个结构化编程革命的粉笔,可以说是公开的Dijkstra的“GOTO Considered Harmful”字母或文章。
如果不了解Fortran所做的各种事情(以及其他语言,其中大部分已被正确地撇开),我们新手很难理解Dijkstra正在处理的问题的范围。哎呀,直到那封信发表十年后我才开始编程(但我确实不幸在Fortran IV中编程了一段时间)。
转到认为有帮助。
我在1975年开始编程。对于20世纪70年代的程序员来说,“转向被认为是有害的”这些词或多或少地表示具有现代控制结构的新编程语言值得尝试。我们确实尝试过新语言。我们很快转换了。我们再也没有回去过。
我们再也没有回去,但是,如果你年轻,那么你从来没有去过那里。
现在,除了作为程序员年龄的指标之外,古代编程语言的背景可能不是很有用。尽管如此,年轻的程序员缺乏这种背景,因此他们不再理解在引入时向其目标受众传达的“转向被认为有害”的口号。
一个人不理解的标语并不是很有启发性。最好忘记这样的口号。这样的口号没有帮助。
然而,这个特殊的口号“Goto被认为是有害的”已经成为了自己的不死生命。
可以转到不被滥用?答:当然可以,但那又怎样?实际上每个编程元素都可能被滥用。例如,卑微的assigned goto比我们想要相信的人更容易受到虐待。
相比之下,我不记得自1990年以来遇到一个单一的,实际的goto滥用实例。
goto最大的问题可能不是技术问题,而是社交问题。有时候不太了解的程序员似乎觉得弃用goto会让他们听起来很聪明。您可能不得不满足这些程序员。这就是人生。
关于goto今天最糟糕的事情是它使用不够。
没有GOTO认为有害的东西。
GOTO是一种工具,作为所有工具,它可以被使用和滥用。
然而,编程世界中有许多工具比使用更容易被滥用,而GOTO就是其中之一。 Delphi的WITH语句是另一个。
就个人而言,我不会在典型代码中使用任何一种,但我已经保证了GOTO和WITH的奇怪用法,并且替代解决方案将包含更多代码。
最好的解决方案是编译器只是警告你关键字被污染了,你必须在语句周围填写一些pragma指令来摆脱警告。
这就像告诉你的孩子不要用剪刀跑。剪刀也不错,但使用它们可能不是保持健康的最佳方法。
自从我开始在linux内核中做了一些事情以来,getos并没有像以前那样困扰我。起初我有点害怕看到他们(内核人员)在我的代码中添加了getos。我已经习惯于在某些有限的环境中使用gotos,现在偶尔也会使用它们。通常,它是一个转到函数末尾进行某种清理和纾困的goto,而不是在函数中的几个位置复制相同的清理和挽救。通常情况下,它不足以传递给另一个功能 - 例如释放一些局部(k)malloc'ed变量是一个典型的例子。
我编写的代码只使用了setjmp / longjmp一次。它是在MIDI鼓音序器程序中。回放发生在与所有用户交互不同的进程中,回放过程使用共享内存和UI进程来获取回放所需的有限信息。当用户想要停止播放时,播放过程只是做了一个“回到开头”的longjmp重新开始,而不是在用户希望它停止时执行的任何地方的一些复杂的展开。它工作得很好,很简单,在这种情况下我从来没有遇到任何与之相关的问题或错误。
setjmp / longjmp有他们的位置 - 但是那个地方是你不可能访问的地方,但很长一段时间。
编辑:我只看了代码。它实际上是我使用的siglongjmp(),而不是longjmp(不是说这是一个大问题,但我忘记了siglongjmp甚至存在。)
它永远不会,只要你能够为自己思考。
如果你用C编写VM,事实证明使用(gcc)计算得到的结果是这样的:
bool
比循环内的传统开关工作得快得多。
char run(char *pc) {
void *opcodes[3] = {&&op_inc, &&op_lda_direct, &&op_hlt};
#define NEXT_INSTR(stride) goto *(opcodes[*(pc += stride)])
NEXT_INSTR(0);
op_inc:
++acc;
NEXT_INSTR(1);
op_lda_direct:
acc = ram[++pc];
NEXT_INSTR(1);
op_hlt:
return acc;
}
可以用于混淆元编程goto
既是高级也是低级控制表达式,因此它没有适合大多数问题的适当设计模式。
它是低级别的,因为goto是一种原始操作,它可以实现像Goto
或while
之类的东西。
从某种意义上讲,它是高级别的,它以一种不间断的方式,以明确的顺序执行代码,除了结构化循环,并将其转换为具有足够的foreach
s,抓取的逻辑片段 - 动态重组的逻辑包。
因此,goto
有一个平淡和邪恶的一面。
平淡无奇的一面是,向上指向的goto可以实现一个完美合理的循环,向下指向的goto可以做一个完全合理的goto
或break
。当然,实际的return
,while
或break
将更具可读性,因为穷人不必模拟return
的效果以获得全局。所以,一般来说一个坏主意。
邪恶的一方涉及一个例程,不使用goto进行while,break或return,而是将其用于所谓的意大利面条逻辑。在这种情况下,goto-happy开发人员正在从goto的迷宫中构建代码片段,理解它的唯一方法是在整体上模拟它,当有很多goto时,这是一个非常累人的任务。我的意思是,想象一下评估代码的麻烦,其中goto
并不恰恰是else
的反转,其中嵌套的if
s可能允许在外部if
等拒绝的某些东西等。
最后,为了真正涵盖这一主题,我们应该注意到,除了Algol之外,基本上所有早期语言最初只根据其版本的if
进行单一陈述。因此,进行条件块的唯一方法是使用反条件对其周围的if-then-else
。疯了,我知道,但我读过一些陈旧的规格。请记住,第一台计算机是用二进制机器代码编程的,所以我认为任何一种HLL都是救星;我猜他们对于HLL的具体功能并不太挑剔。
说过我曾经把一个goto
粘贴到我编写的每个程序中的所有内容“只是为了惹恼纯粹主义者”。
拒绝将GOTO声明用于程序员就像告诉木匠不要使用锤子一样,因为它可能会在锤击钉子时损坏墙壁。一个真正的程序员知道如何以及何时使用GOTO。我跟在其中一些所谓的“结构化程序”后面我看到这样的Horrid代码只是为了避免使用GOTO,我可以拍摄程序员。好吧,为了防御另一方,我一次又一次地看到了一些真正的意大利面条代码,那些程序员也应该被拍摄。
这里只是我发现的一个代码示例。
goto
- - - - - - - - - - - -要么 - - - - - - - - - - -
YORN = ''
LOOP
UNTIL YORN = 'Y' OR YORN = 'N' DO
CRT 'Is this correct? (Y/N) : ':
INPUT YORN
REPEAT
IF YORN = 'N' THEN
CRT 'Aborted!'
STOP
END
“在这个链接10: CRT 'Is this Correct (Y)es/(N)o ':
INPUT YORN
IF YORN='N' THEN
CRT 'Aborted!'
STOP
ENDIF
IF YORN<>'Y' THEN GOTO 10
”
具有讽刺意味的是,消除goto引入了一个错误:省略了spinlock调用。
原始论文应该被认为是“无条件的GOTO被认为是有害的”。它特别提倡一种基于条件(http://kerneltrap.org/node/553/2131)和迭代(if
)结构的编程形式,而不是早期代码常见的测试和跳转。 while
在某些语言或情况下仍然有用,因为没有适当的控制结构。
关于我同意Goto可以使用的唯一地方是当你需要处理错误时,每个特定点发生错误都需要特殊处理。
例如,如果您正在抓取资源并使用信号量或互斥量,则必须按顺序抓取它们,并且应始终以相反的方式释放它们。
有些代码需要非常奇怪的模式来获取这些资源,并且您不能只编写一个易于维护和理解的控制结构来正确处理这些资源的获取和释放以避免死锁。
它总是可以在没有goto的情况下正确完成,但在这种情况下和其他一些Goto实际上是更好的解决方案,主要是为了可读性和可维护性。
-亚当
一个现代的GOTO用法是由C#编译器为yield yield定义的枚举数创建状态机。
GOTO应该由编译器而不是程序员使用。
直到C和C ++(以及其他罪魁祸首)标记了中断并继续,goto将继续发挥作用。
如果GOTO本身是邪恶的,那么编译器就是邪恶的,因为它们会产生JMP。如果跳进一段代码,特别是在指针之后,本身就是邪恶的,那么RETurn指令就是邪恶的。相反,邪恶有可能被滥用。
有时我不得不编写必须跟踪许多对象的应用程序,其中每个对象必须遵循复杂的状态序列以响应事件,但整个事情肯定是单线程。一个典型的状态序列,如果用伪代码表示,将是:
goto
我确定这不是新的,但我在C(++)中处理它的方式是定义一些宏:
request something
wait for it to be done
while some condition
request something
wait for it
if one response
while another condition
request something
wait for it
do something
endwhile
request one more thing
wait for it
else if some other response
... some other similar sequence ...
... etc, etc.
endwhile
然后(假设状态最初为0),上面的结构化状态机变成结构化代码:
#define WAIT(n) do{state=(n); enque(this); return; L##n:;}while(0)
#define DONE state = -1
#define DISPATCH0 if state < 0) return;
#define DISPATCH1 if(state==1) goto L1; DISPATCH0
#define DISPATCH2 if(state==2) goto L2; DISPATCH1
#define DISPATCH3 if(state==3) goto L3; DISPATCH2
#define DISPATCH4 if(state==4) goto L4; DISPATCH3
... as needed ...
对此有一个变化,可以有CALL和RETURN,因此一些状态机可以像其他状态机的子程序一样工作。
这不寻常吗?是。是否需要维护者的一些学习?是。这种学习是否有成效?我认同。如果没有跳转到块的GOTO可以做到吗?不。
我避免使用它,因为同事或经理无疑会在代码审查中或在遇到错误时对其使用提出质疑。虽然我认为它有用(例如错误处理案例) - 但是你会遇到其他开发人员,他们会遇到某种类型的问题。
这不值得。
我实际上发现自己被迫使用goto,因为我真的想不出更好(更快)的方式来编写这段代码:
我有一个复杂的对象,我需要对它进行一些操作。如果对象处于一种状态,那么我可以做一个快速版本的操作,否则我不得不做一个慢速版本的操作。事情是,在某些情况下,在慢速操作的中间,有可能意识到这可以通过快速操作完成。
{
DISPATCH4; // or as high a number as needed
request something;
WAIT(1); // each WAIT has a different number
while (some condition){
request something;
WAIT(2);
if (one response){
while (another condition){
request something;
WAIT(3);
do something;
}
request one more thing;
WAIT(4);
}
else if (some other response){
... some other similar sequence ...
}
... etc, etc.
}
DONE;
}
这是一个速度关键的实时UI代码,所以老实说我认为GOTO在这里是合理的。
雨果
几乎所有可以使用goto的情况,你都可以使用其他结构来做同样的事情。无论如何,编译器都使用Goto。
我个人从不明确地使用它,也不需要。
我从这里得到的任何答案中都没有看到的一点是,'goto'解决方案通常比经常提到的结构化编程解决方案更有效。
考虑多嵌套循环的情况,使用'goto'而不是一堆SomeObject someObject;
if (someObject.IsComplex()) // this test is trivial
{
// begin slow calculations here
if (result of calculations)
{
// just discovered that I could use the fast calculation !
goto Fast_Calculations;
}
// do the rest of the slow calculations here
return;
}
if (someObject.IsmediumComplex()) // this test is slightly less trivial
{
Fast_Calculations:
// Do fast calculations
return;
}
// object is simple, no calculations needed.
部分显然更有效。解决方案“将你的循环置于函数中并使用返回”通常是完全不合理的。在循环使用局部变量的可能情况下,您现在必须通过函数参数传递它们,可能会处理由此产生的额外麻烦。
现在考虑清理案例,我经常使用它,并且很常见,因为可能对许多语言中没有的try {} catch {}结构负责。完成相同操作所需的检查和额外变量的数量远远超过跳转的一个或两个指令,而且额外的功能解决方案根本不是解决方案。你无法告诉我,它更易于管理或更具可读性。
现在代码空间,堆栈使用和执行时间在许多情况下对许多程序员来说可能不够重要,但是当你在一个只有2KB代码空间的嵌入式环境中工作时,需要50个字节的额外指令来避免明确定义'goto'是可笑的,这并不像许多高级程序员所认为的那样罕见。
“goto有害”的说法对于向结构化编程非常有帮助,即使它总是过于泛化。在这一点上,我们都听到它足够警惕使用它(我们应该)。当它显然是工作的正确工具时,我们不需要害怕它。
有时在单个函数中使用GOTO替代异常处理是有效的:
if (f() == false) goto err_cleanup;
if (g() == false) goto err_cleanup;
if (h() == false) goto err_cleanup;
return;
err_cleanup:
...
COM代码似乎经常陷入这种模式。
您可以使用它来破坏深度嵌套的循环,但大多数情况下,您的代码可以重构为更清晰而没有深层嵌套循环。
我只记得使用过一次goto。我有一系列五个嵌套计数循环,我需要能够根据某些条件从内部早期打破整个结构:
for{
for{
for{
for{
for{
if(stuff){
GOTO ENDOFLOOPS;
}
}
}
}
}
}
ENDOFLOOPS:
我可以轻松地声明一个布尔中断变量并将其用作每个循环的条件的一部分,但在这个实例中我决定GOTO同样实用且同样可读。
没有迅猛龙攻击我。
我们已经有了这个discussion,我支持my point。
此外,我厌倦了将高级语言结构描述为“伪装的goto
”的人,因为他们显然根本没有说明这一点。例如:
甚至Scheme中的高级连续控制结构也可以被描述为复杂的goto。
这完全是胡说八道。每个控制结构都可以用goto
来实现,但这种观察完全是微不足道的,毫无用处。 goto
因其积极影响而被认为是有害的,但由于其负面影响而已被结构化编程所消除。
同样地,说“GOTO是一种工具,并且作为所有工具,它可以被使用和滥用”完全不合适。没有现代建筑工人会使用岩石并声称它“是一种工具。”岩石已被锤子取代。 goto
已被控制结构所取代。如果建筑工人在没有锤子的情况下被困在野外,当然他会使用岩石代替。如果程序员必须使用没有特征X的劣质编程语言,那么当然她可能不得不使用goto
。但如果她在其他任何地方使用它而不是相应的语言功能,她显然不能正确理解语言并错误地使用它。它真的很简单。
Goto在我的列表中非常低,只是为了它而包含在程序中。这并不意味着它是不可接受的。
Goto可以用于状态机。循环中的switch语句(按典型重要性顺序):( a)实际上不代表控制流,(b)丑陋,(c)取决于语言和编译器,可能效率低下。所以你最终会为每个状态编写一个函数,并执行“return NEXT_STATE;”之类的操作。甚至看起来像goto。
当然,很难以一种易于理解的方式对状态机进行编码。然而,使用goto没有任何困难,并且通过使用替代控制结构不能减少任何困难。除非您的语言具有“状态机”构造。我没有。
在极少数情况下,您的算法在通过一组有限的允许转换(gotos)连接的节点(状态)的路径中最容易理解,而不是通过任何更具体的控制流(循环,条件,诸如此类) ),然后在代码中应该是明确的。你应该绘制一个漂亮的图表。
setjmp / longjmp可以很好地实现异常或类似异常的行为。虽然没有得到普遍赞扬,但例外通常被认为是“有效”的控制结构。
setjmp / longjmp比goto“更危险”,因为它们更难以正确使用,从不介意理解。
从来没有,也没有任何语言可以编写糟糕的代码。 - 唐纳德克努特
从C中取出goto不会让在C中编写好的代码变得更容易。实际上,它宁愿忽略C应该能够作为一种美化的汇编语言。
接下来它将是“被认为有害的指针”,然后“鸭子打字被认为是有害的”。然后,当他们拿走你不安全的编程结构时,谁会留下来为你辩护?嗯?
在Kernel Trap的Linux: Using goto In Kernel Code中,与Linus Torvalds讨论了一个关于在Linux代码中使用GOTO的“新人”。那里有一些非常好的点,而Linus穿着那种平常的傲慢:)
一些段落:
Linus:“不,你被CS人员洗脑了,他们认为Niklaus Wirth实际上知道他在说什么。他没有。他没有一个fr c c的线索。”
-
Linus:“我认为goto很好,而且它们通常比大量缩进更具可读性。”
-
Linus:“当然,在像Pascal这样的愚蠢语言中,标签无法描述,goto可能会很糟糕。”
在C中,goto
仅在当前函数的范围内工作,这往往会定位任何潜在的错误。 setjmp
和longjmp
更加危险,非本地化,复杂化和依赖于实施。然而,在实践中,它们太模糊,并且不常见导致许多问题。
我相信cazxswpoi在C中的危险被夸大了。请记住,最初的goto
论证发生在像老式的BASIC这样的语言时代,初学者会写这样的意大利面条代码:
goto
在这里,Linus描述了3420 IF A > 2 THEN GOTO 1430
的恰当用法:goto
(第7章)。
今天,很难看到关于http://www.kernel.org/doc/Documentation/CodingStyle声明的大问题,因为“结构化编程”人们大多赢得了辩论,今天的语言有足够的控制流结构来避免GOTO
。
计算现代C程序中GOTO
s的数量。现在添加goto
,break
和continue
语句的数量。此外,添加你使用return
,if
,else
,while
或switch
的次数。这就是当你在Dijkstra写信时,如果你在1968年写FORTRAN或BASIC时你的程序会有多少case
s。
当时的编程语言缺乏控制流程。例如,在最初的达特茅斯基础:
GOTO
声明没有IF
。如果你想要一个,你必须写:
ELSE
100 IF NOT condition THEN GOTO 200
...stuff to do if condition is true...
190 GOTO 300
200 REM else
...stuff to do if condition is false...
300 REM end if
声明不需要IF
,它仍然限于一条线,通常由ELSE
组成。GOTO
声明。对于非DO...LOOP
循环,你必须以明确的FOR
或GOTO
结束回到开头。IF...GOTO
。你必须使用SELECT CASE
。所以,你在程序中最终得到了很多ON...GOTO
s。并且你不能依赖于GOTO
s在单个子程序中的限制(因为GOTO
是子程序的弱概念),所以这些GOSUB...RETURN
s可以去任何地方。显然,这使得控制流程很难遵循。
这就是反GOTO
运动的来源。